一个字符串列中常常塞着多个值,例如样本编号 "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" 为例:
A、B、C分别代表不同字段:使用separate_wider_delim();- 三段都是同一种标签:使用
separate_longer_delim(); - 没有分隔符,但每两位代表一个字段:使用
separate_wider_position(); - 没有分隔符,每两位代表一条同类记录:使用
separate_longer_position(); - 字段边界必须用模式描述:使用
separate_wider_regex()。
拆成多列是在解释一个复合字段的内部结构,拆成多行是在展开一个单元格中的重复值。这个语义差别比函数名称本身更重要。
与 pivot 操作的区别
separate_* 拆分的是单元格内部的字符串;pivot_longer() 和 pivot_wider() 改变的是多列之间的表格形状。
例如,把 height_2024 拆成指标与年份,是解析列名或字符串;把 height_2024、height_2025 两列转换为 year 和 height 两列,则是宽表转长表。两类操作可能连续出现,但解决的不是同一个问题。
导入后先诊断再拆分
实际数据中,分隔符数量、编码长度和正则模式经常并不完全一致。稳妥的流程是:
- 保留原始字符串列;
- 统计或抽查不同结构;
- 使用默认报错或 debug 模式定位异常;
- 明确异常值的业务含义;
- 最后才选择合并、补齐或丢弃多余部分。
这样,拆分函数不仅改变表格形状,也承担了一次结构验证。