
本文介绍如何使用 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,避免单次内存峰值过高。










