Polars中rolling_mean链式调用.over()与分步执行结果不一致问题咨询
Polars链式
.over()中rolling_mean与sum的差异:原因与解决方案 你观察到的这个现象是Polars中窗口函数链式调用的特定行为,并非bug,而是滚动窗口操作和普通聚合函数在窗口上下文处理上的本质差异导致的。
为什么会有差异?
- 普通聚合函数(如
sum):当你调用sum().over('grp')时,Polars会计算每个grp组的总和,然后将这个单一值广播到组内的每一行。此时后续的.over('date')是基于已经广播完成的结果进行分组排名,逻辑清晰且能正确执行。 - 滚动窗口函数(如
rolling_mean):rolling_mean(2).over('grp')的结果是每行对应其窗口内的计算值。当你直接链式调用.rank().over('date')时,Polars会将整个表达式的上下文切换到date分组,导致滚动窗口的计算逻辑被重置——它会尝试在date分组内执行滚动均值,而不是先按grp计算完滚动均值再按date排名,最终返回null。
不使用临时列的解决方案
要保证求值顺序,你可以用pipe()方法将两个窗口操作明确分开,确保先完成第一个窗口的计算,再对结果执行第二个窗口的排名:
import polars as pl pldf = pl.DataFrame({'grp': ['a', 'a', 'b', 'b'], 'date':[1,2,1,2], 'val': [0.5] * 4}) # 使用pipe确保求值顺序 expr = pl.col('val').rolling_mean(2).over('grp').pipe(lambda x: x.rank().over('date')) result = pldf.with_columns(expr.alias('rank')) print(result)
执行后会得到和分步执行一致的结果:
shape: (4, 4) ┌─────┬──────┬─────┬──────┐ │ grp ┆ date ┆ val ┆ rank │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ f64 ┆ f64 │ ╞═════╪══════╪═════╪══════╡ │ a ┆ 1 ┆ 0.5 ┆ 1.5 │ │ a ┆ 2 ┆ 0.5 ┆ 1.5 │ │ b ┆ 1 ┆ 0.5 ┆ 1.5 │ │ b ┆ 2 ┆ 0.5 ┆ 1.5 │ └─────┴──────┴─────┴──────┘
另外,你也可以用子查询的方式实现:
expr = pl.select(pl.col('val').rolling_mean(2).over('grp')).rank().over('date') result = pldf.with_columns(expr.alias('rank'))
总结
这是Polars处理链式窗口函数时的已知限制:滚动窗口操作的上下文依赖原始分组,直接链式调用.over()会重置上下文,导致计算逻辑异常。通过pipe()或子查询的方式,可以在不引入临时列的前提下,明确控制求值顺序,得到正确结果。
内容的提问来源于stack exchange,提问作者noob_191
相关产品推荐
相关产品推荐

