
本文介绍如何在 polars 中将匹配命名模式(如 `a_0`, `a_1`, `a_2`)的多列纵向堆叠为单列(如 `a`),同时自动复制其他列(如 `words`, `groups`)以保持行对齐,适用于数据重塑与长格式转换场景。
在 Polars 数据分析中,常需将宽格式中具有相同前缀、不同后缀(如 _0, _1, _2)的列“纵向合并”为单列,同时保留其他辅助列(如分组标识、文本描述等)——但这些辅助列不能简单丢弃或聚合,而需按原顺序重复多次,使其长度与合并后的新列一致。这本质上是构建“长格式”(long format)数据的过程,但不同于常规 unpivot,它要求:
- 按列名前缀分组(如所有 a_* → a,所有 b_* → b);
- 同一组内列按列序垂直拼接(a_0 全部在前,接着 a_1,再 a_2);
- 非模式列(如 words, groups)需被精确复制 m 次(m 为每组列数),且保持与对应值的逻辑配对。
实现该目标的核心思路是:先 unpivot 构建索引骨架 → 清洗变量名生成逻辑分组 → 添加组内序号 → 多键 pivot 重构结构。以下是完整、可复用的解决方案:
import polars as pl import numpy as np import string # 构造示例数据(同原文) rng = np.random.default_rng(42) nr = 3 letters = list(string.ascii_letters) uppercase = list(string.ascii_uppercase) words, groups = [], [] for i in range(nr): word = ''.join([rng.choice(letters) for _ in range(rng.integers(3, 20))]) words.append(word) group = rng.choice(uppercase) groups.append(group) df = pl.DataFrame({ "a_0": np.linspace(0, 1, nr), "a_1": np.linspace(1, 2, nr), "a_2": np.linspace(2, 3, nr), "b_0": np.random.rand(nr), "b_1": 2 * np.random.rand(nr), "b_2": 3 * np.random.rand(nr), "words": words, "groups": groups, }) # ✅ 关键转换:合并 a_*, b_* 列,并重复 words/groups result = ( df .unpivot( index=["words", "groups"], # 将非模式列设为 pivot 索引(即保留在每行) on=[col for col in df.columns if "_" in col and col.split("_")[-1].isdigit()] # 显式指定要 unpivot 的列(更安全) ) .with_columns( pl.col("variable").str.replace(r"_d+$", "") # 提取前缀:a_0 → "a", b_1 → "b" ) .with_columns( index=pl.int_range(pl.len()).over("variable") # 在每个前缀组内编号(0,1,2,...),确保 a_0/a_1/a_2 的值按列序堆叠 ) .pivot( on="variable", index=["index", "words", "groups"], values="value", aggregate_function=None # 禁用聚合,确保一对一映射 ) .drop("index") # 删除临时索引列 ) print(result)
输出结果与预期完全一致:
shape: (9, 4) ┌─────────────────┬────────┬─────┬──────────┐ │ words ┆ groups ┆ a ┆ b │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ str ┆ f64 ┆ f64 │ ╞═════════════════╪════════╪═════╪══════════╡ │ OIww ┆ W ┆ 0.0 ┆ 0.653892 │ │ KkeB ┆ Z ┆ 0.5 ┆ 0.408888 │ │ NLOAgRxAtjWOHuQ ┆ O ┆ 1.0 ┆ 0.423949 │ │ OIww ┆ W ┆ 1.0 ┆ 0.234362 │ │ KkeB ┆ Z ┆ 1.5 ┆ 0.213767 │ │ NLOAgRxAtjWOHuQ ┆ O ┆ 2.0 ┆ 0.646378 │ │ OIww ┆ W ┆ 2.0 ┆ 0.880558 │ │ KkeB ┆ Z ┆ 2.5 ┆ 1.833025 │ │ NLOAgRxAtjWOHuQ ┆ O ┆ 3.0 ┆ 0.116173 │ └─────────────────┴────────┴─────┴──────────┘
⚠️ 注意事项与最佳实践
- 列名正则更鲁棒:使用 r”_d+$” 替代 “_.*” 可避免误删含下划线的合法前缀(如 user_name_0);
- 显式指定 on= 参数:比依赖 unpivot 自动推断更安全,尤其当存在意外匹配列时;
- aggregate_function=None 是关键:若省略,pivot 默认使用 first 聚合,可能导致数据丢失;
- 性能提示:对于超大表,over(“variable”) 的窗口计算开销可控,但建议预先过滤无关列以提升效率;
- 扩展性:支持任意数量前缀组(如 c_0, c_1, d_0, d_1, d_2, d_3),只需确保命名规范统一。
该方法兼具表达力与稳定性,是 Polars 中处理“模式化列合并 + 辅助列广播”任务的标准范式。