使用.SD时R data.table慢聚合

时间:2013-03-07 14:17:23

标签: r data.table

我正在对data.table(优秀包!!!)进行一些聚合,我发现.SD变量对很多东西非常有用。但是,当有许多组时,使用它会显着减慢计算速度。举个例子:

# A moderately big data.table
x = data.table(id=sample(1e4,1e5,replace=T),
               code=factor(sample(2,1e5,replace=T)),
               z=runif(1e5)
              )

setkey(x,id,code)

system.time(x[,list(code2=nrow(.SD[code==2]), total=.N), by=id])
##  user  system elapsed 
##  6.226   0.000   6.242

system.time(x[,list(code2=sum(code==2), total=.N), by=id])
## user  system elapsed 
## 0.497   0.000   0.498

system.time(x[,list(code2=.SD[code==2,.N], total=.N), by=id])
## user  system elapsed 
## 6.152   0.000   6.168

我做错了吗?我应该避免.SD支持单个列吗?提前谢谢。

2 个答案:

答案 0 :(得分:12)

  

我做错了什么,即我应该避免.SD支持单个列吗?

是的,确切地说。如果您确实使用.SD中的所有数据,请仅使用.SD。您可能还会发现nrow()的调用以及[.data.tablej的子查询调用也是罪魁祸首:使用Rprof进行确认。

查看FAQ 2.1的最后几句话:

  

FAQ 2.1如何避免写一个非常长的j表达式?你说我应该使用列名,但我有很多列。
  分组时,j表达式可以使用列名作为变量   你知道,但它也可以使用引用的保留符号.SD   每个组的Data.table的子集(不包括分组   列)。所以总结一下你的所有专栏   DT[,lapply(.SD,sum),by=grp]。这可能看起来很棘手,但它很快   写并快速运行。请注意,您不必创建匿名   功能。查看时间插图和维基以与其他人进行比较   方法。 .SD对象在内部和外部有效实现   比将参数传递给函数更有效。 请不要这样做   虽然:DT[,sum(.SD[,"sales",with=FALSE]),by=grp]。这很有效但非常   低效且不优雅。这是有意的:   DT[,sum(sales),by=grp]可能会快100倍。

另见FAQ 3.1的第​​一个项目:

  

FAQ 3.1我有20列和大量行。为什么一列的表达如此之快?
  有几个原因:
    - 只有那一列   分组,其他19个被忽略,因为data.table检查j   表达并意识到它不使用其他列。

data.table检查j并看到.SD符号时,效率增益就会消失。即使您不使用其所有列,也必须为每个组填充整个.SD子集。 data.table很难知道您实际使用的.SD哪些列(例如j可能包含if)。但是,如果你无论如何都需要它们,那当然没关系,例如DT[,lapply(.SD,sum),by=...]。这是.SD的理想用途。

所以,是的,尽可能避免.SD。直接使用列名来为data.table优化j提供最佳机会。 .SD中仅存在符号j非常重要。

这就是.SDcols被引入的原因。因此,如果您只想要一个子集,则可以告诉data.table哪些列应该在.SD中。否则,data.table将填充.SD以及所有列,以防j需要它们。

答案 1 :(得分:4)

尝试通过将计算分为两步,然后合并生成的数据框来解决此问题:

system.time({
  x2 <- x[code==2, list(code2=.N), by=id]
  xt <- x[, list(total=.N), by=id]
  print(x2[xt])
})

在我的机器上,它运行0.04秒而不是7.42秒,即比原始代码快〜200倍:

         id code2 total
   1:     1     6    14
   2:     2     8    10
   3:     3     7    13
   4:     4     5    13
   5:     5     9    18
  ---                  
9995:  9996     4     9
9996:  9997     3     6
9997:  9998     6    10
9998:  9999     3     4
9999: 10000     3     6
   user  system elapsed 
   0.05    0.00    0.04