之前的博客《R数据分析当中的化整为零(Split-Apply-Combine)策略》有提到一些关于lapply, sapply, vapply的内容。对于tapply只是粗粗带过。
今天使用tapply的过程中遇到了一个非常令人困惑的问题,于是不得不仔细研究一下这个tapply。
tapply的使用很简单,当数据矩阵需要按其中的某一列,(或者几列,很少使用到)的内容来分组,在组内对数据需要使用指定的函数来运算时,就可以使用tapply。举个例子,有数据:
a<-data.frame(name=c("tom","sam","mik","ali"),age=c(8,9,8,9),score=c(50,100,70,90)) a name age score 1 tom 8 50 2 sam 9 100 3 mik 8 70 4 ali 9 90 |
如果我们需要按不同age来计算平均分的话,就可以使用到tapply.它的原型是,
tapply(X, INDEX, FUN = NULL, …, simplify = TRUE)
tapply(a[,"score"], a[,"age"], mean) 8 9 60 95 |
似乎很简单的东西。好了,我们现在把数据变换一下,
> a<-data.frame(name=c("sam","tom","ali","mik"),age=c(9,8,9,8),score=c(100,50,90,70)) > a name age score 1 sam 9 100 2 tom 8 50 3 ali 9 90 4 mik 8 70 > tapply(a[,"score"], a[,"age"], mean) 8 9 60 95 |
我们变换了数据的排序,但是在使用了tapply之后,它输出的结果还是与之前的一致。这一点非常有意思。这一点是否会导致一些潜在的错误呢?
我们还把数据变得更复杂一点。
> a<-data.frame(name=c("sam","tom","ali","mik"),age=c(9,8,9,8),math=c(100,50,90,70),verbal=c(90,60,96,80)) > a name age math verbal 1 sam 9 100 90 2 tom 8 50 60 3 ali 9 90 96 4 mik 8 70 80 |
我们现在需要对不同年龄段的成绩进行求平均,下面的写法是否正确呢?
> ages<-unique(a$age) > ages [1] 9 8 > b<-matrix(nrow=length(ages),ncol=2) > rownames(b)<-ages > colnames(b)<-c("math","verbal") > for(i in 1:2){b[,i]<-tapply(a[,i+2],a[,"age"],mean)} |
我们来看看结果是什么?
> b math verbal 9 60 70 8 95 93 |
其实很容易看出来,这里的rownames对应出现了错误。修改过来也很容易,
> ages<-levels(as.factor(a$age)) > ages [1] "8" "9" > rownames(b)<-ages > b math verbal 8 60 70 9 95 93 |
这一看似很容易被识别的错误,但却很容易在大量数据处理时无法识别。就是因为对tapply没有理解透。tapply的排序方法是输入factor的levels。