04使用 tidyr 转换长表与宽表

使用 pivot_longer 和 pivot_wider 在长宽格式之间转换,并处理列名编码、缺失组合、多值列与重复键。

2026-03-05
RTidyversetidyrpivot_longerpivot_wider
本章目录 · 9

长表让每行表示一条观测,每列表示一个变量;宽表则可能把同一个变量的不同条件分散在多列。两种形状没有绝对优劣,关键是当前操作需要什么结构。

宽表转长表

library(dplyr)
library(tidyr)

wide <- tibble(
  id = 1:3,
  gene_A = c(10, 20, 30),
  gene_B = c(15, 25, 35)
)

long <- wide |>
  pivot_longer(
    cols = starts_with("gene_"),
    names_to = "gene",
    values_to = "expression"
  )

结果中,一个 id 对应多行,每行保存一个基因及其表达量。

选择需要拉长的列

cols 使用 tidyselect 语法:

wide |> pivot_longer(gene_A:gene_B)
wide |> pivot_longer(-id)
wide |> pivot_longer(where(is.numeric) & !id)

应明确哪些列是标识变量,哪些列名实际上编码了变量值。误把 ID 或固定协变量拉长,会改变观测单位。

从列名中提取信息

列名 A_1A_2B_1 同时编码类型和重复编号:

wide2 <- tibble(
  id = 1:2,
  A_1 = c(5, 6),
  A_2 = c(7, 8),
  B_1 = c(9, 10)
)

wide2 |>
  pivot_longer(
    cols = -id,
    names_to = c("type", "replicate"),
    names_sep = "_",
    values_to = "value"
  )

结构更复杂时使用正则捕获组:

wide2 |>
  pivot_longer(
    cols = -id,
    names_to = c("type", "replicate"),
    names_pattern = "([A-Z])_(\\d+)",
    values_to = "value"
  )

如果只需要移除固定前缀:

wide |>
  pivot_longer(
    starts_with("gene_"),
    names_to = "gene",
    names_prefix = "gene_",
    values_to = "expression"
  )

缺失值与不存在的组合

wide2 |>
  pivot_longer(
    cols = -id,
    names_to = c("type", "replicate"),
    names_sep = "_",
    values_to = "value",
    values_drop_na = TRUE
  )

values_drop_na = TRUE 会删除值为 NA 的结果行。这适合宽表中的 NA 代表组合不存在的情况;如果它代表一次真实但缺失的测量,删除后会隐藏缺失信息。

长表转宽表

long2 <- tibble(
  id = c(1, 1, 2, 2),
  key = c("A", "B", "A", "B"),
  value = c(10, 20, 30, 40)
)

long2 |>
  pivot_wider(
    names_from = key,
    values_from = value
  )

names_from 的取值成为新列名,values_from 提供单元格值,其余列共同构成行标识。

填充缺失组合

scores <- tibble(
  name = c("Alice", "Alice", "Bob"),
  test = c("Math", "Science", "Math"),
  score = c(80, 90, 70)
)

scores |>
  pivot_wider(
    names_from = test,
    values_from = score,
    values_fill = NA
  )

只有当缺失组合在业务上确实代表零时,才使用 values_fill = 0。没有记录和观测值为零是不同状态。

同时展开多个值列

measurements <- tibble(
  id = c(1, 1, 2, 2),
  visit = c("baseline", "followup", "baseline", "followup"),
  weight = c(70, 68, 82, 79),
  pressure = c(130, 124, 145, 136)
)

measurements |>
  pivot_wider(
    names_from = visit,
    values_from = c(weight, pressure),
    names_sep = "_"
  )

输出列类似 weight_baselinepressure_followup

重复键

若同一个行标识与列标识对应多个值,宽表中的一个单元格无法容纳它们:

duplicated <- tibble(
  id = c(1, 1),
  key = c("A", "A"),
  value = c(10, 12)
)

先检查重复来自录入问题、重复测量还是缺少标识列:

duplicated |> count(id, key) |> filter(n > 1)

确实需要汇总时,显式指定规则:

duplicated |>
  pivot_wider(
    names_from = key,
    values_from = value,
    values_fn = mean
  )

不要为了消除重复警告就随意取均值;聚合规则会改变数据含义。

能否往返转换

pivot_longer()pivot_wider() 在键唯一、类型兼容且没有删除缺失的情况下可以互相还原。实际工作中,往返失败通常揭示重复键、隐含标识列或缺失组合。

长宽转换不是单纯排版。它重新定义“每行是什么观测、每列是什么变量”,因此应先写清楚数据的观测单位和唯一键。