非日期格式天数列的30天滚动求和实现方案问询
解决数值型Day列的30天滚动求和问题
你原代码的问题在于:rolling(30, on='day')中的30指定的是窗口包含的行数,而非基于day数值的时间范围,所以会计算最近30行的求和,而非30天内的数据。下面提供三种针对性的解决方案:
方案一:分组+行级筛选(小数据量适用)
直接针对每个客户分组,对组内每一行筛选出day在当前行day-30到day范围内的foo值求和:
import pandas as pd import numpy as np # 示例数据 df = pd.DataFrame({'client': ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'], 'day': [319, 323, 336, 352, 379, 424, 461, 486, 496, 499, 303, 334, 346, 373, 374, 395, 401, 408, 458, 492], 'foo': [5.0, 2.0, np.nan, np.nan, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan, 8.0, 7.0, 22.0, np.nan, 13.0, np.nan, np.nan, 5.0, 11.0, np.nan]}) def calc_30day_sum(group): group['foo_30day'] = group.apply( lambda row: group.loc[(group['day'] >= row['day'] - 30) & (group['day'] <= row['day']), 'foo'].sum(skipna=True), axis=1 ) return group df = df.groupby('client').apply(calc_30day_sum).reset_index(drop=True) print(df)
运行后会得到正确的按天数范围的求和结果,比如client A的第1行(day=323)会包含day=319和323的foo值,求和为5.0+2.0=7.0。
方案二:merge_asof批量关联(大数据量推荐)
如果数据量较大,行级循环效率偏低,可以用merge_asof批量匹配每个行对应的30天范围数据,再分组求和:
# 先按client和day排序(merge_asof要求输入已排序) df_sorted = df.sort_values(['client', 'day']).reset_index(drop=True) # 复制数据作为匹配源,计算day的下限 df_match = df_sorted.copy() df_match['day_low'] = df_match['day'] - 30 # 匹配每个client下,day在[day_low, day]范围内的所有记录 merged = pd.merge_asof( df_sorted, df_match[['client', 'day', 'foo']], on='day', by='client', direction='backward', tolerance=30 ) # 按原数据索引分组求和,再合并回原表 df_sorted['foo_30day'] = merged.groupby(merged.index)['foo_y'].sum(skipna=True) df = df.merge(df_sorted[['client', 'day', 'foo_30day']], on=['client', 'day'], how='left') print(df)
这种方法利用向量化操作,比循环apply效率提升显著,适合处理十万级以上的数据量。
方案三:转换为日期格式后使用滚动窗口
如果day代表的是明确的日期偏移(比如当年的第几天、从某个起始日开始的累计天数),可以转换为日期格式后使用rolling('30D'):
# 假设day是2023年的第几天,起始日期可根据实际情况调整 df['date'] = pd.to_datetime('2023-01-01') + pd.to_timedelta(df['day'] - 1, unit='D') # 按client分组,基于日期计算30天滚动求和 df['foo_30day'] = df.groupby('client').rolling('30D', on='date', min_periods=1)['foo'].sum().values # 可选:删除临时日期列 df = df.drop('date', axis=1) print(df)
这种方法代码简洁,前提是你能明确day对应的日期规则。
内容的提问来源于stack exchange,提问作者amance
相关产品推荐
相关产品推荐

