如何在 Polars 中将标量数值列广播加到列表列的每个元素上

9次阅读

如何在 Polars 中将标量数值列广播加到列表列的每个元素上

polars 1.10.0+ 支持列表列与标量列的原生广播算术运算,可直接使用 `pl.col(“lst”) + pl.col(“val”)` 实现逐元素相加,无需 `map_elements` 或嵌套 `list.eval`,简洁高效且性能优异。

在 Polars 中对列表列(list[i64])执行“按行广播”式算术运算(例如将 val 列中每行的标量值加到对应 lst 列中每个列表元素上),曾长期受限于 list.eval 不支持跨列引用的限制——如 pl.col(‘lst’).list.eval(pl.element() + pl.col(‘val’)) 会报错 named columns are not allowed in list.eval。

好消息是:自 Polars v1.10.0 起,这一需求已原生支持!
现在只需一行表达式即可优雅、向量化地完成操作:

import polars as pl  df = pl.DataFrame({     'lst': [[0, 1], [9, 8]],     'val': [3, 4] })  result = df.with_columns(     pl.col("lst") + pl.col("val") ) print(result)

输出:

shape: (2, 2) ┌───────────┬─────┐ │ lst       ┆ val │ │ ---       ┆ --- │ │ list[i64] ┆ i64 │ ╞═══════════╪═════╡ │ [3, 4]    ┆ 3   │ │ [13, 12]  ┆ 4   │ └───────────┴─────┘

原理说明:Polars 自动将标量列 val 按行广播(row-wise broadcast)至同行列的列表中每个元素,等效于对每个 (lst_i, val_i) 执行 [x + val_i for x in lst_i],全程零 python 循环,纯 rust 后端执行,性能远超 map_elements。

⚠️ 注意事项

  • 此特性仅适用于 Polars ≥ 1.10.0(发布于 2024 年 4 月)。请通过 polars.__version__ 确认版本,旧版本需升级。
  • 支持所有基础算术运算符:+, -, *, /, //, %, **,行为一致。
  • 若列表长度不一(如 [[1], [2, 3, 4]]),广播仍正常工作——每个列表独立与其对应标量运算。
  • 不支持混合类型(如 list[str] + i64),类型需兼容(如 list[i64] + i64)。

? 备选方案(兼容旧版本或特殊场景)
若暂无法升级,可借助 list.to_Struct() + struct 广播(要求列表长度一致或指定 n_field_strategy=”max_width”):

# 仅当列表长度统一时推荐;否则需先填充/截断 df.with_columns(     (pl.col("lst").list.to_struct(n_field_strategy="max_width")       + pl.struct(pl.col("val"))).alias("lst") )

但该方式返回 struct 类型,后续需 .struct.unnest() 才能还原为列表,不如原生广播直观。

? 总结:优先使用 pl.col(“lst”) + pl.col(“val”) —— 它是 Polars 官方推荐、高性能、声明式、且完全符合直觉的解决方案。升级到 1.10.0+ 后,告别 map_elements 的黑盒开销与类型隐患,让列表列算术真正“融入” Polars 的表达式引擎。

text=ZqhQzanResources