R语言小白学习笔记6—分组操作
- 笔记链接
- 学习笔记6—分组操作
- 6.1 apply函数族
- 6.1.1apply函数
- 6.1.2 lapply和sapply函数
- 6.1.3 mapply函数
- 6.2 aggregate函数
- 6.3 plyr包
- 6.3.1 ddply函数
- 6.3.2 plyr的辅助函数
- 6.4 data.table包
笔记链接
学习笔记1—R语言基础.
学习笔记2—高级数据结构.
学习笔记3—R语言读取数据.
学习笔记4—统计图.
学习笔记5—编写R语言函数和简单的控制循环语句.
学习笔记6—分组操作
数据分析中数据处理会占据大部分的工作时间,经常需要“分离—应用—合并”的操作,R语言中有许多不同的迭代数据的方法。
6.1 apply函数族
6.1.1apply函数
apply函数只能用于矩阵,即所有元素必须是同类型的数据。如果在其他对象(如数据框)上使用,apply函数会先将其转化为矩阵。
apply函数第一个参数是操作的矩阵对象,第二个参数是应用函数的维度,1代表对行进行操作,2代表对列,第三个参数是处理数据所调用的函数。
例:求矩阵行或者列的和
> theMatrix <- matrix(1:9, nrow=3)
> apply(theMatrix, 1, sum)
[1] 12 15 18
> theMatrix
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> apply(theMatrix, 2, sum)
[1] 6 15 24
即使向量中只有一个元素是NA,它的求和结果也是NA,所以可以通过设置参数na.rm=TRUE,这样会把NA元素去掉再相加。
例:
> theMatrix[2, 1] <- NA
> theMatrix
[,1] [,2] [,3]
[1,] 1 4 7
[2,] NA 5 8
[3,] 3 6 9
> apply(theMatrix, 1, sum)
[1] 12 NA 18
> apply(theMatrix, 1, sum, na.rm=TRUE)
[1] 12 13 18
6.1.2 lapply和sapply函数
lapply函数的工作原理是将某个函数应用到一个列表的每个元素,并将结果作为list返回
> theList <- list(A=matrix(1:9, 3), B=1:5, C=matrix(1:4, 2), D=2)
> lapply(theList, sum)
$A
[1] 45
$B
[1] 15
$C
[1] 10
$D
[1] 2
sapply函数可以将结果作为一个向量返回
> sapply(theList, sum)
A B C D
45 15 10 2
6.1.3 mapply函数
mapply函数能够将某个函数应用到多个列表的每个元素。
这种情况我们通常习惯用循环,但mapply函数更简单。
> firstList <- list(A=matrix(1:16, 4), B=matrix(1:16, 2), C=1:5)
> secondList <- list(A=matrix(1:16, 4), B=matrix(1:16, 8), C=15:1)
> mapply(identical, firstList, secondList)
A B C
TRUE FALSE FALSE
#identical是判断其是否相同
> simpleFunc <- function(x, y)
+ {
+ NROW(x) + NROW(y)
+ }
> mapply(simpleFunc, firstList, secondList)
A B C
8 10 20
6.2 aggregate函数
aggregate函数支持聚合和分组操作,调用aggregate函数的方式很多,最方便:formula(翻译:公式)
formula包括被符号“~”分开的两部分,左侧是待操作的变量,右侧是以其为依据进行分组的一个或多个变量。
例:这里使用ggplot2包中的diamonds数据
> data(diamonds, package = 'ggplot2')
> head(diamonds)
carat cut color clarity depth table price x
1 0.23 Ideal E SI2 61.5 55 326 3.95
2 0.21 Premium E SI1 59.8 61 326 3.89
3 0.23 Good E VS1 56.9 65 327 4.05
4 0.29 Premium I VS2 62.4 58 334 4.20
5 0.31 Good J SI2 63.3 58 335 4.34
6 0.24 Very Good J VVS2 62.8 57 336 3.94
y z
1 3.98 2.43
2 3.84 2.31
3 4.07 2.31
4 4.23 2.63
5 4.35 2.75
6 3.96 2.48
之后计算cut各种类型的平均价格。
aggregate函数第一个参数是formula,它指定price变量按照cut变量的值分组,第二个参数是操作的数据,第三个参数是应用于数据的函数。
> aggregate(price ~ cut, diamonds, mean)
cut price
1 Fair 4358.758
2 Good 3928.864
3 Very Good 3981.760
4 Premium 4584.258
5 Ideal 3457.542
根据多个变量分组数据:
> aggregate(price ~ cut + color, diamonds, mean)
cut color price
1 Fair D 4291.061
2 Good D 3405.382
3 Very Good D 3470.467
4 Premium D 3631.293
5 Ideal D 2629.095
6 Fair E 3682.312
7 Good E 3423.644
8 Very Good E 3214.652
9 Premium E 3538.914
10 Ideal E 2597.550
11 Fair F 3827.003
12 Good F 3495.750
13 Very Good F 3778.820
14 Premium F 4324.890
15 Ideal F 3374.939
16 Fair G 4239.255
17 Good G 4123.482
18 Very Good G 3872.754
19 Premium G 4500.742
20 Ideal G 3720.706
21 Fair H 5135.683
22 Good H 4276.255
23 Very Good H 4535.390
24 Premium H 5216.707
25 Ideal H 3889.335
26 Fair I 4685.446
27 Good I 5078.533
28 Very Good I 5255.880
29 Premium I 5946.181
30 Ideal I 4451.970
31 Fair J 4975.655
32 Good J 4574.173
33 Very Good J 5103.513
34 Premium J 6294.592
35 Ideal J 4918.186
如果要聚合两个变量,可以用cbind函数:
> aggregate(cbind(price, carat) ~ cut, diamonds, mean)
cut price carat
1 Fair 4358.758 1.0461366
2 Good 3928.864 0.8491847
3 Very Good 3981.760 0.8063814
4 Premium 4584.258 0.8919549
5 Ideal 3457.542 0.7028370
但aggregate函数运行很慢,可以用plyr、dplyr和data.table以更快运行。
6.3 plyr包
plyr包的核心是ddply、llply和ldply等函数,其后三个字母总是ply,第一个字母表示输入数据类型,第二个字母表示输出数据类型。
6.3.1 ddply函数
ddply函数输入类型为数据框,根据指定变量对数据集分类并进行相应的运算,然后返回一个数据框。
例:这次引用plyr包中的数据集baseball
> library(plyr)
> head(baseball)
id year stint team lg g ab r h X2b X3b hr
4 ansonca01 1871 1 RC1 25 120 29 39 11 3 0
44 forceda01 1871 1 WS3 32 162 45 45 9 4 0
68 mathebo01 1871 1 FW1 19 89 15 24 3 1 0
99 startjo01 1871 1 NY2 33 161 35 58 5 1 1
102 suttoez01 1871 1 CL1 29 128 35 45 3 7 3
106 whitede01 1871 1 CL1 29 146 40 47 6 5 1
rbi sb cs bb so ibb hbp sh sf gidp
4 16 6 2 2 1 NA NA NA NA NA
44 29 8 0 4 0 NA NA NA NA NA
68 10 2 1 2 0 NA NA NA NA NA
99 34 4 2 3 0 NA NA NA NA NA
102 23 3 1 1 0 NA NA NA NA NA
106 21 2 2 4 1 NA NA NA NA NA
现在对数据进行处理,目的是计算每个球员在其职业生涯的OBP指标(上垒率)
计算公式:OBP=(H+BB+HBP)/(AB+BB+HBP+SF)
1954年前,SF(高飞牺牲打)算作牺牲打的一部分,所以1954年前的SF设为0.
其次原始数据许多缺失部分,我们也将其设为0.
另外,剔除掉一个赛季小于50打数的球员数据:
> baseball$sf[baseball$year < 1954] <- 0
> any(is.na(baseball$sf))
[1] FALSE
> baseball$hbp[is.na(baseball$hbp)] <- 0
> any(is.na(baseball$hbp))
[1] FALSE
> baseball <- baseball[baseball$ab >= 50,]
计算每个球员在指定年份的OBP只需进行向量操作:
> baseball$OBP <- with(baseball, (h + bb + hbp) / (ab + bb + hbp + sf))
> tail(baseball)
id year stint team lg g ab r h X2b
89499 claytro01 2007 1 TOR AL 69 189 23 48 14
89502 cirilje01 2007 1 MIN AL 50 153 18 40 9
89521 bondsba01 2007 1 SFN NL 126 340 75 94 14
89523 biggicr01 2007 1 HOU NL 141 517 68 130 31
89530 ausmubr01 2007 1 HOU NL 117 349 38 82 16
89533 aloumo01 2007 1 NYN NL 87 328 51 112 19
X3b hr rbi sb cs bb so ibb hbp sh sf gidp
89499 0 1 12 2 1 14 50 0 1 3 3 8
89502 2 2 21 2 0 15 13 0 1 3 2 9
89521 0 28 66 5 0 132 54 43 3 0 2 13
89523 3 10 50 4 3 23 112 0 3 7 5 5
89530 3 3 25 6 1 37 74 3 6 4 1 11
89533 1 13 49 3 0 27 30 5 2 0 3 13
OBP
89499 0.3043478
89502 0.3274854
89521 0.4800839
89523 0.2846715
89530 0.3180662
89533 0.3916667
这里使用了新函数with,该函数可以对指定数据框的列进行操作,且无需每次操作都指明数据框的名称。
为了计算每个球员在其整个职业生涯的OBP指标,我们需要先对分子上的变量进行求和,再除以分母上的变量之和。(指OBP公式的分子分母)
首先定义一个函数完成上述功能,再使用ddply函数进行计算。
> obp <- function(data)
+ {
+ c(OBP=with(data, sum(h + bb + hbp) / sum(ab + bb + hbp + sf)))
+ }
> careerOBP <- ddply(baseball, .variables = "id", .fun = obp)
> careerOBP <- careerOBP[order(careerOBP$OBP, decreasing = TRUE),]
> head(careerOBP, 10)
id OBP
1089 willite01 0.4816861
875 ruthba01 0.4742209
658 mcgrajo01 0.4657478
356 gehrilo01 0.4477848
85 bondsba01 0.4444622
476 hornsro01 0.4339068
184 cobbty01 0.4329655
327 foxxji01 0.4290509
953 speaktr01 0.4283386
191 collied01 0.4251246
6.3.2 plyr的辅助函数
plyr包有很多辅助函数,如:
each函数,能够在使用像aggregate这样的函数时调用多个函数。缺点:使用each函数时不能为调用的函数添加额外的参数。
> aggregate(price ~ cut, diamonds, each(mean, median))
cut price.mean price.median
1 Fair 4358.758 3282.000
2 Good 3928.864 3050.500
3 Very Good 3981.760 2648.000
4 Premium 4584.258 3185.000
5 Ideal 3457.542 1810.000
idata.frame函数,该函数创建了一个数据框的引用地址,使得取子集更快,并且能够更有效地利用内存。
哈哈,这里我用例子试了一下发现结果正好相反,但又输入一遍发现时间相同,可能是受到数据集大小的影响吧。
> system.time(dlply(baseball, "id", nrow))
用户 系统 流逝
0.07 0.00 0.08
> iBaseball <- idata.frame(baseball)
> system.time(dlply(iBaseball, "id", nrow))
用户 系统 流逝
0.10 0.02 0.11
> system.time(dlply(iBaseball, "id", nrow))
用户 系统 流逝
0.09 0.00 0.10
> system.time(dlply(baseball, "id", nrow))
用户 系统 流逝
0.10 0.00 0.09
6.4 data.table包
data.table包扩展和增强了data.frame的功能。(data.frame在高级数据结构这章笔记里介绍过)
data.table包运行快的原因是它有类似于数据库一样的索引,这使得其在获取值、分组操作和合并时访问速度更快。
因为data.table包里边的例子和前边都相同,只是比较了使用时间和数据类型,所以就不举例了。