日常在工作中会碰到很多数据批量处理的问题,有的时候单独造轮子很费时间,这个时候我发现了dplyr这个R包,能帮助你解决数据处理中的绝大多数难题。dplyr是tidyverse中的一个核心包,用来进行数据操作。主要包括以下5个核心函数。
filter()
按值筛选观测
arrange()
对行进行重新排序
select()
按名称选取变量
mutate()
使用现有变量的函数创建新变量
summarize()
将多个值总结为一个摘要统计量
这些函数都可以和group_by()
函数联合起来使用,group_by()
可以改变以上每个函数的作用范围,让其在整个数据集上的操作变为在每个分组上分别操作,这五个函数的工作方式都是相同的:
1.第一个参数是一个数据框。
2.随后的采纳数使用变量名称(不带引号)描述了在数据框上进行的操作。
3.输出结果是一个新的数据框。
Installation
# The easiest way to get dplyr is to install the whole tidyverse:
install.packages("tidyverse")
# Alternatively, install just dplyr:
install.packages("dplyr")
Demo
下面将以一个航班信息的数据集来演示一下dplyr
这个包的用法。
#安装并加载这个数据集
install.packages('nycflights13')
library(nycflights13)
1.1 使用filter()筛选行
filter()
函数可以基于观测的值筛选出一个观测子集。第一个参数是数据框名称,第二个参数及随后的参数是用来筛选数据框的表达式。
#筛选出1月1日出发的航班
> a <- filter(flights,month==1,day==1)
> head(a)
# A tibble: 6 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr>
1 2013 1 1 517 515 2 830 819 11 UA
2 2013 1 1 533 529 4 850 830 20 UA
3 2013 1 1 542 540 2 923 850 33 AA
4 2013 1 1 544 545 -1 1004 1022 -18 B6
5 2013 1 1 554 600 -6 812 837 -25 DL
6 2013 1 1 554 558 -4 740 728 12 UA
# ... with 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
1.1.1 比较运算符
R提供了一套标准的比较运算符:>,>=,<,<=,!=(不等于),==(等于)。
1.1.2 逻辑运算符
filter()
中的多个参数是由“与”组合起来的:每个表达式都必须为真才能让一行观测包含在输出中。如果要实现其他类型的组合,你需要使用布尔运算符: &表示"与"(也就是交集), | 表示“或”, ! 表示"非"。
#找出11月或12月出发的航班
> b <- filter(flights,month==11 | month ==12)
> head(b)
# A tibble: 6 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr>
1 2013 11 1 5 2359 6 352 345 7 B6
2 2013 11 1 35 2250 105 123 2356 87 B6
3 2013 11 1 455 500 -5 641 651 -10 US
4 2013 11 1 539 545 -6 856 827 29 UA
5 2013 11 1 542 545 -3 831 855 -24 AA
6 2013 11 1 549 600 -11 912 923 -11 UA
# ... with 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
上述代码的简写形式为:
c <- filter(flights,month %in% c(11,12))
其中,x %in% y
表示 选出x是y中的一个值时的所有行。
filter()
只能筛选出条件为TRUE的行;它会排除那些条件为FALSE和NA的行,如果想保留缺失值,可以明确指出:
> df <- tibble(x=c(1,NA,3)) #tibble()用于构建一个数据框
> head(df)
# A tibble: 3 x 1
x
<dbl>
1 1
2 NA
3 3
> filter(df,x>1)
# A tibble: 1 x 1
x
<dbl>
1 3
> filter(df,is.na(x) | x > 1)
# A tibble: 2 x 1
x
<dbl>
1 NA
2 3
1.2 使用arrange()
排列行
arrange()
函数的工作方式与filter()
函数非常相似,但前者不选择行,而是改变行的顺序。它接受一个数据框和一组作为排序依据的列名作为参数。如果列名不止一个,那么就使用后面的列在前面排序的基础上继续排序。
#将flights按年月日排序
> d <- arrange(flights,year,month,day)
> head(d)
# A tibble: 6 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr>
1 2013 1 1 517 515 2 830 819 11 UA
2 2013 1 1 533 529 4 850 830 20 UA
3 2013 1 1 542 540 2 923 850 33 AA
4 2013 1 1 544 545 -1 1004 1022 -18 B6
5 2013 1 1 554 600 -6 812 837 -25 DL
6 2013 1 1 554 558 -4 740 728 12 UA
# ... with 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
使用desc()
可以按列进行降序排序。
> e <- arrange(flights,desc(arr_delay))
> head(e)
# A tibble: 6 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr>
1 2013 1 9 641 900 1301 1242 1530 1272 HA
2 2013 6 15 1432 1935 1137 1607 2120 1127 MQ
3 2013 1 10 1121 1635 1126 1239 1810 1109 MQ
4 2013 9 20 1139 1845 1014 1457 2210 1007 AA
5 2013 7 22 845 1600 1005 1044 1815 989 MQ
6 2013 4 10 1100 1900 960 1342 2211 931 DL
# ... with 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
缺失值总是排在最后面。
> df <- tibble(x=c(5,NA,2))
> arrange(df,x)
# A tibble: 3 x 1
x
<dbl>
1 2
2 5
3 NA
#将缺失值排在前面
> arrange(df,desc(is.na(x)))
# A tibble: 3 x 1
x
<dbl>
1 NA
2 5
3 2
1.3 使用select()选择列
通过基于变量名的操作,select()
可以让你快速生成一个有用的变量子集。
#按名称选择列
> f <- select(flights,year,month,day)
> head(f)
# A tibble: 6 x 3
year month day
<int> <int> <int>
1 2013 1 1
2 2013 1 1
3 2013 1 1
4 2013 1 1
5 2013 1 1
6 2013 1 1
# 选择‘year’和‘day’之间的所有列(包括year和day)
> g <- select(flights,year:day)
> head(g)
# A tibble: 6 x 3
year month day
<int> <int> <int>
1 2013 1 1
2 2013 1 1
3 2013 1 1
4 2013 1 1
5 2013 1 1
6 2013 1 1
#选择不再‘year’和‘day’之间的所有列(不包括‘year’和‘day’)
> h <- select(flights,-(year:day))
> head(h)
# A tibble: 6 x 16
dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum
<int> <int> <dbl> <int> <int> <dbl> <chr> <int> <chr>
1 517 515 2 830 819 11 UA 1545 N14228
2 533 529 4 850 830 20 UA 1714 N24211
3 542 540 2 923 850 33 AA 1141 N619AA
4 544 545 -1 1004 1022 -18 B6 725 N804JB
5 554 600 -6 812 837 -25 DL 461 N668DN
6 554 558 -4 740 728 12 UA 1696 N39463
# ... with 7 more variables: origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
还可以在select()
函数中使用一些辅助函数。
start_with("abc")
匹配以abc开头的名称
ends_with("xyz")
匹配以xyz结尾的名称
contains("ijk")
匹配包含ijk的名称
matches("(.) \ \ 1")
选择匹配正则表达式的那些变量,这个正则表达式会匹配名称中含有重复字符的变量。
num_range('x',1:3)
匹配x1,x2和x3
将select()
函数和everything()
辅助函数结合起来使用,从而将几个变量移到数据框的开头。
> i <- select(flights,time_hour,air_time,everything())
> head(i)
# A tibble: 6 x 19
time_hour air_time year month day dep_time sched_dep_time dep_delay arr_time
<dttm> <dbl> <int> <int> <int> <int> <int> <dbl> <int>
1 2013-01-01 05:00:00 227 2013 1 1 517 515 2 830
2 2013-01-01 05:00:00 227 2013 1 1 533 529 4 850
3 2013-01-01 05:00:00 160 2013 1 1 542 540 2 923
4 2013-01-01 05:00:00 183 2013 1 1 544 545 -1 1004
5 2013-01-01 06:00:00 116 2013 1 1 554 600 -6 812
6 2013-01-01 05:00:00 150 2013 1 1 554 558 -4 740
# ... with 10 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
# flight <int>, tailnum <chr>, origin <chr>, dest <chr>, distance <dbl>, hour <dbl>,
# minute <dbl>
1.4 使用mutate()
添加新变量
mutate()
总是能够添加新列到数据集的最后,查看所有列的最简单的方式就是使用view()
函数。
#先产生一个狭窄的数据集
> flights_sml <- select(flights,year:day,ends_with("delay"),distance,air_time)
> head(flights_sml)
# A tibble: 6 x 7
year month day dep_delay arr_delay distance air_time
<int> <int> <int> <dbl> <dbl> <dbl> <dbl>
1 2013 1 1 2 11 1400 227
2 2013 1 1 4 20 1416 227
3 2013 1 1 2 33 1089 160
4 2013 1 1 -1 -18 1576 183
5 2013 1 1 -6 -25 762 116
6 2013 1 1 -4 12 719 150
#添加新列
> j <- mutate(flights_sml,gain=arr_delay - dep_delay,speed= distance/air_time * 60)
> head(j)
# A tibble: 6 x 9
year month day dep_delay arr_delay distance air_time gain speed
<int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2013 1 1 2 11 1400 227 9 370.
2 2013 1 1 4 20 1416 227 16 374.
3 2013 1 1 2 33 1089 160 31 408.
4 2013 1 1 -1 -18 1576 183 -17 517.
5 2013 1 1 -6 -25 762 116 -19 394.
6 2013 1 1 -4 12 719 150 16 288.
创建新列后可以立马使用,如果只想保留新变量,可以使用transmute()
函数。
> k <- transmute(flights,gain=arr_delay-dep_delay,hours=air_time/60,gain_per_hour=gain/hours)
> head(k)
# A tibble: 6 x 3
gain hours gain_per_hour
<dbl> <dbl> <dbl>
1 9 3.78 2.38
2 16 3.78 4.23
3 31 2.67 11.6
4 -17 3.05 -5.57
5 -19 1.93 -9.83
6 16 2.5 6.4
1.5 使用summarize()进行分组摘要
summarize()
可以将数据框折叠成一行。
> l <- summarise(flights,delay=mean(dep_delay,na.rm = T))
> head(l)
# A tibble: 1 x 1
delay
<dbl>
1 12.6
如果summarize()
不与group_by()
一起使用,那么它本身也没有大用。group_by()
可以将分析单位从整个数据集更改为单个分组。
#分组后就变成year,month,day相同的为一组,来计算它们的平均延误时间
> by_day <- group_by(flights,year,month,day)
> m <- summarise(by_day,delay=mean(dep_delay,na.rm = T)) #通过设置na.rm=T,可以在计算前去除缺失值,否则会得到大量的缺失值。
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
> head(m)
# A tibble: 6 x 4
# Groups: year, month [1]
year month day delay
<int> <int> <int> <dbl>
1 2013 1 1 11.5
2 2013 1 2 13.9
3 2013 1 3 11.0
4 2013 1 4 8.95
5 2013 1 5 5.73
6 2013 1 6 7.15
使用管道%>%
提高代码的可读性,重点在于转换的过程而不是转换的对象。在阅读代码的时候,%>%
读作 然后。以下这个例子研究每个目的地的距离和平均延误时间之间的关系。
> delay <- flights %>%
group_by(dest) %>%
summarise(
count=n(), #对分组后的dest各元素个数进行计数
dist=mean(distance,na.rm = T),
delay=mean(arr_delay,na.rm = T)
) %>%
filter(count > 20,dest != "HNL")
> head(delay)
# A tibble: 6 x 4
dest count dist delay
<chr> <int> <dbl> <dbl>
1 ABQ 254 1826 4.38
2 ACK 265 199 4.85
3 ALB 439 143 14.4
4 ATL 17215 757. 11.3
5 AUS 2439 1514. 6.02
6 AVL 275 584. 8.00
寻找航班数量和平均延误时间之间的关系。
> library(ggplot2)
> not_cancelled <- flights %>%
filter(!is.na(dep_delay),!is.na(arr_delay))
> delays <- not_cancelled %>%
group_by(tailnum) %>%
summarise(
delay=mean(arr_delay,na.rm = T),
n=n
)
> ggplot(data = delays,mapping = aes(x=n,y=delay))+
geom_point()
查看上述图形时,通常应该筛选掉那些观测数量非常少的分组,这样就可以避免受到特别小的分组中极端变动的影响,进而更好的发现数据模式。
delays %>%
filter(n>25) %>%
ggplot(mapping = aes(x=n,y=delay)) +
geom_point()
另一个案例:使用Lahman
包中的数据来计算大联盟的每个棒球队员的打击率(安打数/打数)。
#转换成tibble,以便输出更美观
> install.packages("Lahman")
> library(Lahman)
> batting <- as_tibble(Lahman::Batting)
> batters <- batting %>%
group_by(playerID) %>%
summarise(
ba=sum(H,na.rm = T)/sum(AB,na.rm = T),
ab=sum(AB,na.rm = T)
)
> batters %>%
filter(ab>100) %>%
ggplot(mapping = aes(x=ab,y=ba))+
geom_point()+
geom_smooth(se=F)
1.6 常用的摘要函数
只使用均值,计数和求和是远远不够的,R中还提供了很多其他的常用摘要函数。
1.6.1 位置度量
median(x)
用来求中位数,50%的x大于它,同时50%的x小于它。
将聚合函数和逻辑筛选组合起来使用。
> not_cancelled %>%
> group_by(year,month,day) %>%
summarise(
#平均延误时间
avg_delay1=mean(arr_delay),
#平均正延误时间
avg_delay2=mean(arr_delay[arr_delay>0])
)
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 5
# Groups: year, month [12]
year month day avg_delay1 avg_delay2
<int> <int> <int> <dbl> <dbl>
1 2013 1 1 12.7 32.5
2 2013 1 2 12.7 32.0
3 2013 1 3 5.73 27.7
4 2013 1 4 -1.93 28.3
5 2013 1 5 -1.53 22.6
6 2013 1 6 4.24 24.4
7 2013 1 7 -4.95 27.8
8 2013 1 8 -3.23 20.8
9 2013 1 9 -0.264 25.6
10 2013 1 10 -5.90 27.3
# ... with 355 more rows
1.6.2 秩的度量:min(x),quantile(x,0.25)和max(x)
quantile(x,0.25)会找出x中从小到大顺序大于前25%而小于后75%的值。
示例:找出每天最早和最晚的航班何时出发.
> n <- not_cancelled %>%
group_by(year,month,day) %>%
summarise(
first=min(dep_time),
last=max(dep_time)
)
> head(n)
# A tibble: 6 x 5
# Groups: year, month [1]
year month day first last
<int> <int> <int> <int> <int>
1 2013 1 1 517 2356
2 2013 1 2 42 2354
3 2013 1 3 32 2349
4 2013 1 4 25 2358
5 2013 1 5 14 2357
6 2013 1 6 16 2355
1.6.3 计数
前面已经使用过n()
来返回当前分组的大小。如果想计算出非缺失值的数量,可以使用sum(!is.na(x))
。如果想要计算出唯一值的数量,可以使用n_distinct(x)
。
#查看哪个目的地具有最多的航空公司
> o <- not_cancelled %>%
group_by(dest) %>%
summarise(carriers=n_distinct(carrier)) %>% #只计算唯一值
arrange(desc(carriers))
> head(o)
# A tibble: 6 x 2
dest carriers
<chr> <int>
1 ATL 7
2 BOS 7
3 CLT 7
4 ORD 7
5 TPA 7
6 AUS 6
###关于n_distinct()的理解,可以运行一下代码,实际上是去除唯一值的重复值,只看唯一值的数量。
> x <- sample(1:10, 1e5, rep = TRUE)
> length(unique(x))
> n_distinct(x) #与上一行代码相当
因为计数太常用了,所以dplyr提供了一个简单的辅助函数,用于只需要计数的情况。
> not_cancelled %>%
count(dest)
#计算每架飞机飞行的总里程,实际上就是求和。
> not_cancelled %>%
count(tailnum,wt=distance)
1.6.4 逻辑值的计数和比例
当与数值型函数一同使用时,TRUE会转换成1,FALSE会转换成0,这使得sum()
和mean()
非常适用于逻辑值:sum(x)
可以找出x中TRUE的数量,mean(x)
则可以找出比例。
#早上五点前出发的有多少架航班
> not_cancelled %>%
+ group_by(year,month,day) %>%
+ summarise(n_nearly=sum(dep_time<500))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day n_nearly
<int> <int> <int> <int>
1 2013 1 1 0
2 2013 1 2 3
3 2013 1 3 4
4 2013 1 4 3
5 2013 1 5 3
6 2013 1 6 2
7 2013 1 7 2
8 2013 1 8 1
9 2013 1 9 3
10 2013 1 10 3
# ... with 355 more rows
#延误超过1小时的航班比例是多少
> not_cancelled %>%
+ group_by(year,month,day) %>%
+ summarise(hour_perc=mean(arr_delay>60))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day hour_perc
<int> <int> <int> <dbl>
1 2013 1 1 0.0722
2 2013 1 2 0.0851
3 2013 1 3 0.0567
4 2013 1 4 0.0396
5 2013 1 5 0.0349
6 2013 1 6 0.0470
7 2013 1 7 0.0333
8 2013 1 8 0.0213
9 2013 1 9 0.0202
10 2013 1 10 0.0183
# ... with 355 more rows
1.7 分组新变量(和筛选器)
虽然与summarize()
函数结合起来使用是最有效的,但分组也可以与mutate()
和filter()
函数结合,以完成非常便捷的操作。
示例一:找出每个分组中最差的成员。
> flights_sml %>%
+ group_by(year,month,day) %>%
+ filter(rank(desc(arr_delay))<5)
# A tibble: 1,464 x 7
# Groups: year, month, day [365]
year month day dep_delay arr_delay distance air_time
<int> <int> <int> <dbl> <dbl> <dbl> <dbl>
1 2013 1 1 853 851 184 41
2 2013 1 1 290 338 1134 213
3 2013 1 1 260 263 266 46
4 2013 1 1 379 456 1092 222
5 2013 1 2 268 288 1092 203
6 2013 1 2 334 323 937 150
7 2013 1 2 337 368 2586 346
8 2013 1 2 379 359 1620 228
9 2013 1 3 174 176 1008 152
10 2013 1 3 268 270 1069 158
# ... with 1,454 more rows
其中对rank()
函数的使用做一个补充。
rank()
函数是对一维度数组、向量x 进行排序。若x 为数值,则按照小数在前大数在后的原则进行排序。
rank()
将数据分为确定值与缺失值两种。缺失值可按先后排在确定值之前(na.last = FALSE);也可排在之后(na.last = TRUE),;也可保留,不参与排序(na.last = "keep")。
"first" 是最基本的排序,小数在前大数在后,相同元素先者在前后者在后。
"max" 是相同元素都取该组中最好的水平,即通常所讲的并列排序。
"min" 是相同元素都取该组中最差的水平,可以增大序列的等级差异。
"average" 是相同元素都取该组中的平均水平,该水平可能是个小数。
"random" 是相同元素随机编排次序,避免了“先到先得”,“权重”优于“先后顺序”的机制增大了随机的程度。
> rank(t <- c(6.8, 8.1, 7.2))
[1] 1 3 2
示例二:找出大于某个阈值的所有分组:
> popular_dests <- flights %>%
+ group_by(dest) %>%
+ filter(n()>365)
> head(popular_dests)
# A tibble: 6 x 19
# Groups: dest [5]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr>
1 2013 1 1 517 515 2 830 819 11 UA
2 2013 1 1 533 529 4 850 830 20 UA
3 2013 1 1 542 540 2 923 850 33 AA
4 2013 1 1 544 545 -1 1004 1022 -18 B6
5 2013 1 1 554 600 -6 812 837 -25 DL
6 2013 1 1 554 558 -4 740 728 12 UA
# ... with 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
参考链接:
1.https://dplyr.tidyverse.org/
2.《R for Data Science》