复习一些高频参数的使用技巧,包括:
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的含义是:
"这一部分列名,直接决定生成哪个变量"