15

我正在对 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 来支持单个列吗?提前致谢。

4

2 回答 2

14

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

对,就是这样。仅.SD当您确实在使用里面的所有数据时才使用.SD。您可能还会发现对 inside 的调用和对内部nrow()的子查询调用也是罪魁祸首:用于确认。[.data.tablejRprof

见FAQ 2.1的最后几句:

FAQ 2.1 如何避免写一个很长的 j 表达式?你说过我应该使用列名,但我有很多列。
分组时,j表达式可以使用列名作为变量,如您所知,但它也可以使用保留符号.SD,该符号引用每个组的 Data.table 的子集(不包括分组列)。所以总结一下你所有的专栏,它只是 DT[,lapply(.SD,sum),by=grp]. 这可能看起来很棘手,但它的编写速度快,运行速度快。请注意,您不必创建匿名函数。有关与其他方法的比较,请参阅时间插图和 wiki。该.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了哪些列(例如,可能包含s)。但是,如果您无论如何都需要它们,那当然没关系,例如在. 这是..SDjifDT[,lapply(.SD,sum),by=...].SD

所以,是的,.SD尽可能避免。直接使用列名给data.table的优化j最好的机会。符号的存在.SDj重要。

这就是为什么.SDcols要介绍的。因此,如果您只想要一个子集,您可以知道data.table应该在哪些列中。.SD否则,data.table将填充.SD所有列以防万一j需要它们。

于 2013-03-07T14:44:00.497 回答
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 
于 2013-03-07T14:24:29.980 回答