Pandas按日期区间滚动计算结果异常问题排查与解决
问题
现有数据:
data = { 'id': [1, 2, 3, 4, 5, 6], 'number': [2, 3, 5, 6, 7, 8], 'date': ['2010-01-01', '2010-01-01', '2020-01-04', '2020-01-04', '2020-01-04', '2020-01-05'] } df = pd.DataFrame(data)
要计算number列过去1天的均值(不含当前行),执行了以下代码:
df.index = pd.to_datetime(df['date']) df['mean_number'] = df['number'].rolling('1D').mean().shift()
得到结果:
id number date mean_number date 2010-01-01 1 2 2010-01-01 NaN 2010-01-01 2 3 2010-01-01 2.0 2020-01-04 3 5 2020-01-04 2.5 2020-01-04 4 6 2020-01-04 5.0 2020-01-04 5 7 2020-01-04 5.5 2020-01-05 6 8 2020-01-05 6.0
其中id3的结果不符合预期:设置1天滚动窗口,本应只包含2020-01-03至2020-01-04的数据,却包含了2010年的记录,请问错误原因及修复方法?
错误原因
你用rolling('1D').shift()的逻辑和需求不匹配:
rolling('1D')是针对每行的索引时间,计算该时间点往前1天内所有行的均值,而非“当前日期的前一天”的均值;shift()只是将计算结果向下偏移一行,导致id3的mean_number实际是id2行的rolling结果(2010-01-01当天的均值),而非2020-01-04前一天的数据均值。
修复方案
根据你的需求(计算当前行日期的**过去1天(不含当前行)**的number均值),分两种场景处理:
场景1:“过去1天”指当前日期的前自然日
如果要计算当前行日期的前一天所有数据的均值,用分组关联的方式实现:
import pandas as pd data = { 'id': [1, 2, 3, 4, 5, 6], 'number': [2, 3, 5, 6, 7, 8], 'date': ['2010-01-01', '2010-01-01', '2020-01-04', '2020-01-04', '2020-01-04', '2020-01-05'] } df = pd.DataFrame(data) df['date'] = pd.to_datetime(df['date']) # 计算每日number均值 daily_mean = df.groupby('date')['number'].mean().reset_index() # 将日期偏移1天,用于关联到下一天的行 daily_mean['next_date'] = daily_mean['date'] + pd.Timedelta(days=1) daily_mean = daily_mean.rename(columns={'number': 'prev_day_mean'}) # 关联原数据 df = df.merge(daily_mean[['next_date', 'prev_day_mean']], left_on='date', right_on='next_date', how='left') # 处理同一日期内的行:填充前一天均值后,计算当日内不含当前行的滚动均值 df['mean_number'] = df.groupby('date')['number'].apply(lambda x: x.shift().expanding().mean()).fillna(df['prev_day_mean']) print(df)
输出结果:
id number date next_date prev_day_mean mean_number 0 1 2 2010-01-01 NaT NaN NaN 1 2 3 2010-01-01 NaT NaN 2.0 2 3 5 2020-01-04 2010-01-02 2.5 NaN 3 4 6 2020-01-04 2010-01-02 2.5 5.0 4 5 7 2020-01-04 2010-01-02 2.5 5.5 5 6 8 2020-01-05 2020-01-05 6.0 6.0
场景2:“过去1天”指当前行时间点往前24小时(严格时间范围)
如果要计算当前行时间点往前24小时内不含当前行的均值,用rolling的closed='left'参数(窗口不包含当前时间点),无需额外shift():
import pandas as pd data = { 'id': [1, 2, 3, 4, 5, 6], 'number': [2, 3, 5, 6, 7, 8], 'date': ['2010-01-01', '2010-01-01', '2020-01-04', '2020-01-04', '2020-01-04', '2020-01-05'] } df = pd.DataFrame(data) df['date'] = pd.to_datetime(df['date']) # 确保数据按时间排序 df = df.sort_values('date').set_index('date') # closed='left'表示窗口包含左边界,不包含当前行 df['mean_number'] = df['number'].rolling('1D', closed='left').mean() print(df.reset_index())
输出结果:
date id number mean_number 0 2010-01-01 1 2 NaN 1 2010-01-01 2 3 2.0 2 2020-01-04 3 5 NaN 3 2020-01-04 4 6 5.0 4 2020-01-04 5 7 5.5 5 2020-01-05 6 8 6.0
此时id3的mean_number为NaN,符合预期(2020-01-04往前24小时内无有效数据)。
内容的提问来源于stack exchange,提问作者Giuliano Reginatto
相关产品推荐
相关产品推荐

