使用 Polars 实现稀疏数据的行转列(Pivot)操作

11次阅读

使用 Polars 实现稀疏数据的行转列(Pivot)操作

本文介绍如何使用 polars 的 `pivot` 方法,将长格式稀疏数据(id-key-value 三元组)高效转换为宽格式列向量表示,适用于大规模稀疏特征工程场景。

在处理稀疏特征数据(如用户行为、基因表达、推荐系统 item-tag 关系等)时,原始存储常采用紧凑的三列结构(id, key, value),以节省空间并便于增量更新;但在建模或矩阵运算阶段,往往需要将其展开为宽表形式——即每个唯一 key 成为一列,id 作为行索引,对应 value 填入单元格,缺失值自动填充为 NULL。Polars 提供了原生、高性能的 pivot 操作来完成这一转换,远优于手动分组拼接或循环构造。

✅ 正确用法:df.pivot(columns, index)

只需一行代码即可完成转换:

import polars as pl  df = pl.from_repr(""" ┌─────┬─────┬───────┐ │ id  ┆ key ┆ value │ │ --- ┆ --- ┆ ---   │ │ str ┆ str ┆ i64   │ ╞═════╪═════╪═══════╡ │ a   ┆ m1  ┆ 1     │ │ a   ┆ m2  ┆ 2     │ │ a   ┆ m3  ┆ 1     │ │ b   ┆ m2  ┆ 4     │ │ c   ┆ m1  ┆ 2     │ │ c   ┆ m3  ┆ 6     │ │ d   ┆ m4  ┆ 4     │ │ e   ┆ m2  ┆ 1     │ └─────┴─────┴───────┘ """)  result = df.pivot(on="key", index="id", values="value") print(result)

输出为标准宽表(5 行 × 5 列),自动对齐所有 key(m1–m4)作为列名,并保留原始数据类型(i64),缺失位置统一为 null:

shape: (5, 5) ┌─────┬──────┬──────┬──────┬──────┐ │ id  ┆ m1   ┆ m2   ┆ m3   ┆ m4   │ │ --- ┆ ---  ┆ ---  ┆ ---  ┆ ---  │ │ str ┆ i64  ┆ i64  ┆ i64  ┆ i64  │ ╞═════╪══════╪══════╪══════╪══════╡ │ a   ┆ 1    ┆ 2    ┆ 1    ┆ null │ │ b   ┆ null ┆ 4    ┆ null ┆ null │ │ c   ┆ 2    ┆ null ┆ 6    ┆ null │ │ d   ┆ null ┆ null ┆ null ┆ 4    │ │ e   ┆ null ┆ 1    ┆ null ┆ null │ └─────┴──────┴──────┴──────┴──────┘

? 参数说明: on=”key”:指定要“展开”的列(即新列名来源); index=”id”:指定行标识列(即新表的行索引); values=”value”:指定填充单元格的数值列(默认为 value,可省略)。

⚠️ 注意事项与进阶提示

  • 仅支持 eager 模式:pivot() 是 eager-only 方法,不适用于 LazyFrame。若需在 lazy 流程中 pivot,须先 .collect(),或改用 group_by().agg() + Struct + unnest 组合实现(参见 Polars 官方文档 Lazy Pivot 替代方案)。
  • 自动推断列名:on 列的所有唯一值将自动作为新列名,无需预先枚举(但要求 on 列值为合法标识符;含特殊字符时建议先清洗)。
  • 聚合冲突处理:若同一 (id, key) 出现多行,默认取首值;如需自定义聚合(如 sum、mean),需显式传入 aggregate_function 参数,例如:
    df.pivot(on="key", index="id", values="value", aggregate_function=pl.col("value").sum())
  • 内存友好性:Polars 的 pivot 底层基于哈希分组和稀疏列分配,对高基数稀疏数据表现优异,显著优于 pandas 的 pivot_table(尤其在百万级以上记录时)。

✅ 总结

将稀疏长表转为宽表是特征工程中的高频操作。Polars 的 pivot() 方法以简洁语法、明确语义和卓越性能,成为该任务的理想选择。掌握其核心参数与限制(尤其是 eager/lazy 差异),可大幅提升数据预处理 pipeline 的可读性与执行效率。对于超大规模场景,建议配合 scan_parquet() + collect() 分块 pivot,避免单次内存峰值过高。

text=ZqhQzanResources