# 第二章 工作流:基础
# 2.1 代码基础
首先,我们可以将 R 当做计算器使用
> 1 / 200 * 30 | |
[1] 0.15 | |
> (59 + 73 + 2) / 3 | |
[1] 44.66667 | |
> sin(pi / 2) | |
[1] 1 |
使用 <- 来创建新对象:
x <- 3 * 4 |
创建对象的所有 R 语句(即赋值语句)都有同样的形式:
object_name <- value |
在阅读这行代码时,你可以在脑海中默念 “某个对象名得到了某个值”。如果你觉得 “<-” 太繁琐你可以使用 RStudio 快捷键:Alt+-(Alt 加上减号)。
# 2.2 对象名称
对象名称必须以字母开头,并且只能包含字母、数字、_ 和.。如果想让对象名称具有描述性,那么就应该在使用多个单词时遵循某种命名惯例。我推荐使用 snake_case 命名法,也就是使用小写单词,并用_ 分隔:
i_use_snake_case | |
otherPeopleUseCamelCase | |
some.people.use.periods | |
And_aFew.People_RENOUNCEconvention |
可以通过输入对象名称来查看这个对象:
> x | |
[1] 12 |
再进行一次赋值:
r_rocks <- 2 ^ 3 | |
## 查看一下这个对象 | |
r_rock | |
#> Error: object 'r_rock' not found | |
R_rocks | |
#> Error: object 'R_rocks' not found |
# 2.3 函数调用
R 中有大量内置函数。调用方式如下:
function_name(arg1 = val1, arg2 = val2, ...) |
我们尝试使用 seq () 函数,输入参数 1, 10:
> seq(1,10) | |
[1] 1 2 3 4 5 6 7 8 9 10 |
输入以下代码,你会发现 RStudio 也会自动完成一对双引号以方便输入:
x <- "hello world" |
引号和括号必须一直成对出现。RStudio 会尽力帮助我们,但还是有出错并导致不匹配的可能。如果出现不匹配,R 会显示一个 + 号:
> x <- "hello | |
+ |
+ 号表明 R 在等待继续输入;它认为你还没有完成输入。这通常意味着你漏掉了一个 " 或者)。
如果进行了一次赋值,R 不会显示出赋值结果。你最好立刻检查一下:
> y <- seq (1, 10, length.out = 5) | |
> y | |
[1] 1.00 3.25 5.50 7.75 10.00 | |
## 或者采取简化方式 | |
> (y <- seq (1, 10, length.out = 5)) | |
[1] 1.00 3.25 5.50 7.75 10.00 |
# 第三章 使用 dplyr 进行数据转换
# 3.1 简介
本章将用 dplyr 包来转换数据,并介绍一个新的数据集:2013 年从纽约市出发的航班信息。
# 3.1.1 准备工作
本章将重点讨论如何使用 tidyverse 中的另一个核心 R 包 ——dplyr 包。我们使用 nycflights13 包中的数据来说明 dplyr 包的核心理念,并使用 ggplot2 来帮助我们理解数据。
# 3.1.2 nycflights13
使用 nycflights13::flights。这个数据框包含了 2013 年从纽约市出发的所有 336 776 次航班的信息。该数据来自于美国交通统计局,可以使用?flights 查看其说明文档:
> flights | |
# A tibble: 336,776 x 19 | |
year month day dep_time sched_dep_time dep_delay | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 1 517 515 2 | |
2 2013 1 1 533 529 4 | |
3 2013 1 1 542 540 2 | |
4 2013 1 1 544 545 -1 | |
5 2013 1 1 554 600 -6 | |
6 2013 1 1 554 558 -4 | |
7 2013 1 1 555 600 -5 | |
8 2013 1 1 557 600 -3 | |
9 2013 1 1 557 600 -3 | |
10 2013 1 1 558 600 -2 | |
# ... with 336,766 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, | |
# dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>, | |
# minute <dbl>, time_hour <dttm> |
列名下面有一行 3 个或 4 个字母的缩写。它们描述了每个变量的类型。
・int 表示整数型变量。
・dbl 表示双精度浮点数型变量,或称实数。
・chr 表示字符向量,或称字符串。
・dttm 表示日期时间(日期 + 时间)型变量。
还有另外 3 种常用的变量类型,虽然没有在这个数据集中出现,但很快就会在本书后面遇到。
・lgl 表示逻辑型变量,是一个仅包括 TRUE 和 FALSE 的向量。
・fctr 表示因子,R 用其来表示具有固定数目的值的分类变量。
・date 表示日期型变量。
# 3.1.3 dplyr 基础
5 个 dplyr 核心函数:
・按值筛选观测(filter ())。
・对行进行重新排序(arrange ())。
・按名称选取变量(select ())。
・使用现有变量的函数创建新变量(mutate ())。
・将多个值总结为一个摘要统计量(summarize ())。
这些函数都可以和 group_by () 函数联合起来使用,group_by () 函数可以改变以上每个函数的作用范围,让其从在整个数据集上操作变为在每个分组上分别操作。这 6 个函数构成了数据处理语言的基本操作。
前面 5 个函数的工作方式都是相同的。
(1) 第一个参数是一个数据框。
(2) 随后的参数使用变量名称(不带引号)描述了在数据框上进行的操作。
(3) 输出结果是一个新数据框。
利用以上这些属性可以很轻松地将多个简单步骤链接起来,从而得到非常复杂的结果。接下来我们将深入了解,看看如何使用这些操作。
# 3.2 使用 filter () 筛选行
filter () 函数可以基于观测的值筛选出一个观测子集。第一个参数是数据框名称,第二个参数以及随后的参数是用来筛选数据框的表达式。例如,我们可以使用以下代码筛选出 1 月 1 日的所有航班:
> jan1 <-filter(flights, month == 1, day == 1) | |
> jan1 | |
# A tibble: 842 x 19 | |
year month day dep_time sched_dep_time dep_delay | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 1 517 515 2 | |
2 2013 1 1 533 529 4 | |
3 2013 1 1 542 540 2 | |
4 2013 1 1 544 545 -1 | |
5 2013 1 1 554 600 -6 | |
6 2013 1 1 554 558 -4 | |
7 2013 1 1 555 600 -5 | |
8 2013 1 1 557 600 -3 | |
9 2013 1 1 557 600 -3 | |
10 2013 1 1 558 600 -2 | |
# ... with 832 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, | |
# dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>, | |
# minute <dbl>, time_hour <dttm> |
# 3.2.1 比较运算符
R 提供了一套标准的比较运算符:>、>=、<、<=、!=(不等于)和 ==(等于)。当开始使用 R 时,最容易犯的错误就是使用 = 而不是 == 来测试是否相等。
在使用 == 进行比较时,你可能还会遇到另一个常见问题:浮点数。
> sqrt(2)^2==2 | |
[1] FALSE | |
> 1/49*49==1 | |
[1] FALSE |
# 3.2.2 逻辑运算符
filter () 中的多个参数是由 “与” 组合起来的:每个表达式都必须为真才能让一行观测包
含在输出中。如果要实现其他类型的组合,你需要使用布尔运算符:& 表示 “与”、| 表示
“或”、! 表示 “非”。
以下代码可以找出 11 月或 12 月出发的所有航班:
filter(flights, month == 11 | month == 12) |
不能写成 filter (flights, month == 11 |12) 这种形式。这种形式的文字翻译确实是 “找出 11 月或 12 月出发的所有航班”,但在代码中则不是这个意思,代码中的含义是找出所有出发月份为 11 | 12 的航班。11 | 12 这个逻辑表达式的值为 TRUE,在数字语境中(如本例),TRUE 就是 1,所以这段代码找出的不是 11 月或 12 月出发的航班,而是 1 月出发的所有航班。
这种问题有一个有用的简写形式:x % in% y。这会选取出 x 是 y 中的一个值时的所有行。
我们可以使用这种形式重写上面的代码:
nov_dec <- filter(flights, month %in% c(11, 12)) |
可以使用德摩根定律将复杂的筛选条件进行简化:!(x & y) 等价于!x | !y、!(x |y) 等价于!x & !y。例如,如果想要找出延误时间(到达或出发)不多于 2 小时的航班,那么使用以下两种筛选方式均可:
filter(flights, !(arr_delay > 120 | dep_delay > 120)) | |
filter(flights, arr_delay <= 120, dep_delay <= 120) |
filter () 函数中使用的是复杂的、包含多个部分的表达式,就需要考虑用一个明确的变量来代替它。这样检查代码会容易很多。我们很快就会介绍如何创建新变量。
# 3.2.3 缺失值
R 的一个重要特征使得比较运算更加复杂,这个特征就是缺失值,或称 NA(not available,不可用)。如果运算中包含了未知值,那么运算结果一般来说也是个未知值:
> NA + 10 | |
[1] NA | |
> NA / 2 | |
[1] NA |
如果想要确定一个值是否为缺失值,可以使用 is.na () 函数:
> x <- NA | |
> is.na(x) | |
[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 |
# 3.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 | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 1 517 515 2 | |
2 2013 1 1 533 529 4 | |
3 2013 1 1 542 540 2 | |
4 2013 1 1 544 545 -1 | |
5 2013 1 1 554 600 -6 | |
6 2013 1 1 554 558 -4 | |
7 2013 1 1 555 600 -5 | |
8 2013 1 1 557 600 -3 | |
9 2013 1 1 557 600 -3 | |
10 2013 1 1 558 600 -2 | |
# ... with 336,766 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, | |
# dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>, | |
# minute <dbl>, time_hour <dttm> |
使用 desc () 可以按列进行降序排序:
> arrange(flights, desc(arr_delay)) | |
# A tibble: 336,776 x 19 | |
year month day dep_time sched_dep_time dep_delay | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 9 641 900 1301 | |
2 2013 6 15 1432 1935 1137 | |
3 2013 1 10 1121 1635 1126 | |
4 2013 9 20 1139 1845 1014 | |
5 2013 7 22 845 1600 1005 | |
6 2013 4 10 1100 1900 960 | |
7 2013 3 17 2321 810 911 | |
8 2013 7 22 2257 759 898 | |
9 2013 12 5 756 1700 896 | |
10 2013 5 3 1133 2055 878 | |
# ... with 336,766 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, 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, 2, NA)) | |
> arrange(df, x) | |
# A tibble: 3 x 1 | |
x | |
<dbl> | |
1 2 | |
2 5 | |
3 NA |
# 3.4 使用 select () 选择列
如今,数据集有几百甚至几千个变量已经司空见惯。这种情况下,如何找出真正感兴趣的那些变量经常是我们面临的第一个挑战。通过基于变量名的操作,select () 函数可以让你快速生成一个有用的变量子集。
# 按名称选择列 | |
> 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 | |
<int> <int> <dbl> <int> <int> | |
1 517 515 2 830 819 | |
2 533 529 4 850 830 | |
3 542 540 2 923 850 | |
4 544 545 -1 1004 1022 | |
5 554 600 -6 812 837 | |
6 554 558 -4 740 728 | |
7 555 600 -5 913 854 | |
8 557 600 -3 709 723 | |
9 557 600 -3 838 846 | |
10 558 600 -2 753 745 | |
# ... with 336,766 more rows, and 11 more variables: | |
# arr_delay <dbl>, carrier <chr>, flight <int>, | |
# tailnum <chr>, origin <chr>, 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"):选择匹配正则表达式的那些变量。这个正则表达式会匹配名称中有重复字符的变量。你将在第 10 章中学习到更多关于正则表达式的知识。
・num_range ("x", 1:3):匹配 x1、x2 和 x3。
使用 select () 函数的变体 rename () 函数来重命名变量,以保留所有未明确提及的变量:
> rename(flights, tail_num = tailnum) | |
# A tibble: 336,776 x 19 | |
year month day dep_time sched_dep_time dep_delay | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 1 517 515 2 | |
2 2013 1 1 533 529 4 | |
3 2013 1 1 542 540 2 | |
4 2013 1 1 544 545 -1 | |
5 2013 1 1 554 600 -6 | |
6 2013 1 1 554 558 -4 | |
7 2013 1 1 555 600 -5 | |
8 2013 1 1 557 600 -3 | |
9 2013 1 1 557 600 -3 | |
10 2013 1 1 558 600 -2 | |
# ... with 336,766 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, flight <int>, 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 | |
<dttm> <dbl> <int> <int> <int> <int> | |
1 2013-01-01 05:00:00 227 2013 1 1 517 | |
2 2013-01-01 05:00:00 227 2013 1 1 533 | |
3 2013-01-01 05:00:00 160 2013 1 1 542 | |
4 2013-01-01 05:00:00 183 2013 1 1 544 | |
5 2013-01-01 06:00:00 116 2013 1 1 554 | |
6 2013-01-01 05:00:00 150 2013 1 1 554 | |
7 2013-01-01 06:00:00 158 2013 1 1 555 | |
8 2013-01-01 06:00:00 53 2013 1 1 557 | |
9 2013-01-01 06:00:00 140 2013 1 1 557 | |
10 2013-01-01 06:00:00 138 2013 1 1 558 | |
# ... with 336,766 more rows, and 13 more variables: | |
# sched_dep_time <int>, dep_delay <dbl>, arr_time <int>, | |
# sched_arr_time <int>, arr_delay <dbl>, carrier <chr>, | |
# flight <int>, tailnum <chr>, origin <chr>, dest <chr>, | |
# distance <dbl>, hour <dbl>, minute <dbl> |
# 3.5 使用 mutate () 添加新变量
除了选择现有的列,我们还经常需要添加新列,新列是现有列的函数。这就是 mutate () 函数的作用。
mutate () 总是将新列添加在数据集的最后,因此我们需要先创建一个更狭窄的数据集,以便能够看到新变量。记住,当使用 RStudio 时,查看所有列的最简单的方法就是使用 View () 函数:
flights_sml <- select(flights, | |
year:day, | |
ends_with("delay"), | |
distance, | |
air_time | |
) | |
mutate(flights_sml, | |
gain = arr_delay - dep_delay, | |
speed = distance / air_time * 60 | |
) | |
# A tibble: 336,776 x 9 | |
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 | |
7 2013 1 1 -5 19 1065 158 | |
8 2013 1 1 -3 -14 229 53 | |
9 2013 1 1 -3 -8 944 140 | |
10 2013 1 1 -2 8 733 138 | |
# ... with 336,766 more rows, and 2 more variables: | |
# gain <dbl>, speed <dbl> |
一旦创建,新列就可以立即使用:
> mutate(flights_sml, | |
+ gain = arr_delay - dep_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 | |
<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 | |
7 2013 1 1 -5 19 1065 158 | |
8 2013 1 1 -3 -14 229 53 | |
9 2013 1 1 -3 -8 944 140 | |
10 2013 1 1 -2 8 733 138 | |
# ... with 336,766 more rows, and 3 more variables: | |
# gain <dbl>, hours <dbl>, gain_per_hour <dbl> |
如果只想保留新变量,可以使用 transmute () 函数:
> transmute(flights, | |
+ gain = arr_delay - dep_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 |
# 3.5.1 常用创建函数
创建新变量的多种函数可供你同 mutate () 一同使用。最重要的一点是,这种函数必须是向量化的:它必须接受一个向量作为输入,并返回一个向量作为输出,而且输入向量与输出向量具有同样数目的分量。我们无法列出所有可能用到的创建函数,但可以介绍一下那些比较常用的。
算术运算符:+、-、*、/、^
它们都是向量化的,使用所谓的 “循环法则”。如果一个参数比另一个参数短,那么前者会自动扩展到同样的长度。当某个参数是单个数值时,这种方式是最有效的:air_time / 60、hours * 60 + minute 等。
算术运算符的另一用途是与我们后面将很快学到的聚集函数结合起来使用。例如,x /sum (x) 可以计算出各个分量在总数中的比例,y – mean (y) 可以计算出分量与均值之间的差值。
模运算符:%/% 和 %%
%/%(整数除法)和 %%(求余)满足 x == y * (x %/% y) + (x %% y)。模运算非常好用,因为它可以拆分整数。例如,在航班数据集中,你可以根据 dep_time 计算出 hour 和 minute:
> transmute(flights, | |
+ dep_time, | |
+ hour = dep_time %/% 100, | |
+ minute = dep_time %% 100 | |
+ ) | |
# A tibble: 336,776 x 3 | |
dep_time hour minute | |
<int> <dbl> <dbl> | |
1 517 5 17 | |
2 533 5 33 | |
3 542 5 42 | |
4 544 5 44 | |
5 554 5 54 | |
6 554 5 54 | |
7 555 5 55 | |
8 557 5 57 | |
9 557 5 57 | |
10 558 5 58 | |
# ... with 336,766 more rows |
对数函数:log ()、log2 () 和 log10 ()
在处理取值范围横跨多个数量级的数据时,对数是特别有用的一种转换方式。它还可以将乘法转换成加法,我们将在本书的第四部分中介绍这个功能。
其他条件相同的情况下,我推荐使用 log2 () 函数,因为很容易对其进行解释:对数标度的数值增加 1 个单位,意味着初始数值加倍;减少 1 个单位,则意味着初始数值减半。
偏移函数
lead () 和 lag
() 函数可以返回一个序列的领先值和滞后值。它们可以计算出序列的移动差值(如 x – lag (x))或发现序列何时发生了变化(x != lag (x))。它们与 group_by () 组合使用时特别有用,你很快就会学到 group_by () 这个函数:
> (x <- 1:10) | |
[1] 1 2 3 4 5 6 7 8 9 10 | |
> lag(x) | |
[1] NA 1 2 3 4 5 6 7 8 9 | |
> lead(x) | |
[1] 2 3 4 5 6 7 8 9 10 NA |
** 累加和滚动聚合 **
R 提供了计算累加和、累加积、累加最小值和累加最大值的函数:cumsum ()、cumprod ()、commin () 和 cummax ();dplyr 还提供了 cummean () 函数以计算累加均值。
> x | |
[1] 1 2 3 4 5 6 7 8 9 10 | |
> cumsum(x) | |
[1] 1 3 6 10 15 21 28 36 45 55 | |
> cummean(x) | |
[1] 1.000000 1.000000 1.333333 1.750000 2.200000 2.666667 | |
[7] 3.142857 3.625000 4.111111 4.600000 |
逻辑比较:<、<=、>、>= 和!=
如果需要进行一系列复杂的逻辑运算,那么最好将中间结果保存在新变量中,这样就可以检查是否每一步都符合预期。
排秩
排秩函数有很多,但你应该从 min_rank () 函数开始,它可以完成最常用的排秩任务(如第一、第二、第三、第四)。默认的排秩方式是,最小的值获得最前面的名次,使用 desc (x) 可以让最大的值获得最前面的名次
> y <- c(1, 2, 2, NA, 3, 4) | |
> min_rank(y) | |
[1] 1 2 2 NA 4 5 | |
> min_rank(desc(y)) | |
[1] 5 3 3 NA 2 1 |
如果 min_rank () 无法满足需要,那么可以看一下其变体 row_number ()、dense_rank ()、percent_rank ()、cume_dist () 和 ntile ()。
> row_number(y) | |
[1] 1 2 3 NA 4 5 | |
> dense_rank(y) | |
[1] 1 2 2 NA 3 4 | |
> percent_rank(y) | |
[1] 0.00 0.25 0.25 NA 0.75 1.00 | |
> cume_dist(y) | |
[1] 0.2 0.6 0.6 NA 0.8 1.0 |
# 3.6 使用 summarize () 进行分组摘要
最后一个核心函数是 summarize (),它可以将数据框折叠成一行。group_by () 可以将分析单位从整个数据集更改为单个分组。接下来,在分组后的数据框上使用 dplyr 函数时,它们会自动地应用到每个分组。例如,如果对按日期分组的一个数据框应用与上面完全相同的代码,那么我们就可以得到每日平均延误时间:
> by_day <- group_by(flights, year, month, day) | |
> summarize(by_day, 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 |
# 3.6.1 使用管道组合多种操作
假设我们想要研究每个目的地的距离和平均延误时间之间的关系。使用已经了解的 dplyr
包功能,你可能会写出以下代码:
by_dest <- group_by(flights, dest) | |
delay <- summarize(by_dest, | |
count = n(), | |
dist = mean(distance, na.rm = TRUE), | |
delay = mean(arr_delay, na.rm = TRUE) | |
) | |
delay <- filter(delay, count > 20, dest != "HNL") |
这样做不得不对每个中间数据框命名,会影响我们的分析速度。解决这个问题的另一种方法是使用管道,%>%
delays <- flights %>% | |
group_by(dest) %>% | |
summarize( | |
count = n(), | |
dist = mean(distance, na.rm = TRUE), | |
delay = mean(arr_delay, na.rm = TRUE) | |
) %>% | |
filter(count > 20, dest != "HNL") |
使用这种方法时,x %>% f (y) 会转换为 f (x, y),x %>% f (y) %>% g (z) 会转换为 g (f (x,y), z),以此类推。你可以使用管道重写多种操作,将其变为能够从左到右或从上到下阅读。
# 3.6.2 缺失值
在前面使用了参数 na.rm, 如果没有设置这个参数,会发生什么情况呢?
>by_day <- group_by (flights, year, month, day) | |
>summarize (by_day, delay = mean (dep_delay)) | |
## 使用管道 | |
> flights %>% | |
+ group_by (year, month, day) %>% | |
+ summarize (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) %>% | |
+ summarize(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) %>% | |
+ summarize(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 |
# 3.6.3 计数
聚合操作中包括一个计数(n ())或非缺失值的计数(sum (!is_na ()))是个好主意。这样你就可以检查一下,以确保自己没有基于非常少量的数据作出结论。例如,我们查看一下具有最长平均延误时间的飞机(通过机尾编号进行识别):
delays <- not_cancelled %>% | |
group_by(tailnum) %>% | |
summarize( | |
delay = mean(arr_delay) | |
) | |
ggplot(data = delays, mapping = aes(x = delay)) + | |
geom_freqpoly(binwidth = 10) |
接下来,画一张航班数量和平均延误时间的散点图,以便获得更深刻的理解:
delays <- not_cancelled %>% | |
group_by (tailnum) %>% #-- 根据 tailnum 对 not_cancelled 分组 | |
summarize ( | |
delay = mean (arr_delay, na.rm = TRUE), | |
n = n () #-- 根据每个组求 arr_delay 的平均值并计数 | |
) | |
# A tibble: 4,037 x 3 | |
tailnum delay n | |
<chr> <dbl> <int> | |
1 D942DN 31.5 4 | |
2 N0EGMQ 9.98 352 | |
3 N10156 12.7 145 | |
4 N102UW 2.94 48 | |
5 N103US -6.93 46 | |
6 N104UW 1.80 46 | |
7 N10575 20.7 269 | |
8 N105UW -0.267 45 | |
9 N107US -5.73 41 | |
10 N108UW -1.25 60 | |
# ... with 4,027 more rows | |
ggplot (data = delays, mapping = aes (x = n, y = delay)) + | |
geom_point (alpha = 1/10) |
当航班数量非常少时,平均延误时间的变动特别大。这张图的形状非常能够说明问题:当绘制均值(或其他摘要统计量)和分组规模的关系时,你总能看到随着样本量的增加,变动在不断减小。
查看此类图形时,通常应该筛选掉那些观测数量非常少的分组,这样你就可以避免受到特
别小的分组中的极端变动的影响,进而更好地发现数据模式。
> delays %>% | |
+ filter(n > 25) %>% | |
+ ggplot(mapping = aes(x = n, y = delay)) + | |
+ geom_point(alpha = 1/10) |
# 3.6.4 常用的摘要函数
只使用均值、计数和求和是远远不够的,R 中还提供了很多其他的常用的摘要函数。
位置度量
我们已经使用过 mean (x),但 median (x) 也非常有用。均值是总数除以个数;中位数则
是这样一个值:50% 的 x 大于它,同时 50% 的 x 小于它。
> not_cancelled %>% | |
+ group_by (year, month, day) %>% | |
+ summarize ( | |
+ # 平均延误时间: | |
+ 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 |
分散程度度量:sd (x)、IQR (x) 和 mad (x)
均方误差(又称标准误差,standard deviation,sd)是分散程度的标准度量方式。四分位距 IQR () 和绝对中位差 mad (x) 基本等价,更适合有离群点的情况:
> # 为什么到某些目的地的距离比到其他目的地更多变? | |
> not_cancelled %>% | |
+ group_by (dest) %>% | |
+ summarize (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 (x)、quantile (x, 0.25) 和 max (x)
分位数是中位数的扩展。例如,quantile (x, 0.25) 会找出 x 中按从小到大顺序大于前 25% 而小于后 75% 的值:
> # 每天最早和最晚的航班何时出发? | |
> not_cancelled %>% | |
+ group_by (year, month, day) %>% | |
+ summarize ( | |
+ 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)
这几个函数的作用与 x [1]、x [2] 和 x [length (x)] 相同,只是当定位不存在时(比如尝试从只有两个元素的分组中得到第三个元素),前者允许你设置一个默认值。例如,我们可以找出每天最早和最晚出发的航班:
> not_cancelled %>% | |
+ group_by(year, month, day) %>% | |
+ summarize( | |
+ 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 (),它不需要任何参数,并返回当前分组的大小。如果想要计算出非缺失值的数量,可以使用 sum (!is.na (x))。要想计算出唯一值的数量,可以使用 n_distinct (x):
> # 哪个目的地具有最多的航空公司? | |
> not_cancelled %>% | |
+ group_by (dest) %>% | |
+ summarize (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 |
因为计数太常用了,所以 dplyr 提供了一个简单的辅助函数,用于只需要计数的情况:
> 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 |
逻辑值的计数和比例:sum (x> 10) 和 mean (y == 0)
当与数值型函数一同使用时,TRUE 会转换为 1,FALSE 会转换为 0。这使得 sum () 和 mean () 非常适用于逻辑值:sum (x) 可以找出 x 中 TRUE 的数量,mean (x) 则可以找出比例。
> # 多少架航班是在早上 5 点前出发的?(这通常表明前一天延误的航班数量) | |
> not_cancelled %>% | |
+ group_by (year, month, day) %>% | |
+ summarize (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) %>% | |
+ summarize (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 |
# 3.6.5 按多个变量分组
当使用多个变量进行分组时,每次的摘要统计会用掉一个分组变量。这样就可以轻松地对数据集进行循序渐进的分析
> daily <- group_by (flights, year, month, day) | |
> (per_day <- summarize (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 <- summarize (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 | |
# 可以发现 day 分组已经消失 |
# 3.6.6 取消分组
如果想要取消分组,并回到未分组的数据继续操作,那么可以使用 ungroup () 函数:
> daily %>% | |
+ ungroup () %>% # 不再按日期分组 | |
+ summarize (flights = n ()) # 所有航班 | |
# A tibble: 1 x 1 | |
flights | |
<int> | |
1 336776 |
# 3.7 分组新变量
虽然与 summarize () 函数结合起来使用是最有效的,但分组也可以与 mutate () 和 filter () 函数结合,以完成非常便捷的操作。
找出每个分组中最差的成员:
> flights_sml %>% | |
+ group_by(year, month, day) %>% | |
+ filter(rank(desc(arr_delay)) < 10) | |
# A tibble: 3,306 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 157 174 213 60 | |
5 2013 1 1 216 222 708 121 | |
6 2013 1 1 255 250 589 115 | |
7 2013 1 1 285 246 1085 146 | |
8 2013 1 1 192 191 199 44 | |
9 2013 1 1 379 456 1092 222 | |
10 2013 1 2 224 207 550 94 | |
# ... with 3,296 more rows |
找出大于某个阈值的所有分组:
> 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 | |
<int> <int> <int> <int> <int> <dbl> | |
1 2013 1 1 517 515 2 | |
2 2013 1 1 533 529 4 | |
3 2013 1 1 542 540 2 | |
4 2013 1 1 544 545 -1 | |
5 2013 1 1 554 600 -6 | |
6 2013 1 1 554 558 -4 | |
7 2013 1 1 555 600 -5 | |
8 2013 1 1 557 600 -3 | |
9 2013 1 1 557 600 -3 | |
10 2013 1 1 558 600 -2 | |
# ... with 332,567 more rows, and 13 more variables: | |
# arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, | |
# carrier <chr>, flight <int>, 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 |