Pandas中按日期计算指定日期前won列均值的实现问题
问题:基于日期计算历史
won列均值(同日期值一致) 我有一个包含10万+行的Pandas DataFrame,需要按日期计算won列的历史均值:对每一行,计算所有created_on早于当前行日期的行中won的均值。核心要求是同一日期的所有行结果必须相同,而非逐行累积的均值。
我的尝试与问题
- 单个日期计算函数可行:
def get_win_prop(df, d) -> float: mask = (df['created_on'] < d) prop = df[mask].won.mean() return prop get_win_prop(d, '2022-10-25') # 该调用能返回正确结果
- 但使用
pd.assign()批量计算时,所有结果均为NaN:
d.assign(rolling_won_prop = lambda x: get_win_prop(x, x.created_on))
- 自己实现了循环方案,但效率不足:
result = [] for i in d.created_on.unique(): prev_vals = d[d['created_on'] < i] result.append(prev_vals.won.mean()) d.merge(pd.DataFrame({'created_on': d.created_on.unique(), 'rolling_won_prop': result}), how='left')
示例数据:
import pandas as pd from pandas import Timestamp d = pd.DataFrame({'created_on': [Timestamp('2022-09-22 00:00:00'), Timestamp('2022-10-14 00:00:00'),Timestamp('2022-10-19 00:00:00'),Timestamp('2022-10-25 00:00:00'),Timestamp('2022-11-02 00:00:00'), Timestamp('2022-11-04 00:00:00'),Timestamp('2022-11-16 00:00:00'),Timestamp('2022-11-28 00:00:00'),Timestamp('2022-11-28 00:00:00'),Timestamp('2022-12-07 00:00:00'), Timestamp('2022-12-21 00:00:00'),Timestamp('2022-12-21 00:00:00'),Timestamp('2022-12-21 00:00:00'),Timestamp('2022-12-21 00:00:00')], 'n_lines': [7, 3, 7, 6, 6, 4, 5, 3, 10, 3, 6, 6, 9, 6], 'n_pieces': [606, 202, 706, 765, 255, 803, 1004, 2702, 1909, 546, 555, 555, 558,555], 'quote_total': [1780.4299999999998, 3575.4600000000005, 11762.079999999994, 6725.160000000002, 995.9300000000001, 1644.2100000000003, 2620.2299999999996, 8082.090000000001, 5302.320000000001, 1959.7599999999998, 8734.67, 9792.3, 0.0, 9720.71], 'won': [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0]})
问题分析与解决方案
为什么assign调用返回全NaN?
你的get_win_prop函数设计为接收单个日期值,但x.created_on是Series(整列数据)。此时df['created_on'] < d会生成一个布尔矩阵而非单个掩码,最终计算均值时无法得到有效数值,返回全NaN。
高效的Pandas原生实现
基础版(无分组)
核心思路:先按日期聚合统计,再计算排除当前日期的累积统计量,最后映射回原DataFrame,确保同日期结果一致。
# 1. 按日期聚合,计算每日won总和与记录数 daily_agg = d.groupby('created_on').agg( sum_won=('won', 'sum'), count_rows=('won', 'count') ).sort_index() # 2. 计算滞后的累积值(shift(1)排除当前日期的统计) daily_agg['lag_cumsum_won'] = daily_agg['sum_won'].cumsum().shift(1) daily_agg['lag_cumsum_rows'] = daily_agg['count_rows'].cumsum().shift(1) # 3. 计算历史均值,首日期无历史数据则为NaN daily_agg['rolling_won_prop'] = daily_agg['lag_cumsum_won'] / daily_agg['lag_cumsum_rows'] # 4. 合并回原数据 result = d.merge(daily_agg[['rolling_won_prop']], on='created_on', how='left')
分组版(按客户ID)
若需按客户ID分组计算,只需在聚合时加入分组键,再按客户分组计算累积值:
# 假设DataFrame包含customer_id列 daily_agg_grouped = d.groupby(['customer_id', 'created_on']).agg( sum_won=('won', 'sum'), count_rows=('won', 'count') ).sort_index() # 按客户分组计算滞后累积值 daily_agg_grouped['lag_cumsum_won'] = daily_agg_grouped.groupby('customer_id')['sum_won'].cumsum().shift(1) daily_agg_grouped['lag_cumsum_rows'] = daily_agg_grouped.groupby('customer_id')['count_rows'].cumsum().shift(1) daily_agg_grouped['rolling_won_prop'] = daily_agg_grouped['lag_cumsum_won'] / daily_agg_grouped['lag_cumsum_rows'] # 合并回原数据 result_grouped = d.merge(daily_agg_grouped[['rolling_won_prop']], on=['customer_id', 'created_on'], how='left')
方案优势
- 矢量化操作:聚合与累积均为Pandas内部优化的操作,处理10万+行数据效率远高于循环
- 严格满足同日期结果一致的要求
- 分组场景下逻辑清晰,可直接扩展
内容的提问来源于stack exchange,提问作者Steven
相关产品推荐
相关产品推荐

