最稳妥方式是直接调用 .rolling(window).mean(),它自动处理 nan、边界对齐及时序索引;自定义统计须用 .apply() 并注意输入为 series、返回标量、避免闭包与全局状态,复杂逻辑可结合 numba 加速。

用 pandas.Series.rolling() 计算移动平均线最稳妥
直接调用 .rolling(window).mean() 是最常用也最不易出错的方式,它自动处理 NaN 填充、边界对齐和时序索引对齐问题。别手写循环或用 numpy.convolve —— 容易漏掉首尾截断、权重不均、索引错位。
-
window支持整数(如20)、时间字符串(如'5D',需索引为DatetimeIndex)或pd.offsets对象 - 默认
min_periods=1,但计算 20 日均线时建议显式设为min_periods=20,避免前 19 行返回非空值干扰判断 - 若原始数据含缺失值,
.rolling()默认跳过 NaN 参与计算;想强制要求窗口内无 NaN,得先dropna()或用min_periods控制有效值下限
自定义滑动窗口统计量必须用 rolling().apply(),但要注意函数签名和 axis
.apply() 接收一个函数,该函数输入是当前窗口的 Series(不是 DataFrame),返回标量。常见错误是传入带 axis 参数的 numpy 函数(如 np.std(x, axis=0)),其实没必要——窗口内就是一维。
- 自定义函数必须返回单个 Python 标量(
float、int等),不能返回np.ndarray或Series - 避免在
.apply()里做重操作(如拟合模型),性能会断崖下跌;可先用.rolling().agg()调用内置函数加速,再补足逻辑 - 若需访问窗口外数据(比如计算相对前一日变化率),
.apply()不适用,应改用shift()配合布尔索引或iloc手动切片
rolling().apply() 中使用 lambda 易踩坑:闭包变量未绑定、无法序列化
写成 df['ma'].rolling(10).apply(lambda x: my_func(x, param=0.5)) 看似简洁,但 param 值在定义时捕获,后续修改变量不会生效;更重要的是,这类 lambda 无法被 pickle,导致多进程(如 dask 或 joblib)报错 Can't pickle local object。
- 推荐封装为具名函数:
def weighted_std(x, alpha=0.5): return np.std(x) * alpha,再调用.apply(weighted_std, alpha=0.5) - 参数必须通过
**kwargs传入,不能靠外部作用域变量;且所有参数类型需为基本类型(int/float/str),不可传list或自定义类实例 - 若函数内部依赖全局状态(如计数器、缓存),
.apply()每次调用都是独立上下文,状态不会累积
高频场景下 numba.jit 加速 rolling().apply() 有硬限制
当窗口统计逻辑复杂且数据量大(>100 万行),纯 Python .apply() 会成为瓶颈。用 @numba.jit(nopython=True) 能提速 5–50 倍,但前提是函数只用支持的 numpy/Python 子集。
-
numba不支持pandas对象,输入x必须是np.ndarray;需提前用.to_numpy()转换,且确保无 NaN(numba对np.nan处理不稳定) - 不能在 jit 函数里调用
len()、print()、isinstance()或任何非 nopython 模式函数 - 首次调用会编译,后续才快;若窗口大小动态变化(如
window=lambda i: i//100 + 5),jit 失效,得回退到普通模式
实际业务中,移动平均线本身很少需要定制逻辑,真正卡点的是带条件的滑动统计(比如“过去 10 笔交易中价格高于均值的次数”)——这种必须手动构造布尔窗口并用 .sum(),而不是依赖 .apply()。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











