分组汇总是对二维结构化数据中的某个字段(或多个字段)分组,并对组内字段进行汇总的算法,下面的例子将展示R语言实现分组汇总的几种办法。为了更加通用,例子中的分组字段是2个,汇总算法也是2种。

 案例描述:

        请将数据框orders按照CLIENT和SELLERID分组,并在组内对AMOUNT字段分别求和、求最大值。

        说明:orders存储着订单记录,其来源可以是数据库也可以是文件,比如:

orders<-read.table("sales.txt",sep="\t", header=TRUE)。其前几行数据如下:

 

ORDERID   CLIENT SELLERID  AMOUNT  ORDERDATE 
1            1  WVF Vip             5     440.0     2009-02-03 
2            2  UFS Com          13    1863.4     2009-07-05 
3            3     SWFR              2    1813.0     2009-07-08 
4            4  JFS Pep            27     670.8      2009-07-08 
5            5      DSG             15    3730.0     2009-07-09

方法1:aggregate函数

 代码:

result1<-aggregate(orders$AMOUNT, orders[,c("SELLERID","CLIENT")],sum) 
result2<-aggregate(orders$AMOUNT, orders[,c("SELLERID","CLIENT")],max) 
result<-cbind(result1,result2$x)


部分计算结果:

SELLERID   CLIENT       x        result2$x 
87           16      OLF   780.6        625.2 
88           19      OLF   144.8        144.8 
89           24      OLF  1191.2       1191.2 
90            5     PAER  1414.8       1414.8 
91            6     PAER  2545.2       2545.2

   代码解读: 

1.从名字就可以看出,aggregate是专用于分组汇总的函数,它的输入参数和计算结果都是数据框,用法相对简单。 

2.aggregate函数不能对分组后的数据进行多种汇总计算,因此要用两句代码分别实现sum和max算法,最后再用cbind拼合。显然,上述代码在性能和易用性上存在不足。 

3.aggregate函数对分组字段的顺序有一个奇怪的要求:必须反向排列。鉴于这个怪要求,先对CLIENT分组再对SELLERID分组就必须写成:orders[,c("SELLERID","CLIENT")]。如果按照正常的思维习惯写代码,结果将是错误的。 

4.不仅代码的写法违反正常的思维习惯,计算后的结果也很怪异:SELLERID字段会排在CLIENT之前。事实上,为了使计算结果更符合业务逻辑,上述的代码还要继续加工才行。 

      总结: 

      aggregate函数勉强可用,但在性能和方便性上存在不足,代码的写法、计算结果、业务逻辑这三者不一致。 

方法2:split+lapply函数 

代码: 

sp<-split(orders,orders[,c("SELLERID","CLIENT")],drop=TRUE) 
result1<-lapply(sp,FUN=function(x) sum(x$AMOUNT)) 
result2<-lapply(sp,FUN=function(x) max(x$AMOUNT)) 
result<-cbind(result1,result2)

部分计算结果 

result1 result2 
16.OLF     780.6   625.2   
19.OLF     144.8   144.8   
24.OLF     1191.2  1191.2  
5.PAER     1414.8  1414.8  
6.PAER     2545.2  2545.2

   代码解读: 

1.Split函数的作用是将数据框按照指定字段分组,但不做后续计算。lapply函数可以对每组数据都执行同样的算法。Split和lapply两者结合可以实现本案例。 

2. 由于分组后的数据可以复用,因此本算法比aggregate性能更高。 

3.Lapply函数也不支持多种统计方法,因此也要用两句代码分别实现sum和max算法,最后再用cbind拼合。另外,本算法还要额外用到split函数,因此在易用性上没有改进,反而是更差了。 

4. 分组顺序仍然要违反正常的思维习惯,必须反写成:orders[,c("SELLERID","CLIENT")]。 

5. 计算结果需要大幅加工,很不方便。可以看到,计算结果中的第一列实际上是“SELLERID.CLIENT”,我们需要把它拆分成两列并调换顺序才行。 

      总结: 

      本算法在性能上有所提高,但在易用性上明显不足,在代码写法、业务逻辑、计算结果上仍然存在不一致。 

      Lapply是apply函数族的一份子,类似的函数还有sapply和tapply。其中sapply的用法和lapply的区别只在参数上,如下: 

sp<-split(orders,orders[,c("SELLERID","CLIENT")],drop=TRUE) 
result1<-sapply(sp,simplify=FALSE,FUN=function(x) sum(x$AMOUNT)) 
result2<-sapply(sp,simplify=FALSE,FUN=function(x) max(x$AMOUNT)) 
result<-cbind(result1,result2)

      tapply专用于数据框,按理说最适合解决本案例,但事实并非如此。tapply只对单字段分组适用,在进行双字段联合分组时其结果为二维矩阵,用户还需要进行复杂的处理才行,比如tapply(orders$AMOUNT, orders[,c("SELLERID","CLIENT")],function(x) sum(x)),这句代码的计算结果是: 

CLIENT 
SELLERID ARO    BDR    BON     BSF  CHO CHOP    D  DSG 
     1        NA       NA      NA       NA    NA    NA     NA   NA 
     2        NA       NA      NA       NA    NA    NA     NA   NA 
     3        NA       NA      NA       NA    NA    NA     NA   NA 
     4        NA       NA      NA       NA    NA    NA     NA   NA 
     5        NA       NA      NA       NA   1174  NA     NA   NA 
     6        NA       NA    2564.4   NA    NA    NA     NA   NA 
     7        NA       NA      NA       NA    NA    NA     NA  288 
     8        NA       NA      NA       NA    NA    NA     NA   NA 
     9        NA       NA      NA     982.0  NA    NA     NA   NA

第三方库函数 

      用R的内置函数进行分组汇总会存在各种缺点,这时可以考虑用第三方库函数,比如Reshape、stack等。这些第三方库函数的稳定性和计算效率一般不如内置函数,而且资料较少,实现本案例有一定的难度。这里就不再一一列举了。