前言
通常,我们读取的数据不可能完全满足我们后续的分析需求,因此需要对读取进来的数据进行处理。
比如,创建一些新的变量或总结,或者只是想重命名变量或重新排列观察结果,以便使数据更易于使用。
在本节及后续几节,我们将介绍如何使用 tidyverse
中的 dplyr
包来对数据进行操作。
我们使用的数据是 2013
年从纽约出发的航班信息。
1 介绍
1.1 准备
在这里我们的重点是介绍 dplyr
包的使用,并使用的 nycflights13
包中的数据来说明 dplyr
的核心思想,同时会加入一些 ggplot2
展示数据的示例,帮助我们理解。
注:ggplot2
包的详细介绍将会放在后续的 R
绘图系列中。
# 安装 nycflights13 包
install.packages("nycflights13")
# 导入
library(nycflights13)
# 导入 tidyverse
> library(tidyverse)
─ Attaching packages ─────────────────────────────── tidyverse 1.3.0 ─
✓ ggplot2 3.3.3 ✓ purrr 0.3.4
✓ tibble 3.0.4 ✓ dplyr 1.0.2
✓ tidyr 1.1.2 ✓ stringr 1.4.0
✓ readr 1.4.0 ✓ forcats 0.5.0
─ Conflicts ───────────────────────────────── tidyverse_conflicts() ─
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
# 或者只导入 dplyr
> library(dplyr)
请注意导入 tidyverse
时打印的冲突信息,它告诉你 dplyr
会覆盖基本 R
中的某些函数。
如果要在加载 dplyr
后使用这些函数的基本版本,则需要使用其全名 stats::filter()
和 stats::lag()
1.2 nycflights13
为了探索 dplyr
的基本数据操作,我们将使用 nycflights13::flights
此数据包含 2013
年从纽约起飞的所有 336776
架次航班,这些数据来自美国交通统计局。
> flights
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 517 515 2 830 819 11 UA 1545
2 2013 1 1 533 529 4 850 830 20 UA 1714
3 2013 1 1 542 540 2 923 850 33 AA 1141
4 2013 1 1 544 545 -1 1004 1022 -18 B6 725
5 2013 1 1 554 600 -6 812 837 -25 DL 461
6 2013 1 1 554 558 -4 740 728 12 UA 1696
7 2013 1 1 555 600 -5 913 854 19 B6 507
8 2013 1 1 557 600 -3 709 723 -14 EV 5708
9 2013 1 1 557 600 -3 838 846 -8 B6 79
10 2013 1 1 558 600 -2 753 745 8 AA 301
# … with 336,766 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
从打印信息可以看出是一个 tibble
类型,而不是 data.frame
类型
每列的类型代表的含义是:
-
int
: 代表整数 -
dbl
: 代表double
或者实数 -
chr
: 代表字符向量或字符串 -
dttm
: 代表日期-时间
还有另外三种没在这些数据中出现的类型
-
lgl
: 代表逻辑值 -
fctr
: 代表因子,即分类变量 -
date
: 代表日期
1.3 dplyr 基础
在这里,我们将介绍五个关键的 dplyr
动词函数,这些函数可帮助您解决绝大多数的数据处理难题
filter()
: 对值进行筛选arrange()
: 对行进行重排select()
: 根据变量名提取变量mutate()
: 使用现有变量创建新变量summarise()
: 将许多值汇总成一个摘要
这些函数都可以与 groupby()
一起使用,groupby()
将每个函数的作用域从对整个数据集进行操作改为对其分组进行操作
所有动词的工作方式相似:
- 第一个参数是数据框
- 后面的参数使用变量名描述如何处理数据框
- 返回的结果是一个新的数据框
将这些属性结合在一起,可以轻松地将多个简单步骤连接在一起,以获得复杂的结果。让我们深入看看这些动词是如何工作的
2 filter
filter()
允许您根据观察值对其进行筛选子集
第一个参数是数据框的名称。第二个和随后的参数是过滤数据框的表达式
例如,我们可以通过以下方式选择 1
月 1
日的所有航班
> filter(flights, month == 1, day == 1)
# A tibble: 842 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 517 515 2 830 819 11 UA 1545
2 2013 1 1 533 529 4 850 830 20 UA 1714
3 2013 1 1 542 540 2 923 850 33 AA 1141
4 2013 1 1 544 545 -1 1004 1022 -18 B6 725
5 2013 1 1 554 600 -6 812 837 -25 DL 461
6 2013 1 1 554 558 -4 740 728 12 UA 1696
7 2013 1 1 555 600 -5 913 854 19 B6 507
8 2013 1 1 557 600 -3 709 723 -14 EV 5708
9 2013 1 1 557 600 -3 838 846 -8 B6 79
10 2013 1 1 558 600 -2 753 745 8 AA 301
# … with 832 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
当您运行上面这行代码时,dplyr
执行过滤操作并返回一个新的数据框。
dplyr
函数不会修改其输入,因此如果要保存结果,则需要使用赋值运算符
jan1 <- filter(flights, month == 1, day == 1)
R
要么打印出结果,要么将其保存到变量中。如果你想同时做这两件事,你可以用圆括号括起来
> (dec25 <- filter(flights, month == 12, day == 25))
# A tibble: 719 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 12 25 456 500 -4 649 651 -2 US 1895
2 2013 12 25 524 515 9 805 814 -9 UA 1016
3 2013 12 25 542 540 2 832 850 -18 AA 2243
4 2013 12 25 546 550 -4 1022 1027 -5 B6 939
5 2013 12 25 556 600 -4 730 745 -15 AA 301
6 2013 12 25 557 600 -3 743 752 -9 DL 731
7 2013 12 25 557 600 -3 818 831 -13 DL 904
8 2013 12 25 559 600 -1 855 856 -1 B6 371
9 2013 12 25 559 600 -1 849 855 -6 B6 605
10 2013 12 25 600 600 0 850 846 4 B6 583
# … with 709 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
2.1 比较
要有效地使用过滤,您必须知道如何使用比较运算符来选择需要的值
>, >=, <, <=, != , ==
注意,上面的筛选参数用的是 ==
而不是 =
,如果你用错了
> filter(flights, month = 1)
错误: Problem with `filter()` input `..1`.
x Input `..1` is named.
ℹ This usually means that you've used `=` instead of `==`.
ℹ Did you mean `month == 1`?
Run `rlang::last_error()` to see where the error occurred.
就会产生错误。
使用 ==
时可能会遇到的另一个常见问题是: 浮点数。
> sqrt(2) ^ 2 == 2
[1] FALSE
> 1 / 49 * 49 == 1
[1] FALSE
计算机使用有限精度算法(显然是不能存储无限多的数字)所以你看到的每个数字都是近似值
我们使用 near()
函数代替 ==
> near(sqrt(2) ^ 2, 2)
[1] TRUE
> near(1 / 49 * 49, 1)
[1] TRUE
2.2 逻辑运算
filter()
的多个参数会以逻辑与的方式连接,而对于其他类型的组合,您需要自己使用布尔操作符:& | !
我们用下面的代码查找所有在 11
月或 12
月起飞的航班
filter(flights, month == 11 | month == 12)
你不能写成
filter(flights, month == (11 | 12))
它会找到等于 11 | 12
的所有月份,这个表达式的结果是 TRUE
在一个数字上下文中(如这里),TRUE
会变成 1
,因此它会查找 1
月份的所有航班,而不是 11
月或 12
月。这真是太让人困惑了.
解决这一问题的一个有用的简写就是
x %in% y
返回一个长度等于 x
的逻辑值向量,即 x
中的每一个元素是否出现在 y
中。
我们重写上面的代码
nov_dec <- filter(flights, month %in% c(11, 12))
有时你可以通过摩根定律来简化复杂的子集,如
!(x & y) => !x | !y
!(x | y) => !x & !y
例如,如果您想查找延迟(到达或离开时)不超过两小时的航班,可以使用下面两个过滤器中的任何一个
filter(flights, !(arr_delay > 120 | dep_delay > 120))
filter(flights, arr_delay <= 120, dep_delay <= 120)
R
中也有 &&
和 ||
,但是不要在这里使用它们
2.3 缺失值
R
的一个重要特性是缺失值,也就是 NA
。
NA
表示一个未知值,几乎任何涉及未知值的操作也将是未知的
> NA > 5
[1] NA
> 10 == NA
[1] NA
> NA + 10
[1] NA
> NA / 2
[1] NA
最令人困惑的结果是这个
> NA == NA
[1] NA
是不是想不明白,看看下面的例子,也许可以帮助你理解这是为什么
# Let x be Mary's age. We don't know how old she is.
x <- NA
# Let y be John's age. We don't know how old he is.
y <- NA
# Are John and Mary the same age?
x == y
#> [1] NA
# We don't know!
可以使用 is.na()
判断一个值是不是缺失值
> is.na(NA)
[1] TRUE
filter()
仅包含条件为 TRUE
的行;它不包括 FALSE
和 NA
值。如果要保留缺失值,需要明确指定
> df <- tibble(x = c(1, NA, 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
2.4 思考练习
- 提取下面所有的航班信息
- 迟到两个或两个多小时
- 飞往休斯敦(
IAH
或HOU
) United
,American
,或Delta
经营的航班- 夏季出发(
July
,August
,September
) - 延迟到达超过两小时,但未延迟起飞
- 至少晚点了一小时,其中飞行时间占了超过
30
分钟 - 午夜至凌晨
6
点之间出发(0-6
)
-
另一个有用的
dplyr
过滤函数是between()
,它有什么作用?您可以使用它来简化解决前面问题的代码吗? -
有多少航班缺失了
dep_time
?同时缺少哪些其他变量?想想这些行代表什么? -
为什么
NA ^ 0
不是缺失值?为什么NA | TRUE
不是缺失值?为什么FALSE & NA
不是缺失值?你能弄清楚一般规则吗?(NA * 0
是一个棘手的反例)
感谢花花同学的上期参考答案
http://note.youdao.com/s/DPASe3xt
3 arrange
arrange()
的工作原理与 filter()
类似,只是它不选择行,而是更改行的顺序
它需要传入一个数据框和一组列名(或更复杂的表达式)来排序。如果你提供了多个列名,每个额外的列将被用来打破前面列值的联系
> arrange(flights, year, month, day)
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 517 515 2 830 819 11 UA 1545
2 2013 1 1 533 529 4 850 830 20 UA 1714
3 2013 1 1 542 540 2 923 850 33 AA 1141
4 2013 1 1 544 545 -1 1004 1022 -18 B6 725
5 2013 1 1 554 600 -6 812 837 -25 DL 461
6 2013 1 1 554 558 -4 740 728 12 UA 1696
7 2013 1 1 555 600 -5 913 854 19 B6 507
8 2013 1 1 557 600 -3 709 723 -14 EV 5708
9 2013 1 1 557 600 -3 838 846 -8 B6 79
10 2013 1 1 558 600 -2 753 745 8 AA 301
# … with 336,766 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
使用 desc()
按降序重新排序
> arrange(flights, desc(dep_delay))
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 9 641 900 1301 1242 1530 1272 HA 51
2 2013 6 15 1432 1935 1137 1607 2120 1127 MQ 3535
3 2013 1 10 1121 1635 1126 1239 1810 1109 MQ 3695
4 2013 9 20 1139 1845 1014 1457 2210 1007 AA 177
5 2013 7 22 845 1600 1005 1044 1815 989 MQ 3075
6 2013 4 10 1100 1900 960 1342 2211 931 DL 2391
7 2013 3 17 2321 810 911 135 1020 915 DL 2119
8 2013 6 27 959 1900 899 1236 2226 850 DL 2007
9 2013 7 22 2257 759 898 121 1026 895 DL 2047
10 2013 12 5 756 1700 896 1058 2020 878 AA 172
# … with 336,766 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
缺失值总是排在最后
> df <- tibble(x = c(5, 2, NA))
> arrange(df, x)
# A tibble: 3 x 1
x
<dbl>
1 2
2 5
3 NA
> arrange(df, desc(x))
# A tibble: 3 x 1
x
<dbl>
1 5
2 2
3 NA
3.1 思考练习
- 如何使用
arrange()
将所有缺失的值排在开头位置?(提示:使用is.na()
) - 对航班进行排序,找出延误最多的航班。找到最早起飞的航班。
- 对航班进行排序,找出最快(速度最快)的航班。
- 哪个航班飞行最远?哪一个飞行最短?
4 select
获取包含数百上千个变量的数据并不少见。在这种情况下,第一个挑战往往是缩小你真正感兴趣的变量范围
select()
允许您使用基于变量名称的操作快速提取一个子集。
select()
对于 flights
数据不是很有用,因为只有 19
个变量,但是用法是一致的
# 按名称选择列
> select(flights, year, month, day)
# A tibble: 336,776 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
7 2013 1 1
8 2013 1 1
9 2013 1 1
10 2013 1 1
# … with 336,766 more rows
# 选取 year 和 day 之间的列
> select(flights, year:day)
# A tibble: 336,776 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
7 2013 1 1
8 2013 1 1
9 2013 1 1
10 2013 1 1
# … with 336,766 more rows
# 选取所有除了 year 到 day 的列
> select(flights, -(year:day))
# A tibble: 336,776 x 16
dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin
<int> <int> <dbl> <int> <int> <dbl> <chr> <int> <chr> <chr>
1 517 515 2 830 819 11 UA 1545 N14228 EWR
2 533 529 4 850 830 20 UA 1714 N24211 LGA
3 542 540 2 923 850 33 AA 1141 N619AA JFK
4 544 545 -1 1004 1022 -18 B6 725 N804JB JFK
5 554 600 -6 812 837 -25 DL 461 N668DN LGA
6 554 558 -4 740 728 12 UA 1696 N39463 EWR
7 555 600 -5 913 854 19 B6 507 N516JB EWR
8 557 600 -3 709 723 -14 EV 5708 N829AS LGA
9 557 600 -3 838 846 -8 B6 79 N593JB JFK
10 558 600 -2 753 745 8 AA 301 N3ALAA LGA
# … with 336,766 more rows, and 6 more variables: dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
>
有许多辅助函数可以在 select()
中使用
-
starts_with("abc")
: 匹配以abc
开头的列名 -
ends_with("xyz")
: 匹配xyz
结尾的列名 -
contains("ijk")
: 匹配包含ijk
的列名 -
matches("(.)\\1")
: 正则表达式匹配列名,它匹配任何包含重复字符的变量,记住需要对\
进行转义 -
num_range("x", 1:3)
: 匹配x1
,x2
和x3
select()
可以用来重命名变量,但很少用到它,因为它会删除所有没有显式提到的变量
可以使用 rename()
,它是 select()
的一个变体,保存所有未显式提到的变量
> rename(flights, tail_num = tailnum)
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 517 515 2 830 819 11 UA 1545
2 2013 1 1 533 529 4 850 830 20 UA 1714
3 2013 1 1 542 540 2 923 850 33 AA 1141
4 2013 1 1 544 545 -1 1004 1022 -18 B6 725
5 2013 1 1 554 600 -6 812 837 -25 DL 461
6 2013 1 1 554 558 -4 740 728 12 UA 1696
7 2013 1 1 555 600 -5 913 854 19 B6 507
8 2013 1 1 557 600 -3 709 723 -14 EV 5708
9 2013 1 1 557 600 -3 838 846 -8 B6 79
10 2013 1 1 558 600 -2 753 745 8 AA 301
# … with 336,766 more rows, and 8 more variables: tail_num <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
将 select()
与 everything()
辅助函数结合使用。可以将一些变量移到数据框的开头或结尾
> select(flights, time_hour, air_time, everything())
# A tibble: 336,776 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
7 2013-01-01 06:00:00 158 2013 1 1 555 600 -5 913
8 2013-01-01 06:00:00 53 2013 1 1 557 600 -3 709
9 2013-01-01 06:00:00 140 2013 1 1 557 600 -3 838
10 2013-01-01 06:00:00 138 2013 1 1 558 600 -2 753
# … with 336,766 more rows, and 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>
4.1 思考练习
- 想出尽可能多的方法从航班中选择
dep_time
、dep_delay
、arr_time
和arr_dela
列 - 如果在
select()
多次调用变量名会发生什么? any_of()
函数的作用是什么? 为什么和下面这个向量结合起来会有帮助呢?
vars <- c("year", "month", "day", "dep_delay", "arr_delay")
- 以下代码运行的结果会让您感到惊讶吗?
select
如何处理默认情况?如何更改默认行为?
select(flights, contains("TIME"))
5 mutate
除了选择现有的列之外,添加与现有列相关的新列通常也很有用。这就是 mutate()
的作用
mutate()
总是在数据集的末尾添加新列,因此我们将首先创建一个更窄的数据集,以便可以看到新变量
> flights_sml <- select(flights,
+ year:day,
+ ends_with("delay"),
+ distance,
+ air_time
+ )
> mutate(flights_sml,
+ gain = dep_delay - arr_delay,
+ speed = distance / air_time * 60
+ )
# A tibble: 336,776 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.
7 2013 1 1 -5 19 1065 158 -24 404.
8 2013 1 1 -3 -14 229 53 11 259.
9 2013 1 1 -3 -8 944 140 5 405.
10 2013 1 1 -2 8 733 138 -10 319.
# … with 336,766 more rows
注意: 你可以在 mutate()
中引用你刚刚创建的列
> mutate(flights_sml,
+ gain = dep_delay - arr_delay,
+ hours = air_time / 60,
+ gain_per_hour = gain / hours
+ )
# A tibble: 336,776 x 10
year month day dep_delay arr_delay distance air_time gain hours gain_per_hour
<int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2013 1 1 2 11 1400 227 -9 3.78 -2.38
2 2013 1 1 4 20 1416 227 -16 3.78 -4.23
3 2013 1 1 2 33 1089 160 -31 2.67 -11.6
4 2013 1 1 -1 -18 1576 183 17 3.05 5.57
5 2013 1 1 -6 -25 762 116 19 1.93 9.83
6 2013 1 1 -4 12 719 150 -16 2.5 -6.4
7 2013 1 1 -5 19 1065 158 -24 2.63 -9.11
8 2013 1 1 -3 -14 229 53 11 0.883 12.5
9 2013 1 1 -3 -8 944 140 5 2.33 2.14
10 2013 1 1 -2 8 733 138 -10 2.3 -4.35
# … with 336,766 more rows
如果你只想保留这些新变量,请使用 transmute()
> transmute(flights,
+ gain = dep_delay - arr_delay,
+ hours = air_time / 60,
+ gain_per_hour = gain / hours
+ )
# A tibble: 336,776 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
7 -24 2.63 -9.11
8 11 0.883 12.5
9 5 2.33 2.14
10 -10 2.3 -4.35
# … with 336,766 more rows
5.1 思考练习
-
目前,
dep_time
和sched_dep_time
便于查看,但是很难计算,因为它们并不是真正的连续数字(517 => 5:17
)。将它们转换成更方便的分钟数 -
比较
air_time
和arr_time - dep_time
,你期望看到什么结果?但是输出了什么结果?该怎么解决这个问题 -
比较
dep_time
,sched_dep_time
和dep_delay
,你觉得这三个数字会有什么联系? -
使用排名函数找出延误次数最多的
10
个航班,你想怎样处理(min_rank()
) -
1:3 + 1:10
返回什么?为什么? -
R
提供了什么三角函数?
感谢花花同学的上期参考答案:
http://note.youdao.com/s/NXHvKdvA
6 summarise
最后一个重要的动词 summarise
,它将所有信息汇总为一行
> summarise(flights, delay = mean(dep_delay, na.rm = TRUE))
# A tibble: 1 x 1
delay
<dbl>
1 12.6
一般很少单独使用 summarise()
,而是配合 group_by()
函数使用。这样就从对所有数据统计变成对每个分组进行统计,有助于直观了解组与组之间的差异。
当你在分组数据上使用 dplyr
的动词函数时,它们会自动应用的每个分组上。例如,我们将相同的代码应用于按日期分组的数据中,我们将获得每个日期的平均延迟。
> flights %>% group_by(year, month, day)
%>% summarise(delay=mean(dep_delay, na.rm = TRUE))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
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
7 2013 1 7 5.42
8 2013 1 8 2.55
9 2013 1 9 2.28
10 2013 1 10 2.84
# … with 355 more rows
group_by()
和 summarise()
结合使用是 dplyr
最常用的工具之一。
6.1 用管道连接多个操作
比如,我们想要了解每个地方的距离和平均延迟之间的关系
> by_dest <- group_by(flights, dest)
> delay <- summarise(by_dest,
+ count = n(), # 计算每个分组的大小
+ dist = mean(distance, na.rm = TRUE),
+ delay = mean(arr_delay, na.rm = TRUE)
+ )
`summarise()` ungrouping output (override with `.groups` argument)
> delay <- filter(delay, count > 20, dest != "HNL")
>
> ggplot(data = delay, mapping = aes(x = dist, y = delay)) +
+ geom_point(aes(size = count), alpha = 1/3) +
+ geom_smooth(se = FALSE)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
我们分三步获取数据:
- 将航班根据目的地分组
- 计算距离、平均延误和航班数的汇总信息
- 过滤掉噪声点和檀香山机场,该机场距离下一个最近的机场几乎是其两倍。
编写这段代码是很让人沮丧的,因为你需要给中间结果指定变量,取名字又是一个纠结的问题。尽管你可能觉得取名字无关紧要,但是最好也是要见名知意的比较好。所以,这也会影响我们的开发效率(个人是很赞成这种说法的,哈哈)。
所以我们引入的管道操作符 %>%
,让我们来修改一下上面的代码
> delays <- flights %>%
+ group_by(dest) %>%
+ summarise(
+ count = n(),
+ dist = mean(distance, na.rm = TRUE),
+ delay = mean(arr_delay, na.rm = TRUE)
+ ) %>%
+ filter(count > 20, dest != "HNL")
`summarise()` ungrouping output (override with `.groups` argument)
这使我们的注意力集中在数据的转换上,而不是转换为什么东西,可以让代码变得更加容易阅读。
但是,你也看到了 ggplot2
并没有使用管道操作,因为 ggplot2
出现在管道操作之前,而它的下一代 ggvis
已经可以支持管道操作了,但是这个包还没完全成熟。
6.2 缺失值
你可能想知道我们上面使用的 na.rm
参数,如果我们不使用它会怎么样?
> flights %>%
+ group_by(year, month, day) %>%
+ summarise(mean = mean(dep_delay))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day mean
<int> <int> <int> <dbl>
1 2013 1 1 NA
2 2013 1 2 NA
3 2013 1 3 NA
4 2013 1 4 NA
5 2013 1 5 NA
6 2013 1 6 NA
7 2013 1 7 NA
8 2013 1 8 NA
9 2013 1 9 NA
10 2013 1 10 NA
# … with 355 more rows
我们得到了一列缺失值,因为聚合函数通常遵循缺失值规则:
如果输入中包含任何缺失值,那么输出的结果也将是缺失值。幸运的是,所有聚合函数都有一个 na.rm
参数,在计算之前删除缺失值
> flights %>%
+ group_by(year, month, day) %>%
+ summarise(mean = mean(dep_delay, na.rm = TRUE))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day mean
<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
7 2013 1 7 5.42
8 2013 1 8 2.55
9 2013 1 9 2.28
10 2013 1 10 2.84
# … with 355 more rows
在这种情况下,缺失值表示取消的航班,我们也可以通过首先删除取消的航班来解决问题。
我们将保存这个数据集,以便在接下来的几个示例中重用它。
> not_cancelled <- flights %>%
+ filter(!is.na(dep_delay), !is.na(arr_delay))
>
> not_cancelled %>%
+ group_by(year, month, day) %>%
+ summarise(mean = mean(dep_delay))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day mean
<int> <int> <int> <dbl>
1 2013 1 1 11.4
2 2013 1 2 13.7
3 2013 1 3 10.9
4 2013 1 4 8.97
5 2013 1 5 5.73
6 2013 1 6 7.15
7 2013 1 7 5.42
8 2013 1 8 2.56
9 2013 1 9 2.30
10 2013 1 10 2.84
# … with 355 more rows
6.3 计数
无论何时进行任何聚合操作,都最好包含一个计数(n()
),或计算非缺失值(sum(!is.na(x))
),这样你就可以确认支持你的结论的数据基数。
例如,让我们看一下平均延迟最高的飞机(通过其尾号标识)
> delays <- not_cancelled %>%
+ group_by(tailnum) %>%
+ summarise(
+ delay = mean(arr_delay)
+ )
`summarise()` ungrouping output (override with `.groups` argument)
> #> `summarise()` ungrouping output (override with `.groups` argument)
>
> ggplot(data = delays, mapping = aes(x = delay)) +
+ geom_freqpoly(binwidth = 10)
从图上可以看到,有些飞机平均延误 5
小时(300
分钟)
实际上,这个事有些微妙。如果我们画一个航班数量与平均延误的散点图,我们可以得到更多的信息
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarise(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
#> `summarise()` ungrouping output (override with `.groups` argument)
ggplot(data = delays, mapping = aes(x = n, y = delay)) +
geom_point(alpha = 1/10, color='blue')
这一点也不奇怪,当航班很少时,平均延误的变化要大得多.
这个图的形状非常有特点:你会发现随着样本大小的增加,变化会减小。
在观察这类图时,可以先筛选出观察次数最少的组,这样你可以在最小的组中看到更多的模式和更少的极端变化情况。
下面,我们将向你展示将 ggplot2
集成到 dplyr
流中的简便方式
> delays %>%
+ filter(n > 25) %>%
+ ggplot(mapping = aes(x = n, y = delay)) +
+ geom_point(alpha = 1/10, color='green')
这种模式还有一种常见的变体。让我们看看棒球击球手平均表现与他们击球次数的关系
在这里,我使用 Lahman
软件包中的数据来计算每个棒球大联盟运动员的击球平均值(命中次数/尝试次数)
当我们将击球手的技术(以平均击球数 ba
来衡量)与击球机会(以击球 ab
来衡量)来绘图时,您会看到两种模式
- 如上所述,当我们获得更多的数据点时,组中的变化会减小
- 技能(
ba
)和击球机会(ab
)之间存在正相关关系。显然,这是因为球队控制着谁可以上场,他们肯定会挑选自己最好的球员。
batting <- as_tibble(Lahman::Batting)
batters <- batting %>%
group_by(playerID) %>%
summarise(
ba = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
ab = sum(AB, na.rm = TRUE)
)
batters %>%
filter(ab > 100) %>%
ggplot(mapping = aes(x = ab, y = ba)) +
geom_point(color='sienna') +
geom_smooth(se = FALSE)
6.4 汇总函数
尽管 means
、counts
和 sum
三个汇总函数已经满足大多数要求了,但是 R
还提供了许多汇总函数
- 位置:
mean
:均值median
:中位值
> not_cancelled %>%
+ group_by(year, month, day) %>%
+ summarise(
+ avg_delay1 = mean(arr_delay),
+ avg_delay2 = mean(arr_delay[arr_delay > 0]) # the average positive delay
+ )
`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
- 散度:
sd(x)
:标准差IQR(x)
:四分位范围mad(x)
:绝对中位差
> not_cancelled %>%
+ group_by(dest) %>%
+ summarise(distance_sd = sd(distance)) %>%
+ arrange(desc(distance_sd))
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 104 x 2
dest distance_sd
<chr> <dbl>
1 EGE 10.5
2 SAN 10.4
3 SFO 10.2
4 HNL 10.0
5 SEA 9.98
6 LAS 9.91
7 PDX 9.87
8 PHX 9.86
9 LAX 9.66
10 IND 9.46
# … with 94 more rows
- 秩次:
min
:最小值max
:最大值quantile
:分位数
> not_cancelled %>%
+ group_by(year, month, day) %>%
+ summarise(
+ first = min(dep_time),
+ last = max(dep_time)
+ )
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 5
# Groups: year, month [12]
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
7 2013 1 7 49 2359
8 2013 1 8 454 2351
9 2013 1 9 2 2252
10 2013 1 10 3 2320
# … with 355 more rows
- 位置:
first(x)
:第一个数nth(x, 2)
:第二个数last(x)
:最后一个数
> not_cancelled %>%
+ group_by(year, month, day) %>%
+ summarise(
+ first_dep = first(dep_time),
+ last_dep = last(dep_time)
+ )
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 5
# Groups: year, month [12]
year month day first_dep last_dep
<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
7 2013 1 7 49 2359
8 2013 1 8 454 2351
9 2013 1 9 2 2252
10 2013 1 10 3 2320
# … with 355 more rows
- 计数:
n()
:返回当前组的大小n_distinct(x)
:计算不同值的数量sum(!is.na(x))
:计算非缺失值的数量
> not_cancelled %>%
+ group_by(dest) %>%
+ summarise(carriers = n_distinct(carrier)) %>%
+ arrange(desc(carriers))
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 104 x 2
dest carriers
<chr> <int>
1 ATL 7
2 BOS 7
3 CLT 7
4 ORD 7
5 TPA 7
6 AUS 6
7 DCA 6
8 DTW 6
9 IAD 6
10 MSP 6
# … with 94 more rows
简单统计到达的目的地的数目
> not_cancelled %>%
+ count(dest)
# A tibble: 104 x 2
dest n
<chr> <int>
1 ABQ 254
2 ACK 264
3 ALB 418
4 ANC 8
5 ATL 16837
6 AUS 2411
7 AVL 261
8 BDL 412
9 BGR 358
10 BHM 269
# … with 94 more rows
通过添加权重系数,可以计算飞行的总英里数
> not_cancelled %>%
+ count(tailnum, wt = distance)
# A tibble: 4,037 x 2
tailnum n
<chr> <dbl>
1 D942DN 3418
2 N0EGMQ 239143
3 N10156 109664
4 N102UW 25722
5 N103US 24619
6 N104UW 24616
7 N10575 139903
8 N105UW 23618
9 N107US 21677
10 N108UW 32070
# … with 4,027 more rows
- 逻辑值:
当使用数值型的函数时,TRUE
会变成1
,FALSE
变为0
。可以方便的与sum
、mean
结合使用
> not_cancelled %>%
+ group_by(year, month, day) %>%
+ summarise(n_early = 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_early
<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_prop = 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_prop
<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
6.5 多变量分组
通过对多变量分组,可以进行逐步汇总
> daily <- group_by(flights, year, month, day)
> (per_day <- summarise(daily, flights = n()))
`summarise()` regrouping output by 'year', 'month' (override with `.groups` argument)
# A tibble: 365 x 4
# Groups: year, month [12]
year month day flights
<int> <int> <int> <int>
1 2013 1 1 842
2 2013 1 2 943
3 2013 1 3 914
4 2013 1 4 915
5 2013 1 5 720
6 2013 1 6 832
7 2013 1 7 933
8 2013 1 8 899
9 2013 1 9 902
10 2013 1 10 932
# … with 355 more rows
> (per_month <- summarise(per_day, flights = sum(flights)))
`summarise()` regrouping output by 'year' (override with `.groups` argument)
# A tibble: 12 x 3
# Groups: year [1]
year month flights
<int> <int> <int>
1 2013 1 27004
2 2013 2 24951
3 2013 3 28834
4 2013 4 28330
5 2013 5 28796
6 2013 6 28243
7 2013 7 29425
8 2013 8 29327
9 2013 9 27574
10 2013 10 28889
11 2013 11 27268
12 2013 12 28135
> (per_year <- summarise(per_month, flights = sum(flights)))
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 1 x 2
year flights
<int> <int>
1 2013 336776
6.6 取消分组
如果您想要删除分组并对未分组的数据操作,可以使用 ungroup
> daily %>%
+ ungroup() %>%
+ summarise(flights = n())
# A tibble: 1 x 1
flights
<int>
1 336776
6.7 思考练习
- 至少用
5
种不同方式评估一组航班的延误情况。并考虑以下情形:
- 航班有
50%
的概率提前15
分钟,50%
的概率晚点15
分钟 - 航班总是晚点
10
分钟 - 航班有
50%
的概率提前30
分钟,50%
的概率晚点30
分钟 - 航班用
99%
的概率准点,迟到两小时的几率只有1%
到达延迟和出发延迟,哪个更重要
- 想出另一种方法,得到与下面代码相同的结果(不使用
count()
)
not_cancelled %>% count(dest)
not_cancelled %>% count(tailnum, wt = distance)
-
我们对航班取消的定义(
is.na(dep_delay) | is.na(arr_delay)
)并不是最好的,为什么?哪一列才是最重要的 -
看看每天取消的航班数量,是否有规律?取消航班的比例与平均延误是否有关?
-
哪家航空公司的延误最严重。挑战:你能分清坏机场和坏航空公司的影响吗?(提示:
flights %>% group_by(carrier, dest) %>% summarise(n())
) -
count
的sort
参数的用处是什么,怎么使用?
7. group + mutate/filter
分组与 summarise()
结合使用是最有用的,但是你也可以与 mutate()
和 filter()
结合使用
- 找到每个组中最差的成员
> flights %>%
+ group_by(year, month, day) %>%
+ filter(rank(desc(arr_delay)) < 10)
# A tibble: 3,306 x 19
# Groups: year, month, day [365]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 848 1835 853 1001 1950 851 MQ 3944
2 2013 1 1 1815 1325 290 2120 1542 338 EV 4417
3 2013 1 1 1842 1422 260 1958 1535 263 EV 4633
4 2013 1 1 1942 1705 157 2124 1830 174 MQ 4410
5 2013 1 1 2006 1630 216 2230 1848 222 EV 4644
6 2013 1 1 2115 1700 255 2330 1920 250 9E 3347
7 2013 1 1 2205 1720 285 46 2040 246 AA 1999
8 2013 1 1 2312 2000 192 21 2110 191 EV 4312
9 2013 1 1 2343 1724 379 314 1938 456 EV 4321
10 2013 1 2 1244 900 224 1431 1104 207 EV 4412
# … with 3,296 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
- 查找所有大于阈值的组
> popular_dests <- flights %>%
+ group_by(dest) %>%
+ filter(n() > 365)
> popular_dests
# A tibble: 332,577 x 19
# Groups: dest [77]
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
<int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int>
1 2013 1 1 517 515 2 830 819 11 UA 1545
2 2013 1 1 533 529 4 850 830 20 UA 1714
3 2013 1 1 542 540 2 923 850 33 AA 1141
4 2013 1 1 544 545 -1 1004 1022 -18 B6 725
5 2013 1 1 554 600 -6 812 837 -25 DL 461
6 2013 1 1 554 558 -4 740 728 12 UA 1696
7 2013 1 1 555 600 -5 913 854 19 B6 507
8 2013 1 1 557 600 -3 709 723 -14 EV 5708
9 2013 1 1 557 600 -3 838 846 -8 B6 79
10 2013 1 1 558 600 -2 753 745 8 AA 301
# … with 332,567 more rows, and 8 more variables: tailnum <chr>, origin <chr>, dest <chr>,
# air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
- 标准化每组指标
> popular_dests %>%
+ filter(arr_delay > 0) %>%
+ mutate(prop_delay = arr_delay / sum(arr_delay)) %>%
+ select(year:day, dest, arr_delay, prop_delay)
# A tibble: 131,106 x 6
# Groups: dest [77]
year month day dest arr_delay prop_delay
<int> <int> <int> <chr> <dbl> <dbl>
1 2013 1 1 IAH 11 0.000111
2 2013 1 1 IAH 20 0.000201
3 2013 1 1 MIA 33 0.000235
4 2013 1 1 ORD 12 0.0000424
5 2013 1 1 FLL 19 0.0000938
6 2013 1 1 ORD 8 0.0000283
7 2013 1 1 LAX 7 0.0000344
8 2013 1 1 DFW 31 0.000282
9 2013 1 1 ATL 12 0.0000400
10 2013 1 1 DTW 16 0.000116
# … with 131,096 more rows
7.1 思考练习
-
哪架飞机(
tailnum
)的准点率最底? -
如果要尽量避免延误,应该在一天中的什么时间乘坐飞机?
-
计算每个目的地的总延迟分钟数。计算每个航班其占目的地总延迟的比例。
-
使用
lag()
,探索某一航班的延迟与其前一个航班的延迟相关性。 -
统计至少有两家航空公司飞过的目的地。利用目的地数量给航空公司排名。
感谢花花同学的上期参考答案:
http://note.youdao.com/s/NXHvKdvA