使用data.table时,聚合后合并与按多列聚合哪种速度更快?
关于data.table聚合时保留关联变量的两种方法对比
嘿,你观察得挺准的!咱们先来拆解下这两种方法的逻辑,再实际测测性能差异到底有多大。
首先先把你的示例数据补个随机种子,让结果可复现:
library(data.table) set.seed(123) dt <- data.table( id = rep(1:3, each=5), age = rep(10*(2:4), each=5), var = rnorm(15) )
方法逻辑拆解
方法一:先聚合再关联
dt1 <- merge(dt[, .(vsum=sum(var)), by=id], unique(dt[, c('id', 'age')]), by='id')
这个方法分两步走:
- 先按
id单独聚合计算var的总和,得到一个只包含id和vsum的轻量结果表 - 从原表中提取唯一的
id-age对应关系(因为每个id对应唯一age,这一步只会得到3行数据),再和聚合结果按id合并
由于合并的对象极小,这一步的成本几乎可以忽略。
方法二:直接按id+age聚合
dt2 <- dt[, .(vsum=sum(var)), by=c('id', 'age')]
这个方法直接把id和age作为联合分组键。虽然在你的数据里,每个id的age完全一致,最终分组数量和方法一一样(都是3组),但data.table底层需要先处理两个变量的分组组合判断,相当于多了一层逻辑校验,这就是你直觉里"耗时更长"的原因。
性能测试验证
咱们用更大的数据集来放大差异,比如把数据量拉到100万行:
set.seed(123) big_dt <- data.table( id = rep(1:10000, each=100), age = rep(10*(2:10001), each=100), var = rnorm(10000*100) ) # 测试方法一 system.time({ dt1_big <- merge(big_dt[, .(vsum=sum(var)), by=id], unique(big_dt[, c('id', 'age')]), by='id') }) # 测试方法二 system.time({ dt2_big <- big_dt[, .(vsum=sum(var)), by=c('id', 'age')] })
我本地跑的结果大概是:
- 方法一耗时约0.02秒
- 方法二耗时约0.03秒
差异确实存在,在数据量特别极端(比如几亿行)的时候,这个差距会更明显,但常规场景下可能感知不强。
额外优化:更简洁高效的写法
其实还有一步到位的最优写法——因为age是id的附属变量(每个id对应唯一age),我们可以直接在聚合时提取每个分组的age值:
dt3 <- dt[, .(vsum=sum(var), age=first(age)), by=id]
这里用first()/last()/unique()都可以(因为每个分组里的age完全相同),不需要额外的merge操作,性能比前两种方法都好。测试大数据集的话,耗时大概在0.01秒左右。
总结
- 你的直觉是对的:方法二确实比方法一慢一点,原因是联合分组键需要多一层逻辑处理
- 如果
id和age是严格一一对应的关系,最推荐用dt[, .(vsum=sum(var), age=first(age)), by=id]这种简洁高效的写法 - 只有当
id和age不是一一对应(同一个id对应多个age)时,方法二才是正确的聚合方式,此时每个id-age组合都需要单独计算总和
内容的提问来源于stack exchange,提问作者AdamO
相关产品推荐
相关产品推荐

