pandas 窗口操作(Windowing Operations)完全指南:Rolling / Expanding / EWM 窗口函数与自定义索引器 API 详解

pandas 窗口操作(Windowing Operations)完全指南:Rolling / Expanding / EWM 窗口函数与自定义索引器 API 详解 pandas 窗口操作Windowing Operations完全指南Rolling / Expanding / EWM 窗口函数与自定义索引器 API 详解【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas导读本文基于 pandas 官方 API 参考 doc/source/reference/window.rst 与配套用户指南 doc/source/user_guide/window.rst系统梳理 pandas 四大类窗口操作滚动窗口 Rolling、加权窗口 Window、扩展窗口 Expanding、指数加权窗口 EWM的返回对象、聚合函数清单与核心参数语义并结合pandas/core/window/与pandas/core/indexers/的源码实现深入讲解底层原理。读完本文你将掌握.rolling()/.expanding()/.ewm()三类 API 的完整方法矩阵、win_type加权窗口的用法、自定义窗口边界的BaseIndexer协议以及min_periods、center、closed、methodtable、online 更新等高级特性的准确行为。一、窗口操作总览四种窗口类型与返回对象pandas 的窗口操作windowing operation是一种在滑动分片上执行聚合的操作API 风格与groupby一致先由Series/DataFrame调用窗口方法并传入必要参数得到窗口对象再在窗口对象上调用聚合函数。概念方法返回对象支持时间窗口支持 groupby 链式支持 table 模式支持 online 计算滚动窗口 Rollingrollingpandas.api.typing.Rolling是是是否加权窗口 Weightedrolling带win_typepandas.api.typing.Window否否否否扩展窗口 Expandingexpandingpandas.api.typing.Expanding否是是否指数加权窗口 EWMewmpandas.api.typing.ExponentialMovingWindow否是否是三种窗口对象在类型标注层面由 pandas/api/typing/init.py 导出具体实现类位于 pandas/core/window/rolling.pyRolling、Window、pandas/core/window/expanding.pyExpanding和 pandas/core/window/ewm.pyExponentialMovingWindow。这些方法定义在Series/DataFrame的公共基类 pandas/core/generic.pyrolling在L11915、expanding在L12214、ewm在L12287。最基础的用法import pandas as pd import numpy as np s pd.Series(range(5)) s.rolling(window2).sum() # 0 NaN # 1 1.0 # 2 3.0 # 3 5.0 # 4 7.0窗口由从当前观测点向前回看 window 长度形成也可通过迭代查看每个窗口的分区for window in s.rolling(window2): print(window)两个通用约束来自用户指南的显式说明窗口操作目前仅支持数值数据整数与浮点且结果恒为float64mean、sum、var、std等聚合由于底层算法累加求和当数值量级相差达到1/np.finfo(np.double).eps约 4.5×10¹⁵时可能出现数值截断pandas 使用 Kahan 求和算法计算滚动求和以尽量保持精度。二、Rolling 滚动窗口函数完整方法清单与参数语义pandas.api.typing.Rolling实例由DataFrame.rolling与Series.rolling调用返回。API 参考文档列出其全部聚合方法count、sum、mean、median、var、std、min、max、first、last、corr、cov、skew、kurt、apply、pipe、aggregate、quantile、sem、rank、nunique。这些方法定义在 pandas/core/window/rolling.py 的RollingAndExpandingMixinL1537为Rolling与Expanding共享及RollingL1955中其中count、sum、max等直接调用 Cython 扩展模块pandas._libs.window.aggregations中的roll_sum、roll_max等底层例程。2.1rolling()构造参数以Series/DataFrame通用实现为准在 pandas/core/generic.py#L11915 的签名中window窗口间隔。整数表示固定观测数窗口timedelta / 字符串 / offset表示时间跨度窗口仅适用于 datetimelike 索引且必须是固定频率如2D、1hB营业日、ME月末等非固定频率会抛ValueErrorBaseIndexer 子类则按自定义get_window_bounds计算边界。min_periods窗口内非np.nan观测的最小数量不足则结果为NaN。默认值规则按 offset 指定窗口时默认为1按整数指定窗口时默认为window即窗口大小。min_periodsNone等价于取窗口大小。center默认False标签取窗口右缘True时标签取窗口中心。win_type默认None等权重传入字符串则启用 scipy 加权窗口见第三节。on对DataFrame指定用于计算窗口的列标签或索引层级而非 DataFrame 自身索引且该列的值会成为rawFalse时传给Rolling.apply的Series的索引。closed窗口端点包含性默认None等价rightright(first, last] 包含最后一点left[first, last) 包含第一点both[first, last] 全部包含neither(first, last) 两端都排除。step每隔step个结果计算一次等价[::step]切片window必须是整数非None/1时结果形状与输入不同。methodsingle默认逐列/逐行执行或table整个对象上执行仅在调用聚合方法时指定enginenumba才可用。2.2 固定窗口与时间窗口times [2020-01-01, 2020-01-03, 2020-01-04, 2020-01-05, 2020-01-29] s pd.Series(range(5), indexpd.DatetimeIndex(times)) # 固定 2 个观测的窗口 s.rolling(window2).sum() # 覆盖 2 天观测的窗口基于时间跨度 s.rolling(window2D).sum()使用时间 offset 时对应的时间索引必须单调。2.3 窗口居中对齐centerings pd.Series(range(10)) s.rolling(window5).mean() # 标签对齐右缘 s.rolling(window5, centerTrue).mean() # 标签对齐中心center同样适用于 datetimelike 索引的时间窗口df pd.DataFrame( {A: [0, 1, 2, 3, 4]}, indexpd.date_range(2020, periods5, freq1D) ) df.rolling(2D, centerFalse).mean() df.rolling(2D, centerTrue).mean()2.4 窗口端点closed的实战价值closed常用于避免当期信息污染历史信息的场景——右端点开放意味着统计量只计算到该时刻之前、不含该时刻df pd.DataFrame( {x: 1}, index[ pd.Timestamp(20130101 09:00:01), pd.Timestamp(20130101 09:00:02), pd.Timestamp(20130101 09:00:03), pd.Timestamp(20130101 09:00:04), pd.Timestamp(20130101 09:00:06), ], ) df[right] df.rolling(2s, closedright).x.sum() # 默认 df[both] df.rolling(2s, closedboth).x.sum() df[left] df.rolling(2s, closedleft).x.sum() df[neither] df.rolling(2s, closedneither).x.sum()2.5 min_periods 与 missing 值s pd.Series([np.nan, 1, 2, np.nan, np.nan, 3]) s.rolling(window3, min_periods1).sum() s.rolling(window3, min_periods2).sum() s.rolling(window3, min_periodsNone).sum() # 等价于 min_periods32.6 二元窗口函数cov 与 corrRolling.cov与Rolling.corr支持三种组合两个Series计算配对统计量DataFrame/Series对 DataFrame 每列与该 Series 计算返回 DataFrameDataFrame/DataFrame默认按列名匹配计算传入pairwiseTrue时对每对列计算返回以日期为一级索引的MultiIndexDataFrame缺失值按逐对完整观测忽略。df pd.DataFrame( np.random.randn(10, 4), indexpd.date_range(2020-01-01, periods10), columns[A, B, C, D], ).cumsum() df2 df[:4] df2.rolling(window2).corr(df2[B]) covs df[[B, C, D]].rolling(window4).cov( df[[A, B, C]], pairwiseTrue )注意pairwise 方式下假设缺失数据完全随机时可得到无偏协方差估计但估计出的协方差矩阵不保证半正定可能导致相关性的绝对值大于 1 或协方差矩阵不可逆。2.7 Rolling.apply通用滚动计算Rolling.apply接受额外func参数执行任意单值聚合。rawFalse默认时窗口被包装为Series对象rawTrue时直接传入 ndarraydef mad(x): return np.fabs(x - x.mean()).mean() s pd.Series(range(10)) s.rolling(window4).apply(mad, rawTrue)从源码 pandas/core/window/rolling.py#L1542 看apply支持enginecython默认不接受engine_kwargs与enginenumba要求rawTruerawFalse时窗口还会被包成以self._on即on参数指定的索引为索引的Series见L1604-L1608。2.8 Numba 引擎与 table 模式使用 Numba可选依赖时enginenumbaengine_kwargs该字典会同时传给numba.jit装饰器、用户函数与窗口循环且raw必须为True。Numba 会在两处被应用一是对标准 Python 函数做 JIT已 JIT 的函数不再重复 JIT二是对对每个窗口应用函数的 for 循环做 JIT。mean、median、max、min、sum、std、var也支持engine与engine_kwargs参数。methodtable允许在整个DataFrame上执行窗口操作而不是逐列执行对多列 DataFrame 有性能收益且可以在窗口函数中利用其他列——例如用Rolling.apply实现加权均值权重来自独立列def weighted_mean(x): arr np.ones((1, x.shape[1])) arr[:, :2] (x[:, :2] * x[:, 2]).sum(axis0) / x[:, 2].sum() return arr df pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]]) df.rolling(2, methodtable, min_periods0).apply( weighted_mean, rawTrue, enginenumba )table 模式仅当方法调用中指定enginenumba时才可用见 pandas/core/generic.py#L12016-L12022 的method参数说明。三、Weighted 加权窗口函数win_type在.rolling()中传入win_type参数即产生加权非矩形窗口常用于滤波与谱估计。win_type必须是 scipy.signal 窗口函数 名称对应的字符串因此需要安装 scipyscipy 窗口方法的补充参数需在聚合函数调用中指定。API 参考中Windowpandas.api.typing.Window支持四个聚合方法mean、sum、var、std。其实现类位于 pandas/core/window/rolling.py#L862。s pd.Series(range(10)) s.rolling(window5).mean() # 等权重 s.rolling(window5, win_typetriang).mean() # 三角窗 s.rolling(window5, win_typegaussian).mean(std0.1) # 补充 scipy 参数四、Expanding 扩展窗口函数pandas.api.typing.Expanding由DataFrame.expanding/Series.expanding调用返回窗口从序列起点累计到当前点即截止该时刻的全部可用数据。它是滚动统计的特例以下两种写法完全等价df pd.DataFrame(range(5)) df.rolling(windowlen(df), min_periods1).mean() df.expanding(min_periods1).mean()API 参考为Expanding列出与Rolling相同的 21 个聚合方法count、sum、mean、median、var、std、min、max、first、last、corr、cov、skew、kurt、apply、pipe、aggregate、quantile、sem、rank、nunique。实现类为 pandas/core/window/expanding.py#L43 的Expanding与Rolling共同继承RollingAndExpandingMixin因此共享apply的 cython/numba 双引擎逻辑。所有窗口操作都支持aggregate别名agg一次应用多种聚合df pd.DataFrame({A: range(5), B: range(10, 15)}) df.expanding().agg([sum, mean, std])五、Exponentially Weighted 指数加权窗口函数pandas.api.typing.ExponentialMovingWindow由DataFrame.ewm/Series.ewm调用返回实现类在 pandas/core/window/ewm.py#L127。它类似 expanding 窗口但每个历史点相对当前点按指数衰减。API 参考列出的方法mean、sum、std、var、corr、cov。一般加权移动平均公式$$y_t \frac{\sum_{i0}^t w_i x_{t-i}}{\sum_{i0}^t w_i}$$5.1 衰减参数com / span / halflife / alpha四选一必须恰好指定其中一个除非配合times三者与平滑因子 α 的关系$$\alpha \begin{cases} \frac{2}{s 1}, \text{span } s \geq 1\[4pt] \frac{1}{1 c}, \text{com } c \geq 0\[4pt] 1 - e^{\frac{\log 0.5}{h}}, \text{halflife } h 0 \end{cases}$$span对应俗称的N 日 EW 移动平均comcenter of mass质心物理意义更直观与 span 的关系为 $c (s-1)/2$halflife权重衰减到一半所需周期alpha直接指定平滑因子须满足 $0 \alpha \leq 1$。从 pandas/core/window/ewm.py#L127-L163 的文档可见若提供times且adjustTrue可同时提供halflife与com/span/alpha之一若times且adjustFalse则halflife必须是唯一的衰减参数。5.2 adjustTrue 与 adjustFalse 两种权重变体adjustTrue默认使用权重 $w_i (1-\alpha)^i$$$y_t \frac{x_t (1-\alpha)x_{t-1} (1-\alpha)^2 x_{t-2} \cdots (1-\alpha)^t x_0}{1 (1-\alpha) (1-\alpha)^2 \cdots (1-\alpha)^t}$$adjustFalse使用递推式 $y_0 x_0$$y_t (1-\alpha)y_{t-1} \alpha x_t$等价于权重 $w_i \alpha(1-\alpha)^i$$it$且 $w_t(1-\alpha)^t$。对无限历史序列两种变体数学上等价adjustFalse隐式假设 $x_0$ 是截至该点的无限序列的指数加权矩。5.3 基于 times 的 halflife当传入时间戳序列times时halflife可写成 timedelta 可换算单位表示观测值衰减到一半所需的时间df pd.DataFrame({B: [0, 1, 2, np.nan, 4]}) times [2020-01-01, 2020-01-03, 2020-01-10, 2020-01-15, 2020-01-17] df.ewm(halflife4 days, timespd.DatetimeIndex(times)).mean()对应公式为$$y_t \frac{\sum_{i0}^t 0.5^{\frac{t_t - t_i}{\lambda}} x_i}{\sum_{i0}^t 0.5^{\frac{t_t - t_i}{\lambda}}}$$其中 $\lambda$ 即 halflife。5.4 ignore_na中间缺失值的权重处理ignore_naFalse默认时按绝对位置计算权重中间的空值会影响结果ignore_naTrue时计算权重时忽略中间空值。例如adjustTrue下对序列3, NaN, 5ignore_naFalse$\frac{(1-\alpha)^2 \cdot 3 1 \cdot 5}{(1-\alpha)^2 1}$ignore_naTrue$\frac{(1-\alpha) \cdot 3 1 \cdot 5}{(1-\alpha) 1}$adjustFalse下同理ignore_naFalse为 $\frac{(1-\alpha)^2 \cdot 3 \alpha \cdot 5}{(1-\alpha)^2 \alpha}$ignore_naTrue为 $\frac{(1-\alpha) \cdot 3 \alpha \cdot 5}{(1-\alpha) \alpha}$。用户指南特别提示adjustFalse的递推式 $y_t (1-\alpha)y_{t-1} \alpha x_t$ 仅在无缺失值时成立此时权重和恰为 1出现缺失值后剩余观测的权重和不再为 1会被重新归一化因此不能直接对该递推式两侧的值做替换。可实测验证ser pd.Series([3, np.nan, 5]) ser.ewm(alpha2 / 3, adjustFalse, ignore_naFalse).mean() ser.ewm(alpha2 / 3, adjustFalse, ignore_naTrue).mean()5.5 bias方差/标准差/协方差的有偏与无偏ExponentialMovingWindow.var、std、cov支持bias参数。biasTrue时ewmvar(x) ewma(x**2) - ewma(x)**2biasFalse默认时对有偏方差乘上去偏因子$$\frac{\left(\sum_{i0}^t w_i\right)^2}{\left(\sum_{i0}^t w_i\right)^2 - \sum_{i0}^t w_i^2}$$当 $w_i 1$ 时该因子退化为常见的 $N/(N-1)$$Nt1$。5.6 online 在线计算EWM 支持online()方法先以聚合方法调用预热初始状态之后传入update参数继续计算df pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]]) df.ewm(0.5).mean() online_ewm df.head(2).ewm(0.5).online() online_ewm.mean() # 先预热 online_ewm.mean(updatedf.tail(1)) # 传入新数据继续计算六、Window Indexer自定义窗口边界的三种内建类除整数与时间 offset 外rolling的window参数还接受BaseIndexer子类API 参考Window indexer一节定义于 pandas/core/indexers/objects.py通过pandas.api.indexers命名空间对外暴露。6.1 BaseIndexer 协议BaseIndexerpandas/core/indexers/objects.py#L21是自定义窗口边界的基类构造参数为index_array默认None可用于不规则时间戳场景与window_size默认0其余**kwargs会被设置为实例属性供get_window_bounds使用。子类必须实现def get_window_bounds(self, num_values, min_periods, center, closed, step): ...该方法返回(start, end)两个 int64 ndarray分别表示每个窗口的起始与结束下标num_values、min_periods、center、closed、step由顶层 rolling API 自动传入因此自定义方法必须始终接受这些参数。未实现时基类直接抛NotImplementedErrorL105。6.2 自定义 indexer 示例若希望在use_expanding为True处使用扩展窗口、否则用大小为 1 的窗口from pandas.api.indexers import BaseIndexer use_expanding [True, False, True, False, True] df pd.DataFrame({values: range(5)}) class CustomIndexer(BaseIndexer): def get_window_bounds(self, num_values, min_periods, center, closed, step): start np.empty(num_values, dtypenp.int64) end np.empty(num_values, dtypenp.int64) for i in range(num_values): if self.use_expanding[i]: start[i] 0 end[i] i 1 else: start[i] i end[i] i self.window_size return start, end indexer CustomIndexer(window_size1, use_expandinguse_expanding) df.rolling(indexer).sum()6.3 VariableOffsetWindowIndexer非固定频率 offsetVariableOffsetWindowIndexerpandas/core/indexers/objects.py#L211允许对BusinessDay这类非固定频率 offset 做滚动操作rolling直接传入B等非固定频率会抛ValueError见 pandas/core/generic.py#L11944-L11946from pandas.api.indexers import VariableOffsetWindowIndexer df pd.DataFrame(range(10), indexpd.date_range(2020, periods10)) offset pd.offsets.BDay(1) indexer VariableOffsetWindowIndexer(indexdf.index, offsetoffset) df.rolling(indexer).sum()6.4 FixedForwardWindowIndexer前向滚动窗口当未来信息可用例如每个数据点本身是一条完整实验时间序列时可用FixedForwardWindowIndexerpandas/core/indexers/objects.py#L429实现闭式固定宽度的前向窗口from pandas.api.indexers import FixedForwardWindowIndexer indexer FixedForwardWindowIndexer(window_size2) df.rolling(indexer, min_periods1).sum()等效做法是切片 → 滚动聚合 → 翻转df pd.DataFrame( data[ [pd.Timestamp(2018-01-01 00:00:00), 100], [pd.Timestamp(2018-01-01 00:00:01), 101], [pd.Timestamp(2018-01-01 00:00:03), 103], [pd.Timestamp(2018-01-01 00:00:04), 111], ], columns[time, value], ).set_index(time) reversed_df df[::-1].rolling(2s).sum()[::-1]用户指南还提到 pandas/core/indexers/objects.py 中提供FixedWindowIndexer、ExpandingIndexerL390、ExponentialMovingWindowIndexerL637等其他内建实现可作为自定义 indexer 的参考范本。七、与 groupby 链式组合rolling、expanding、ewm均支持与groupby链式使用先按指定键分组再对每组执行窗口操作对应实现RollingGroupby、ExpandingGroupby、ExponentialMovingWindowGroupby分别位于 pandas/core/window/rolling.py#L3540、pandas/core/window/expanding.py#L1456、pandas/core/window/ewm.py#L1016df pd.DataFrame({A: [a, b, a, b, a], B: range(5)}) df.groupby(A).expanding().sum()八、源码实现速览从 API 到底层 Cython关注点仓库位置Series/DataFrame的rolling/expanding/ewm入口pandas/core/generic.py#L11915、L12214、L12287Window加权窗口实现pandas/core/window/rolling.py#L862Rolling/RollingAndExpandingMixinpandas/core/window/rolling.py#L1955 / L1537Expanding实现pandas/core/window/expanding.py#L43ExponentialMovingWindow实现pandas/core/window/ewm.py#L127Numba 引擎辅助generate_numba_apply_func、table 函数等pandas/core/window/numba_.pyEWM online 状态对象pandas/core/window/online.pyBaseIndexer及内建 indexerpandas/core/indexers/objects.py窗口边界计算的 Cython 底层calculate_variable_window_bounds等pandas/_libs/window/indexers.pyx 及 pandas/_libs/window/aggregations.pyx从源码结构可以推断Rolling与Expanding共享RollingAndExpandingMixin的聚合方法骨架仅在窗口边界固定/时间/自定义 vs 累计上不同Window是独立的BaseWindow子类专门承接 scipy 加权窗口而ExponentialMovingWindow的 mean/sum/std/var/corr/cov 各自实现递归或加权公式。聚合计算最终落到pandas._libs.window.aggregations的 Cython 例程如roll_sum、roll_max这也是 Kahan 求和等数值精度措施的实施位置。九、小结与速查选型速查固定窗口用.rolling(windowint)时间跨度窗口用.rolling(window2D)索引需单调累计统计用.expanding()衰减加权用.ewm()滤波/谱估计用.rolling(win_type...)前向窗口用FixedForwardWindowIndexer非固定频率 offset 用VariableOffsetWindowIndexer全表多列加速用methodtableenginenumba。通用参数min_periods时间窗口默认 1、整数窗口默认window、center标签居中、closed端点包含性、step结果降采样、aggregate/agg多聚合。返回对象.rolling()返回Rolling带win_type时返回Window.expanding()返回Expanding.ewm()返回ExponentialMovingWindow三者均为pandas.api.typing下的类型支持 IDE 类型提示。如需查看每个方法的完整文档字符串与示例可继续阅读 doc/source/reference/window.rst 对应的api/目录自动生成页以及 doc/source/user_guide/window.rst 的完整用户指南。【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考