如何在Polars中按组基于历史行值递归计算加权目标列?
Polars中按组实现递归指数衰减加权目标值计算
问题描述
我正在使用Python的Polars库计算DataFrame中的weighted_goals列,要求每行的weighted_goals值依赖于goals列的历史值,并按Team分组通过窗口函数(over("Team"))计算。
需要实现的递归公式(展开形式):
weighted_goals = 0.8 * goals[n] + 0.2 * (0.8 * goals[n-1] + 0.2 * (0.8 * goals[n-2] ...)
该计算会为每个团队的历史goals值赋予指数衰减权重,示例DataFrame如下:
import polars as pl data = pl.DataFrame({ "Team": ["A", "A", "A", "B", "B", "B"], "Week": [1, 2, 3, 1, 2, 3], "goals": [2, 1, 3, 1, 4, 2] })
期望输出的weighted_goals列如下:
| Team | Week | goals | weighted_goals |
|---|---|---|---|
| A | 1 | 2 | 2 |
| A | 2 | 1 | 1.2 |
| A | 3 | 3 | 2.56 |
| B | 1 | 1 | 1 |
| B | 2 | 4 | 3.2 |
| B | 3 | 2 | 2.56 |
需要找到无需显式循环的高效实现方式。
解决方案
首先可以观察到,你给出的展开式可以简化为递推公式:
- 对于每个团队的第一行:
weighted_goals[0] = goals[0] - 对于后续行:
weighted_goals[n] = 0.8 * goals[n] + 0.2 * weighted_goals[n-1]
这个递推关系和原展开式完全等价,且更适合用Polars的窗口函数高效实现。
实现代码
利用Polars的cum_eval函数(窗口内的累积计算),结合分组窗口即可完成:
import polars as pl data = pl.DataFrame({ "Team": ["A", "A", "A", "B", "B", "B"], "Week": [1, 2, 3, 1, 2, 3], "goals": [2, 1, 3, 1, 4, 2] }) result = data.with_columns( pl.col("goals") .cum_eval( lambda series: pl.when(series.len() == 1) .then(series[0]) .otherwise(0.8 * series[-1] + 0.2 * series[-2]) ) .over("Team") .alias("weighted_goals") ) print(result)
代码说明
cum_eval函数:在窗口内逐行累积计算,每次迭代会传入当前已计算的结果序列(包括当前行之前的weighted_goals值)。- 分组窗口
over("Team"):确保每个团队的计算独立进行,不会跨团队引用数据。 - 递推逻辑:
- 当序列长度为1(即团队的第一行),直接返回当前
goals值作为初始weighted_goals。 - 对于后续行,使用递推公式计算:当前
goals的0.8倍加上上一行weighted_goals的0.2倍。
- 当序列长度为1(即团队的第一行),直接返回当前
输出验证
运行代码后得到的结果与预期完全一致:
shape: (6, 4) ┌──────┬──────┬───────┬────────────────┐ │ Team ┆ Week ┆ goals ┆ weighted_goals │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ f64 │ ╞══════╪══════╪═══════╪════════════════╡ │ A ┆ 1 ┆ 2 ┆ 2.0 │ │ A ┆ 2 ┆ 1 ┆ 1.2 │ │ A ┆ 3 ┆ 3 ┆ 2.56 │ │ B ┆ 1 ┆ 1 ┆ 1.0 │ │ B ┆ 2 ┆ 4 ┆ 3.2 │ │ B ┆ 3 ┆ 2 ┆ 2.56 │ └──────┴──────┴───────┴────────────────┘
这种方式完全避免了显式循环,利用Polars的向量化计算特性,处理大数据集时效率远高于循环实现。
内容的提问来源于stack exchange,提问作者Pedro_Siqueira
相关产品推荐
相关产品推荐

