《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生信宝库

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

相关推荐
SelectDB5 小时前
Doris Catalog 已上线!性能提升 200x,全面优于 JDBC Catalog,跨集群查询迈入高性能分析时代
数据库·数据分析·apache
F_D_Z7 小时前
【Python】家庭用电数据的时序分析
python·数据分析·时序分析·序列分解
可观测性用观测云8 小时前
华为云 LTS 日志上报到观测云最佳实践
数据分析
Maxwell_li115 小时前
新冠检测例子学习查准率和召回率
学习·机器学习·数据分析·回归·numpy·pandas
ClouGence15 小时前
打通复杂医疗数据链路:某头部医疗服务商的数据底座落地经验分享
数据库·经验分享·数据分析
Tiger Z17 小时前
《R for Data Science (2e)》免费中文翻译 (第15章) --- Regular expression(2)
数据分析·r语言·数据科学·免费书籍
天桥下的卖艺者17 小时前
R语言演示对没有吸收状态的马尔科夫链分析
开发语言·r语言
Biomamba生信基地17 小时前
用R语言画生信基地圣诞树~
开发语言·r语言·单细胞·空间转录组·biomamba生信基地
七夜zippoe17 小时前
AutoGen数据分析智能体实战:让Excel自动说话
人工智能·数据分析·excel·智能体·autogen
刘永鑫Adam18 小时前
Nature Methods | 诸奇赟组-Scikit-bio:用于生物组学数据分析的基础Python库
人工智能·python·算法·机器学习·数据分析