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