如何展平/合并重叠的时间段

Posted

技术标签:

【中文标题】如何展平/合并重叠的时间段【英文标题】:How to flatten / merge overlapping time periods 【发布时间】:2015-05-10 09:09:18 【问题描述】:

我有大量的时间段数据集,由“开始”和“结束”列定义。一些时期重叠。

我想组合(展平/合并/折叠)所有重叠的时间段,以获得一个“开始”值和一个“结束”值。

一些示例数据:

  ID      start        end
1  A 2013-01-01 2013-01-05
2  A 2013-01-01 2013-01-05
3  A 2013-01-02 2013-01-03
4  A 2013-01-04 2013-01-06
5  A 2013-01-07 2013-01-09
6  A 2013-01-08 2013-01-11
7  A 2013-01-12 2013-01-15

想要的结果:

  ID      start        end
1  A 2013-01-01 2013-01-06
2  A 2013-01-07 2013-01-11
3  A 2013-01-12 2013-01-15

我尝试过的:

  require(dplyr)
  data <- structure(list(ID = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L), class = "factor", .Label = "A"), 
    start = structure(c(1356998400, 1356998400, 1357084800, 1357257600, 
    1357516800, 1357603200, 1357948800), tzone = "UTC", class = c("POSIXct", 
    "POSIXt")), end = structure(c(1357344000, 1357344000, 1357171200, 
    1357430400, 1357689600, 1357862400, 1358208000), tzone = "UTC", class = c("POSIXct", 
    "POSIXt"))), .Names = c("ID", "start", "end"), row.names = c(NA, 
-7L), class = "data.frame")

remove.overlaps <- function(data)
data2 <- data
for ( i in 1:length(unique(data$start))) 
x3 <- filter(data2, start>=data$start[i] & start<=data$end[i])
x4 <- x3[1,]
x4$end <- max(x3$end)
data2 <- filter(data2, start<data$start[i] | start>data$end[i])
data2 <- rbind(data2,x4)  

data2 <- na.omit(data2)

data <- remove.overlaps(data)

【问题讨论】:

【参考方案1】:

这是一个可能的解决方案。这里的基本思想是使用cummax 函数将滞后的start 日期与“直到现在”的最大结束日期进行比较,并创建一个将数据分组的索引

data %>%
  arrange(ID, start) %>% # as suggested by @Jonno in case the data is unsorted
  group_by(ID) %>%
  mutate(indx = c(0, cumsum(as.numeric(lead(start)) >
                     cummax(as.numeric(end)))[-n()])) %>%
  group_by(ID, indx) %>%
  summarise(start = first(start), end = last(end))

# Source: local data frame [3 x 4]
# Groups: ID
# 
#   ID indx      start        end
# 1  A    0 2013-01-01 2013-01-06
# 2  A    1 2013-01-07 2013-01-11
# 3  A    2 2013-01-12 2013-01-15

【讨论】:

感谢您的精彩回答!但是问题是,当我在真实数据集上使用该函数时,日期最终以第二种格式保存,我必须将汇总变量包装在 as.POSIXct() 中以将它们转换回任何想法,为什么? 不确定你的意思...当我将结果保存在某个变量中时,startend 都属于POSIXct... 顺便说一句,如果您使用多个 ID,则必须通过排列(数据,ID,开始)进行安排,因为滞后不受分组影响,因此可能会从 ID 组之外获取日期,从而弄乱最终结构.这不是问题的一部分,但我后来发现很难。 [-n()] 有什么作用?我能够根据自己的需要进行调整(类似的情况,但日期之间允许 90 天仍然算作“重叠”),但我不得不逐字复制 [-n()] 而没有真正理解它的作用。 啊哈!我想到了。 (它正在删除cumsum 中的最后一项,以适应向量开头添加的0。)【参考方案2】:

@David Arenburg 的回答很好 - 但我遇到了一个问题,即较早的时间间隔在较晚的时间间隔后结束 - 但在 summarise 调用中使用 last 导致错误的结束日期。我建议将 first(start)last(end) 更改为 min(start)max(end)

data %>%
  group_by(ID) %>%
  mutate(indx = c(0, cumsum(as.numeric(lead(start)) >
                     cummax(as.numeric(end)))[-n()])) %>%
  group_by(ID, indx) %>%
  summarise(start = min(start), end = max(end))

此外,正如@Jonno Bourne 所提到的,在应用该方法之前,按start 和任何分组变量进行排序很重要。

【讨论】:

【参考方案3】:

为了完整起见,the IRanges package on Bioconductor 有一些简洁的函数可用于处理日期或日期时间范围。其中之一是 reduce() 函数,它合并重叠或相邻的范围。

但是,有一个缺点,因为IRanges 适用于整数范围(因此得名),因此使用IRanges 函数的便利是以DatePOSIXct 对象来回转换为代价的。

另外,dplyr 似乎与IRanges 配合得不好(至少从我对dplyr 的有限经验来看)所以我使用data.table

library(data.table)
options(datatable.print.class = TRUE)
library(IRanges)
library(lubridate)

setDT(data)[, 
  ir <- reduce(IRanges(as.numeric(start), as.numeric(end)))
  .(start = as_datetime(start(ir)), end = as_datetime(end(ir)))
, by = ID]
       ID      start        end
   <fctr>     <POSc>     <POSc>
1:      A 2013-01-01 2013-01-06
2:      A 2013-01-07 2013-01-11
3:      A 2013-01-12 2013-01-15

代码变体是

setDT(data)[, as.data.table(reduce(IRanges(as.numeric(start), as.numeric(end))))[
  , lapply(.SD, as_datetime), .SDcols = -"width"], 
  by = ID]

在这两种变体中,lubridate 包中的as_datetime() 用于在将数字转换为POSIXct 对象时指定原点。

看看IRanges 方法与David's answer 的基准比较会很有趣。

【讨论】:

除了折叠具有重叠间隔的行之外,如果我还想取另一列的最小值,我们该怎么做?例如data &lt;- structure(list(ID = structure(c(1L, 1L, 1L, 1L), .Label = "A", class = "factor"), start = structure(c(15706, 15706, 15707, 15709), class = "Date"), end = structure(c(15710, 15710, 15708, 15711), class = "Date"), value = c(3L, 7L, 8L, 5L)), class = "data.frame", row.names = c(NA, -4L)) 然后value 列给出3 @HNSKD,这应该作为一个单独的问题与它自己的minimal reproducible example一起发布,请。但一个快速的答案是:library(data.table); setDT(data)[order(start, end), grp := cumsum(cummax(shift(as.numeric(end), fill = 0)) &lt; as.numeric(start))][, .(start = min(start), end = max(end), value = min(value)), by = grp]【参考方案4】:

看来我参加聚会有点晚了,但我拿走了@zach 的代码,并在下面使用data.table 重新编写了它。我没有进行全面的测试,但这似乎比tidy 版本快20%。 (我无法测试IRange 方法,因为该包还不适用于 R 3.5.1)

此外,fwiw,接受的答案没有捕捉到一个日期范围完全在另一个日期范围内的边缘情况(例如,2018-07-072017-07-142018-05-012018-12-01 内)。 @zach 的回答确实捕捉到了这种极端情况。

library(data.table)

start_col = c("2018-01-01","2018-03-01","2018-03-10","2018-03-20","2018-04-10","2018-05-01","2018-05-05","2018-05-10","2018-07-07")
end_col = c("2018-01-21","2018-03-21","2018-03-31","2018-04-09","2018-04-30","2018-05-21","2018-05-26","2018-05-30","2018-07-14")

# create fake data, double it, add ID
# change row 17, such that each ID grouping is a little different
# also adds an edge case in which one date range is totally within another
# (this is the edge case not currently captured by the accepted answer)
d <- data.table(start_col = as.Date(start_col), end_col = as.Date(end_col))
d2<- rbind(d,d)
d2[1:(.N/2), ID := 1]
d2[(.N/2 +1):.N, ID := 2]
d2[17,end_col := as.Date('2018-12-01')]

# set keys (also orders)
setkey(d2, ID, start_col, end_col)

# get rid of overlapping transactions and do the date math
squished <- d2[,.(START_DT = start_col, 
                  END_DT = end_col, 
                  indx = c(0, cumsum(as.numeric(lead(start_col)) > cummax(as.numeric(end_col)))[-.N])),
               keyby=ID
               ][,.(start=min(START_DT), 
                    end = max(END_DT)),
                 by=c("ID","indx")
                 ]

【讨论】:

以上是关于如何展平/合并重叠的时间段的主要内容,如果未能解决你的问题,请参考以下文章

Pandas:如何在两个不同的重叠时间序列上合并两个数据帧

为合并和展平树结构设置合并

如何将两个列表合并到一个展平中是行不通的

合并或展平背景节点以提高游戏性能?

比这更简单的方法来展平或合并 php 数组?

展平和合并数组,然后将它们拆分回来