tidyverse介绍

tidyverse

tidyverse 是由 Hadley Wickham 及其团队引入的 R 编程语言的一系列开源软件包 , 这些软件包共享整洁数据 (tidy data) 的底层设计理念. tidyverse 系列包的特征是广泛利用管道操作以及 R 的非标准求值.

简而言之, tidyverse 提供了数据分析的一整套流程, 包括

  • 数据读取
  • 数据整理
  • 数据变换
  • 可视化

此外, tidyverse 的风格也影响了一大批 R 包:

要使用 tidyverse 中的各个包, 只需 library(tidyverse)

magrittr

magrittr 是 tidyverse 管道操作的基础包, 提供了多种管道符, 最常用的是 %>%. 管道符允许将前一步的结果作为下一个函数的第一个参数, 极大提升了代码的可读性.

常用管道符 (已被 tidyverse 引入):

  • %>%:将左侧作为右侧函数的第一个参数

  • %T>%:将左侧传递给右侧函数但返回左侧值, 常用于插入中间操作 (如 plot, print 这类依赖副作用的函数)

此外, magrittr 还提供了两个实用的管道符:

  • %$%:将左侧包含的 names 暴露给右侧

  • %<>%:在 %>% 的基础上, 将结果赋值回左侧

magrittr 总是将左侧变量传递给右侧函数作为第一个参数, 但有时我们需要传给其他参数. 此时可以使用 . 代表左侧变量:

tibble

tibble 是对 R 基础数据框的重构, 仅保留了多年来被证明实用的特性, 由 tibble 包提供.

有许多种方式创建一个 tibble:

  • 使用 tibble 直接创建

  • 使用 as_tibble 从现有的 data.frame 创建 (支持的更多数据结构见文档)

此外, 还有 tribble, as_tibble_row, as_tibble_col, enframe 等函数可创建 tibble.

readr: 数据读取

readr 提供了一系列风格统一的函数以读取各种文件并转换为 tibble.

  • read_csv(): 读取逗号分隔文件
  • read_tsv(): 读取制表符分隔文件
  • read_delim(): 读取任意分隔符的文本文件
  • read_table(): 读取空白字符分隔的表格

其中, read_csv()read_tsv() 可以看作 read_delim() 的特例.

readr 会自动推断列的类型, 但也允许手动修正.

另外常用的参数还包括

  • col_names: 是否把第一行当作列名
  • skip: 跳过文件开头若干行
  • n_max: 限制最大读取行数

另一个常见的需求是读取 excel 文件. readxl 包中的 read_excel() 实现了这个功能.

此外, 还存在与 read_*() 对应的 write_*() 函数族, 用于将 tibble 保存至各种格式的文件.

tidyr: 整洁数据

Hadley Wickham 将 tidy data 定义为满足如下条件的数据格式:

  • Each variable is a column; each column is a variable.
  • Each observation is a row; each row is an observation.
  • Each value is a cell; each cell is a single value.

这种结构很接近我们熟悉的 \(n \times p\) 矩阵. 典型的 tidy data 如

student_id subject score
1 math 85
1 english 90
2 math 78
2 english 88
3 math 90
3 english 95

tidyr 提供了一系列函数, 用于将不同形式的数据转换为 tidy data.

pivot_longer

最常见的非 tidy 数据是宽表, 即分类变量成为列名.

注意其中每个 cell 中的 count 唯一对应一个 religion 和一个 income. 此时可以使用 pivot_longer() 将宽表转化为长表, 成为 tidy data:

pivot_wider

有时长表中的列仅作为不同变量的 indicator 而没有实际意义, 此时应将其包含的每个变量作为一列.

此时可以使用 pivot_wider() 将宽表转化为长表, 成为 tidy data:

separate_longer_delim

有时候同一个 cell 中以字符串形式存储了同一变量的多个值:

此时可以使用 separate_longer_delim() 将 cell 含多个值的一行拆分成 cell 含一个值的多行, 成为 tidy data:

separate_wider_delim

有时候同一个 cell 中以字符串形式存储了多个变量的值:

此时可以使用 separate_wider_delim() 将含多个变量的一列拆分成含一个变量的多列, 成为 tidy data:

数据清洗

此外, tidyr 还包含一些数据清洗的函数:

  • complete(): 补全所有可能的组合
  • drop_na(): 删除含缺失值的行
  • replace_na(): 用指定值替换缺失值
  • fill(): 用前/后非缺失值填充缺失值

dplyr: 数据变换

dplyr 是 tidyverse 的核心包. 其提供了一组风格一致的动词, 以完成数据处理中所有常见操作.

  • 行操作
    • slice(): 行切片
    • filter(): 行筛选
    • distinct(): 行去重
    • arrange(): 行排序
  • 列操作
    • select(): 选择列
    • mutate(): 修改/新增列
    • rename(): 修改列名
    • relocate(): 列排序
  • 分组计算: group_by(), summarise()
  • 合并数据框: bind_rows(), bind_cols(), *_join()

行操作

slice

slice() 根据索引向量对行进行切片.

filter

filter() 根据条件过滤出符合要求的行, 参数是作为条件的逻辑向量 (通常由某些列向量运算得到).

distinct

distinct() 去除重复行, 参数是作为去重依据的向量.

arrange

arrange() 对行排序, 参数是作为排序依据的向量.

列操作

select

select() 用于提取 tibble 的列子集. 其接受一个 <tidy-select> 参数

mutate

mutate() 用于修改列/新增列/删除列. 注意

  • mutate() 会返回一个新的 tibble 而不是原地修改
  • mutate() 不会改变 tibble 的行数, 即只能新建/修改列为等长向量

rename

rename() 用于重命名 tibble 的列. 其语法为 rename(new_name = old_name).

rename_with() 用于批量修改 tibble 的列名. 它对选定的列名应用同一个函数以生成新的列名.

relocate

relocate() 用于调整 tibble 中列的位置. 可以将指定的列移动到表头/表尾/某一列的前后.

分组操作

group_by() 用于将 tibble 按一个或多个变量分组, 通常与 summarise() 搭配使用, 实现分组统计.

group_by

group_by() 用于按指定变量对 tibble 分组, 返回被分组的 tibble.

被分组的 tibble 不会立即改变, 但 mutate, filter, arrange 等函数作用于其上时会自动对每个组分别执行操作.

分组进行某个操作可以理解为:

  1. 将原 tibble 按照分组变量的值分割成多个行数较小的 tibble
  2. 对每个小 tibble 执行某个操作
  3. 将各个小 tibble 在行方向合并, 得到的结果形状与原 tibble 一致

使用 ungroup() 可以取消分组:

summarise

对于已使用 group_by 分组的 tibble, 使用 summarise() 可进行分组聚合. 使用 summarise() 后 tibble 默认将退出分组状态 (可由参数 .groups 控制).

与之前的分组计算不同, 使用 summarise() 的分组聚合会改变结果的行数. summarise() 要求每组的计算结果必须是长度为 1 的向量或行数为 1 的数据框.

reframe

使用 group_by + mutate/summarise 要求每组的计算结果为等长向量/标量, 这对分组计算造成了很大的限制.

比如, 如果我们希望计算每组的多个分位数, 此时每组计算结果的行数取决于分位数的数量.

此时我们可以使用 reframe(), 其允许每组的计算结果

注意: 这里我们在单个 verb 中使用了 .by 参数. 这与 group_by 的效果一致. 事实上, 可以与 group_by 联动的许多操作都支持直接传入 .by 参数, 无需显式 group_by (如 mutate, filter, summarise).

合并数据框

bind_rows / bind_cols

  • bind_rows(): 按行拼接多个数据框 (要求列名一致或自动补齐缺失列)
  • bind_cols(): 按列拼接多个数据框 (要求行数一致)

left_join / right_join

  • left_join(x, y, by = ...): 将 xy.by 列匹配, 保留 x 的行但可能丢弃 y 的行
  • left_join(x, y, by = ...): 将 xy.by 列匹配, 保留 y 的行但可能丢弃 x 的行
  • full_join(x, y, by = ...): 将 xy.by 列匹配, 保留 xy 中所有的行, 必要时用缺失值填充结果

<tidy-select> 语法

tidyverse 中常常出现参数需要提供列名子集 (通常是 cols 参数) 的情况. tidyselect 包提供了一套 DSL (domain specific language) 以快速构建 tibble 中特定的列名子集.

我们创建一个 tibble 用作演示:

按位置/列名构建

  • c(col1, col2, col3): 选择特定列 (col* 可以是数字/列名)
  • start:end: 选择从 startend 的所有列 (startend 可以是数字/列名)
  • everything(): 选择所有列
  • last_col(): 选择最后一列

按字符向量构建

可以用已存在的字符向量选择列

  • all_of(vars): 严格匹配, 选择 vars 中的所有列
  • any_of(vars): 宽松匹配, 选择 vars 中存在的列

正则匹配构建

  • starts_with("match"): 匹配以 “match” 开头的列名
  • ends_with("match"): 匹配以 “match” 结尾的列名
  • contains("match"): 匹配包含 “match” 的字符串的列名
  • matches("match"): 匹配符合正则表达式的列名
  • num_range("prefix", range): 匹配带有数字后缀的列名

条件匹配构建

使用 where() 结合返回 bool 的函数 (如 is.numeric, is.character, is.logical 等) 可以按条件选择列

结合多个 selection

可以像操作逻辑运算多个 selection

  • -/!: 取反
  • &: 交集
  • |: 并集
  • c(): combine 多个 selection 对应的列

<data-masking> 参数

tidyverse 中的许多参数被标记为 <data-masking>, 这代表该参数位置我们输入的表达式中的所有 symbol 会在 tibble 环境中求值, 而不是在全局环境中求值, 即 tibble 中的变量 “mask” 了全局环境中的变量.

通常, <data-masking> 参数收到的值是一个向量, 如刚刚的例子. 但有时这个参数上需要传入多个列的组合, 如 distinct() 中可能需要根据多列的值去重. 为此, 存在两个函数以在 <data-masking> 参数中以 <tidy-select> 语法选择多个列, 返回一个 tibble.

purrr

purrr 提供的 map_* 族函数可以对某个向量的所有元素应用同一个函数, 并得到结果.

使用 for 循环往往是需要对某个向量执行同样的操作. 由于 R 中一切操作都是函数调用, 因此 purrr 可以完全替代 for 循环.

purrr 包含:

  • map(.x, .f, ...): 对 .x 的每个元素应用函数 .f, 返回列表
  • map_lgl(.x, .f, ...): 返回逻辑向量
  • map_int(.x, .f, ...): 返回整数向量
  • map_dbl(.x, .f, ...): 返回数值向量
  • map_chr(.x, .f, ...): 返回字符向量
  • map_dfr(.x, .f, ...): 返回按行拼接的数据框
  • map_dfc(.x, .f, ...): 返回按列拼接的数据框

furrr: 并行计算

使用 purrr 的好处除了提高代码可读性以外, 还在于其可以无缝切换到并行计算.

furrr 基于 future 提供的并行计算后端, 实现了一系列 future_map_* 函数, 与 purrrmap_* 函数的用法几乎一致.

stringr

stringr 是 tidyverse 中用于字符串处理的包, 提供了一套一致、易用的字符串操作函数。常用函数包括:

  • str_sub(): 提取/替换字符串的子串.
  • str_glue(): 使用花括号 {} 在字符串中内嵌变量
  • str_extract(), str_extract_all(): 提取匹配的子串
  • str_replace(), str_replace_all(): 替换匹配的子串
  • str_split(): 按分隔符拆分字符串

forcats

forcats 提供了各种对因子的操作. 之所以出现这个包是因为 base R 中对因子的常见需求操作起来十分繁杂.

lubridate

lubridate 的产生原因与 forcats 类似, 主要处理日期类数据.

Cheatsheets

tidyverse 的回顾与反思

Hadley 对 tidyverse 发展历史的回顾

tidyverse 将数据操作拆分成很多动词, 以符合自然语言习惯与其设计哲学. 但这也导致了学习成本的增加. 例如, 行筛选其实就是基于逻辑向量对行索引, 行排序就是基于 order 向量对行索引, 但 tidyverse 却拆分成了 slice()/filter()/arrange() 三个动词.

统计之都转载: 从另一个视角看 tidyverse

Back to top