PAYATHON 2026

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 组合,而且这些行也需要明确的先后次序,可以再添加合适的业务排序键。

备注:内容仅供参考。