前言
通常,我们读取的数据不可能完全满足我们后续的分析需求,因此需要对读取进来的数据进行处理。
比如,创建一些新的变量或总结,或者只是想重命名变量或重新排列观察结果,以便使数据更易于使用。
在本节及后续几节,我们将介绍如何使用 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
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 , origin , dest ,
# air_time , distance , hour , minute , time_hour
从打印信息可以看出是一个 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
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 , origin , dest ,
# air_time , distance , hour , minute , time_hour
当您运行上面这行代码时,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
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 , origin , dest ,
# air_time , distance , hour , minute , time_hour
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
1 3
> filter(df, is.na(x) | x > 1)
# A tibble: 2 x 1
x
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