使用R,data.table中的多个组合按组计算均值和nmiss

时间:2017-04-22 16:37:01

标签: r data.table grouping na

我想计算具有大型数据集的多个组合的NA的平均值和计数。对于某些测试数据,这可能是最容易解释的。我在Macbook Pro上使用最新版本的R和data.table包(数据很大,> 1M行)。 (注意:在发布之后我注意到我不小心使用了sum()而不是mean()用于下面的“m =”变量。我没有编辑它,因为我不想重新运行所有内容,并且不要认为这很重要)

set.seed(4)
YR = data.table(yr=1962:2015)
ID = data.table(id=10001:11000)
ID2 = data.table(id2 = 20001:20050)
DT <- YR[,as.list(ID), by = yr] # intentional cartesian join
DT <- DT[,as.list(ID2), by = .(yr, id)] # intentional cartesian join
rm("YR","ID","ID2")
# 2.7M obs, now add data
DT[,`:=` (ratio = rep(sample(10),each=27000)+rnorm(nrow(DT)))]
DT <- DT[round(ratio %% 5) == 0, ratio:=NA] # make some of the ratios NA
DT[,`:=` (keep = as.integer(rnorm(nrow(DT)) > 0.7)) ] # add in the indicator variable
# do it again
DT[,`:=` (ratio2 = rep(sample(10),each=27000)+rnorm(nrow(DT)))]
DT <- DT[round(ratio2 %% 4) == 0, ratio2:=NA] # make some of the ratios NA
DT[,`:=` (keep2 = as.integer(rnorm(nrow(DT)) > 0.7)) ] # add in the indicator variable

所以,我所拥有的是识别信息(yr,id,id2)和我要总结的数据:keep1 | 2,ratio1 | 2。特别是通过yr-id,我想使用keep和keep2计算平均比率和ratio2(从而压缩id2)。我想通过保持/保持2计算比率和比率2或通过保持*比率,保持2 *比率,保持*比率2和保持2 *比率2的矩阵乘法来实现这一点。

首先,我这样做的方式得到了正确的答案,但速度很慢:

system.time(test1 <- DT[,.SD[keep == 1,.(m = sum(ratio,na.rm = TRUE), 
                                nmiss = sum(is.na(ratio)) )
                      ],by=.(yr,id)])
   user  system elapsed 
 23.083   0.191  23.319 

这几乎同时适用。我认为首先对主数据进行子集而不是在.SD中进行子集可能更快:

system.time(test2 <- DT[keep == 1,.SD[,.(m = sum(ratio,na.rm = TRUE), 
                                nmiss = sum(is.na(ratio)) )
                       ],by=.(yr,id)])
   user  system elapsed 
 23.723   0.208  23.963

这些方法中的任何一个的问题是我需要为每个keep变量进行单独的计算。因此我尝试了这种方式:

system.time(test3 <- DT[,.SD[,.( m = sum(ratio*keep,na.rm = TRUE),
                                 nmiss = sum(is.na(ratio*keep)) )
                       ],by=.(yr,id)])
   user  system elapsed 
 25.997   0.191  26.217 

这允许我将所有公式放在一起(我可以添加ratio*keep2ratio2*keepratio2*keep2)但是1.它更慢而且2.它没有得到正确的NA的数量(参见nmiss列):

> summary(test1)
       yr             id              m               nmiss      
 Min.   :1962   Min.   :10001   Min.   : -2.154   Min.   :0.000  
 1st Qu.:1975   1st Qu.:10251   1st Qu.: 30.925   1st Qu.:0.000  
 Median :1988   Median :10500   Median : 53.828   Median :1.000  
 Mean   :1988   Mean   :10500   Mean   : 59.653   Mean   :1.207  
 3rd Qu.:2002   3rd Qu.:10750   3rd Qu.: 85.550   3rd Qu.:2.000  
 Max.   :2015   Max.   :11000   Max.   :211.552   Max.   :9.000  
> summary(test2)
       yr             id              m               nmiss      
 Min.   :1962   Min.   :10001   Min.   : -2.154   Min.   :0.000  
 1st Qu.:1975   1st Qu.:10251   1st Qu.: 30.925   1st Qu.:0.000  
 Median :1988   Median :10500   Median : 53.828   Median :1.000  
 Mean   :1988   Mean   :10500   Mean   : 59.653   Mean   :1.207  
 3rd Qu.:2002   3rd Qu.:10750   3rd Qu.: 85.550   3rd Qu.:2.000  
 Max.   :2015   Max.   :11000   Max.   :211.552   Max.   :9.000  
> summary(test3)
       yr             id              m               nmiss      
 Min.   :1962   Min.   :10001   Min.   : -2.154   Min.   : 0.00  
 1st Qu.:1975   1st Qu.:10251   1st Qu.: 30.925   1st Qu.: 2.00  
 Median :1988   Median :10500   Median : 53.828   Median : 4.00  
 Mean   :1988   Mean   :10500   Mean   : 59.653   Mean   : 4.99  
 3rd Qu.:2002   3rd Qu.:10750   3rd Qu.: 85.550   3rd Qu.: 8.00  
 Max.   :2015   Max.   :11000   Max.   :211.552   Max.   :20.00  

通过yr-id获取我的4种摘要信息组合的最快方法是什么? 现在,我使用选项1或2重复两次(一次用于保持,再次用于keep2)

1 个答案:

答案 0 :(得分:1)

您可以直接在j

中的表达式中进行汇总
# solution A: summarize in `.SD`:
system.time({
    test2 <- DT[keep == 1,
                .SD[, .(m = sum(ratio, na.rm = TRUE),
                        nmiss = sum(is.na(ratio)))],
                by = .(yr, id), verbose = T]
})
#    user  system elapsed 
#  22.359   0.439  22.561

# solution B: summarize directly in j:
system.time({
    test2 <- DT[keep == 1, .(m = sum(ratio, na.rm = T),
                             nmiss = sum(is.na(ratio))),
                by = .(yr, id), verbose = T]
})
#    user  system elapsed 
#   0.118   0.077   0.195
添加了

verbose = T以显示两种方法之间的区别:

解决方案A:

  

lapply优化已开启,j未更改为&#39; .SD [,list(m = sum(ratio,   na.rm = TRUE),nmiss = sum(is.na(ratio)))]&#39; GForce开启了,左边是j   不变

     

旧平均优化已开启,左侧j未更改。

     

制作每个小组并运行j(GForce FALSE)......   j的结果是

     
    

命名列表。在和上创建相同的名称效率非常低     每个小组都重复一次。

  
     

当j = list(...)时,检测到任何名称,   在分组完成后移除并放回,以提高效率。   例如,使用j = transform()可以防止加速(考虑   改为:=)。此消息可能会在将来升级为警告。

     

收集不连续的群体为54000组收取0.058s   对于54000个电话,eval(j)花费了22.487秒   22.521秒

对于解决方案B:

  

...

     

从位置查找组大小(可以避免保存RAM)   ... 0秒lapply优化打开,j未更改为&#39; list(sum(ratio,   na.rm = T),sum(is.na(ratio)))&#39;

     

GForce开启,左边j不变

     

旧平均优化打开,左边j不变。制作每个小组和   运行j(GForce FALSE)......收集不连续的群体   对于54000组,0.027s eval(j)为54000次呼叫花了0.079s   0.168秒

主要区别在于B中的摘要被视为命名列表,当有许多组时(这个数据为54k组!),这是非常慢的。对于此类型的类似基准,请参阅this one

对于第二部分(你的test3): 我们首先没有按keep = 1过滤列。因此NA中的keep != nmiss也计入NA。因此,{{1}}的计数不同。