05使用 tidyr 拆分字符串列

根据分隔符、固定宽度或正则表达式,将字符串列横向拆成多列或纵向展开成多行。

2026-03-02
RtidyrData WranglingStrings
本章目录 · 9

一个字符串列中常常塞着多个值,例如样本编号 "case_liver_01"、标签列表 "red,blue",或者定长编码 "ABCDEF"。拆分前先要回答两个问题:依据什么切开,以及结果应当增加列还是增加行。

tidyr 的 separate_* 家族把这两个维度写进了函数名:

输出方向 拆分规则 函数
横向,多列 分隔符 separate_wider_delim()
横向,多列 固定宽度 separate_wider_position()
横向,多列 正则表达式 separate_wider_regex()
纵向,多行 分隔符 separate_longer_delim()
纵向,多行 固定宽度 separate_longer_position()

wider 表示一行变宽,longer 表示一行展开为多行。先确定输出形状,再选择拆分规则,通常就能确定函数。

library(tibble)
library(tidyr)

按分隔符拆成多列

separate_wider_delim() 适合结构中存在明确分隔符,并且每一段具有不同含义的字符串。

samples <- tibble(
  code = c("case_liver_01", "control_blood_02")
)

samples |>
  separate_wider_delim(
    code,
    delim = "_",
    names = c("group", "tissue", "replicate")
  )

结果中每一段成为独立列:

group tissue replicate
case liver 01
control blood 02

names 的数量同时表达了期望的片段数。如果实际数据包含过少或过多的片段,函数默认报错,促使我们先检查异常值。

处理片段数量不一致

片段过多时,可以将多余部分合并到最后一列:

x <- tibble(code = c("A_01", "B_batch_02"))

x |>
  separate_wider_delim(
    code,
    delim = "_",
    names = c("group", "id"),
    too_many = "merge"
  )

片段过少时,可以从开头对齐并在末尾补缺失值:

x <- tibble(code = c("A_01", "B"))

x |>
  separate_wider_delim(
    code,
    delim = "_",
    names = c("group", "id"),
    too_few = "align_start"
  )

也可以先使用 too_few = "debug"too_many = "debug" 找到不符合结构的记录。生产流程中不应仅为了消除报错就丢弃额外片段;应先判断它们是脏数据,还是字符串结构本来就不固定。

按固定宽度拆成多列

separate_wider_position() 适合没有分隔符、但各字段宽度固定的编码。

codes <- tibble(code = c("ABCDEF", "XYZ123"))

codes |>
  separate_wider_position(
    code,
    widths = c(prefix = 2, middle = 2, suffix = 2)
  )
prefix middle suffix
AB CD EF
XY Z1 23

widths 是一个命名数值向量:名称成为新列名,数值指定对应字段的字符宽度。

字符串长度不足时,默认报错。确认短值可以补齐后,可从开头对齐:

codes <- tibble(code = c("ABCDEF", "XYZ"))

codes |>
  separate_wider_position(
    code,
    widths = c(a = 2, b = 2, c = 2),
    too_few = "align_start"
  )

固定宽度拆分依赖字符位置。开始前应确认字段是否真的定长,以及全角字符、空格或异常编码会不会改变位置。

按正则表达式拆成多列

当字段结构由模式而不是单一分隔符决定时,使用 separate_wider_regex()。命名的正则模式会形成输出列,未命名的模式只负责匹配分隔部分。

measurements <- tibble(value = c("12.5kg", "8.0kg", "100.2kg"))

measurements |>
  separate_wider_regex(
    value,
    patterns = c(amount = "[0-9.]+", unit = "[A-Za-z]+")
  )

也可以明确匹配字段之间的字符:

samples <- tibble(code = c("case_01", "control_02"))

samples |>
  separate_wider_regex(
    code,
    patterns = c(group = ".*", "_", replicate = "[0-9]+")
  )

正则表达式适合结构稳定但边界复杂的编码。如果只是按一个固定字符切开,separate_wider_delim() 更容易阅读。

按分隔符拆成多行

当一个单元格保存同一类事物的多个值时,通常应增加行而不是增加列。separate_longer_delim() 会复制该行的其他字段,并让每个片段各占一行。

records <- tibble(
  id = c(1, 2),
  tags = c("red,blue", "green")
)

records |>
  separate_longer_delim(tags, delim = ",")
id tags
1 red
1 blue
2 green

这个结果符合“一格一个值”的整洁数据原则,后续可以直接按 tags 分组、计数或连接。

如果需要同时拆分多列,应先确认各列在每行中产生相同数量的片段,否则字段之间的对应关系可能并不明确。

按固定宽度拆成多行

separate_longer_position() 将每个字符串按相同宽度切片,并将切片纵向展开。它适合连续编码和等长序列片段。

sequences <- tibble(
  seq_id = c("s1", "s2"),
  sequence = c("ATGCTA", "CGTAAA")
)

sequences |>
  separate_longer_position(sequence, width = 3)
seq_id sequence
s1 ATG
s1 CTA
s2 CGT
s2 AAA

若字符串长度不是 width 的整数倍,最后一段会短于指定宽度。空字符串默认不会产生结果行;需要保留时可以设置 keep_empty = TRUE

如何选择

"A_B_C" 为例:

  • ABC 分别代表不同字段:使用 separate_wider_delim()
  • 三段都是同一种标签:使用 separate_longer_delim()
  • 没有分隔符,但每两位代表一个字段:使用 separate_wider_position()
  • 没有分隔符,每两位代表一条同类记录:使用 separate_longer_position()
  • 字段边界必须用模式描述:使用 separate_wider_regex()

拆成多列是在解释一个复合字段的内部结构,拆成多行是在展开一个单元格中的重复值。这个语义差别比函数名称本身更重要。

与 pivot 操作的区别

separate_* 拆分的是单元格内部的字符串;pivot_longer()pivot_wider() 改变的是多列之间的表格形状。

例如,把 height_2024 拆成指标与年份,是解析列名或字符串;把 height_2024height_2025 两列转换为 yearheight 两列,则是宽表转长表。两类操作可能连续出现,但解决的不是同一个问题。

导入后先诊断再拆分

实际数据中,分隔符数量、编码长度和正则模式经常并不完全一致。稳妥的流程是:

  1. 保留原始字符串列;
  2. 统计或抽查不同结构;
  3. 使用默认报错或 debug 模式定位异常;
  4. 明确异常值的业务含义;
  5. 最后才选择合并、补齐或丢弃多余部分。

这样,拆分函数不仅改变表格形状,也承担了一次结构验证。