You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

定制化周移动平均函数开发求助:时间序列窗口计算问题

解决方案

要实现你需要的窗口大小为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:04:57