R数据科学笔记:2

第二章 工作流:基础

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

转载请注明周小钊的博客>>R4ds2

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342