计算许多区间的并集

时间:2015-10-05 08:35:02

标签: r lubridate

我想获得许多(超过2个)间隔的并集:

df <- data.frame(id=c(1, 2, 3),
             interval=c(
               new_interval(ymd("2001-01-01"), ymd("2002-01-01")),
               new_interval(ymd("2001-01-01"), ymd("2004-01-01")),
               new_interval(ymd("2001-02-01"), ymd("2002-01-01"))
               ))
df
#   id                       interval
# 1  1 2001-01-01 UTC--2002-01-01 UTC
# 2  2 2001-01-01 UTC--2004-01-01 UTC
# 3  3 2001-02-01 UTC--2002-01-01 UTC

lubridate::union(lubridate::union(df$interval[1], df$interval[2]),
                 df$interval[3])
# [1] 2001-01-01 UTC--2004-01-01 UTC

这是正确的结果。

但为什么lubridate::union不适用于Reduce

Reduce(lubridate::union, df$interval )
# [1] 31536000 94608000 28857600

对象似乎也被转换为数字太子(在应用union之前)。

https://stackoverflow.com/questions/32909358/maintain-attributes-of-objects-of-class-lubridate-interval

相关

3 个答案:

答案 0 :(得分:4)

这不起作用的原因不是Reduce()。相反,它是as.list(),当提供的x参数不是开头的列表时,它应用于Reduce()内的x。相关的行是Reduce()中的第8行和第9行,如下所示。

head(Reduce, 9)
# ...                                                           
# 8      if (!is.vector(x) || is.object(x))                   
# 9          x <- as.list(x)                                  

快速检查if()条件可以确认这一点。

!is.vector(df$interval) || is.object(df$interval)
# [1] TRUE

因此,在as.list()的通话中df$interval使用了Reduce(),这意味着df$interval成为

as.list(df$interval)
# [[1]]
# [1] 31536000
#
# [[2]]
# [1] 94608000
#
# [[3]]
# [1] 28857600

Reduce()中发生任何重要操作之前(实际上这是我们目的最重要的操作)。这使得Reduce()输出合理;它返回所有三个,因为它们是唯一的。

如果您确实需要使用Reduce(),则可以绕过列表检查并首先使用for()循环构建您自己的列表(因为lapply()也不起作用)。然后我们可以将其提供给Reduce()并获得正确的所需输出。

x <- vector("list", length(df$interval))
for(i in seq_along(x)) x[[i]] <- df$interval[i]

Reduce(lubridate::union, x)
# [1] 2001-01-01 UTC--2004-01-01 UTC

但最好为 Interval 类编写as.list()方法并将其放在脚本的顶部。我们可以使用与上面相同的代码。

as.list.Interval <- function(x, ...) {
    out <- vector("list", length(x))
    for(i in seq_along(x)) out[[i]] <- x[i]
    out
}

Reduce(lubridate::union, df$interval)
# [1] 2001-01-01 UTC--2004-01-01 UTC

另请注意,您可以通过抓取起始位置并使用int_end()来执行此操作。

interval(min(slot(df$interval, "start")), max(int_end(df$interval)))
# [1] 2001-01-01 UTC--2004-01-01 UTC

答案 1 :(得分:0)

我不知道Reduce案例,但我会这样做:

library(dplyr)
library(stringr)

df  %>% 
  mutate(interval = str_trim(str_replace_all(interval, "(--|UTC)", " ")),
         int_start = word(interval), 
         int_end = word(interval, -1)) %>% 
  summarise(interval = str_c(min(int_start), 
                             max(int_end), 
                             sep = "--"))
# result
                interval
1 2001-01-01--2004-01-01

答案 2 :(得分:0)