HOWTO · Python

Python 中 NumPy 数组的滑动平均值

本教程演示了如何在 python 中计算 numpy 数组的滑动平均值

本页内容

简单移动平均是对固定大小窗口内连续数值求平均。对于 NumPy 数组,可以直接用 numpy.convolve 或 numpy.cumsum 计算;也可以使用 bottleneck.move_mean,或通过 pandas 的 rolling().mean() 计算。窗口大小为 4,表示每个结果都概括相邻的 4 个输入值。移动平均可以平滑短期波动,让趋势更容易观察;加权移动平均则会给较新的数据更大权重。

下面的示例数据包含 12 个值。第一种方法会为每个完整的 4 值窗口返回一个结果,因此会得到 9 个值。应根据每个平均值需要概括的观测数量来选择窗口大小;改变窗口大小会同时改变平滑程度和有效窗口结果的数量。

Simple Moving Average

使用 numpy.convolve 计算 NumPy 数组的移动平均

numpy.convolve 用于计算线性卷积。这里,第二个数组由 4 个 1 组成,因此每个卷积结果都是一个 4 值窗口的总和。将每个总和除以 4,就得到该窗口的平均值。valid 模式只返回完全落在输入范围内的窗口,因此 12 个输入值和大小为 4 的窗口会产生 9 个结果。这些结果从左到右与各窗口对应:第一个结果是输入位置 0 到 3 的平均值,最后一个是位置 8 到 11 的平均值。

import numpy as np


def moving_average(values, window):
    weights = np.ones(window) / window
    return np.convolve(values, weights, mode="valid")

data = np.array([10, 5, 8, 9, 15, 22, 26, 11, 15, 16, 18, 7])
print(moving_average(data, 4))
[ 8.    9.25 13.5  18.   18.5  18.5  17.   15.   14.  ]

使用 numpy.cumsum 计算 NumPy 数组的移动平均

利用累积和,可以用窗口末尾的累积总和减去窗口之前的累积总和,得到该窗口的总和。第一个窗口之前没有可供相减的累积总和,因此函数从第一个完整窗口开始切片。这样得到的 9 个平均值与 numpy.convolve 示例相同。如果你想了解如何通过运行总和逐个计算窗口总和,这种方法很实用;将每个窗口总和除以窗口大小,即可得到平均值。

import numpy as np


def moving_average(values, window):
    cumulative = np.cumsum(values, dtype=float)
    cumulative[window:] -= cumulative[:-window]
    return cumulative[window - 1:] / window

data = np.array([10, 5, 8, 9, 15, 22, 26, 11, 15, 16, 18, 7])
print(moving_average(data, 4))
[ 8.    9.25 13.5  18.   18.5  18.5  17.   15.   14.  ]

使用 bottleneck 模块计算移动平均

bottleneck 包提供了 move_mean,可用于计算数组的移动平均。默认情况下,窗口必须完整,因此最初 3 个位置还没有对应的 4 值平均值;第一个平均值出现在索引 3 处。结果会保留与输入相同的长度,开头这些位置以 nan 表示。示例会在 Bottleneck 可用时使用它;如果没有安装这个可选包,也提供了一个简短的纯 Python 等效实现。当你需要让每个输入值都对应一个结果位置时,可以采用这种形式;开头的 nan 表示完整窗口尚未形成。

from importlib.util import find_spec


data = [10, 5, 8, 9, 15, 22, 26, 11, 15, 16, 18, 7]
window = 4

if find_spec("bottleneck") is not None:
    import bottleneck as bn

    result = bn.move_mean(data, window=window).tolist()
else:
    result = [float("nan")] * (window - 1)
    result.extend(
        sum(data[start:start + window]) / window
        for start in range(len(data) - window + 1)
    )

print(result)
[nan, nan, nan, 8.0, 9.25, 13.5, 18.0, 18.5, 18.5, 17.0, 15.0, 14.0]

使用 pandas 模块计算移动平均

对于存储在 pandas Series 中的时间序列数据,可以调用 rolling(4).mean() 计算每个完整 4 值窗口的平均值。默认情况下,Series 会保留原有长度和索引。开头 3 个条目是 NaN,因为这些位置还没有足够的值组成完整窗口;第一个计算出的平均值与索引 3 对齐。此处的数值与其他示例相同,而 Series 索引便于将每个结果对应回原始观测值。如果窗口大小来自变量,也可以将该变量传给 rolling()。

import pandas as pd


data = [10, 5, 8, 9, 15, 22, 26, 11, 15, 16, 18, 7]
series = pd.Series(data)
print(series.rolling(4).mean())
0       NaN
1       NaN
2       NaN
3      8.00
4      9.25
5     13.50
6     18.00
7     18.50
8     18.50
9     17.00
10    15.00
11    14.00
dtype: float64