
本文介绍使用 pl.Expr.over() 在 Polars LazyFrame 中高效实现“按符号分组计算价格变动”并直接扩展为新列,避免 group_by + join + explode 的冗余操作,兼顾性能与可读性。
本文介绍使用 `pl.expr.over()` 在 polars lazyframe 中高效实现“按符号分组计算价格变动”并直接扩展为新列,避免 group_by + join + explode 的冗余操作,兼顾性能与可读性。
在 Polars 中,当需要为原始数据的每一行添加一个依赖于同组内其他行值的计算列(如每个 symbol 内 mid_price 的一阶差分),常见的误区是先 group_by().agg() 得到聚合结果,再 join 回原表——这不仅引入了 list 列需额外 explode() 处理的复杂性,更违背了 LazyFrame 的惰性执行优化原则,且易导致行序错位或内存膨胀。
正确且推荐的方式是使用 over() 窗口表达式:它允许你在不改变原始行数和顺序的前提下,对指定分组(如 "symbols")内所有行独立应用聚合/变换函数(如 .diff(1)、.rank()、.cumsum() 等),天然支持逐行对齐输出。
✅ 推荐方案:over() 实现分组内逐行计算
import polars as pl
# 假设 df 是你的 LazyFrame
df_with_change = df.with_columns(
pl.col("mid_price")
.diff(1) # 计算相邻行差值(首行为 null)
.over("symbols") # 按 symbols 分组独立计算
.alias("change")
)
该写法简洁、语义清晰,并在 LazyFrame 阶段即完成逻辑定义,后续 .collect() 时由 Polars 引擎自动优化执行计划(例如复用排序、避免中间 materialization)。
? 示例验证
import polars as pl
import datetime
df = pl.LazyFrame({
"symbols": ["SOL_USDT"] * 3 + ["BTC_USDT"] * 3,
"time": [
datetime.datetime(2024, 3, 1, 9, 0),
datetime.datetime(2024, 3, 1, 9, 1),
datetime.datetime(2024, 3, 1, 9, 2),
datetime.datetime(2024, 3, 1, 9, 0),
datetime.datetime(2024, 3, 1, 9, 1),
datetime.datetime(2024, 3, 1, 9, 2),
],
"mid_price": [126.0, 126.25, 125.7, 38500.0, 38520.5, 38495.8],
})
result = df.with_columns(
pl.col("mid_price").diff(1).over("symbols").alias("change")
).collect()
print(result)
输出将严格保持原始行序,且 change 列精确对应每行在其 symbols 组内的差分值:
shape: (6, 4) ┌────────────┬─────────────────────┬───────────┬──────────┐ │ symbols ┆ time ┆ mid_price ┆ change │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ datetime[μs] ┆ f64 ┆ f64 │ ╞════════════╪═════════════════════╪═══════════╪══════════╡ │ SOL_USDT ┆ 2024-03-01 09:00:00 ┆ 126.0 ┆ null │ │ SOL_USDT ┆ 2024-03-01 09:01:00 ┆ 126.25 ┆ 0.25 │ │ SOL_USDT ┆ 2024-03-01 09:02:00 ┆ 125.7 ┆ -0.55 │ │ BTC_USDT ┆ 2024-03-01 09:00:00 ┆ 38500.0 ┆ null │ │ BTC_USDT ┆ 2024-03-01 09:01:00 ┆ 38520.5 ┆ 20.5 │ │ BTC_USDT ┆ 2024-03-01 09:02:00 ┆ 38495.8 ┆ -24.7 │ └────────────┴─────────────────────┴───────────┴──────────┘
⚠️ 注意事项与最佳实践
- 无需显式排序:over() 默认不保证组内顺序;若 diff() 依赖时间顺序,请先用 .sort(["symbols", "time"]) 确保逻辑正确。
- 避免 group_by().agg() + join:该模式会强制 materialize 分组结果,丧失 LazyFrame 流式优势,且需额外 explode() 对齐,易出错。
- over() 支持丰富表达式:除 .diff() 外,还可组合 .shift()、.rank(method="ordinal")、.rolling_mean(window_size=5) 等,全部支持惰性求值。
- 性能提示:对于超大 LazyFrame,over() 通常比手动 join 更快,因 Polars 可对其执行向量化分组扫描,而非构建哈希表 + 多次查找。
综上,pl.Expr.over() 是 Polars 中处理“分组内逐行计算并扩展为新列”任务的标准、高效且地道的解决方案。










