分组汇总是对二维结构化数据中的某个字段(或多个字段)分组,并对组内字段进行汇总的算法,下面的例子将展示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等。这些第三方库函数的稳定性和计算效率一般不如内置函数,而且资料较少,实现本案例有一定的难度。这里就不再一一列举了。