问题 R - 如何根据多个因素在不同的data.table列上运行average和max并返回原始的colnames


我正在改变我的R代码 data.frame + plyr 至 data.table因为我需要一种更快,更节省内存的方法来处理大数据集。不幸的是,我的R技能非常有限,而且我整天都在撞墙。如果这里的SO专家可以启发,将不胜感激。

我的目标

  • 基于2个函数(平均值和最大值)在我的data.table中聚合行在所选列上运行(列名通过向量传递),而按列分组也通过向量传递。
  • 生成的DT应包含原始列名称。
  • 应该  是不必要的DT复制,以节省内存

我的测试代码

DT = data.table( a=LETTERS[c(1,1,1:4)],b=4:9, c=3:8, d = rnorm(6), 
                 e=LETTERS[c(rep(25,3),rep(26,3))], key="a" )

GrpVar1 <- "a"
GrpVar2 <- "e"
VarToMax <- "b"
VarToAve <- c( "c", "d")

我尝试了什么,但没有为我工作

DT[, list( b=max( b ), c=mean(c), d=mean(d) ), by=c( GrpVar1, GrpVar2 ) ]  
# Hard-code col name - not what I want

DT[, list( max( get(VarToMax) ), mean( get(VarToAve) )), by=c( GrpVar1, GrpVar2 ) ]  
# Col names become 'V1', 'V2', worse, 1 column goes missing - Not what I want either

DT[, list( get(VarToMax)=max( get(VarToMax) ), 
           get(VarToAve)=mean( get(VarToAve) ) ), by=c( GrpVar1, GrpVar2 ) ]
# Above code gave Error!

补充问题

基于我对DT的非常有限的理解, with = F 参数应该指示R解析VarToMax和VarToAve的值,但运行下面的代码会导致错误。

DT[, list( max(VarToMax), mean(VarToAve) ), by=c( GrpVar1, GrpVar2 ), with=F ]

# Error in `[.data.table`(DT, , list(max(VarToMax), mean(VarToAve)), by = c(GrpVar1,  : 
#   object 'ansvals' not found
# In addition: Warning message:
# In mean.default(VarToAve) :
#   argument is not numeric or logical: returning NA

现有的SO解决方案无济于事

Arun的  是我如何达到这一点,但我很困难。他的另一个  运用 lapply 和 .SDcols 涉及创建2个额外的DT,这不符合我的内存保存要求。

dt1 <- dt[, lapply(.SD, sum), by=ID, .SDcols=c(3,4)]
dt2 <- dt[, lapply(.SD, head, 1), by=ID, .SDcols=c(2)]

我对data.table很困惑!非常感激任何的帮助!


10682
2018-02-02 13:41


起源



答案:


这是我谦虚的尝试

DT[, as.list(c(setNames(max(get(VarToMax)), VarToMax), 
               lapply(.SD[, VarToAve, with = FALSE], mean))), 
     c(GrpVar1, GrpVar2)]    
#    a e b c          d
# 1: A Y 6 4 -0.8000173
# 2: B Z 7 6  0.2508633
# 3: C Z 8 7  1.1966517
# 4: D Z 9 8  1.7291615

或者,为了获得最大效率,您可以使用 colMeans 和 eval(as.name()) 组合代替 lapply 和 get

DT[, as.list(c(setNames(max(eval(as.name(VarToMax))), VarToMax), 
             colMeans(.SD[, VarToAve, with = FALSE]))), 
     c(GrpVar1, GrpVar2)]   
#    a e b c          d
# 1: A Y 6 4 -0.8000173
# 2: B Z 7 6  0.2508633
# 3: C Z 8 7  1.1966517
# 4: D Z 9 8  1.7291615

5
2018-02-02 14:01



该 colMeans 解决方案实际上可能并不理想。看到 这个答案 来自阿伦。 - shadow
是的,你可能是对的。值得测试多列上的内存效率 - 多次调用 .SD 通过 lapply (基本上是多次调用整个数据集,目前还没有优化),或者转换为矩阵。 - David Arenburg
我的猜测是,它取决于实际数据和(可能)取决于速度与内存效率的重要程度。 - shadow


答案:


这是我谦虚的尝试

DT[, as.list(c(setNames(max(get(VarToMax)), VarToMax), 
               lapply(.SD[, VarToAve, with = FALSE], mean))), 
     c(GrpVar1, GrpVar2)]    
#    a e b c          d
# 1: A Y 6 4 -0.8000173
# 2: B Z 7 6  0.2508633
# 3: C Z 8 7  1.1966517
# 4: D Z 9 8  1.7291615

或者,为了获得最大效率,您可以使用 colMeans 和 eval(as.name()) 组合代替 lapply 和 get

DT[, as.list(c(setNames(max(eval(as.name(VarToMax))), VarToMax), 
             colMeans(.SD[, VarToAve, with = FALSE]))), 
     c(GrpVar1, GrpVar2)]   
#    a e b c          d
# 1: A Y 6 4 -0.8000173
# 2: B Z 7 6  0.2508633
# 3: C Z 8 7  1.1966517
# 4: D Z 9 8  1.7291615

5
2018-02-02 14:01



该 colMeans 解决方案实际上可能并不理想。看到 这个答案 来自阿伦。 - shadow
是的,你可能是对的。值得测试多列上的内存效率 - 多次调用 .SD 通过 lapply (基本上是多次调用整个数据集,目前还没有优化),或者转换为矩阵。 - David Arenburg
我的猜测是,它取决于实际数据和(可能)取决于速度与内存效率的重要程度。 - shadow


与@David Arenburg类似的方式,但使用 .SDcols 为了简化表示法。我也会在合并之前显示代码。

DTaves <- DT[, lapply(.SD, mean), .SDcols = VarToAve, by = c(GrpVar1, GrpVar2)]
DTmaxs <- DT[, lapply(.SD, max), .SDcols = VarToMax, by = c(GrpVar1, GrpVar2)]
merge(DTmaxs, DTaves)
##    a e b c          d
## 1: A Y 6 4  0.2230091
## 2: B Z 7 6  0.5909434
## 3: C Z 8 7 -0.4828223
## 4: D Z 9 8 -1.3591240

或者,您可以通过子集化来一次完成此操作 .SD  和使用 with = FALSE

DT[, c(lapply(.SD[, VarToAve, with=FALSE], mean), 
       lapply(.SD[, VarToMax, with=FALSE], max)), 
   by = c(GrpVar1, GrpVar2)]
##    a e c          d b
## 1: A Y 4  0.2230091 6
## 2: B Z 6  0.5909434 7
## 3: C Z 7 -0.4828223 8
## 4: D Z 8 -1.3591240 9

6
2018-02-02 14:07



谢谢@shadow,我不知道我们可以分组 .SD 并使用 with=FALSE 在lapply本身。 - NoviceProg