rolling().apply()返回nan或结果错误,根本原因是未正确处理series输入、未设min_periods、函数未返回标量或隐式修改输入;需显式指定min_periods=1、确保函数接收series返回标量、避免索引丢失和原地修改。

为什么 rolling().apply() 有时返回 NaN 或结果不对
直接传入普通函数给 rolling().apply() 时,Pandas 默认会把窗口内数据作为一维 Series 传入,但如果你的函数依赖索引对齐、多列协同计算,或者用了 np.nan 判断逻辑,就容易出错。更隐蔽的问题是:当窗口长度不足(比如前几行)且未显式设置 min_periods,Pandas 默认跳过这些位置,返回 NaN —— 这不是 bug,是默认行为。
实操建议:
- 始终显式指定
min_periods=1(或你需要的最小窗口大小),避免开头大量NaN - 在自定义函数内部加
if len(x) 等兜底逻辑,而不是依赖外部填充 - 若函数需访问原始 DataFrame 的其他列(比如用 A 列算 B 列的滚动比率),
rolling().apply()不支持跨列;必须改用rolling().agg()配合命名元组,或先构造辅助列
如何让自定义函数接收多列输入(比如同时用 price 和 volume)
rolling() 本身只作用于单个 Series 或 DataFrame 的每列独立计算。想让函数“看到”多列组合,得先把它们打包成一个结构化输入。最稳妥的方式是:对整个 DataFrame 调用 rolling(),再用 apply() 接收每块子 DataFrame。
实操示例(计算价格与成交量的滚动协方差):
df['price_vol_cov'] = df[['price', 'volume']].rolling(window=5, min_periods=1).apply(
lambda x: x.cov().iloc[0, 1] if len(x) >= 2 else np.nan
)
注意点:
-
x在这里是一个DataFrame,含当前窗口全部行和你选的两列 -
cov()返回矩阵,iloc[0,1]取 price 与 volume 的协方差项 - 不能写
lambda x: np.cov(x['price'], x['volume'])[0,1]—— 因为np.cov对 NaN 处理更敏感,且不保证行列顺序
性能瓶颈在哪?什么时候该换 numba 加速
纯 Python 函数 + rolling().apply() 在窗口大、数据量超过 10 万行时会明显变慢,因为 Pandas 每次都构造新 Series 或 DataFrame 对象传入。真正卡住的是函数调用开销,不是计算本身。
实操判断与替换:
- 先用
%timeit测 baseline:df['col'].rolling(30).apply(my_func) - 如果耗时 > 1s,且
my_func是数值密集型(如分位数、自相关、自定义平滑),考虑用numba.jit编译 - 关键限制:numba 函数只能接收
numpy.ndarray,所以得写成:df['col'].rolling(30).apply(lambda x: my_jitted_func(x.values)) - 别忘了
@numba.jit(nopython=True)里禁用 pandas/numpy 高级 API(如pd.qcut、np.quantile不支持),改用np.partition手写中位数
为什么 rolling().agg() 比 apply() 更稳,但灵活性受限
agg() 底层走的是优化路径,支持字符串方法名('mean')、内置函数(np.sum)、甚至字典映射({'col1': 'max', 'col2': 'std'}),它不经过 Python 解释器逐行调用,所以快且不易出 NaN。
但它的“自定义”能力有限:
- 不能传带闭包的函数(比如依赖外部变量
threshold的判定逻辑) - 不支持返回多个值(如同时输出均值和标准差)——除非用
namedtuple并配合pd.Series构造,但会触发apply回退路径 - 若真需要多输出,不如拆成多个
rolling().agg()调用,比一个慢apply()更可靠
复杂点永远在边界:窗口对齐方式(closed='right' vs 'left')、时序数据中的时间窗口(window='7D')、以及自定义函数里是否隐式修改了输入数组 —— 这些细节不报错,但结果可能和直觉相反。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











