《R for Data Science (2e)》免费中文翻译 (第12章) --- Logical vectors(2)

写在前面

本系列推文为《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.4 汇总

  • 12.5 条件转换

  • 12.6 总结

12.4 汇总

以下部分将介绍几种用于汇总逻辑向量的实用技巧。 除了专门针对逻辑向量的函数外,您也可以使用适用于数值向量的函数。

12.4.1 逻辑汇总

逻辑向量主要有两种汇总方式:any()all()any(x) 相当于 |,只要 x 中存在任意 TRUE 就会返回 TRUEall(x) 相当于 &,只有当 x 中所有值都为 TRUE 时才会返回 TRUE。 与所有汇总函数一样,若存在缺失值它们会返回 NA,而通过设置 na.rm = TRUE 可忽略缺失值。

例如,我们可以用 all()any() 判断是否所有航班的起飞延误都不超过一小时,或者是否存在到达延误五小时以上的航班。 结合 group_by() 还能实现按日统计:

复制代码
flights |> 
  group_by(year, month, day) |> 
  summarize(
    all_delayed = all(dep_delay <= 60, na.rm = TRUE),
    any_long_delay = any(arr_delay >= 300, na.rm = TRUE),
    .groups = "drop"
  )
#> # A tibble: 365 × 5
#>    year month   day all_delayed any_long_delay
#>   <int> <int> <int> <lgl>       <lgl>         
#> 1  2013     1     1 FALSE       TRUE          
#> 2  2013     1     2 FALSE       TRUE          
#> 3  2013     1     3 FALSE       FALSE         
#> 4  2013     1     4 FALSE       FALSE         
#> 5  2013     1     5 FALSE       TRUE          
#> 6  2013     1     6 FALSE       FALSE         
#> # ℹ 359 more rows

不过在大多数情况下,any()all() 显得过于笼统,我们往往需要更详细地了解 TRUEFALSE 的具体数量。 这就引出了数值型汇总方法。

12.4.2 逻辑向量的数值总结

当在数值型语境中使用逻辑向量时,TRUE 会转换为 1,FALSE 会转换为 0。 这使得 sum()mean() 在处理逻辑向量时特别有用:sum(x) 给出 TRUE 的数量,而 mean(x) 则给出 TRUE 的比例(因为 mean() 就是 sum() 除以 length())。

例如,通过这种方式,我们可以计算起飞延误不超过一小时的航班比例,以及到达延误五小时以上的航班数量:

复制代码
flights |> 
  group_by(year, month, day) |> 
  summarize(
    all_delayed = mean(dep_delay <= 60, na.rm = TRUE),
    any_long_delay = sum(arr_delay >= 300, na.rm = TRUE),
    .groups = "drop"
  )
#> # A tibble: 365 × 5
#>    year month   day all_delayed any_long_delay
#>   <int> <int> <int>       <dbl>          <int>
#> 1  2013     1     1       0.939              3
#> 2  2013     1     2       0.914              3
#> 3  2013     1     3       0.941              0
#> 4  2013     1     4       0.953              0
#> 5  2013     1     5       0.964              1
#> 6  2013     1     6       0.959              0
#> # ℹ 359 more rows

12.4.3 逻辑子集

逻辑向量在数据汇总中还有一个最终用途:您可以使用逻辑向量对单个变量进行子集筛选,从而聚焦感兴趣的数据子集。 这里需要用到基础的 [(称为"子集")操作符,Section 27.2 将对此进行更详细的讲解。

假设我们只想分析实际发生延误的航班的平均延误时间。 一种实现方法是先筛选出延误航班,再计算平均延误:

复制代码
flights |> 
  filter(arr_delay > 0) |> 
  group_by(year, month, day) |> 
  summarize(
    behind = mean(arr_delay),
    n = n(),
    .groups = "drop"
  )
#> # A tibble: 365 × 5
#>    year month   day behind     n
#>   <int> <int> <int>  <dbl> <int>
#> 1  2013     1     1   32.5   461
#> 2  2013     1     2   32.0   535
#> 3  2013     1     3   27.7   460
#> 4  2013     1     4   28.3   297
#> 5  2013     1     5   22.6   238
#> 6  2013     1     6   24.4   381
#> # ℹ 359 more rows

这种方法虽然可行,但如果我们还想计算提前抵达航班的平均延误时间呢? 那就需要额外执行一次筛选步骤,再想办法合并两个数据框。 其实您可以直接使用 [ 进行内联筛选:arr_delay[arr_delay > 0] 将仅返回正的到达延误值。

由此可得:

复制代码
flights |> 
  group_by(year, month, day) |> 
  summarize(
    behind = mean(arr_delay[arr_delay > 0], na.rm = TRUE),
    ahead = mean(arr_delay[arr_delay < 0], na.rm = TRUE),
    n = n(),
    .groups = "drop"
  )
#> # A tibble: 365 × 6
#>    year month   day behind ahead     n
#>   <int> <int> <int>  <dbl> <dbl> <int>
#> 1  2013     1     1   32.5 -12.5   842
#> 2  2013     1     2   32.0 -14.3   943
#> 3  2013     1     3   27.7 -18.2   914
#> 4  2013     1     4   28.3 -17.0   915
#> 5  2013     1     5   22.6 -14.0   720
#> 6  2013     1     6   24.4 -13.6   832
#> # ℹ 359 more rows

还需注意分组规模的差异:第一段代码中 n() 统计的是每日延误航班数;第二段中 n() 统计的则是航班总数。

12.4.4 练习

  1. sum(is.na(x)) 会返回什么结果?mean(is.na(x)) 呢?

  2. 当对逻辑向量使用 prod() 时返回什么?它等价于哪个逻辑汇总函数?对逻辑向量使用 min() 时返回什么?它等价于哪个逻辑汇总函数?阅读文档并进行一些实验验证。

12.5 条件转换

逻辑向量最强大的功能之一是用于条件转换,即对满足条件 x 的情况执行一种操作,对满足条件 y 的情况执行另一种操作。 这需要用到两个重要函数:if_else()case_when()

12.5.1 if_else()

当需要为 TRUE 条件返回一个值,为 FALSE 条件返回另一个值时,可以使用 dplyr::if_else()if_else() 前三个参数是必须的。 第一个参数 condition 是逻辑向量;第二个参数 true 指定条件为真时的返回值;第三个参数 false 指定条件为假时的返回值。

从一个简单示例开始,我们将数字向量标记为"+ve"(正数)或"-ve"(负数):

复制代码
x <- c(-3:3, NA)
if_else(x > 0, "+ve", "-ve")
#> [1] "-ve" "-ve" "-ve" "-ve" "+ve" "+ve" "+ve" NA

还有一个可选的第四个参数,missing 用于处理输入为 NA 的情况:

复制代码
if_else(x > 0, "+ve", "-ve", "???")
#> [1] "-ve" "-ve" "-ve" "-ve" "+ve" "+ve" "+ve" "???"

你还可以为 truefalse 参数使用向量。 例如,这样可以创建一个最小化的 abs() 实现:

复制代码
if_else(x < 0, -x, x)
#> [1]  3  2  1  0  1  2  3 NA

目前所有参数都使用了相同的向量,但你当然可以混合搭配。 例如,可以实现一个简单版的 coalesce() 如下:

复制代码
x1 <- c(NA, 1, 2, NA)
y1 <- c(3, NA, 4, 6)
if_else(is.na(x1), y1, x1)
#> [1] 3 1 2 6

你可能已经注意到我们上面的标签示例有一个小问题:零既不是正数也不是负数。 我们可以通过增加一个额外的 if_else() 来解决这个问题:

复制代码
if_else(x == 0, "0", if_else(x < 0, "-ve", "+ve"), "???")
#> [1] "-ve" "-ve" "-ve" "0"   "+ve" "+ve" "+ve" "???"

这样的代码已经开始变得难以阅读,若条件增多会更加混乱。 此时可以改用 dplyr::case_when()

12.5.2 case_when()

dplyr 的 case_when() 借鉴了 SQL 的 CASE 语句,能够灵活地为不同条件执行不同运算。 其特殊语法与 tidyverse 其他函数风格迥异。 它采用 condition ~ output 的配对形式。condition 必须是逻辑向量;当为 TRUE 时,对应的 output 就会被采用。

通过这种方式,我们可以重构之前嵌套的 if_else() 代码如下:

复制代码
x <- c(-3:3, NA)
case_when(
  x == 0   ~ "0",
  x < 0    ~ "-ve", 
  x > 0    ~ "+ve",
  is.na(x) ~ "???"
)
#> [1] "-ve" "-ve" "-ve" "0"   "+ve" "+ve" "+ve" "???"

虽然代码量增加了,但表达更加清晰明确。

为了说明 case_when() 的工作原理,我们先看几个简单例子。 当所有条件都不匹配时,输出结果会返回 NA

复制代码
case_when(
  x < 0 ~ "-ve",
  x > 0 ~ "+ve"
)
#> [1] "-ve" "-ve" "-ve" NA    "+ve" "+ve" "+ve" NA

如果想设置默认值/兜底选项,可在左侧使用 TRUE

复制代码
case_when(
  x < 0 ~ "-ve",
  x > 0 ~ "+ve",
  TRUE ~ "???"
)
#> [1] "-ve" "-ve" "-ve" "???" "+ve" "+ve" "+ve" "???"

需注意,若多个条件同时匹配,只会采用第一个匹配项:

复制代码
case_when(
  x > 0 ~ "+ve",
  x > 2 ~ "big"
)
#> [1] NA    NA    NA    NA    "+ve" "+ve" "+ve" NA

if_else() 类似,你可以在 ~ 两侧使用变量,并根据问题需要自由组合变量。 例如,我们可以用 case_when() 为到达延误生成更易懂的标签:

复制代码
flights |> 
  mutate(
    status = case_when(
      is.na(arr_delay)      ~ "cancelled",
      arr_delay < -30       ~ "very early",
      arr_delay < -15       ~ "early",
      abs(arr_delay) <= 15  ~ "on time",
      arr_delay < 60        ~ "late",
      arr_delay < Inf       ~ "very late",
    ),
    .keep = "used"
  )
#> # A tibble: 336,776 × 2
#>   arr_delay status 
#>       <dbl> <chr>  
#> 1        11 on time
#> 2        20 late   
#> 3        33 late   
#> 4       -18 early  
#> 5       -25 early  
#> 6        12 on time
#> # ℹ 336,770 more rows

编写这类复杂的 case_when() 语句时需要格外谨慎;我最开始两次尝试混用了 <> 运算符,结果不小心创建了相互重叠的条件判断。

12.5.3 兼容类型

请注意,if_else()case_when() 都要求输出值的类型必须兼容。 如果类型不兼容,就会出现如下错误提示:

复制代码
if_else(TRUE, "a", 1)
#> Error in `if_else()`:
#> ! Can't combine `true` <character> and `false` <double>.

case_when(
  x < -1 ~ TRUE,  
  x > 0  ~ now()
)
#> Error in `case_when()`:
#> ! Can't combine `..1 (right)` <logical> and `..2 (right)` <datetime<local>>.

总体而言,兼容的类型相对较少,因为自动转换向量类型常会导致错误。 以下是几种最重要的兼容情况:

  • 数值型与逻辑型向量兼容,如 Section 12.4.2 所述。

  • 字符串与因子兼容(Chapter 16),因为因子本质是具有限定取值范围的字符串

  • Dates 与 date-times,我们将在 Chapter 17 讨论,因为 date 是 date-time 的特殊形式

  • NA,技术上属于逻辑向量,与所有类型兼容,因为所有向量都有表示缺失值的方式。

这些规则无需死记硬背,随着对 tidyverse 的持续使用会自然掌握,因为它们在整套工具中保持高度一致性。

12.5.4 练习

  1. 判断一个数是否为偶数可通过 x %% 2 == 0 实现。 请用此方法结合 if_else() 判断 0 到 20 之间每个数字的奇偶性。

  2. 给定日期向量如 x <- c("Monday", "Saturday", "Wednesday"),使用 ifelse() 标注周末或工作日。

  3. ifelse() 计算数值向量 x 的绝对值。

  4. 编写 case_when() 语句,根据 flights 数据集的 monthday 列标注美国主要节日(如元旦、独立日、感恩节、圣诞节)。 先创建逻辑列,TRUEFALSE,再创建字符列,节日名称或 NA

12.6 总结

逻辑向量的定义很简单,因为每个值只能是 TRUEFALSENA。 但逻辑向量却蕴含着巨大能量。 本章我们学习了如何用 >, <, <=, =>, ==, !=, is.na() 创建逻辑向量,如何用 !, &, | 进行组合运算,以及如何用 any(), all(), sum(), mean() 进行汇总。 你还掌握了能根据逻辑向量返回不同值的强大函数 if_else()case_when()

后续章节我们会反复见到逻辑向量。 例如在 Chapter 14 你将学习 str_detect(x, pattern),它会对 x 中符合模式的元素返回 TRUE,在 Chapter 17 你将通过日期时间比较生成逻辑向量。 但现在,我们将转向另一种重要向量类型:数值向量。


  1. R 通常会自动调用 print 函数(即,x 相当于 print(x) 的简写),但如果你想传入其他参数,显式调用会更方便。

  2. 也就是说,当 x 为 true 或 y 为 true,但不同时为 true 时,xor(x, y) 返回 true。 这正符合我们日常英语中使用 "or" 的方式。 "Both" 通常不是一个可接受的回答,对于"您想要冰淇淋还是蛋糕?"这个问题。

  3. 我们将在 @sec-joins 中详细讲解这一点。

  4. dplyr 的 if_else() 与 base R 的 ifelse() 非常相似。if_else() 相比 ifelse() 有两大优势:可以自定义缺失值的处理方式,且在变量类型不兼容时更可能给出有意义的错误提示。

--------------- 本章结束 ---------------

本期翻译贡献:

  • @TigerZ生信宝库

注:本文已开启快捷转载,欢迎大家转载,只需标明文章出处即可。

相关推荐
YangYang9YangYan8 小时前
职业本科发展路径与规划指南
大数据·人工智能·学习·数据分析
程序员小羊!9 小时前
电商项目练习实操(二)
大数据·数据分析·etl·flume
谅望者9 小时前
数据分析笔记01:数据分析概述
大数据·数据库·数据仓库·数据分析
PPT百科1 天前
PPT插入的图片太大了,怎么缩小一点?
信息可视化·数据分析·powerpoint·wps·ppt模板
青春不败 177-3266-05201 天前
基于最新导则下的生态环境影响评价技术方法及图件制作与案例
r语言·生态学·生物多样性·生态系统服务·环境科学·遥感解译·景观生态学
激动的小非1 天前
电商数据分析报告
大数据·人工智能·数据分析
人大博士的交易之路1 天前
今日行情明日机会——20251113
大数据·数据挖掘·数据分析·缠论·道琼斯结构·涨停板
B站计算机毕业设计之家1 天前
基于Python+Django+双协同过滤豆瓣电影推荐系统 协同过滤推荐算法 爬虫 大数据毕业设计(源码+文档)✅
大数据·爬虫·python·机器学习·数据分析·django·推荐算法
qunshankeji1 天前
战场目标检测:Faster R-CNN与RegNetX-800MF融合实现建筑物人员坦克车辆识别_2
目标检测·r语言·cnn