繁体   English   中英

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

[英]How to separate overlapping time periods into overlapping and non-overlapping periods in R

我正在寻找将重叠和非重叠时期与“lubridate”和“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 一个 2017-04-12 2017-04-21 2017-04-12 UTC--2017-04-21 UTC
ID 2017-04-19 2017-04-25 2017-04-19 UTC--2017-04-25 UTC

我想将它们组合成这样的重叠和非重叠时段,按“ID”和“部分”分组:

ID 部分 开始 结尾 间隔
ID 一个 2017-04-12 2017-04-18 2017-04-12 UTC--2017-04-18 UTC
ID 甲,乙 2017-04-19 2017-04-21 2017-04-19 UTC--2017-04-21 UTC
ID 2017-04-22 2017-04-25 2017-04-22 UTC--2017-04-25 UTC

我试图生成具有重叠周期的中间行,但无法使用“dplyr”package 保持非重叠周期:

df_2 <- df %>%
  group_by(id) %>%
  summarise(drug = paste(drug, collapse = ','),
            start = max(start),
            end = min(end), 
            interval = start %--% end)

非常感谢任何想法或解决方案。 谢谢!

我建议分别创建重叠和非重叠。 如果您希望 output 行数大于输入行数,这通常是必要的。

对于重叠,我会做类似的事情:

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"不会发生)。 这简化了问题。 解决任意数量重叠的更一般情况要复杂得多,并且需要不同的方法。

请注意,您可能还希望将“<=”更改为“<”或从结束日期中减去 1 天,以确保期间不重叠。 这取决于您如何处理时间段的边界条件。

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

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

第 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))

根据您的确切数据和情况:

  • 您可能需要将“<=”更改为“<”或从结束日期中减去 1 天,以确保期间不重叠。 这取决于您如何处理时间段的边界条件。
  • 您可能希望在步骤 1 中删除distinct的,以允许只有一天的时间段。
  • 如果您希望 output 包含part = NA的所有时间段,则在第 1 步中,您可以添加一个非常早的日期(例如 0000-01-01)和一个非常晚的日期(例如 9999-12-31)。
  • 一旦第三步完成,您可能想要过滤掉任何带有part = NA的句点。
  • 根据您的输入数据,您可能会观察到具有相同part的相邻 output 周期。 例如,第 1 行:A 部分的结束日期为 2020-01-01,第 2 行:A 部分的开始日期为 2020-01-02。 查看gaps-and-islands标签以了解解决此问题的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM