
本文详解如何在 Polars 中将 pl.all() 转换为可迭代列表达式列表,结合 pl.cum_reduce 实现高效水平填充(forward/backward fill),并提供兼容默认全列、支持反向顺序的健壮实现。
本文详解如何在 polars 中将 `pl.all()` 转换为可迭代列表达式列表,结合 `pl.cum_reduce` 实现高效水平填充(forward/backward fill),并提供兼容默认全列、支持反向顺序的健壮实现。
在 Polars 中,pl.all() 返回的是一个单表达式(pl.Expr),而非列名或表达式的可迭代对象,因此无法直接用于 for 循环、reversed() 或索引操作——这正是许多用户(如 fill_horizontal 函数开发者)遇到的核心障碍。但通过合理利用 Polars 的元编程能力与表达式构造机制,我们完全可以在不牺牲惰性求值和查询优化的前提下,优雅地解决该问题。
✅ 推荐方案:pl.cum_reduce + 动态列解析
pl.cum_reduce 是专为水平累积操作设计的高效原语,它对一系列表达式从左到右依次应用二元函数(如 pl.coalesce(s2, s1)),天然适配“用右侧非空值向前填充左侧”的逻辑。关键在于:必须确保输入是明确的列表达式列表,而非单个 pl.all() 表达式。
因此,正确做法是:在函数内部根据上下文动态展开 pl.all() 为实际列名列表,并构造对应 pl.col(name) 表达式:
import polars as pl
from typing import Iterable, Optional, Union
from polars._typing import IntoExpr
def fill_horizontal(
exprs: Optional[Iterable[IntoExpr]] = None,
*,
forward: bool = True,
df: Optional[pl.DataFrame] = None
) -> list[pl.Expr]:
"""
生成水平正向/反向填充的表达式列表。
支持传入列名、表达式或 None(此时自动推导 df.columns)。
注意:若 exprs 为 None,df 参数必须提供以获取列信息。
"""
if exprs is None:
if df is None:
raise ValueError("当 exprs=None 时,必须提供 df 参数以获取列名")
# 安全展开 pl.all() → 实际列名列表 → pl.col() 表达式
cols = [pl.col(name) for name in df.columns]
else:
# 将字符串列名转为 pl.col(),保持原生表达式不变
cols = [
pl.col(col) if isinstance(col, str) else col
for col in exprs
]
# 控制顺序:forward=True → 从左到右;False → 从右到左(需反转列表)
ordered_cols = cols if forward else list(reversed(cols))
# 构建累积 coalesce:s2 优先于 s1(即右侧值覆盖左侧空值)
# pl.cum_reduce 返回 struct,需 .struct.unnest() 展开为多列
result_struct = pl.cum_reduce(
lambda s1, s2: pl.coalesce(s2, s1),
ordered_cols
).struct.unnest()
return list(result_struct)
? 使用示例
df = pl.DataFrame({
"col1": [1, None, 2],
"col2": [1, 2, None],
"col3": [None, None, 3],
})
# ✅ 正向填充(默认所有列)
print("Forward fill (all columns):")
print(df.with_columns(fill_horizontal(df=df)))
# ✅ 反向填充(所有列)
print("\nBackward fill (all columns):")
print(df.with_columns(fill_horizontal(forward=False, df=df)))
# ✅ 指定部分列
print("\nForward fill on ['col2', 'col3'] only:")
print(df.with_columns(fill_horizontal(["col2", "col3"], df=df)))
输出:
Forward fill (all columns): shape: (3, 3) ┌──────┬──────┬──────┐ │ col1 ┆ col2 ┆ col3 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╡ │ 1 ┆ 1 ┆ 1 │ │ null ┆ 2 ┆ 2 │ │ 2 ┆ 2 ┆ 3 │ └──────┴──────┴──────┘ Backward fill (all columns): shape: (3, 3) ┌──────┬──────┬──────┐ │ col1 ┆ col2 ┆ col3 │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞══════╪══════╪══════╡ │ 1 ┆ 1 ┆ null │ │ 2 ┆ 2 ┆ null │ │ 2 ┆ 3 ┆ 3 │ └──────┴──────┴──────┘
⚠️ 注意事项与最佳实践
-
不要直接对
pl.all()调用reversed():pl.all()是原子表达式,reversed(pl.all())会抛出TypeError; -
避免硬编码
ncols:早期 workaround 中用pl.nth(range(n, -1, -1))依赖列数预估,易出错且不可维护; -
优先使用
df.columns+pl.col()构造:这是最清晰、最安全、最符合 Polars 设计哲学的方式; -
pl.cum_reduce当前仅支持左→右累积:若需原生右→左累积(如真正意义上的“backward”逻辑),请关注 Polars #21415 并积极投票; -
性能提示:
pl.cum_reduce(...).struct.unnest()在新版 Polars(≥0.20.30)中已高度优化,比手动pl.coalesce循环快数倍,且全程保持惰性计算。
综上,将 pl.all() 视为“列集合的符号”,而非“可迭代对象”,并在运行时通过 DataFrame.schema 或 df.columns 显式解析为列表达式列表,是当前最可靠、最可扩展的工程实践。










