我正在寻找将重叠和非重叠周期与"lubridate"one_answers"dplyr"包(或任何其他可以建议的包(相结合。以下是一个示例数据帧:
vacation_start <- as_date('2017-04-19')
vacation_end <- as_date('2017-04-25')
course_start <- as_date('2017-04-12')
course_end <- as_date('2017-04-21')
course_interval <- interval(course_start, course_end)
vacation_interval <- interval(vacation_start, vacation_end)
df <- data.frame(id = "ID", part = c("A", "B"),
start = c(course_start,vacation_start),
end = c(course_end, vacation_end),
interval = c(course_interval, vacation_interval))
数据帧如下所示:
id | 部分 | 开始结束间隔 | |
---|---|---|---|
ID | A | 2017-04-12 | 2017-03-21 | 2017-04.12 UTC--2017-04-21 UTC
ID | B | 2017-04-19 | 2017-03-25 | 2017 04-19 UTC-2017 04-25 UTC
我的第一个答案假设只有两个周期重叠。这意味着它可以对每个比较使用一个联接。尝试重复此过程超过两个时间段会导致联接数量增加,从而导致效率低下的混乱局面。
为了处理连接任意(或未知(数量的重叠,我们需要一种非常不同的方法。因此,我将此作为一个单独的答案提供。
步骤1:获取所有可能的开始和结束日期的列表
all_start = df %>%
select(id, start)
all_end = df %>%
select(id, start = end)
all_start_and_end = rbind(all_start, all_end) %>%
distinct()
步骤2:创建所有可能的周期的列表
all_periods = all_start_and_end %>%
group_by(id) %>%
mutate(end = lead(start, 1, order_by = start))
步骤3:将原始数据与所有周期重叠,并总结
overlapped = all_periods %>%
left_join(df, by = "id", suffix = c("_1","_2")) %>%
filter(start_1 <= end_2,
start_2 <= end_1) %>%
select(id, part_2, start = start_1, end = end_1) %>%
group_by(id, start, end) %>%
summarise(part = toString(part_2))
根据您的确切数据和情况:
- 您可能想要更改"<"至"<quot;或者从结束日期减去1天,以确保期间不重叠。这取决于您如何处理时间段的边界条件
- 您可能希望在步骤1中删除
distinct
,以允许只有一天的时段 - 在步骤1中,如果您希望输出包含
part = NA
的所有时间段,您可以添加一个非常早的日期(例如0000-01-01(和一个非常晚的日期(如9999-12-31( - 完成第三步后,您可能需要使用
part = NA
过滤掉任何周期 - 根据您的输入数据,您可以观察到具有相同
part
的相邻输出周期。例如,在第1行中:A部分的结束日期为2020-01-01,在第2行中,A部分的开始日期为2020:01-02。查看gaps-and-islands
标签以了解解决此问题的方法
我建议分别创建重叠和非重叠。如果您希望输出行的数量大于输入行的数量,这通常是必要的。
对于重叠,我会做一些类似的事情:
overlap_df = df %>%
inner_join(df, by = "id", suffix = c("_1","_2")) %>%
filter(part_1 < part_2,
start_1 <= end_2,
start_2 <= end_1) %>%
mutate(part = paste0(part_1,",",part_2), # new part label
start = ifelse(start_1 < start_2, start_2, start_1), # latest start date
end = ifelse(end_1 < end_2, end_1, end_2)) %>% # earliest end date
select(ID, part, start, end)
第一个过滤条件确保每个重叠只有一个顺序(例如,只有A,B
,没有B,A
。第二个和第三个过滤条件保证时间段重叠。
对于不重叠,我将区分从不重叠(与另一个周期没有任何重叠的周期(和不重叠(不重叠的周期部分(。
对于从不重叠的,我会做一些类似的事情:
never_overlapped_df = df %>%
left_join(df, by = "id", suffix = c("_1","_2")) %>%
filter(part_1 != part_2) %>%
mutate(overlap = ifelse(start_1 <= end_2 & start_2 <= end_2, 1, 0) %>%
group_by(id, part_1, start_1, end_1) %>%
summarise(num = sum(overlap, na.rm = TRUE)) %>%
filter(is.na(num) | num == 0) %>%
select(id, part = part_1, start = start_1, end = end_1)
这个想法是找到并计算所有的重叠,然后只保留没有任何重叠的记录。
对于非重叠的,我会做一些类似的事情:
non_overlapped_df = df %>%
inner_join(df, by = "id", suffix = c("_1","_2")) %>%
filter(part_1 != part_2,
start_1 <= end_2,
start_2 <= end_1) %>% # parts are different and periods overlap
mutate(start_2 = ifelse(start_1 <= start_2 & start_2 <= end_1, start_2, NA),
end_2 = ifelse(start_1 <= end_2 & end_2 <= end_1, end_2, NA)) %>%
# discard start_2 & end_2 that are not within start_1 and end_1
group_by(id, part_1, start_1, end_1) %>%
summarise(min_start_2 = min(start_2, na.rm = TRUE),
max_end_2 = max(end_2, na.rm = TRUE)) %>%
mutate(start = ifelse(is.na(max_end_2), start_1, max_end_2),
end = ifelse(is.na(min_start_2), end_1, min_start_2)) %>%
select(id, part = part_1, start, end)
然后,您可以将这些与rbind
:组合在一起
output_df = rbind(overlap_df, never_overlapped_df, non_overlapped_df)
请注意,我假设一次最多有一个重叠(例如,part = "A,B,C"
不会发生(。这简化了问题。解决任意数量重叠的更普遍的情况要复杂得多,需要不同的方法。
注意,您可能还想更改"<"至"<quot;或者从结束日期减去1天,以确保期间不重叠。这取决于您如何处理时间段的边界条件。