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

How to vectorize a loop that, for each row, sums a function of the time elapsed between the current and all prior entries of that entity ID in r

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

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

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

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

另请注意,我并不拘泥于成为 data.table;如果需要,我很乐意使用 dplyr 或其他机制来实现这一点。

我认为这可以使用具有适当的非相等连接条件的自连接来实现:

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