r - 以最有效的方式获取间隔期间的事件数

标签 r data.table

样本数据

我有一个包含事件的 data.table (dt),以及一个包含特定时间段内所有分钟数 (dt.minutes) 的 data.table。

dt <- data.table( id    = 1:3, 
                  start = c("2019-01-01 18:00:00", "2019-01-01 19:00:00", "2019-01-01 20:00:00"),
                  end   = c("2019-01-01 21:00:00", "2019-01-01 20:15:00", "2019-01-01 20:30:00") )
dt[, c("start", "end") := lapply( .SD, 
                                  as.POSIXct, 
                                  format = "%Y-%m-%d %H:%M:%S", 
                                  tz = "Europe/Amsterdam"),
   .SDcols = c("start", "end")]

dt.minutes <- data.table( from = seq( from = as.POSIXct( "2019-01-01 00:00:00", 
                                                         format = "%Y-%m-%d %H:%M:%S", 
                                                         tz = "Europe/Amsterdam"), 
                                      to   = as.POSIXct( "2019-01-05 00:00:00", 
                                                         format = "%Y-%m-%d %H:%M:%S", 
                                                         tz = "Europe/Amsterdam"), 
                                      by   = "1 min") )
dt.minutes[, to := from + 59 ][]

setkey( dt, start, end)
setkey( dt.minutes, from, to )

看起来像这样

> dt
   id               start                 end
1:  1 2019-01-01 18:00:00 2019-01-01 21:00:00
2:  2 2019-01-01 19:00:00 2019-01-01 20:15:00
    3:  3 2019-01-01 20:00:00 2019-01-01 20:30:00

> dt.minutes
                     from                  to
   1: 2019-01-01 00:00:00 2019-01-01 00:00:59
   2: 2019-01-01 00:01:00 2019-01-01 00:01:59
   3: 2019-01-01 00:02:00 2019-01-01 00:02:59
   4: 2019-01-01 00:03:00 2019-01-01 00:03:59
   5: 2019-01-01 00:04:00 2019-01-01 00:04:59
  ---                                        
5757: 2019-01-04 23:56:00 2019-01-04 23:56:59
5758: 2019-01-04 23:57:00 2019-01-04 23:57:59
5759: 2019-01-04 23:58:00 2019-01-04 23:58:59
5760: 2019-01-04 23:59:00 2019-01-04 23:59:59
5761: 2019-01-05 00:00:00 2019-01-05 00:00:59

问题

对于 dt.minutes 中的每一行(=分钟),我想知道在这一分钟内发生了多少来自 dt 的事件。

我可以提出两种可能的 data.table 解决方案:

setkey( dt, start, end)
setkey( dt.minutes, from, to ) 

#method 1: non-equi join
ans1 <- dt.minutes[ dt.minutes, N := {
  num = dt[ start <= i.to & end >= i.from ]
  list( nrow(num) )
}, by = .EACHI ][]

#method 2: use foverlaps, summarise on `from` and then update-join
ans2 <- dt.minutes[, N:=0L][ foverlaps( dt, copy(dt.minutes) )[, .(N =.N), by = .(from)], N := i.N, on = .(from)]

这两种方法都有效并提供了我需要的答案

all.equal( ans1, ans2 )
# [1] TRUE

但是当我查看基准时,foverlaps() 以压倒性优势获胜..

# Unit: milliseconds
#          expr       min        lq       mean    median        uq       max neval
# non_equi_join 2074.0594 2097.3363 2111.87762 2100.1306 2116.6965 2171.1653     5
# foverlaps       10.5716   10.8999   10.93622   10.9011   10.9479   11.3606     5
# 

microbenchmark::microbenchmark(
  non_equi_join = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT2, from, to )
    DT2[ DT2, N := {
      num = DT[ start <= i.to & end >= i.from ]
      list( nrow(num) )
    }, by = .EACHI ][]
  },
  foverlaps = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT, start, end)
    setkey( DT2, from, to )
    DT2[, N := 0L][ foverlaps( DT, copy(DT2) )[, .( N = .N ), by = .(from)], N := i.N, on = .(from)]
  }, times = 5L
)

问题

本着更好地理解 data.table 连接的精神,我正在寻找与 foverlaps() 相比我的连接 (ans1) 花费这么长时间(慢 200 倍)的原因(答案 2).

有没有办法提高连接的性能?或者 foverlaps() 只是针对这项工作的优化工具?

或者是否有更快的方法来实现我的目标?

最佳答案

首先,我不确定foverlaps 的默认type 是否是您想要的。 举个例子:

> foverlaps(dt.minutes, dt)[1368]
   id               start                 end                from                  to
1:  1 2019-01-01 18:00:00 2019-01-01 21:00:00 2019-01-01 21:00:00 2019-01-01 21:00:59

这确实像文档指定的那样, 但这似乎不是你想要的 (id 应该是 NA)。 您可能需要 type = "within"


我不熟悉 data.table 的内部结构, 所以以下是有根据的猜测。

在使用 by = .EACHI 时加入时进行总结是为了优化内存使用,而不是速度。 如果连接中的每个结果组都很大, 每次只实现其中的一部分可能是值得的, 但是你传递给 j 的任何代码都是 R 代码 (通常,请参阅下面的评论), 即未编译代码。 加入的基本代码可能完全用 C 语言评估, 但是如果你使用 by = .EACHI, 找到连接的匹配行可能很快, 但是评估 j 本质上变成了 R 中跨组的循环, 如果有很多小组,相关的时间开销会加起来 (就像你的问题)。

我想到了另外两个选择 (并稍微修改了设置), 我系统中的基准如下所示:

library(data.table)

dt <- data.table( id    = 1:3, 
                  start = c("2019-01-01 18:00:00", "2019-01-01 19:00:00", "2019-01-01 20:00:00"),
                  end   = c("2019-01-01 21:00:00", "2019-01-01 20:15:00", "2019-01-01 20:30:00") )
dt[, c("start", "end") := lapply( .SD, 
                                  as.POSIXct, 
                                  format = "%Y-%m-%d %H:%M:%S", 
                                  tz = "Europe/Amsterdam"),
   .SDcols = c("start", "end")]

dt.minutes <- data.table( from = seq( from = as.POSIXct( "2019-01-01 00:00:00", 
                                                         format = "%Y-%m-%d %H:%M:%S", 
                                                         tz = "Europe/Amsterdam"), 
                                      to   = as.POSIXct( "2019-01-05 00:00:00", 
                                                         format = "%Y-%m-%d %H:%M:%S", 
                                                         tz = "Europe/Amsterdam"), 
                                      by   = "1 min") )
dt.minutes[, to := from + 59 ]

library(microbenchmark)

microbenchmark::microbenchmark(
  times = 5L,
  non_equi_join = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT, start, end)
    setkey( DT2, from, to )
    DT2[ DT2, N := {
      num = DT[ start <= i.to & end >= i.from ]
      list( nrow(num) )
    }, by = .EACHI ]
  },
  foverlaps = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT, start, end)
    setkey( DT2, from, to )
    DT2[, N := 0L][ foverlaps( DT, copy(DT2) )[, .( N = .N ), by = .(from)], N := i.N, on = .(from)]
  },
  nej = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT, start, end)
    setkey( DT2, from, to )
    DT2[, N := DT[.SD, .(id, start), on = .(start <= from, end >= to), allow.cartesian = TRUE
                  ][, sum(!is.na(id)), by = "start"]$V1]
  },
  fo = {
    DT <- copy(dt)
    DT2 <- copy(dt.minutes)
    setkey( DT, start, end)
    setkey( DT2, from, to )
    DT2[, N := foverlaps(DT2, DT, type="within", which=TRUE)[, sum(!is.na(yid)), by="xid"]$V1]
  }
)
Unit: milliseconds
          expr       min        lq       mean    median        uq       max neval
 non_equi_join 2506.3448 2535.3132 2597.71440 2565.4727 2647.7538 2733.6875     5
     foverlaps   13.8878   14.3945   14.66726   14.9400   15.0491   15.0649     5
           nej   11.6391   12.0179   13.89408   13.2644   13.3602   19.1888     5
            fo   11.4082   12.7889   13.77820   12.9216   13.0430   18.7293     5

*由于我在开头提到的关于type 的内容,我的版本结果与您的不匹配。

我们可以看到它们并不比你的快多少, 但值得注意的是 nej 版本。 还使用了非等连接, 但没有 by = .EACHI。 连接的整个结果首先被具体化, 然后才汇总结果, 在这种情况下速度更快。 不幸的是我不能告诉你确切的原因 (同样,不熟悉内部结构), 但一般的经验法则应该是 by = .EACHI 应该只在你期望结果中没有几个大组时使用, 或者j中的代码是否可以通过data.table进行优化。

顺便说一句,在 fo 版本中,我使用 which = TRUE 来避免从连接返回所有列, 仅返回索引。 由于条目的数量很重要, 返回匹配的索引的工作方式类似。 在这种情况下并没有太大的不同。

*请注意,foverlaps 的文档提到通常应在 x 中提供较大的表格。

编辑:Frank 的版本似乎是最快的:

dt.minutes[, n := dt[.SD, on=.(start <= from, end >= to), allow.cartesian=TRUE, .N, by=.EACHI]$N]

关于r - 以最有效的方式获取间隔期间的事件数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58027333/

相关文章:

r - 当表通过选择列为 ("index"时,data.table 中的辅助键 "copied"属性会丢失

r - data.table 中的列类有什么限制?

r - 如何在 R Markdown 中水平绘制决策树?

r - 在水平条形图中的刻度标签之间添加部分标题

r - 使用 `ROW` 作为列名时出现 ggplot facet_wrap 错误

r - 无法使用r data.table包执行计算

检索 fread 使用的列分隔符

对巨大数据集进行重复连接

arrays - 从 R 中的数据框创建三维数组

r - 使用 R nloptr 包进行最小化 - 多重等式约束