dplyr是由Hadley Wickham主持开发和维护的一个主要针对数据框快速计算、整合的函数包,同时提供一些经常使用函数的高速写法以及几个开源数据库的链接。此包是plyr包的深化功能包,其名字中的字母“d”即来源于data frame,以示其专一于数据框数据的整理和操做。咱们将在本章中着重介绍一些数据处理方面的经常使用功能函数。shell
1.1管道函数数据库
在前面的简介中,咱们计算了cran上的可用的函数包的数量:app
> contrib.url("http://mirrors.xmu.edu.cn/CRAN","source")%>%available.packages%>%nrow函数
[1] 6450工具
在以上的代码中,咱们使用了“%>%”这个符号(或者说函数),这能够被称为管道函数。管道函数在其余的语言(好比shell)中也常常被使用,而其余的函数包中也有相似的功能函数(pipeR包和magrittr包),咱们稍后也会对magrittr包中管道函数作介绍。性能
“%>%”这个管道函数把左件的值发送给右件的表达式,并做为右件表达式函数的第一个参数。测试
左件%>%右件url
一般来讲,能够把“%>%”读做then,即而后。component
若是你须要同时操做多个数据集或者多个函数时,使用管道函数将会更加方便、快速和有逻辑性。好比以上的计算函数包数量的代码若改成通常写法:orm
nrow(available.packages(contrib.url("http://mirrors.xmu.edu.cn/CRAN","source")))
或者是这样:
x=contrib.url("http://mirrors.xmu.edu.cn/CRAN","source")
x= available.packages (x)
nrow(x)
对比这三段代码,第二段代码包含了3对括号,可读性比较差;第三段代码将之分为三句,代码量增长,而且留下了x这个中间产物;而第一段代码乍一看很长,但实际上对于代码的运行、传递机制表现得很清晰,各阶段的函数分工明确,逻辑清楚,若是习惯使用后,可用性至关好。
值得一提的是,管道函数还能够用在自定义函数(function)中,好比咱们定义一个对向量中的数求整后取绝对值求和的函数。
通常代码:
> f1=function(x)sum(abs(round(x)))
管道函数代码
> f2=.%>%round%>abs%>%sum
咱们来看看源码的显示:
> f1
function(x)sum(abs(round(x)))
> f2
Functional sequence with the following components:
1. round(.)
2. abs(.)
3. sum(.)
Use 'functions' to extract the individual functions.
毫无疑问的是,二者并无什么不一样,来看下面的结果。
> set.seed(1000)
> a=rnorm(20,10,10)
> a%>f1
[1] 161
> a%>f2
[1] 161
固然,R中并不只仅只有这一个管道函数,一样由Hadley Wickham开发的magrittr包中介绍了其余功能的管道函数。
函数名 功能
%<>% 在%>%的基础上,会把右件的最终返回值返回给左件(注意是最终)。
%T>% 把左件的值传入后,不产生任何返回值(你能够对计算的中间过程画个图,再接着计算)。
%$% 选取左件中的任意个变量名来操做,但左件中原来的数据将不会被保存,仅剩下计算后的值。
> test=data.frame(x=a,y=sample(0:1,20,replace=T))%T>%
+ plot%$%
+ f2(x)
> test%<>%"*"(10)
以上代码简单地运用了上述3个管道函数,第一行先构建一个数据框对数据框画散点图并对x变量运算赋值给test,第二行对test乘以10倍。
管道函数是一个很是方便快捷的工具,在本书后续的其余程序里,咱们将大量地使用管道函数,以增长代码的可读性,减小代码量。
1.2基础函数
在数据分析中,咱们每每受限于分析目标和具体实现两个瓶颈。在平常交流中,数据分析师们也常常表示本身大概会分配70%-80%的项目时间用于数据处理,毫无疑问,Hadley Wickham所开发的一系列函数包也都围绕着如何减小数据处理时间这个目标来进行。可以快捷方便地表达并实现本身心中的目标,才有更多的时间用于思考和分析。
dplyr包的函数能处理很大一部分结构化数据处理场景中所需的功能,筛选、排序、变量选择、变形、汇总等。
1.2.1 filter筛选
filter按照筛选条件或逻辑筛选出符合目标的子集,与base中的subset十分类似,不过跟其余dplyr包中的基础函数同样,filter中能够直接调用数据框中的变量名,而无需attach或者使用”$”。
举个栗子:iris数据集中Species不等于setosa和virginica,且Sepal.Width大于等于3.2.
> iris%>%filter(!Species%in%c("setosa","virginica"),Sepal.Width>=3.2)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 7.0 3.2 4.7 1.4 versicolor
2 6.4 3.2 4.5 1.5 versicolor
3 6.3 3.3 4.7 1.6 versicolor
4 5.9 3.2 4.8 1.8 versicolor
5 6.0 3.4 4.5 1.6 versicolor
1.2.2 arrange排序
arrange能够根据变量名依次对数据框进行排序,靠前的变量优先级越高,对变量名使用desc函数即为倒序。plyr(咱们之后会介绍的一个包,一样出品自Hadley Wickham)中也有一个相同的此函数。
在R的base中,可使用order来实现相同功能。
> arrange(mtcars, cyl, disp)%>%head(3)
mpg cyl disp hp drat wt qsec vs am gear carb
1 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1
2 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2
3 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1
> arrange(mtcars, desc(disp))%>%head(3)
mpg cyl disp hp drat wt qsec vs am gear carb
1 10.4 8 472 205 2.93 5.250 17.98 0 0 3 4
2 10.4 8 460 215 3.00 5.424 17.82 0 0 3 4
3 14.7 8 440 230 3.23 5.345 17.42 0 0 3 4
Base写法:
mtcars[with(mtcars,order(cyl,disp)),]%>%head(3)
mtcars[order(mtcars$cyl,mtcars$disp),]%>%head(3)
提及排序,就不能忘记了排名,dplyr中有一系列排名的函数,就是ranking系列。
函数名 功能
row_number 通用排名,并列的名次结果按前后顺序不同,靠前出现的元素排名在前
min_rank 通用排名,并列的名次结果同样,占用下一名次。
dense_rank 中国式排名,并列排名不占用名次,如:不管有几个并列第2名,以后的排名仍应该是第3名
percent_rank 按百分比的排名
cume_dist 累计分布区间的排名
ntile 粗略地把向量按堆排名,n便是堆的数量
> set.seed(1000)
> x=sample(1:5,7,replace=T)
> print(x)
[1] 2 4 1 4 3 1 4
> row_number(x)
[1] 3 5 1 6 4 2 7
> min_rank(x)
[1] 3 5 1 5 4 1 5
> dense_rank(x)
[1] 2 4 1 4 3 1 4
> percent_rank(x)
[1] 0.3333333 0.6666667 0.0000000 0.6666667 0.5000000 0.0000000 0.6666667
> cume_dist(x)
[1] 0.4285714 1.0000000 0.2857143 1.0000000 0.5714286 0.2857143 1.0000000
> ntile(x,3)
[1] 1 2 1 3 2 1 3
另外一个函数top_n还实现了min_rank和filter的组合,用以筛选按排序显示的数据框。
1.2.3 select变量选择
对于一个稍微了解SQL或者更多的数据分析师来讲,select天然是再熟悉不过的一个单词了,这里的select在某种程度上也相似于SQL中的select,其功能是按变量名选择数据框中的变量。
> select(mtcars,mpg,cyl,carb)%>%head(3)
mpg cyl carb
Mazda RX4 21.0 6 4
Mazda RX4 Wag 21.0 6 4
Datsun 710 22.8 4 1
同时,“-”即减号也是能够运用在这里的,选择除此之外的变量:
> select(mtcars,-mpg,-cyl,-carb)%>%head(3)
disp hp drat wt qsec vs am gear
Mazda RX4 160 110 3.90 2.620 16.46 0 1 4
Mazda RX4 Wag 160 110 3.90 2.875 17.02 0 1 4
Datsun 710 108 93 3.85 2.320 18.61 1 1 4
也能够把用“:”,把变量名链接起来,这货把变量当成数字了:
> select(mtcars,mpg:vs)%>%head(3)
mpg cyl disp hp drat wt qsec vs
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1
固然,直接使用数字也是能够的:
> select(mtcars,2:8)%>%head(3)
cyl disp hp drat wt qsec vs
Mazda RX4 6 160 110 3.90 2.620 16.46 0
Mazda RX4 Wag 6 160 110 3.90 2.875 17.02 0
Datsun 710 4 108 93 3.85 2.320 18.61 1
PS:这里要乱入一个鸡肋函数,slice,按行号筛选(若是这算筛选的话)数据框。
slice(mtcars,1:2);mtcars[1:2,]
slice(mtcars,n())
slice(mtcars,30:n())
诚如所言,有点鸡肋,彻底能够用filter和row_number来实现,或者直接用“[]”好了,不过,天知道。
第三行代码的n()是计数的一个函数,不能单独使用。
1.2.4 mutate变量变形
mutate能够对数据框中已有的变量进行操做或者增长变量,值得称赞的是,一段mutate的代码中,靠后的变量操做能够操做前期新添加或改变的变量,这是transform所不具有的特性。
> mutate(mtcars,V1=mpg/cyl,V2=disp/hp,V3=V1+V2)%>%
+ select(V1:V3)%>%
+ head(3)
V1 V2 V3
1 3.5 1.454545 4.954545
2 3.5 1.454545 4.954545
3 5.7 1.161290 6.861290
同时若你嫌麻烦一个个地对变量进行操做,还可使用mutate_each函数对数据框中的变量批量操做,经过调整funs(即functions)和vars(variables)参数控制functions的数量,以及参与变形的variables,这里控制variables的技巧与select函数类似。
iris%>%mutate_each(funs(dense_rank)) iris%>%mutate_each(funs(dense_rank,min_rank),-Petal.Width)
其家族内的另外一个函数,transmute,返回值中不包含原数据集变量,只保留计算转换后的变量。
1.2.5 summarise数据汇总
summarise是对数据框中的变量调用函数进行数据汇总,单一地说来,其与plyr包中的summarise是同样的,不过,咱们即将介绍dplyr包中的另外一大功能,分组计算,使用分组计算的summarise能作的事情就多了很是多,其能够实现几乎全部的相似于Excel中数据透视表的汇总功能。
> summarise(mtcars,meanDisp=mean(disp),sumMpg=sum(mpg))
meanDisp sumMpg
1 230.7219 642.9
summarise也一样有个each版本,
> iris%>%summarise_each(funs(mean,sum))
Sepal.Length_mean Sepal.Width_mean Petal.Length_mean Petal.Width_mean
1 5.843333 3.057333 3.758 1.199333
Species_mean Sepal.Length_sum Sepal.Width_sum Petal.Length_sum
1 2 876.5 458.6 563.7
Petal.Width_sum Species_sum
1 179.9 300
1.2.6 join——拒绝merge
在base中,咱们使用merge函数来合并两个数据框的行或列。虽然merge如此强大,但其也有个致命的弱点,那就是——慢。咱们构建一个一百万行的随机数据框来测试一下。
> set.seed(1000)
> a=sample(1:1000000,1000000)
> df1=data.frame(a,x=rnorm(1000000))
> df2=data.frame(a=sample(a,1000000,replace=T),y=rnorm(1000000,10,10))
> system.time(merge(df1,df2,by="a",all=T))
用户 系统 流逝
8.11 0.02 8.13
> system.time(full_join(df1,df2,by="a"))
用户 系统 流逝
1.50 0.09 1.59
以上full_join的性能完爆了merge,速度提高了80%左右,若是是把数量级提高到千万的话,join的优点更加明显:
> system.time(merge(df1,df2,by="a",all=T))
用户 系统 流逝
122.26 1.60 123.91
> system.time(full_join(df1,df2,by="a"))
用户 系统 流逝
21.12 0.50 21.63
以上的测试使用的是以下所示的电脑,有兴趣的童鞋也能够自行作作测试:
dplyr包中所带的6个join函数的功能以下所示,其差别仅在于返回值的不一样,咱们假设其形式均为join(x,y):
函数名 功能
inner_join 返回全部在y中能查找到的x的行,且包含x和y的全部列;
left_join 返回全部x的行,且包含x和y的全部列,在y中没有查找到的x的行新增的列的值会以NA填充;
right_join 同上,只是x和y调换了一下;
full_join 返回全部x和y的行和列,未查找的部分一样会被NA填充;
anti_join 返回全部未能在y中能查找到的x的行,也只返回x的列
semi_join 返回全部在y中能查找到的x的行,也只返回x的列
1.3分组操做
提起group_by,想必或多或少接触过SQL的数据分析师们并不陌生。对,你没有听错……
此group_by的语法意义几乎与SQL中的group by彻底同样,其也是针对被group by的变量进行分组的操做与计算,前提是有这样的操做与计算。在1.2.5中,咱们提到了summarise配合使用分组计算能作到很大部分的数据透视表能够作的事情:
> group_by(iris,Species)%>%
+ summarise(mean=mean(Sepal.Length),max=max(Sepal.Width),
+ min=min(Sepal.Width),sd=sd(Petal.Width))%>%
+ ungroup%>%
+ mutate(distTest=max-min)
Source: local data frame [3 x 6]
Species mean max min sd distTest
1 setosa 5.006 4.4 2.3 0.1053856 2.1
2 versicolor 5.936 3.4 2.0 0.1977527 1.4
3 virginica 6.588 3.8 2.2 0.2746501 1.6
在上面这段代码中,咱们根据鸢尾花的种类(Species)进行了分组,统计(summarise)了Sepal.Length的均值,Sepal.Width的最大值和最小值,Petal.Width的标准差。在结束统计后(ungroup的目的是解除group_by的操做),又插入了一个新的字段(mutate),计算不一样种类的Sepal.Width极差。
一般来讲,group_by与summarise这两个函数会放在一块儿使用,不过特殊的应用场景中,group_by也能够单独出现,好比针对每一个组进行一些操做:
> group_by(iris,Species)%>%
+ filter(Petal.Width<=max(Petal.Width)-0.5)%>%
+ ungroup%>%
+ head(3)
Source: local data frame [3 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.5 2.3 4.0 1.3 versicolor
2 5.7 2.8 4.5 1.3 versicolor
3 4.9 2.4 3.3 1.0 versicolor
这里咱们的筛选条件是选出小于每一个种类的鸢尾花的Petal.Width的最大值减去0.5,看起来很绕是吧,反正你说不定哪天就须要作这样的操做了呢。
1.4 rowwise
rowwise这个函数的名字就十分萌萌哒,其还有个兄弟(or姐妹)函数叫作colwise(源自plyr包),二者之间用法虽然不尽相同,却也有着类似的思想(一个按行分组一个按列分组好吗,要知道行和列在data.frame里面差距仍是蛮大的哎)。
对,你理解的没错,其实就是apply(x,1,FUN)啦,可是apply的效率,你懂得……咱们运行一个较大的数据集来测试一下。
> m=matrix(1:16000000,ncol=2)%>�ta.frame
> system.time(m%>%rowwise%>%summarise(sum(X1,X2)))
用户 系统 流逝
10.52 0.00 10.52
> system.time(m%>%apply(1,sum))
用户 系统 流逝
55.87 0.10 55.97
1.5其余工具函数
Hadley Wickham在dplyr中开发了大量用到想哭的函数——是感动哭,若是当年这个包早点出现,说不定我看起来没有如今这么老……
1.5.1 tally系列
tally是一个很方便的计数函数,其根据最初的调用而决定下一次调用n或者sum(n)。它还有其余的小伙伴好比count和n,都是计数家族的。
> iris%>%group_by(Species)%>%tally
Source: local data frame [3 x 2]
Species n
1 setosa 50
2 versicolor 50
3 virginica 50
以上代码与
iris%>%group_by(Species)%>%summarise(n=n())
iris%>%count(Species)
等价,而若是用base写法,即table(iris$Species),但输出结果明显不够友善。
> iris%>%group_by(Species)%>%tally%>%tally
Using n as weighting variable
Source: local data frame [1 x 1]
n
1 150
1.5.2 sample系列
此sample系列是对数据框进行随机抽样,只做用于数据框和dplyr自带的tbl等格式的数据。sample_n为按行数随机抽样,而sample_frac为按比例抽样;其weight参数能够设置抽样的权重而replace参数为有放回抽样。
sample_n(by_cyl,2,replace=TRUE)
sample_n(by_cyl,2,weight=mpg/mean(mpg))
sample_frac(mtcars,0.1)
sample_frac(mtcars,0.1,weight=1/mpg)
1.5.3 cumall系列
dplyr在base的cum系列函数的基础上增长了几个新的累积计算函数,cumall,cummean,cumany。对于cumall和cumany,会将输入的向量转化为逻辑值,其可用于多条件的“和运算”和“或运算”;而cummean更可能是补充cumsum之类的函数,得出累积的平均数。
> x=c(5,2,3,0,1,NA,2)
> cumall(x)
[1] TRUE TRUE TRUE FALSE FALSE FALSE FALSE
> cumany(x)
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE
> cummean(x)
[1] 5.000000 3.500000 3.333333 2.500000 2.200000 NA NA
1.5.4 distinct系列
distinct就和它的名字同样孤独,dplyr中有两个函数,distinct为计算数据集中的惟一值,是unique的高效版本;
> x=sample_n(mtcars,1000000,replace=T)
> system.time(distinct(x))
用户 系统 流逝
0.11 0.00 0.11
> system.time(unique(x))
用户 系统 流逝
11.55 0.01 11.56
而n_distinct至关于length+unique,计算向量中惟一值的个数,不过这个函数的速度反而不及length+unique,这明显不符合Hadley Wickham的风格啊。
> set.seed(1000)
> x=rnorm(10000000)
> system.time(unique(x)%>%length)
用户 系统 流逝
0.78 0.05 0.82
> system.time(n_distinct(x))
用户 系统 流逝
7.08 0.31 7.39
1.5.5 glimpse
顾名思义,对数据的惊鸿一瞥,本函数把数据集倒转,有点像str,但结果更为友善,其会显示出全部的变量名,同时尽可能显示出更多的原始数据,有兴趣的童鞋能够glimpse(mtcars)感觉一下。
1.5.6 failwith
这个函数更多用于构建function时产生的错误值的处理,通常状况咱们须要使用if等语句来判断错误值产生时function的返回值,而使用failwith的话,能够很方便地给予一个返回值给function。
> f=function(x)ifelse(x==1,stop("error"),1)
> f(1)
Error in ifelse(x == 1, stop("error"), 1) : error
> f(2)
[1] 1
> f1=failwith(NA,f)
> f1(1)
Error in ifelse(x == 1, stop("error"), 1) : error
[1] NA
能够看到虽然报出error,可是函数仍然有了返回值。
1.5.7 rbind_all系列
在最新版本的dplyr中,rbind_all已然被做者建议弃用,代替以bind_cols和bind_rows,bind_rows支持各类按行的合并,好比两个变量一致的数据框,或者由多个相同格式数据框组成的一个列表的合并。而使用bind_cols的时候,还须要匹配相同的行,因为强大的join系列的存在,bind_cols相对不太经常使用。
1.5.8 其余函数
between提供了x>= left & x<=right的简略版。
> between(1:5,2,3)
[1] FALSE TRUE TRUE FALSE FALSE
nth系列(first,last,nth)函数,其返回序列(vector)中的第一个(最后一个或者第N个)值。
> x=c(5,2,3,0,1,NA,2)
> first(x)
[1] 5
> last(x)
[1] 2
> nth(x,3)
[1] 3
dplyr包配合其余几个程序包(plyr,tidyr等等)几乎解决了平常数据整理中遇到的大部分问题,其还包括了链接几个开源数据库的函数,可用于远程数据库计算取数;也推出了data_frame和tbl_df等快速方便的数据存储对象;do语句使得在同一数据集中根据不一样分组状况建立不一样的模型更加方便。在后面的章节中,咱们将大量的应用到本包的函数,而且在代码风格上也将大量植入管道函数,这也是咱们在第一章即介绍本包的初衷。