写在前面
本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github,欢迎大家参与贡献,详细信息见:
Books-zh-cn 项目介绍:
Books-zh-cn:开源免费的中文书籍社区
r4ds-zh-cn Github 地址:
https://github.com/Books-zh-cn/r4ds-zh-cn
r4ds-zh-cn 网站地址:
https://books-zh-cn.github.io/r4ds-zh-cn/
目录
-
12.1 介绍
-
12.2 比较
-
12.3 布尔代数
转换
本书的第二部分是深入研究数据可视化。在本书的这一部分中,您将了解您在数据框架内遇到的最重要类型的变量类型,并学习可以使用的工具与它们使用。

Figure 1: 数据转换的选项在很大程度上取决于所涉及的数据类型,这是本书的这一部分的主题。
您可以根据需要阅读这些章节;它们的设计基本上是独一无二的,以便可以逐步阅读它们。
-
12 Logical vectors教您逻辑向量。 这些是最简单的矢量类型,但非常强大。 您将学习如何通过数字比较,如何将它们与布尔代数相结合,如何在摘要中使用它们以及如何将它们用于条件转换。 -
13 Numbers深入探讨数值向量的处理工具,这些工具是数据科学的动力源泉。 你将进一步学习计数方法,以及一系列重要的转换与汇总函数。 -
14 Strings将赋予你处理文本的工具:拆分字符串、精细切割、再重新拼接。 本章重点介绍 stringr 包,同时也会学习更多 tidyr 包中专用于字符串数据提取的函数。 -
15 Regular expressions将带你认识这个强大的字符串处理工具。 从面对混乱字符时的茫然,到能读写复杂字符串模式,本章将完成你的蜕变。 -
16 Factors介绍因子: R 语言存储分类数据的数据类型。 当变量具有固定取值范围时,或需要非字母顺序的字符串排列时,因子便是你的利器。 -
17 Dates and times提供处理日期和时间的关键工具。 虽然日期时间越学越显复杂,但借助 lubridate 包,你将掌握应对常见挑战的方法。 -
18 Missing values深度探讨缺失值问题。 虽然之前零散提及过,但现在是系统讨论的时候了,帮助你理解显式与隐式缺失值的区别,以及转换方式和意义。 -
19 Joins作为本部分的收官之作,将教你合并多个数据框的工具。 学习连接操作会让你深入理解键的概念,并思考如何唯一标识数据集中的每一行。
12.1 介绍
在本章中,您将学习与逻辑向量(logical vectors)合作的工具。逻辑向量是最简单的向量类型,因为每个元素只能是三个可能的值之一:TRUE, FALSE, NA。在原始数据中找到逻辑向量相对较少,但是在几乎所有分析过程中,您都会创建并操纵它们。
我们将首先讨论创建逻辑向量的最常见方法:使用数字比较。然后,您将了解如何使用布尔代数组合不同的逻辑向量以及一些有用的摘要。我们将使用 if_else() 和 case_when() 结束,这是两个有用的函数,用于进行由逻辑向量提供动力的有条件更改。
12.1.1 先决条件
您将在本章中学习的大多数功能由 base R 提供,因此我们不需要 tidyverse,但是我们仍然会加载它,因此我们可以使用 mutate(),filter(),等来处理数据框架。 我们还将继续从 nycflights13::flights 数据集中绘制示例。
library(tidyverse)
library(nycflights13)
但是,随着我们开始涵盖更多工具,并不总是有一个完美的真实示例。 因此,我们将开始使用 c() 来构成一些虚拟数据:
x <- c(1, 2, 3, 5, 7, 11, 13)
x * 2
#> [1] 2 4 6 10 14 22 26
这使解释单个函数更容易,但代价是更难看出它如何应用于你的数据问题。 请记住,我们对自由向量的任何操作,都可以通过 mutate() 等函数对数据框中的变量进行操作。
df <- tibble(x)
df |>
mutate(y = x * 2)
#> # A tibble: 7 × 2
#> x y
#> <dbl> <dbl>
#> 1 1 2
#> 2 2 4
#> 3 3 6
#> 4 5 10
#> 5 7 14
#> 6 11 22
#> # ℹ 1 more row
12.2 比较
创建逻辑向量的一种非常常见的方法是通过数字比较 <, <=, >, >=, !=, ==。 到目前为止,我们主要在 filter() 中瞬时创建了逻辑变量 --- 它们被计算,使用,然后丢弃。 例如,下面的 filter 找出所有白天出发且基本准点到达的航班:
flights |>
filter(dep_time > 600 & dep_time < 2000 & abs(arr_delay) < 20)
#> # A tibble: 172,286 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 601 600 1 844 850
#> 2 2013 1 1 602 610 -8 812 820
#> 3 2013 1 1 602 605 -3 821 805
#> 4 2013 1 1 606 610 -4 858 910
#> 5 2013 1 1 606 610 -4 837 845
#> 6 2013 1 1 607 607 0 858 915
#> # ℹ 172,280 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
知道这是一个快捷方式很有用,你也可以用 mutate() 显式创建底层的逻辑变量:
flights |>
mutate(
daytime = dep_time > 600 & dep_time < 2000,
approx_ontime = abs(arr_delay) < 20,
.keep = "used"
)
#> # A tibble: 336,776 × 4
#> dep_time arr_delay daytime approx_ontime
#> <int> <dbl> <lgl> <lgl>
#> 1 517 11 FALSE TRUE
#> 2 533 20 FALSE FALSE
#> 3 542 33 FALSE FALSE
#> 4 544 -18 FALSE TRUE
#> 5 554 -25 FALSE FALSE
#> 6 554 12 FALSE TRUE
#> # ℹ 336,770 more rows
这在处理更复杂的逻辑时尤为有用,因为命名中间步骤能让代码更易读,也便于检查每一步的计算是否正确。
总的来说,最初的筛选条件等价于:
flights |>
mutate(
daytime = dep_time > 600 & dep_time < 2000,
approx_ontime = abs(arr_delay) < 20,
) |>
filter(daytime & approx_ontime)
12.2.1 浮点比较
当心使用 == 与数字。 例如,看起来这个向量包含数字 1 和 2:
x <- c(1 / 49 * 49, sqrt(2) ^ 2)
x
#> [1] 1 2
但是,如果您测试它们是否相等,则会得到 FALSE:
x == c(1, 2)
#> [1] FALSE FALSE
怎么回事? 计算机以固定小数位数存储数字,因此无法精确表示 1/49 或 sqrt(2),后续计算会出现极微小的误差。 我们可以查看实际存储的精确值,通过调用 print() 并设置 digits 参数:
print(x, digits = 16)
#> [1] 0.9999999999999999 2.0000000000000004
你就能明白为什么 R 默认会舍入这些数字;它们确实非常接近你的预期值。
既然知道了 == 失败的原因,该如何解决呢? 一种方法是使用 dplyr::near(),它会忽略微小的差异:
near(x, c(1, 2))
#> [1] TRUE TRUE
12.2.2 缺失值
缺少值代表未知数,因此它们具有"传染性":几乎所有涉及未知值的操作也将是未知的:
NA > 5
#> [1] NA
10 == NA
#> [1] NA
最令人困惑的结果是:
NA == NA
#> [1] NA
若我们稍加补充背景信息,就更容易理解为何如此:
# We don't know how old Mary is
age_mary <- NA
# We don't know how old John is
age_john <- NA
# Are Mary and John the same age?
age_mary == age_john
#> [1] NA
# We don't know!
因此若想筛选所有 dep_time 为缺失值的航班,以下代码无法奏效,因为 dep_time == NA 会在每一行都返回 NA,而 filter() 会自动剔除缺失值:
flights |>
filter(dep_time == NA)
#> # A tibble: 0 × 19
#> # ℹ 19 variables: year <int>, month <int>, day <int>, dep_time <int>,
#> # sched_dep_time <int>, dep_delay <dbl>, arr_time <int>, ...
相反,我们需要一个新工具:is.na()。
12.2.3 is.na()
is.na(x) 适用于任何类型的向量,对缺失值返回 TRUE,其他情况返回 FALSE:
is.na(c(TRUE, NA, FALSE))
#> [1] FALSE TRUE FALSE
is.na(c(1, NA, 3))
#> [1] FALSE TRUE FALSE
is.na(c("a", NA, "b"))
#> [1] FALSE TRUE FALSE
我们可以用 is.na() 查找所有 dep_time 缺失的行:
flights |>
filter(is.na(dep_time))
#> # A tibble: 8,255 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 NA 1630 NA NA 1815
#> 2 2013 1 1 NA 1935 NA NA 2240
#> 3 2013 1 1 NA 1500 NA NA 1825
#> 4 2013 1 1 NA 600 NA NA 901
#> 5 2013 1 2 NA 1540 NA NA 1747
#> 6 2013 1 2 NA 1620 NA NA 1746
#> # ℹ 8,249 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
is.na() 在 arrange() 中也很有用。arrange() 通常将缺失值排在末尾,但通过先按 is.na() 排序可覆盖这一默认行为:
flights |>
filter(month == 1, day == 1) |>
arrange(dep_time)
#> # A tibble: 842 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 517 515 2 830 819
#> 2 2013 1 1 533 529 4 850 830
#> 3 2013 1 1 542 540 2 923 850
#> 4 2013 1 1 544 545 -1 1004 1022
#> 5 2013 1 1 554 600 -6 812 837
#> 6 2013 1 1 554 558 -4 740 728
#> # ℹ 836 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
flights |>
filter(month == 1, day == 1) |>
arrange(desc(is.na(dep_time)), dep_time)
#> # A tibble: 842 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 NA 1630 NA NA 1815
#> 2 2013 1 1 NA 1935 NA NA 2240
#> 3 2013 1 1 NA 1500 NA NA 1825
#> 4 2013 1 1 NA 600 NA NA 901
#> 5 2013 1 1 517 515 2 830 819
#> 6 2013 1 1 533 529 4 850 830
#> # ℹ 836 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
我们将在 Chapter 18 中回来更深入地探讨缺失值。
12.2.4 练习
-
dplyr::near()是如何工作的?输入near查看源代码。sqrt(2)^2接近 2 吗? -
结合使用
mutate()、is.na()和count()来描述dep_time、sched_dep_time和dep_delay中缺失值的关联情况。
12.3 布尔代数
当存在多个逻辑向量时,可以使用布尔代数进行组合运算。 在 R 中,& 表示 "and",| 表示 "or",! 表示 "not",xor() 表示 "exclusive or"。 例如,df |> filter(!is.na(x)) 会筛选出 x 值非缺失的所有行,而 df |> filter(x < -10 | x > 0) 会筛选出 x 值小于 -10 或大于 0 的所有行。Figure 12.1 完整展示了布尔运算的规则及其运作原理。

Figure 12.1: 完整的布尔运算集合。x 代表左侧圆圈,y 代表右侧圆圈,阴影区域显示各运算符选择的部分。
除了 & 和 | 外,R 还有 && 和 ||。 不要在 dplyr 函数中使用它们! 这些是短路运算符,只会返回单个 TRUE 或 FALSE。 它们对编程很重要,但对数据科学并不适用。
12.3.1 缺失值
布尔代数中缺失值的规则解释起来有些棘手,因为乍看之下似乎不太一致:
df <- tibble(x = c(TRUE, FALSE, NA))
df |>
mutate(
and = x & NA,
or = x | NA
)
#> # A tibble: 3 × 3
#> x and or
#> <lgl> <lgl> <lgl>
#> 1 TRUE NA TRUE
#> 2 FALSE FALSE NA
#> 3 NA NA NA
要理解其中的逻辑,可以思考 NA | TRUE(NA 或 TRUE)的情况。 逻辑向量中的缺失值表示该值可能是 TRUE 或 FALSE。 由于 TRUE | TRUE 和 FALSE | TRUE 的结果都是 TRUE,因为至少有一个为真。NA | TRUE 也必定是 TRUE,因为无论 NA 代表 TRUE 还是 FALSE,结果都为 TRUE。 然而,NA | FALSE 会返回 NA,因为我们无法确定 NA 是 TRUE 还是 FALSE。 类似的逻辑也适用于 NA & FALSE 的情况。
12.3.2 操作顺序
需要注意的是,运算顺序与英语表达习惯不同。 请看以下查找 11 月或 12 月起飞航班的代码:
flights |>
filter(month == 11 | month == 12)
你可能会想按照英语表达习惯写成:"查找所有 11 月或 12 月起飞的航班":
flights |>
filter(month == 11 | 12)
#> # A tibble: 336,776 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 517 515 2 830 819
#> 2 2013 1 1 533 529 4 850 830
#> 3 2013 1 1 542 540 2 923 850
#> 4 2013 1 1 544 545 -1 1004 1022
#> 5 2013 1 1 554 600 -6 812 837
#> 6 2013 1 1 554 558 -4 740 728
#> # ℹ 336,770 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
这段代码虽然不会报错,但似乎并未达到预期效果。 原因是什么? 在这里,R 会首先计算 month == 11 生成逻辑向量,我们称为 nov。 然后执行 nov | 12。 当数值与逻辑运算符结合时,除 0 外的所有数值都会被转换为 TRUE,因此实际等价于 nov | TRUE,这个表达式永远返回 TRUE,最终会导致所有行都被选中:
flights |>
mutate(
nov = month == 11,
final = nov | 12,
.keep = "used"
)
#> # A tibble: 336,776 × 3
#> month nov final
#> <int> <lgl> <lgl>
#> 1 1 FALSE TRUE
#> 2 1 FALSE TRUE
#> 3 1 FALSE TRUE
#> 4 1 FALSE TRUE
#> 5 1 FALSE TRUE
#> 6 1 FALSE TRUE
#> # ℹ 336,770 more rows
12.3.3 %in%
避免混淆 == 和 | 运算顺序的简单方法是使用%in%。x %in% y 会生成一个与 x 等长的逻辑向量,当 x 中的值出现在 y 中时,对应位置返回 TRUE。
1:12 %in% c(1, 5, 11)
#> [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
letters[1:10] %in% c("a", "e", "i", "o", "u")
#> [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE
因此要查找 11 月和 12 月的所有航班,我们可以这样写:
flights |>
filter(month %in% c(11, 12))
需要注意的是,%in%对 NA 的处理规则与 == 不同,因为 NA %in% NA 会返回 TRUE。
c(1, 2, NA) == NA
#> [1] NA NA NA
c(1, 2, NA) %in% NA
#> [1] FALSE FALSE TRUE
这可以成为一个实用的快捷方式:
flights |>
filter(dep_time %in% c(NA, 0800))
#> # A tibble: 8,803 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 800 800 0 1022 1014
#> 2 2013 1 1 800 810 -10 949 955
#> 3 2013 1 1 NA 1630 NA NA 1815
#> 4 2013 1 1 NA 1935 NA NA 2240
#> 5 2013 1 1 NA 1500 NA NA 1825
#> 6 2013 1 1 NA 600 NA NA 901
#> # ℹ 8,797 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, ...
12.3.4 练习
-
查找所有
arr_delay缺失但dep_delay未缺失的航班。查找所有arr_time和sched_arr_time均未缺失,但arr_delay缺失的航班。 -
有多少航班的
dep_time缺失?这些行中还有哪些变量缺失?这些行可能代表什么情况? -
假设
dep_time缺失意味着航班被取消,查看每天取消航班的数量。是否存在某种规律?取消航班的比例与未取消航班的平均延误时间之间是否存在关联?
--------------- 未完待续 ---------------
本期翻译贡献:
@TigerZ生信宝库
注:本文已开启快捷转载,欢迎大家转载,只需标明文章出处即可。