HOWTO · Python

Python 中 NumPy 陣列的滑動平均值

本教程演示瞭如何在 python 中計算 numpy 陣列的滑動平均值

本頁內容

簡單移動平均是對固定大小的連續數值視窗取平均。對 NumPy 陣列而言,可以直接使用 numpy.convolve 或 numpy.cumsum 計算;bottleneck.move_mean 和搭配 rolling().mean() 的 pandas 則提供以套件為基礎的替代方式。視窗大小為 4,表示每個結果都彙整 4 個相鄰的輸入值。移動平均能平滑短期波動,讓趨勢更容易辨識;加權移動平均則會讓較近期的資料占更大的比重。

下方範例資料有 12 個值。第一種方法會為每個完整的四值視窗產生一個結果,因此共有 9 個結果。請依每次平均要彙整的觀測值數量選擇視窗大小;變更視窗大小會同時改變平滑程度,以及有效視窗結果的數量。

Simple Moving Average

使用 numpy.convolve 計算 NumPy 陣列的移動平均

numpy.convolve 會計算線性摺積。這裡的第二個陣列包含四個 1;每個摺積結果都是一個四值視窗的總和。將總和除以 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 陣列的移動平均

使用累積總和時,每個視窗的總和可透過「視窗結尾的累積總和」減去「視窗開始前的累積總和」求得。第一個視窗之前沒有累積總和可供相減,因此函式會從第一個完整視窗的位置開始切片。這會得到與 numpy.convolve 範例相同的 9 個平均值。若想了解如何利用累計總和組合出各視窗總和,這種方法很實用;再將每個總和除以視窗大小,即可換算成平均值。

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,可計算陣列的移動平均。依預設設定,只有完整視窗才會產生結果;視窗大小為 4 時,前 3 個位置尚無四值結果,第一個平均值會出現在索引 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(),計算每個完整四值視窗的平均。依預設,Series 會保留原本的長度與索引。前 3 個項目會是 NaN,因為這些位置尚未累積足夠的 4 個值;第一個計算出的平均值會對齊索引 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