首页 > 解决方案 > 确定有多少时间间隔与每个给定时间间隔 (R) 相交

问题描述

我正在使用R 上的这些数据。这些是前六行——不包括 write.csv 函数总是添加的第一列——:

> head(my_data)
client_id contract_id contract_start contract_end inter_complex
        1           1     15/07/2019   15/07/2020  18092+18458i
        3           3      1/01/2015    1/01/2015  16436+16436i
        5           5     12/06/2020   12/06/2020  18425+18425i
       13          13      1/01/2015    1/01/2015  16436+16436i
       18          18      1/01/2015    1/01/2015  16436+16436i
       19          19      1/01/2015    1/01/2015  16436+16436i

每行代表不同的合同。变量inter_complex是一个复数,其实部是合约开始日期的数字表示,而其虚部类似地表示合约结束日期。如果您想知道,您可以通过执行以下操作来获取该列:

library(tidyverse)
library(lubridate)

chars_2_cplex = function(start, end) {
    cbind(start, end) %>%
    apply(2, compose(as.numeric, dmy)) %*% rbind(1, 1i)
}

my_data %>% transmute(inter_complex = chars_2_cplex(contract_start, contract_end))

我想要的是,对于每个客户 ID 和每个合同,确定有多少与同一客户 ID 关联的合同与该合同相交。换句话说:我想创建一个名为同时的新列,它将为每一行(即每个合约)描述在当前合约处于活动状态的同一时期相应客户有多少活动合约。如果没有为给定合约找到与任何其他合约的交集,则同时的值必须为 1——因为当该合约处于活动状态时,它也是相应客户拥有的唯一活动合约——。

我认为这将有助于获得inter_complex的组合,然后将这些复数组合转换为区间组合,然后使用 lubridate 的 intersect 函数来辨别每个区间组合是否相交。为此,我编写了以下代码:

## This function turns complex numbers into intervals.
cplex_2_inter = function(x) {
    start = x %>% Re() %>% as.integer()
    end = x %>% Im() %>% as.integer()

    interval(as_date(start), as_date(end))
}


## This other function returns a list whose j-th element is a data frame that shows the interceptions
## between combinations of j + 1 intervals.
get_intersections = function(x) {
    max_m = length(x)
    output = vector(mode = "list", length = max_m - 1)

    for (i in 2:max_m) {
        output[[i - 1]] = combn(x, m = i) %>% t() %>% as.data.frame() %>% 
                          mutate_all(cplex_2_inter) %>% rowid_to_column("id") %>%
                          pivot_longer(-id) %>% group_by(id) %>% 
                          mutate(simultaneous = do.call(lubridate::intersect, as.list(value))) %>%
                          mutate(simultaneous = if_else(as.character(simultaneous) != "NA--NA", i, 1L))
    }

    return(output)
}

为了更好地掌握 get_intersections 函数的作用,我建议您运行以下命令:

example = my_data %>% filter(client_id == 1) %>% pull(inter_complex) %>% get_intersections()

数据框显示了间隔对之间example[[1]]是否存在截取——或者,更准确地说,是重叠。数据框example[[2]]显示三个区间的组之间是否存在重叠,等等。

您可能会注意到,根据example[[1]]间隔2019-07-15 UTC--2020-07-15 UTC与其他一些间隔重叠——因此,同时的关联值为2——而根据变量同时example[[2]]的值为 3 。自然地,这个想法是为每个间隔分配其最高的同时值。

由于我不关心全局重叠,而是关心每个客户端 ID 内的重叠,我认为我需要处理分组数据框。我在这个项目上得到的最远的是写这个:

my_data %>% group_by(client_id) %>% group_map(~ get_intersections(.x$inter_complex))

现在谈谈我的问题。1)我已经执行了上面的行,但是这个过程效率不是很高。它已经运行了一整天多一点,但还没有完成。最近我遇到了区间树的概念,但我不是计算机科学家,我需要帮助才能以更智能的方式解决这个问题。2)如果我们坚持我不太聪明的方法来解决这个问题,我仍然需要一个函数来访问由get_intersections返回的列表的每个元素,以便识别和检索与每个元素关联的最高同时值间隔。在这件事上,我也不得不请求帮助。

编辑

关于 Wimpel 的回答,我检查了他们的数据表并发现了这一点。

> DT %>% filter(client_id == 502 & contract_id == 3093) %>%
> select(contract_start, contract_end, contract_intersect)
# Output
   contract_start contract_end contract_intersect
1:     2018-01-11   2019-01-11                  7

也就是说,显示的合同据称与同一客户的其他七份合同重叠。

另一方面,让我们看看在使用我的基于组合的方法时这是否成立。

combs_10_502 = my_data %>% filter(client_id == 502) %>% pull(inter_complex) %>% 
               combn(10) %>% t() %>% as.data.frame() %>% mutate_all(cplex_2_inter) %>% 
               rowid_to_column("id") %>% pivot_longer(-id) %>% group_by(id) %>% 
               mutate(simultaneous = do.call(lubridate::intersect, as.list(value))) %>% 
               ungroup() %>% 
               mutate(simultaneous = if_else(as.character(simultaneous) != "NA--NA", 10L, 1L))    

> combs_10_502 %>% filter(simultaneous == 10) %>% slice(11:20)
# A tibble: 10 x 4
  id    name  value                          simultaneous
  <int> <chr> <Interval>                            <int>
1  24311 V1    2018-01-11 UTC--2019-01-11 UTC        10
2  24311 V2    2018-03-01 UTC--2019-03-01 UTC        10
3  24311 V3    2018-07-11 UTC--2019-07-11 UTC        10
4  24311 V4    2018-04-20 UTC--2019-04-20 UTC        10
5  24311 V5    2018-05-21 UTC--2019-05-21 UTC        10
6  24311 V6    2018-08-10 UTC--2019-08-10 UTC        10
7  24311 V7    2018-08-09 UTC--2019-08-09 UTC        10
8  24311 V8    2018-09-27 UTC--2019-09-27 UTC        10
9  24311 V9    2020-01-03 UTC--2021-01-03 UTC        10
10 24311 V10   2019-12-19 UTC--2020-12-19 UTC        10

相同的合约显示在上面小标题的第一行。可以看出,该合同实际上与给定客户的其他九个合同重叠——这九个显示在剩余的行上——。

我不知道 Wimpel 的解决方案是如何出错的,但我检查了它确实为其他几个合同提供了正确的交叉点数量。现在我知道我正在寻找基于数据表的解决方案,因为流程非常快,但建议的解决方案似乎存在问题。

标签: rinterval-tree

解决方案


我相信你正在寻找这样的东西?

library(data.table)
DT <- fread("https://raw.githubusercontent.com/pazos-feren/Data/main/contracts.csv")
#set dates as real dates
DT[, contract_start := as.Date(contract_start, format = "%d/%m/%Y")]
DT[, contract_end := as.Date(contract_end, format = "%d/%m/%Y")]

setkey(DT, V1)

DT[DT, c("contract_intersect", "contract_intersect_ids") := {
  val = DT[ !V1 == i.V1 & client_id == i.client_id &
              contract_start <= i.contract_end & contract_end >= i.contract_start, ]
  list( nrow(val), paste0(val$contract_id, collapse = ";") )
}, by = .EACHI]

#    V1 client_id contract_id contract_start contract_end inter_complex contract_intersect contract_intersect_ids
# 1:  1         1           1     2019-07-15   2020-07-15  18092+18458i                  2              4162;4168
# 2:  2         3           3     2015-01-01   2015-01-01  16436+16436i                  0                       
# 3:  3         5           5     2020-06-12   2020-06-12  18425+18425i                  0                       
# 4:  4        13          13     2015-01-01   2015-01-01  16436+16436i                  0                       
# 5:  5        18          18     2015-01-01   2015-01-01  16436+16436i                  0                       
# 6:  6        19          19     2015-01-01   2015-01-01  16436+16436i                  0                       

推荐阅读