将聚合字段添加到另一个 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
我写了更多关于这个成语的细节。
我有一个带有主题 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
我写了更多关于这个成语的细节