作为对Arun 的answer 的补充,这里有一个与 OP(350 万行,80K ID)大小相似的数据集,表明键控/非键控聚合并没有太大的不同。所以加速可能是由于避免使用$ 运算符。
set.seed(10)
eg <- function(x) data.table(id=sample(8e4,x,replace=TRUE),timestamp=as.POSIXct(runif(x,min=ISOdatetime(2013,1,1,0,0,0) - 60*60*24*30, max=ISOdatetime(2013,1,1,0,0,0)),origin="1970-01-01"))
df <- eg(3.5e6)
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
unkeyed = df[,min(timestamp),by=id][,table(weekdays(V1))]
,keyed = dfk[,min(timestamp),by=id][,table(weekdays(V1))]
,times=5
)
#Unit: seconds
# expr min lq median uq max
#1 keyed 7.330195 7.381879 7.476096 7.486394 7.690694
#2 unkeyed 7.882838 7.888880 7.924962 7.927297 7.931368
由 Matthew 编辑。
其实上面几乎完全和POSIXct这个类型有关。
> system.time(dfk[,min(timestamp),by=id])
user system elapsed
8.71 0.02 8.72
> dfk[,timestamp:=as.double(timestamp)] # discard POSIXct type to demonstrate
> system.time(dfk[,min(timestamp),by=id])
user system elapsed
0.14 0.02 0.15 # that's more like normal data.table speed
恢复到 POSIXct 并使用 Rprof 显示该类型在 min() 内的 97%(即与 data.table 无关):
$by.total
total.time total.pct self.time self.pct
system.time 8.70 100.00 0.00 0.00
[.data.table 8.64 99.31 0.12 1.38
[ 8.64 99.31 0.00 0.00
min 8.46 97.24 0.46 5.29
Summary.POSIXct 8.00 91.95 0.86 9.89
do.call 5.86 67.36 0.26 2.99
check_tzones 5.46 62.76 0.20 2.30
unique 5.26 60.46 2.04 23.45
sapply 3.74 42.99 0.46 5.29
simplify2array 2.38 27.36 0.16 1.84
NextMethod 1.28 14.71 1.28 14.71
unique.default 1.10 12.64 0.92 10.57
lapply 1.10 12.64 0.76 8.74
unlist 0.60 6.90 0.28 3.22
FUN 0.24 2.76 0.24 2.76
match.fun 0.22 2.53 0.22 2.53
is.factor 0.18 2.07 0.18 2.07
parent.frame 0.14 1.61 0.14 1.61
gc 0.06 0.69 0.06 0.69
duplist 0.04 0.46 0.04 0.46
[.POSIXct 0.02 0.23 0.02 0.23
注意dfk的对象大小:
> object.size(dfk)
40.1 Mb
在data.table 中,对于这么小的尺寸,任何事情都不应该花费 7 秒!它需要大 100 倍 (4GB),j 没有缺陷,然后你可以看到 keyed by 和 ad hoc by 之间的区别。
从 Blue Magister 编辑:
考虑到 Matthew Dowle 的回答,键控/非键控命令之间存在差异。
df <- eg(3.5e6)
df[,timestamp := as.double(timestamp)]
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
unkeyed = df[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
,keyed = dfk[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
,times=10
)
#Unit: milliseconds
# expr min lq median uq max
#1 keyed 340.3177 346.8308 348.7150 354.7337 358.1348
#2 unkeyed 886.1687 888.7061 901.1527 945.6190 1036.3326