如何计算DataFrame双ID列中各ID的上月滚动平均值?
问题描述
我参考了Stack Overflow上的一个Pandas分组滚动平均问题,但我的数据有差异:包含ID 1和ID 2两个ID列(日期格式为dd/mm/yyyy)。
需求是:计算每个特定ID过去一个月的平均得分。比如计算ID 1000的上月平均评分时,要汇总它在ID 1或ID 2列中出现的所有相关得分,且数据集每一行需对应显示ID 1和ID 2的过去30天平均得分。
我尝试了以下代码:
dataset['Date'] = pandas.to_datetime(dataset['Date']) dataset = dataset.sort_values(by=['ID 1','Date']) dataset['ID 1 last month average rating'] = dataset.set_index('Date').groupby('ID 1', sort=False)['ID 1 Score'].rolling('30D', closed='left').mean().round(2).to_numpy() dataset['ID 2 last month average rating'] = dataset.set_index('Date').groupby('ID 2', sort=False)['ID 2 Score'].rolling('30D', closed='left').mean().round(2).to_numpy()
但结果不正确,请问哪里出错了?
更新说明:已更新输入数据,期望输出如下(新增ID1 30D avg rtg和ID2 30D avg rtg两列,以ID1000为例,需汇总它在ID1、ID2列中的历史得分计算截至对应日期的过去30天平均值):
| Date | ID 1 | ID 2 | ID 1 Score | ID 2 Score | ID1 30D avg rtg | ID2 30D avg rtg |
|---|---|---|---|---|---|---|
| 16/7/2022 | 1001 | 1000 | 0.89 | 5.35 | 4.045 | |
| 10/7/2022 | 1000 | 901 | 3.42 | 2.67 | 4.67 | |
| 8/7/2022 | 530 | 1000 | 1.52 | 4.67 |
能否为所有ID实现这样的输出?
问题分析与解决方案
错误原因
你的代码存在两个核心问题:
- 得分拆分统计:仅单独计算
ID 1对应ID 1 Score、ID 2对应ID 2 Score的滚动平均,没有把同一个ID在两列中出现的所有得分合并计算,不符合“汇总同一ID全量得分”的需求。 - 排序逻辑错误:仅按
ID 1+Date排序,未全局按日期排序,导致滚动窗口的时间范围计算偏差。
解决思路
先将原数据拆分为统一的「ID-日期-得分」格式,把同一个ID在两列中的得分合并,再计算每个ID的30天滚动平均,最后将结果映射回原数据集的每一行。
完整代码
import pandas as pd # 1. 处理日期格式,全局按日期排序(dayfirst=True适配dd/mm/yyyy格式) dataset['Date'] = pd.to_datetime(dataset['Date'], dayfirst=True) dataset = dataset.sort_values(by='Date').reset_index(drop=True) # 2. 构建全量ID得分表:合并ID1和ID2的得分记录 id1_records = dataset[['Date', 'ID 1', 'ID 1 Score']].rename(columns={'ID 1': 'ID', 'ID 1 Score': 'Score'}) id2_records = dataset[['Date', 'ID 2', 'ID 2 Score']].rename(columns={'ID 2': 'ID', 'ID 2 Score': 'Score'}) all_id_records = pd.concat([id1_records, id2_records], ignore_index=True) # 3. 计算每个ID的30天滚动平均(closed='left'表示排除当前日期的得分) all_id_records = all_id_records.sort_values(by=['ID', 'Date']) rolling_avg_results = all_id_records.set_index('Date').groupby('ID', sort=False)['Score']\ .rolling('30D', closed='left').mean().reset_index() rolling_avg_results.rename(columns={'Score': '30D_avg_rtg'}, inplace=True) # 4. 将滚动平均映射回原数据集 # 匹配ID1对应的30天平均得分 dataset = dataset.merge(rolling_avg_results, left_on=['Date', 'ID 1'], right_on=['Date', 'ID'], how='left') dataset.rename(columns={'30D_avg_rtg': 'ID1 30D avg rtg'}, inplace=True) dataset.drop('ID', axis=1, inplace=True) # 匹配ID2对应的30天平均得分 dataset = dataset.merge(rolling_avg_results, left_on=['Date', 'ID 2'], right_on=['Date', 'ID'], how='left') dataset.rename(columns={'30D_avg_rtg': 'ID2 30D avg rtg'}, inplace=True) dataset.drop('ID', axis=1, inplace=True) # 按示例格式保留三位小数(可选) dataset['ID1 30D avg rtg'] = dataset['ID1 30D avg rtg'].round(3) dataset['ID2 30D avg rtg'] = dataset['ID2 30D avg rtg'].round(3)
结果验证
用你提供的示例数据运行代码后,会得到与期望一致的输出:
- 16/7/2022行的
ID2 30D avg rtg为4.045,是ID1000在8/7的4.67和10/7的3.42的平均值; - 10/7/2022行的
ID1 30D avg rtg为4.67,是ID1000在8/7的唯一得分。
内容的提问来源于Stack Exchange,提问作者Iason
相关产品推荐
相关产品推荐

