首页 > 解决方案 > 如何矢量化一个循环,该循环对于每一行,求和该实体 ID 在 r 中的当前条目和所有先前条目之间经过的时间的函数

问题描述

我有一个大的 data.table(大约 900k 行),可以用以下示例表示:

        row.id entity.id event.date result
 1:      1       100 2015-01-20     NA
 2:      2       101 2015-01-20     NA
 3:      3       104 2015-01-20     NA
 4:      4       107 2015-01-20     NA
 5:      5       103 2015-01-23     NA
 6:      6       109 2015-01-23     NA
 7:      7       102 2015-01-23     NA
 8:      8       101 2015-01-26     NA
 9:      9       110 2015-01-26     NA
10:     10       112 2015-01-26     NA
11:     11       109 2015-01-26     NA
12:     12       130 2015-01-29     NA
13:     13       100 2015-01-29     NA
14:     14       127 2015-01-29     NA
15:     15       101 2015-01-29     NA
16:     16       119 2015-01-29     NA
17:     17       104 2015-02-03     NA
18:     18       101 2015-02-03     NA
19:     19       125 2015-02-03     NA
20:     20       130 2015-02-03     NA

本质上,我的列包含:代表相关实体的 ID(entity.id);此 ID 参与的事件的日期(请注意,许多且不同数量的实体将参与每个事件)。我需要计算一个因子,对于每个事件日期的每个 entity.id,它(非线性)取决于自输入该实体 ID 的所有先前事件以来经过的时间(以天为单位)。

换句话说,在 data.table 的每一行上,我需要找到所有具有匹配 ID 并且日期早于相关行的事件日期的实例,计算出时间差(以天为单位)在“当前”事件和历史事件之间,并对应用于每个时间段的一些非线性函数求和(在本例中我将使用正方形)。

在上面的示例中,对于 2015 年 3 月 2 日(第 18 行)上的 entity.id = 101,我们需要回顾该 ID 在第 15、8 和 2 行的先前条目,计算与“当前事件(14、8 和 5 天),然后通过将这些时间段的平方求和来计算答案 (14^2 + 8^2 + 5^2) = 196 + 64 + 25 = 285。(实函数稍微复杂一些,但这具有足够的代表性。)

使用 for 循环可以轻松实现这一点,如下所示:

# Create sample dt
dt <- data.table(row.id = 1:20,
     entity.id = c(100, 101, 104, 107, 103, 109, 102, 101, 110, 112,
                   109, 130, 100, 127, 101, 119, 104, 101, 125, 130),
     event.date = as.Date(c("2015-01-20", "2015-01-20", "2015-01-20", "2015-01-20", 
                    "2015-01-23", "2015-01-23", "2015-01-23",
                    "2015-01-26", "2015-01-26", "2015-01-26", "2015-01-26",
                    "2015-01-29", "2015-01-29", "2015-01-29", "2015-01-29", "2015-01-29",
                    "2015-02-03", "2015-02-03", "2015-02-03", "2015-02-03")),
     result = NA)
setkey(dt, row.id)

for (i in 1:nrow(dt)) { #loop through each entry

  # get a subset of dt comprised of rows with this row's entity.id, which occur prior to this row
  event.history <- dt[row.id < i & entity.id == entity.id[i]]

  # calc the sum of the differences between the current row event date and the prior events dates, contained within event.history, squared
  dt$result[i] <- sum( (as.numeric(dt$event.date[i]) - as.numeric(event.history$event.date)) ^2 )
}

不幸的是,在真实数据集上它也非常慢,毫无疑问是因为如果需要大量的子集操作。有没有办法矢量化或加速此操作?我已经搜索和搜索并绞尽脑汁,但无法弄清楚如何根据每行的不同数据在不循环的情况下对行进行子集化。

请注意,我创建了一个 row.id 列以允许我提取所有先前的行(而不是先前的日期),因为两者大致相同(一个实体一天不能参加超过一个活动)并且这种方式要快得多(我认为因为它避免了在进行比较之前将日期强制转换为数字的需要,即 Dt[as.numeric(event_date) < as.numeric(event_date[i])]

另请注意,我并不喜欢它是一个 data.table;如果需要,我很乐意使用 dplyr 或其他机制来实现这一点。

标签: rdatefor-loopvectorization

解决方案


我认为这可以使用具有适当的非 equi 连接标准的自连接来实现:

dt[, result2 := dt[
                   dt,
                   on=c("entity.id","event.date<event.date"),
                   sum(as.numeric(x.event.date - i.event.date)^2), by=.EACHI]$V1
                  ]
dt

这会给出与循环输出匹配的结果,但NA值除外:

#    row.id entity.id event.date result result2
# 1:      1       100 2015-01-20      0      NA
# 2:      2       101 2015-01-20      0      NA
# 3:      3       104 2015-01-20      0      NA
# 4:      4       107 2015-01-20      0      NA
# 5:      5       103 2015-01-23      0      NA
# 6:      6       109 2015-01-23      0      NA
# 7:      7       102 2015-01-23      0      NA
# 8:      8       101 2015-01-26     36      36
# 9:      9       110 2015-01-26      0      NA
#10:     10       112 2015-01-26      0      NA
#11:     11       109 2015-01-26      9       9
#12:     12       130 2015-01-29      0      NA
#13:     13       100 2015-01-29     81      81
#14:     14       127 2015-01-29      0      NA
#15:     15       101 2015-01-29     90      90
#16:     16       119 2015-01-29      0      NA
#17:     17       104 2015-02-03    196     196
#18:     18       101 2015-02-03    285     285
#19:     19       125 2015-02-03      0      NA
#20:     20       130 2015-02-03     25      25

推荐阅读