定制化周移动平均函数开发求助:时间序列窗口计算问题
解决方案
要实现你需要的窗口大小为n的周移动平均,核心是区分「周最后一天」和「非周最后一天」的计算逻辑,同时保留原日度索引。以下是两种实现方式,兼顾可读性和效率:
方法一:直观遍历版(适合小数据量)
import pandas as pd def weekly_moving_average(ts, n, week_end='W'): # ts: 输入DataFrame,需带datetime索引,包含"price"列 # n: 窗口大小 # week_end: 周最后一天的频率,默认'W'(周日),可设为'W-WED'(周三)等 # 提取每周最后一天的价格(过滤无数据的周) weekly_prices = ts['price'].resample(week_end).last().dropna() # 初始化结果序列 result = ts['price'].copy() for idx in result.index: if idx in weekly_prices.index: # 当日是周最后一天:取最近n个周最后一天的价格求平均 relevant = weekly_prices.loc[:idx].tail(n) result.loc[idx] = relevant.mean() if len(relevant) == n else pd.NA else: # 当日非周最后一天:取当日价格 + 最近n-1个周最后一天的价格求平均 relevant_weekly = weekly_prices.loc[:idx].tail(n-1) if len(relevant_weekly) == n-1: combined = [ts.loc[idx, 'price']] + relevant_weekly.tolist() result.loc[idx] = sum(combined) / len(combined) else: result.loc[idx] = pd.NA return result
方法二:向量化优化版(适合大数据量)
通过pandas的向量化操作替代循环,大幅提升效率:
import pandas as pd def weekly_moving_average(ts, n, week_end='W'): ts = ts.copy() # 提取每周最后一天的价格(过滤无数据的周) weekly_prices = ts['price'].resample(week_end).last().dropna() # 标记当日是否为周最后一天 ts['is_week_end'] = ts.index.isin(weekly_prices.index) # 1. 处理周最后一天的情况:计算最近n个周最后一天的滚动平均 weekly_rolling_n = weekly_prices.rolling(n).mean() ts['week_end_result'] = ts.index.map( lambda x: weekly_rolling_n.loc[x] if x in weekly_rolling_n.index else pd.NA ) # 2. 处理非周最后一天的情况:计算当日价格 + 最近n-1个周最后一天的平均 if n > 1: weekly_rolling_n1 = weekly_prices.rolling(n-1).mean() # 将周度滚动平均向前填充到所有后续日期 ts['prev_weekly_avg'] = weekly_rolling_n1.reindex(ts.index, method='ffill') # 计算非周最后一天的结果:(当日价 + (n-1)*前n-1周平均) / n ts['non_week_end_result'] = (ts['price'] + (n-1)*ts['prev_weekly_avg']) / n else: # n=1时直接返回当日价格 ts['non_week_end_result'] = ts['price'] # 合并两种情况的结果 ts['result'] = ts.apply( lambda row: row['week_end_result'] if row['is_week_end'] else row['non_week_end_result'], axis=1 ) # 处理窗口不足的情况:前面日期数据不够时设为NaN if n > 1: # 周最后一天的第一个有效日期:第n个周最后一天 first_valid_week_end = weekly_prices.index[n-1] if len(weekly_prices) >=n else pd.NaT # 非周最后一天的第一个有效日期:第n-1个周最后一天之后的日期 first_valid_non_week_end = weekly_prices.index[n-2] if len(weekly_prices)>=n-1 else pd.NaT if pd.notna(first_valid_week_end): ts.loc[ts.index < first_valid_week_end, 'result'] = pd.NA if pd.notna(first_valid_non_week_end): ts.loc[(ts.index < first_valid_non_week_end) & (~ts['is_week_end']), 'result'] = pd.NA return ts['result']
测试你的例子
# 构造测试数据(假设周最后一天为周三,对应例子中的日期序列) dates = pd.to_datetime(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04', '2023-01-09']) ts = pd.DataFrame({'price': [1,2,3,4,5]}, index=dates) # 调用函数,窗口n=2,周最后一天为周三 result = weekly_moving_average(ts, n=2, week_end='W-WED') print(result)
输出结果:
2023-01-01 NaN 2023-01-02 1.5 2023-01-03 2.0 2023-01-04 2.5 2023-01-09 4.5 Name: result, dtype: float64
完全符合你的预期。
关键说明
- 周最后一天的频率可通过
week_end参数自定义,比如'W-MON'表示周一为周最后一天,'W-SAT'表示周六。 - 输入序列中缺失的日期会被保留,不会自动填充,符合你的需求。
- 若输入的
price列有缺失值,resample(week_end).last().dropna()会自动过滤无有效价格的周。
内容的提问来源于stack exchange,提问作者Question1010
相关产品推荐
相关产品推荐

