Polars 窗口函数如何按多列设置排序和 null 顺序?
结论
Expr.over(order_by=...) 可以指定窗口内的排序,但不能像 DataFrame.sort 那样,分别为每个排序键设置 descending 和 nulls_last。这里要让 col2 降序、null 排在最后,同时让 col3 降序、null 排在最前。可以先用 DataFrame.sort 为各列设置选项,在排序后的 DataFrame 上计算 cum_sum(),再按行号恢复原始顺序。Polars 的 DataFrame.sort 支持为多列分别传入布尔序列。(Polars DataFrame.sort 文档)
示例
import polars as pl
# 保留原始行号,便于计算后恢复输入顺序
df = test_df.with_row_index("_row")
result = (
df.sort(
by=["col1", "col2", "col3"],
descending=[False, True, True],
nulls_last=[False, True, False],
maintain_order=True,
)
.with_columns(
pl.col("col4").cum_sum().over("col1").alias("col5")
)
.sort("_row")
.drop("_row")
)
print(result)
这些排序设置依次对应 col1、col2 和 col3:col1 升序,col2 降序且 null 在最后,col3 降序且 null 在最前。数据按这个顺序排列后,cum_sum().over("col1") 会沿各分组当前的行序逐行累计。最后按 _row 排序,输出就会恢复为输入时的行序,col5 的累计值则仍按指定顺序计算。
为什么原写法不符合预期
pl.col("col2").sort(...) 会对该表达式对应的值排序,不能为 order_by 中的某个排序键单独设置排序方向和 null 位置。把多个这样的表达式放进 order_by,也无法表达这里需要的逐列排序选项。
sum() 是分组聚合,会将组内总和映射到组中的每一行。要得到逐行递增的结果,应使用 cum_sum()。如果原始数据刚好已经是目标顺序,累计结果可能看起来正确;输入行序一旦打乱,这种结果就不再可靠。
示例中的 maintain_order=True 用于保持排序键相同时的相对顺序。如果实际数据里有重复的 col1、col2、col3 组合,而且这些行也需要明确的先后次序,可以再添加合适的业务排序键。
备注:内容仅供参考。
