Polars 中按模式合并列并重复非模式列的完整教程

7次阅读

Polars 中按模式合并列并重复非模式列的完整教程

本文介绍如何在 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 中处理“模式化列合并 + 辅助列广播”任务的标准范式。

text=ZqhQzanResources