来源:《R核心技术手册》
数据分析项目中大多数的时间都用在了准备数据上,一个典型的项目80%的精力都花在分析而进行的发现、清洗和准备数据上。只有不到5%的精力用于分析(剩下的时间都耗在了写报告上面)。
合并数据集
数据分析中最常见的一个障碍是将存储在两个不同地方的数据组合到一起。
粘贴数据结构
R提供了几个函数可以将多个数据结构粘贴成一个数据结构。
paste
paste
函数可以将多个字符型向量连接成一个向量,默认向量的值是用空格分隔的,我们可以通过sep
参数指定分隔符号,而collapse
参数可以用来指定这些值之间的连接符号。
x <- c("a", "b", "c", "d", "e") y <- c("A", "B", "C", "E", "F")# 默认paste(x, y)## [1] "a A" "b B" "c C" "d E" "e F"# 使用自定义分隔符paste(x, y, sep = ",")## [1] "a,A" "b,B" "c,C" "d,E" "e,F"# 指定连接符paste(x, y, sep = "-", collapse = "#")## [1] "a-A#b-B#c-C#d-E#e-F"
另外,使用简化版的paste0
函数只提供连接参数,没有分隔。
paste0(x, y, collapse = "#")## [1] "aA#bB#cC#dE#eF"
rbind 和 cbind
rbind
与cbind
函数分别可以以增加行或列的形式将几个对象(矩阵或者数据框)合并起来。你可以将它想象为以垂直或者水平地将两张表拼在一起。
merge
merge(x, y, by = , by.x = , by.y = , ...)
x
与y
指定用于合并的数据框,by
对应x
和y
共有的列名,后面by.x
与by.y
用于分别指定用于合并的列名。
数据转换
数据框中常用的更改变量的函数是transform
,它定义如下:
transform(`_data`, ...)
这个函数首先要指定一个数据框,跟着是一系列的表达式,表达式中的变量是数据框中的变量,transform
函数会完成每个表达式中的计算,然后返回最终的数据框。
head(mtcars)## mpg cyl disp hp drat wt qsec vs am gear carb## Mazda RX4 21.0 6 160 110 3.90 2.62 16.5 0 1 4 4## Mazda RX4 Wag 21.0 6 160 110 3.90 2.88 17.0 0 1 4 4## Datsun 710 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1## Hornet 4 Drive 21.4 6 258 110 3.08 3.21 19.4 1 0 3 1## Hornet Sportabout 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2## Valiant 18.1 6 225 105 2.76 3.46 20.2 1 0 3 1mtcars.transformed <- transform(mtcars, newVar = disp / hp) mtcars.transformed## mpg cyl disp hp drat wt qsec vs am gear carb## Mazda RX4 21.0 6 160.0 110 3.90 2.62 16.5 0 1 4 4## Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.88 17.0 0 1 4 4## Datsun 710 22.8 4 108.0 93 3.85 2.32 18.6 1 1 4 1## Hornet 4 Drive 21.4 6 258.0 110 3.08 3.21 19.4 1 0 3 1## Hornet Sportabout 18.7 8 360.0 175 3.15 3.44 17.0 0 0 3 2## Valiant 18.1 6 225.0 105 2.76 3.46 20.2 1 0 3 1## Duster 360 14.3 8 360.0 245 3.21 3.57 15.8 0 0 3 4## Merc 240D 24.4 4 146.7 62 3.69 3.19 20.0 1 0 4 2## Merc 230 22.8 4 140.8 95 3.92 3.15 22.9 1 0 4 2## Merc 280 19.2 6 167.6 123 3.92 3.44 18.3 1 0 4 4## Merc 280C 17.8 6 167.6 123 3.92 3.44 18.9 1 0 4 4## Merc 450SE 16.4 8 275.8 180 3.07 4.07 17.4 0 0 3 3## Merc 450SL 17.3 8 275.8 180 3.07 3.73 17.6 0 0 3 3## Merc 450SLC 15.2 8 275.8 180 3.07 3.78 18.0 0 0 3 3## Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.25 18.0 0 0 3 4## Lincoln Continental 10.4 8 460.0 215 3.00 5.42 17.8 0 0 3 4## newVar## Mazda RX4 1.455## Mazda RX4 Wag 1.455## Datsun 710 1.161## Hornet 4 Drive 2.345## Hornet Sportabout 2.057## Valiant 2.143## Duster 360 1.469## Merc 240D 2.366## Merc 230 1.482## Merc 280 1.363## Merc 280C 1.363## Merc 450SE 1.532## Merc 450SL 1.532## Merc 450SLC 1.532## Cadillac Fleetwood 2.302## Lincoln Continental 2.140## [到达getOption("max.print") -- 略过16行]]
对对象的每个元素进行函数运算
apply函数簇
plyr软件包
apply函数众多,参数也有些不同,幸运的是,我们可以使用plyr包来避免这些函数的细节。
plyr包包含了12个命名与其功能有逻辑关联的函数,用于将某个函数运行在某个R对象上,并且返回结果。每个函数的输入都是一个数组、数据框或者列表,输出也都是一个数组、数据框或者列表,或者什么都不输出。
输入 | 输出矩阵 | 输出数据框 | 输出列表 | 不输出 |
---|---|---|---|---|
数组 | aaply | adply | alply | a_ply |
数据框 | daply | ddply | dlply | d_ply |
列表 | laply | ldply | llply | l_ply |
所有的函数都接受下面的参数。
参数 | 描述 | 默认值 |
---|---|---|
.data | 输入的数据对象 | |
.fun | 要运行的函数 | NULL |
.progress | 进度条类型(用create_progress构建);选项可包括“none”,“text”,“tk”和“win” | “none” |
.expand | 若.data是一个数据框,则该参数控制输出如何扩展;.expand=TRUE表示1维输出,.expand=FALSE表示n维输出 | TRUE |
.parallel | 指定是否并行地运行函数(通过foreach) | FALSE |
… | 其他传递给.fun的参数 |
举几个例子:
library(plyr) d <- data.frame(x=1:5, y=6:10)# (1)输入为列表,输出也为列表lapply(d, function(x) 2 ^ x)## $x## [1] 2 4 8 16 32## ## $y## [1] 64 128 256 512 1024# 等价命令是llplyllply(.data=d, .fun=function(x) 2^x)## $x## [1] 2 4 8 16 32## ## $y## [1] 64 128 256 512 1024# (2) 输入为矩阵,输出为列表x = as.matrix(d) apply(X = x, MARGIN = 1, FUN=paste, collapse=",")## [1] "1,6" "2,7" "3,8" "4,9" "5,10"# 等价命令aaply(.data = x, .margins = 1, .fun=paste, collapse=",")## 1 2 3 4 5 ## "1,6" "2,7" "3,8" "4,9" "5,10"# (3)输入为数据框,输出为矩阵t(sapply(d, FUN=function(x) 2 ^ x))## [,1] [,2] [,3] [,4] [,5]## x 2 4 8 16 32## y 64 128 256 512 1024# 等价命令aaply(.data=as.matrix(d), .margins = 2, .fun=function(x) 2 ^ x)## ## X1 1 2 3 4 5## x 2 4 8 16 32## y 64 128 256 512 1024
数据分段
shingle
Shingle对象是因子对象的连续性泛化,一个Shingle对象包括一个数字向量和一组间隔,各个间隔允许重叠,这种结构十分类似于屋檐上的瓦片结构。Shingle对象广泛应用于lattice包,它允许我们轻松地把条件或者分组变量作为连续变量使用。
lattice::shingle(x, intervals = unique(x))## ## Data:## [1] 1 2 3 4 5 6 7 8 9 10## ## Intervals:## min max count## 1 1 6 6## 2 2 7 6## 3 3 8 6## 4 4 9 6## 5 5 10 6## ## Overlap between adjacent intervals:## [1] 5 5 5 5
intervals
参数用来指定在什么地方分割箱子,你可以用一个数值向量来指定分割的位置,也可以使用一个两列的矩阵,每一列表示一个特定的间距。equal.count
函数可以用来创建一个shingle,每个箱子有相同个数的观测值:
lattice::equal.count(x)## ## Data:## [1] 1 2 3 4 5 6 7 8 9 10## ## Intervals:## min max count## 1 0.5 3.5 3## 2 1.5 4.5 3## 3 3.5 6.5 3## 4 4.5 7.5 3## 5 6.5 9.5 3## 6 7.5 10.5 3## ## Overlap between adjacent intervals:## [1] 2 1 2 1 2
Cut
cut
函数可以很方便地将一个连续性变量切割成很多个小片段。输入是一个数值向量,输出是一个因子,因子的每个水平对应输入向量的每个区间范围。
cut(x, breaks, labels = NULL, include.lowest = FALSE, right=TRUE, dig.lab=3, ordered_result = FALSE, ...)
例如,假设我们想要统计平均击球数据在某个范围内的选手的数量,可以使用cut
函数与table
函数:
# 读入示例数据library(nutshell)## 载入需要的程辑包:nutshell.bbdb## 载入需要的程辑包:nutshell.audioscrobblerdata("batting.2008")# 首先在数据框中加入击球平均值batting.2008.AB <- transform(batting.2008, AVG = H / AB)# 选择100 AB以上的球员(为了统计显著性)batting.2008.over100AB <- subset(batting.2008.AB, subset = (AB > 100))# 把结果分为10份battingavg.2008.bins <- cut(batting.2008.over100AB$AVG, breaks = 10) table(battingavg.2008.bins)## battingavg.2008.bins## (0.137,0.163] (0.163,0.189] (0.189,0.215] (0.215,0.24] (0.24,0.266] ## 4 6 24 67 121 ## (0.266,0.292] (0.292,0.318] (0.318,0.344] (0.344,0.37] (0.37,0.396] ## 132 70 11 5 2
利用分组变量合并对象
有时候,我们可能想要将几个相似的对象(向量或者数据框)合并成一个数据框,数据框中有一列用来表示数据的来源。lattice
包中的make.groups
函数可以实现这个功能:
library(lattice)make.groups(...)
例如我们将下面不同的向量合成一个数据框:
hat.sizes <- seq(from = 6.25, to = 7.75, by = .25) pants.sizes <- c(30:34, 36, 38, 40) shoe.sizes <- seq(from=7, to=12) lattice::make.groups(hat.sizes, pants.sizes, shoe.sizes)## data which## hat.sizes1 6.25 hat.sizes## hat.sizes2 6.50 hat.sizes## hat.sizes3 6.75 hat.sizes## hat.sizes4 7.00 hat.sizes## hat.sizes5 7.25 hat.sizes## hat.sizes6 7.50 hat.sizes## hat.sizes7 7.75 hat.sizes## pants.sizes1 30.00 pants.sizes## pants.sizes2 31.00 pants.sizes## pants.sizes3 32.00 pants.sizes## pants.sizes4 33.00 pants.sizes## pants.sizes5 34.00 pants.sizes## pants.sizes6 36.00 pants.sizes## pants.sizes7 38.00 pants.sizes## pants.sizes8 40.00 pants.sizes## shoe.sizes1 7.00 shoe.sizes## shoe.sizes2 8.00 shoe.sizes## shoe.sizes3 9.00 shoe.sizes## shoe.sizes4 10.00 shoe.sizes## shoe.sizes5 11.00 shoe.sizes## shoe.sizes6 12.00 shoe.sizes
随机抽样
有时候数据太多,或者出于统计或计算性能的原因,你想要将数据随机分为几部分构建模型(通常分为训练集、测试集和评估集)。
最简单地方法就是使用sample
函数,它可以对一个向量做随机抽样。
sample(x, size, replace = FALSE, prob = NULL)
当对数据框做sample操作时,实际返回的是列的随机抽样结果,而不是行。因为数据框是向量的列表,sample实际抽样的是这个列表的元素。所以要注意一下。
对于观察结果做行的随机抽样,需要使用sample函数创建一组行号的抽样结果,然后再使用索引选取这些行号所对应的行。比如我们随机抽样batting.2008
数据集的5条记录:
batting.2008[sample(1:nrow(batting.2008), 5), ]## nameLast nameFirst weight height bats throws debut birthYear## 478 Rodriguez Francisco 175 72 R R 2002-09-18 1982## 350 Seanez Rudy 185 70 R R 1989-09-07 1968## 1019 Francisco Ben 190 73 R R 2007-05-01 1981## 1011 Musser Neal 235 73 L L 2007-04-21 1980## 327 Riske David 180 74 R R 1999-08-14 1976## playerID yearID stint teamID lgID G G_batting AB R H 2B 3B HR## 478 rodrifr03 2008 1 LAA AL 76 4 0 0 0 0 0 0## 350 seaneru01 2008 1 PHI NL 42 41 0 0 0 0 0 0## 1019 francbe01 2008 1 CLE AL 121 121 447 65 119 32 0 15## 1011 mussene01 2008 1 KCA AL 1 0 0 0 0 0 0 0## 327 riskeda01 2008 1 MIL NL 45 44 1 0 0 0 0 0## RBI SB CS BB SO IBB HBP SH SF GIDP G_old## 478 0 0 0 0 0 0 0 0 0 0 4## 350 0 0 0 0 0 0 0 0 0 0 41## 1019 54 4 3 40 86 0 6 2 4 10 121## 1011 0 0 0 0 0 0 0 0 0 0 1## 327 0 0 0 0 0 0 0 0 0 0 44
还可以使用这种技术做更复杂的随机抽样,比如你想要随机统计3个对的情况,可以这样:
batting.2008$teamID <- as.factor(batting.2008$teamID) levels(batting.2008$teamID)## [1] "ARI" "ATL" "BAL" "BOS" "CHA" "CHN" "CIN" "CLE" "COL" "DET" "FLO"## [12] "HOU" "KCA" "LAA" "LAN" "MIL" "MIN" "NYA" "NYN" "OAK" "PHI" "PIT"## [23] "SDN" "SEA" "SFN" "SLN" "TBA" "TEX" "TOR" "WAS"# 抽样例子sample(levels(batting.2008$teamID), 3)## [1] "BAL" "SLN" "SEA"# 使用例子batting.2008.3teams <- batting.2008[is.element(batting.2008$teamID, sample(levels(batting.2008$teamID),3)), ] batting.2008.3teams## nameLast nameFirst weight height bats throws debut birthYear## 6 Ardoin Danny 218 72 R R 2000-08-02 1974## 9 Aurilia Rich 170 72 R R 1995-09-06 1971## 19 Beimel Joe 201 74 L L 2001-04-08 1977## 24 Bennett Gary 190 72 R R 1995-09-24 1972## 27 Berroa Angel 175 71 R R 2001-09-18 1978## 30 Blake Casey 200 74 R R 1999-08-14 1973## playerID yearID stint teamID lgID G G_batting AB R H 2B 3B HR## 6 ardoida01 2008 1 LAN NL 24 24 51 3 12 1 0 1## 9 aurilri01 2008 1 SFN NL 140 140 407 33 115 21 1 10## 19 beimejo01 2008 1 LAN NL 71 69 0 0 0 0 0 0## 24 bennega01 2008 1 LAN NL 10 10 21 1 4 1 0 1## 27 berroan01 2008 1 LAN NL 84 84 226 26 52 13 1 1## 30 blakeca01 2008 2 LAN NL 58 58 211 25 53 12 1 10## RBI SB CS BB SO IBB HBP SH SF GIDP G_old## 6 4 1 0 2 10 0 1 0 0 2 24## 9 52 1 1 30 56 4 1 0 2 11 140## 19 0 0 0 0 0 0 0 0 0 0 69## 24 4 0 0 2 0 0 0 0 0 1 10## 27 16 0 0 20 41 4 4 6 0 13 84## 30 23 1 0 16 52 5 4 0 2 9 58## [到达getOption("max.print") -- 略过134行]]
这个函数对于数据的各种复杂抽样非常方便,但你可能还需要用到更复杂的抽样方式,比如分层抽样、整群抽样、最大熵抽样,这些方法都可以在sampling
包中找到。
汇总函数
tapply与aggregate
tapply
函数用于向量的汇总分析,是一个非常灵活的函数。可以设置对向量X的某个子集做汇总,也可以指定汇总函数:
tapply(X, INDEX, FUN = , ..., simplify = )
比如计算各队本垒打的总数:
tapply(X=batting.2008$HR, INDEX=list(batting.2008$teamID), FUN=sum)## ARI ATL BAL BOS CHA CHN CIN CLE COL DET FLO HOU KCA LAA LAN MIL MIN NYA ## 159 130 172 173 235 184 187 171 160 200 208 167 120 159 137 198 111 180 ## NYN OAK PHI PIT SDN SEA SFN SLN TBA TEX TOR WAS ## 172 125 214 153 154 124 94 174 180 194 126 117
也可以使用返回多个值的函数,例如fivenum
计算各个联盟球员击球平均数:
tapply(batting.2008$H/batting.2008$AB, INDEX=list(batting.2008$lgID), fivenum)## $AL## [1] 0.000 0.176 0.249 0.283 1.000## ## $NL## [1] 0.0000 0.0952 0.2173 0.2680 1.0000
plyr包中没有tapply的等价功能。
by是和tapply有密切关系的一个函数,区别在于by
是用于数据框的。下面是一个例子:
by(batting.2008[, c("H", "2B", "3B", "HR")], INDICES = list(batting.2008$lgID, batting.2008$bats), FUN=mean)## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## Warning in mean.default(data[x, , drop = FALSE], ...): 参数不是数值也不是逻## 辑值:回覆NA## : AL## : B## [1] NA## -------------------------------------------------------- ## : NL## : B## [1] NA## -------------------------------------------------------- ## : AL## : L## [1] NA## -------------------------------------------------------- ## : NL## : L## [1] NA## -------------------------------------------------------- ## : AL## : R## [1] NA## -------------------------------------------------------- ## : NL## : R## [1] NA
另一个用于数据汇总的函数是aggregate
:
aggregate(x, by, FUN, ...)
也可以用于时间序列,参数略有不同。
下面看一个按球队统计击球数的例子:
aggregate(x=batting.2008[, c("AB", "H", "BB", "2B", "3B", "HR")], by=list(batting.2008$teamID), FUN=sum)## Group.1 AB H BB 2B 3B HR## 1 ARI 5409 1355 587 318 47 159## 2 ATL 5604 1514 618 316 33 130## 3 BAL 5559 1486 533 322 30 172## 4 BOS 5596 1565 646 353 33 173## 5 CHA 5553 1458 540 296 13 235## 6 CHN 5588 1552 636 329 21 184## 7 CIN 5465 1351 560 269 24 187## 8 CLE 5543 1455 560 339 22 171## 9 COL 5557 1462 570 310 28 160## 10 DET 5641 1529 572 293 41 200## 11 FLO 5499 1397 543 302 28 208## 12 HOU 5451 1432 449 284 22 167## 13 KCA 5608 1507 392 303 28 120## 14 LAA 5540 1486 481 274 25 159## 15 LAN 5506 1455 543 271 29 137## 16 MIL 5535 1398 550 324 35 198## 17 MIN 5641 1572 529 298 49 111## 18 NYA 5572 1512 535 289 20 180## 19 NYN 5606 1491 619 274 38 172## 20 OAK 5451 1318 574 270 23 125## 21 PHI 5509 1407 586 291 36 214## 22 PIT 5628 1454 474 314 21 153## 23 SDN 5568 1390 518 264 27 154## 24 SEA 5643 1498 417 285 20 124## 25 SFN 5543 1452 452 311 37 94## 26 SLN 5636 1585 577 283 26 174## 27 TBA 5541 1443 626 284 37 180## 28 TEX 5728 1619 595 376 35 194## [到达getOption("max.print") -- 略过2行]]
计数
使用tabulate
与table
函数。
reshape包另起一文单独写下,不要将内置的reshape
函数与reshape
包混淆。
作者:王诗翔
链接:https://www.jianshu.com/p/dee2cc9730b1