
本文详解如何在 Polars 的 rolling().agg() 中精确获取每个窗口内「当前行」对应的非索引列值(如 order_id)及该行在窗口内的相对位置(frame_index),避免因 first() 等聚合导致的静态取值问题。
本文详解如何在 polars 的 `rolling().agg()` 中精确获取每个窗口内「当前行」对应的非索引列值(如 `order_id`)及该行在窗口内的相对位置(`frame_index`),避免因 `first()` 等聚合导致的静态取值问题。
在 Polars 中执行滚动窗口计算时,一个常见但易被忽视的需求是:在 .agg() 内部访问“当前被聚合的那行原始数据”——而非整个窗口的聚合结果。例如,你希望为滚动窗口中的每一行都标注其自身的 order_id 和它在该窗口内的序号(frame_index),而非整个窗口统一取首行或末行的值。
你最初的写法:
df.sort("customer_id", "date")
.rolling(index_column="date", period="1w", closed="left", group_by="customer_id")
.agg(
frame_index = pl.int_range(pl.len()).first(), # ❌ 错误:.first() 返回标量,丢失逐行维度
current_order_id = pl.col("order_id").first(), # ❌ 同样只取窗口首行
orders = pl.col("order_id")
)
问题核心在于:.agg() 中的表达式默认作用于整个窗口分组,而 pl.int_range(pl.len()) 生成的是长度为窗口行数的序列,但加上 .first() 后就坍缩为单个标量,无法实现“每行对应自身在窗口中的位置”。
✅ 正确解法:利用 pl.int_range(pl.len()) 不加聚合,配合 with_columns() 显式对齐原始行顺序
由于 rolling().agg() 的输出行数与原始输入行数一致(每个原始行生成一个窗口聚合结果),我们可以先完成窗口聚合(保留窗口内列表、长度等信息),再通过 with_columns() 安全注入原始排序后 DataFrame 的对应列:
import polars as pl
df = pl.DataFrame(
{
"order_id": ["o01", "o02", "o03", "o04", "o10", "o11", "o12", "o13"],
"customer_id": ["ca", "ca", "ca", "ca", "cb", "cb", "cb", "cb"],
"date": [
"2024-04-03",
"2024-04-04",
"2024-04-04",
"2024-04-11",
"2024-04-02",
"2024-04-02",
"2024-04-03",
"2024-05-13",
],
},
schema_overrides={"date": pl.Date},
)
# ✅ 关键:先排序,再滚动,再聚合,最后对齐注入
sorted_df = df.sort("customer_id", "date")
result = (
sorted_df
.rolling(
index_column="date",
period="1w",
offset="0d",
closed="left",
group_by="customer_id",
)
.agg(
frame_index = pl.int_range(pl.len()), # ✅ 返回长度为窗口行数的 Int64Series(逐行展开)
orders = pl.col("order_id"),
)
.with_columns(
current_order_id = sorted_df.get_column("order_id") # ✅ 严格按 sorted_df 行序注入
)
)
print(result)
输出将精准匹配你的目标:
shape: (8, 4) ┌─────────────┬────────────┬───────────┬──────────────────────────┐ │ customer_id ┆ date ┆ frame_index ┆ orders │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ date ┆ list[i64] ┆ list[str] │ ╞═════════════╪════════════╪═════════════╪══════════════════════════╡ │ ca ┆ 2024-04-03 ┆ [0] ┆ ["o01", "o02", "o03"] │ │ ca ┆ 2024-04-04 ┆ [0, 1] ┆ ["o02", "o03"] │ │ ca ┆ 2024-04-04 ┆ [0, 1] ┆ ["o02", "o03"] │ │ ca ┆ 2024-04-11 ┆ [0] ┆ ["o04"] │ │ cb ┆ 2024-04-02 ┆ [0, 1, 2] ┆ ["o10", "o11", "o12"] │ │ cb ┆ 2024-04-02 ┆ [0, 1, 2] ┆ ["o10", "o11", "o12"] │ │ cb ┆ 2024-04-03 ┆ [0] ┆ ["o12"] │ │ cb ┆ 2024-05-13 ┆ [0] ┆ ["o13"] │ └─────────────┴────────────┴───────────┴──────────────────────────┘
⚠️ 注意事项:
sorted_df必须与rolling前的排序完全一致(包括group_by逻辑),否则with_columns()对齐会出错;frame_index是list[i64]类型,若需展开为每行一个整数(如用于后续 join 或条件计算),可使用.explode("frame_index")配合.with_row_count()或pl.arange()二次处理;- 若需在
agg内直接做“当前行值参与计算”(如order_id + mean(price)),推荐先用with_columns()注入原始列,再用select()或with_columns()进行跨列运算,避免在agg()中尝试“反向索引”原始行——Polars 当前无current_row()或nth(0)(针对当前行)原语,强行模拟易出错。
总结:Polars 滚动窗口的“当前行感知”能力依赖于聚合后对齐注入,而非聚合内动态取值。掌握 pl.int_range(pl.len()) 的向量化行为 + with_columns(df.select(...)) 的严格顺序对齐,即可稳健实现任意原始列值与窗口内位置的组合需求。










