如何将R中的重叠时间段划分为重叠和非重叠时间段



我正在寻找将重叠和非重叠周期与"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))

数据帧如下所示:

开始结束2017-04.12 UTC--2017-04-21 UTC2017 04-19 UTC-2017 04-25 UTC
id部分间隔
IDA2017-04-122017-03-21
IDB2017-04-192017-03-25

我的第一个答案假设只有两个周期重叠。这意味着它可以对每个比较使用一个联接。尝试重复此过程超过两个时间段会导致联接数量增加,从而导致效率低下的混乱局面。

为了处理连接任意(或未知(数量的重叠,我们需要一种非常不同的方法。因此,我将此作为一个单独的答案提供。

步骤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天,以确保期间不重叠。这取决于您如何处理时间段的边界条件。

相关内容

  • 没有找到相关文章

最新更新