将聚合字段添加到另一个 table 的 data.table

Adding an aggregated field to a data.table from another table

我有一个带有主题 ID 的小 table 和一个带有这些主题的“事件”的大 table(即每条记录包含事件日期、主题 ID 和许多其他详细信息) 像这样:

set.seed(8)

n <- 20L
dt.subjects <- data.table(sbjID=c(sample(LETTERS,n/2),sample(letters,n/2)))

N <- 1e7L
dt.events <- data.table(
  sbjID  =sample(LETTERS,N,T),
  relDate=sample(seq_len(1000),N,replace=T)
  )
dt.events[, evDate:=as.Date('1990-01-01') + relDate]

#    sbjID relDate     evDate
# 1:     L     975 1992-09-02
# 2:     G     231 1990-08-20
# 3:     H     379 1991-01-15
# 4:     S     916 1992-07-05
# 5:     F     619 1991-09-12
# 6:     G     200 1990-07-20

现在我想向第一个 table 添加一些计算字段(例如,每个主题的最后一个事件)。 早期对data.table语法不是很熟悉的时候,我用的是merge():

dt.subjects <- merge(
  dt.subjects,
  dt.events[,.(lastDate=max(evDate)), by=sbjID],
  by = 'sbjID',
  all.x=T
)

它完成了工作,但看起来很丑陋,如果 lastDate 列已经存在,也会造成麻烦。 现在我使用以下内容:

dt.subjects[, 
        lastDate:=(dt.events[sbjID,max(evDate), on='sbjID']), 
        by=sbjID] 

我觉得这个lastDate:=merge()读起来直观多了。 但是……慢多了! (当我使用这里给出的虚拟数据集时慢了 5 倍,在我的实际数据集中慢了 20 倍) 有没有比我的第二个更快但比第一个更不丑陋的解决方案?

如果我们想要比第二个更快的选项,

system.time({
   dt.subjects2[dt.Events[, .(lastDate = max(evDate)), by = sbjID], 
              lastDate := lastDate, on = .(sbjID)]


})
user  system elapsed 
  0.339   0.016   0.355 

dt.subjects2$lastDate <- NULL
system.time({
dt.subjects2[dt.Events, lastDate := max(evDate), on = .(sbjID), by = .EACHI]
 })
   user  system elapsed 
 17.683   0.078  17.705 

system.time({
 out <- merge(
  dt.subjects,
  dt.Events[,.(lastDate=max(evDate)), by=sbjID],
  by = 'sbjID',
  all.x=TRUE
)


})
 user  system elapsed 
  0.335   0.022   0.357 

使用fmax(来自collapse)可以提高效率

library(collapse)
 system.time({
   tmp <- fmax(dt.Events$evDate, g = dt.Events$sbjID)
   tmp <- data.table(sbjID = names(tmp), 
           lastDate = as.Date(tmp, origin = '1970-01-01'))
   
   dt.subjects2[tmp, lastDate := lastDate, on = .(sbjID)]
 
 
 })
   user  system elapsed 
  0.143   0.005   0.147 

数据

n <- 20L
set.seed(24)
dt.subjects <- data.table(sbjID=c(sample(LETTERS,n/2),sample(letters,n/2)))

N <- 1e7L
dt.Events <- data.table(
  sbjID  =sample(LETTERS,N,T),
  relDate=sample(seq_len(1000),N,replace=T)
  )
dt.Events[, evDate:=as.Date('1990-01-01') + relDate]



dt.subjects2 <- copy(dt.subjects)

OP 的代码为每个组做一个连接:

system.time(
    dt.subjects[, 
        lastDate:=(dt.events[sbjID,max(evDate), on='sbjID']), 
    by=sbjID] 
)
#    user  system elapsed 
#    2.82    0.50    1.31 

使用 by=.EACHI 我们可以将它放到一个连接中:

system.time(
    dt.subjects[, 
        lastDate := dt.events[.SD, max(evDate), on='sbjID', by=.EACHI]$V1
    ] 
)
#    user  system elapsed 
#    0.18    0.05    0.08 

我写了更多关于这个成语的细节