【R】tidyr::pivot_longer / pivot_wider 学习笔记

复习一些高频参数的使用技巧,包括:

values_fill,value_fn
names_glue
names_pattern
.value

0. 准备工作

r 复制代码
library(tidyverse)

1. 为什么需要 pivot?先理解长表与宽表

1.1 一份"分析友好"的长表(tidy data)

r 复制代码
df_long <- tibble(
  id    = c(1, 1, 2, 2),
  year  = c(2022, 2023, 2022, 2023),
  score = c(90, 95, 80, 83)
)

df_long

如何读这张表:

  • 一行 = 一个观测
  • 一个观测 = 某个 id 在某一年 (year) 的一个 score

这是 tidyverse 设计哲学中最核心的结构:

维度 在哪里
个体
时间 列(year)
数值 列(score)

1.2 现实中更常见的宽表

r 复制代码
df_wide <- tibble(
  id = c(1, 2),
  `2022` = c(90, 80),
  `2023` = c(95, 83)
)

df_wide

这种表:

  • 适合报表、Excel
  • 不适合建模、ggplot、group_by

👉 pivot 的目的,就是在这两种形态之间转换


2. 在学 pivot 之前,先回答一个问题

变形之后,你希望"一行"代表什么?

在接下来的例子中,我们先统一一个目标:

一行 = 一个 id

这个决定,将直接决定你后面所有 pivot 的写法。


3. pivot_wider:从长表到宽表(结构重建)

3.1 pivot_wider 的结构公式(不是参数记忆)

text 复制代码
(id_cols) × (names_from) → values_from

含义是:

  • id_cols:定义"一行是谁"
  • names_from:哪一列的值变成新列名
  • values_from:填进单元格的值

3.2 最基础、也是最推荐的写法

r 复制代码
df_long %>%
  pivot_wider(
    id_cols     = id,
    names_from  = year,
    values_from = score
  )

结果:

复制代码
# A tibble: 2 × 3
     id `2022` `2023`
  <dbl>  <dbl>  <dbl>
1     1     90     95
2     2     80     83

你现在应该能清楚地说出:

  • 一行 = 一个 id
  • 列 = 年份
  • 值 = score

3.3 为什么不建议省略 id_cols

你可能见过:

r 复制代码
pivot_wider(names_from = year, values_from = score)

在当前数据下它能跑,是因为:

  • tidyr 自动把剩余列(这里只有 id)当作行身份

⚠️ 风险在于

  • 这是"刚好没出事"
  • 一旦前面多一个变量、一次 join、一次 mutate
    行身份就可能悄悄改变

教学建议

初学阶段,始终显式写 id_cols


4. 当数据变复杂:行身份真的明确吗?

4.1 在原结构上引入一个新维度

r 复制代码
df_long2 <- tibble(
  id    = c(1, 1, 1, 1),
  sex   = c("M", "M", "F", "F"),
  year  = c(2022, 2023, 2022, 2023),
  score = c(90, 95, 88, 92)
)

df_long2

4.2 不写 id_cols,tidyr 会"猜"

r 复制代码
df_long2 %>%
  pivot_wider(
    names_from  = year,
    values_from = score
  )

tidyr 实际做的是:

r 复制代码
id_cols = c(id, sex)

结果:

复制代码
id | sex | 2022 | 2023

⚠️ 重点不在于"能不能跑",而在于:

你是否真的想让"一行 = id × sex"?


5. values_fn:当映射不是一对一时

5.1 构造一个真实世界中常见的问题

r 复制代码
df_long3 <- tibble(
  id    = c(1, 1, 1, 1, 1),
  sex   = c("M", "M", "M", "F", "F"),
  year  = c(2022, 2022, 2023, 2022, 2023),
  score = c(90, 91, 95, 88, 92)
)

同一个 (id, sex, year) 出现了多次。


5.2 直接 pivot 会失败

r 复制代码
df_long3 %>%
  pivot_wider(
    id_cols     = c(id, sex),
    names_from  = year,
    values_from = score
  )

原因不是函数问题,而是结构问题

text 复制代码
(id, sex, year) → score
不是一对一关系

5.3 用 values_fn 明确你的处理方式

r 复制代码
df_long3 %>%
  pivot_wider(
    id_cols     = c(id, sex),
    names_from  = year,
    values_from = score,
    values_fn   = mean
  )

values_fn 不是"修 bug",
而是你在声明:重复观测如何被压缩


6. values_fill:当某些组合根本不存在

r 复制代码
df_long4 <- tibble(
  id    = c(1, 2),
  year  = c(2022, 2023),
  score = c(90, 80)
)
df_long4 %>%
  pivot_wider(
    id_cols     = id,
    names_from  = year,
    values_from = score
  )

自然会产生 NA

如果语义上希望补 0:

r 复制代码
df_long4 %>%
  pivot_wider(
    id_cols     = id,
    names_from  = year,
    values_from = score,
    values_fill = 0
  )

7. 当 names_from 不止一个变量:names_glue

7.1 一个非常自然的需求

我们继续使用前面已经出现过的结构:

r 复制代码
df_long5 <- tibble(
  id    = c(1, 1, 1, 1),
  sex   = c("M", "M", "F", "F"),
  year  = c(2022, 2023, 2022, 2023),
  score = c(90, 95, 88, 92)
)

目标:

  • 一行 = 一个 id
  • 列名同时包含 性别 + 年份

7.2 默认行为:tidyr 自动拼接列名

r 复制代码
df_long5 %>%
  pivot_wider(
    id_cols     = id,
    names_from  = c(sex, year),
    values_from = score
  )

得到列名:

复制代码
M_2022, M_2023, F_2022, F_2023

问题在于:

  • 顺序是隐式规则
  • 分隔符是实现细节
  • 列名语义不明确

7.3 使用 names_glue 显式控制列名语义

r 复制代码
df_long5 %>%
  pivot_wider(
    id_cols     = id,
    names_from  = c(sex, year),
    values_from = score,
    names_glue  = "{sex}_{year}"
  )

如果你想换顺序:

r 复制代码
names_glue = "{year}_{sex}"

或者:

r 复制代码
names_glue = "score_{sex}_{year}"

names_glue 只影响列名,不影响结构

它是语义层参数,不是结构修补参数。


8. pivot_longer:从宽表回到长表(结构展开)

r 复制代码
df_wide %>%
  pivot_longer(
    cols      = c("2022", "2023"),
    names_to  = "year",
    values_to = "score"
  )

理解重点:

  • pivot_longer = 把"列名"变成"值"
  • 很少产生结构冲突
  • 常用于恢复 tidy 数据

9. 列名里有信息时:names_pattern

r 复制代码
df_wide2 <- tibble(
  id = 1:3,
  Score_2022 = c(90, 80, 70),
  Score_2023 = c(95, 83, 72)
)
df_wide2 %>%
  pivot_longer(
    cols = starts_with("Score"),
    names_to = c("metric", "year"),
    names_pattern = "(.+)_(.+)",
    values_to = "value"
  )

这一步的本质是:

把"字符串里的信息"还原成变量


10. .value:一次生成多个观测变量

如果变量名本身藏在列名中:

r 复制代码
df_long4 <- tibble(
  id = 1,
  year = 2022,
  score_A = 90,
  rank_A  = 1
)
r 复制代码
df_long4 %>%
  pivot_longer(
    cols = -c(id, year),
    names_to = c(".value", "group"),
    names_sep = "_" #或 names_pattern="(.+)_(.+)"
  )

.value 的含义是:
"这一部分列名,直接决定生成哪个变量"

相关推荐
AI视觉网奇2 小时前
ue metahuman 数字人换装笔记MetaTailor
笔记
ljt27249606612 小时前
Compose笔记(六十五)--插槽模式
android·笔记·android jetpack
d111111111d2 小时前
STM32平衡车测试,定时中断读取速度
笔记·stm32·单片机·嵌入式硬件·学习·模块测试
海棠AI实验室2 小时前
Python 学习路线图:从 0 到 1 的最短闭环
开发语言·python·学习
lkbhua莱克瓦242 小时前
基础-事务
开发语言·数据库·笔记·mysql·事务
fantasy_arch2 小时前
LSTM模型学习分析
人工智能·学习·lstm
潲爺3 小时前
《Java 8-21 高频特性实战(上):5 个场景解决 50% 开发问题(附可运行代码)》
java·开发语言·笔记·学习
2301_800050993 小时前
ceph分布式存储
笔记·分布式·ceph
YJlio3 小时前
Contig 学习笔记(13.5):整理现有文件碎片的策略与批量实战
笔记·学习·stable diffusion