计算日期范围间隔中的部分重叠数

时间:2019-03-25 20:55:18

标签: r performance for-loop data.table apply

我需要对具有约15万行的数据框运行循环。但是,循环需要检查每一行,并检查检查数据集中每隔一行的条件。我的代码对于玩具数据集工作正常,可以生成正确的值,但是对于我的实际数据集来说太慢了。我让它运行了几个小时,但仍然没有完成。所以我希望有人对如何解决这个问题有更好的主意。

#R version 3.5.1 Windows 64-bit

#Example dataset
my_df <- data.frame("PERSON" = c("A","A","A","B","A","A","B"),
                    "DATE_START" = c("2019-01-15","2019-01-10","2019-01-20","2019-01-19","2018-12-20","2018-03-03","2019-05-01"),
                    "DATE_FINISH" = c("2019-01-30","2019-01-18","2019-02-05","2019-01-23","2019-02-10","2018-04-01","2019-06-06")
                    )
#Each row is a task that the assigned person is working on
my_df
   PERSON   DATE_START DATE_FINISH
1       A   2019-01-15  2019-01-30
2       A   2019-01-10  2019-01-18
3       A   2019-01-20  2019-02-05
4       B   2019-01-19  2019-01-23
5       A   2018-12-20  2019-02-10
6       A   2018-03-03  2018-04-01
7       B   2019-05-01  2019-06-06

我想知道FOR第1行,人A在开始日期和结束日期之间还有多少其他任务重叠? (包括其上的行)

所以我要寻找的答案是

   PERSON   DATE_START DATE_FINISH  NUMBER_OF_TASKS
1       A   2019-01-15  2019-01-30  4
2       A   2019-01-10  2019-01-18  3
3       A   2019-01-20  2019-02-05  3
4       B   2019-01-19  2019-01-23  1
5       A   2018-12-20  2019-02-10  4
6       A   2018-03-03  2018-04-01  1
7       B   2019-05-01  2019-06-06  1

因此,这基本上表示对于第1行,人A有4个未完成的任务

我尝试为包含日期范围作为数字值的每一行创建一个列表元素,然后检查是否存在重叠,我使用%in%运算符比较未列出的范围

我已经使用lapply函数完成了类似的操作(此处未显示),但是同样的问题只需要永久执行即可。

##This is what I currently have

temp_list <- list()
num_open_tasks <- c()
open_work_cc <- c()

##Create a list of length = nrow(my_df)
##Each element in the list is a range of dates coerced to numeric
for(i in 1:nrow(my_df)){
  temp_list[[i]] <- as.numeric(my_df$DATE_START[i]) : 
                    as.numeric(my_df$DATE_FINISH[i])
}


for(i in 1:nrow(my_df)){
  for(j in 1:nrow(my_df)){

##If elements from the temp_list overlap by 5 days, the overlap = 5
##I'm just checking if the overlap is greater than 0 (is there any overlap at all)
##And if the tasks belongs to the same person or not    
open_work_cc[j] <- ifelse(sum(unlist(temp_list[[i]]) %in% 
                              unlist(temp_list[[j]])) > 0 &
                              my_df$PERSON[i] == my_df$PERSON[j] 
                              ,1,0
                           )
open_work_cc_total <- sum(open_work_cc)

  }
  num_open_tasks[i] <- open_work_cc_total

}
my_df <- cbind(my_df, num_open_tasks)

此方法返回我希望的列,其中填充了正确的值。 但我想有一种使用某种形式的split / apply / combine的方法更优雅,更快速。感谢所有帮助

1 个答案:

答案 0 :(得分:3)

foverlaps中的

data.table可能是R中最快的方法。我认为以下代码可以满足您的要求:

library(data.table)
setDT(my_df)

my_df[, DATE_START_N:=as.numeric(as.Date(DATE_START))]
my_df[, DATE_FINISH_N:=as.numeric(as.Date(DATE_FINISH))]

setkey(my_df, PERSON, DATE_START_N,DATE_FINISH_N)

my_df[,NUMBER_OF_TASKS:=foverlaps(my_df,my_df,which=TRUE)[,.N,by=xid]$N]
my_df

为更加清楚起见:foverlaps(my_df,my_df,which=TRUE)PERSON内的日期范围内进行自联接(联接由setkey确定)。请注意,type的间隔联接的默认参数是"any",这是间隔上的部分匹配:即,您想要的是什么。

指定which=TRUE仅提供x和y中的匹配索引(而不是此处不需要的实际联接数据)。对foverlaps的调用返回一个类data.table的对象,该对象随后被聚合以获取xid定义的每个组中的行数(仅是my_df的行)使用方括号函数调用[,.N,by=xid]。这些计数用$N提取到向量中,并分配给NUMBER_OF_TASKS中的新列my_df