移動平均(Moving Average)は、時系列データの平滑化に最もよく使われるフィルタリング手法です。ノイズ除去、トレンド抽出、センサデータの前処理など幅広い場面で活用されます。
この記事では、代表的な3つの移動平均フィルタを数理的に定義し、Python実装と性能比較を行います。
単純移動平均 (Simple Moving Average, SMA)
定義
SMAは、直近 \(N\) 個のデータ点の算術平均です。
\[ y_t = \frac{1}{N} \sum_{i=0}^{N-1} x_{t-i} \tag{1} \]すべてのデータ点に等しい重み \(1/N\) を与えます。\(N\) が**窓幅(ウィンドウサイズ)**で、大きいほど平滑化が強くなります。
特性
- 長所: 実装が簡単。高周波ノイズの除去に効果的
- 短所: 急激な変化への応答が遅い(遅延が \(\frac{N-1}{2}\) サンプル)。窓幅分のデータをバッファとして保持する必要がある
Python実装
import numpy as np
def sma(x, window):
"""単純移動平均フィルタ"""
kernel = np.ones(window) / window
# 'valid' モードで出力(端の不完全な窓を除外)
return np.convolve(x, kernel, mode='same')
NumPyのconvolveを使うと、SMAは畳み込み演算として簡潔に書けます。
加重移動平均 (Weighted Moving Average, WMA)
定義
WMAは、新しいデータほど大きな重みを付ける移動平均です。重みは線形に減少します。
\[ y_t = \frac{\sum_{i=0}^{N-1} (N - i) \cdot x_{t-i}}{\sum_{i=0}^{N-1} (N - i)} = \frac{2}{N(N+1)} \sum_{i=0}^{N-1} (N - i) \cdot x_{t-i} \tag{2} \]最新のデータ点に重み \(N\)、1ステップ前のデータ点に重み \(N-1\)、…、\(N-1\) ステップ前のデータ点に重み \(1\) を付け、重みの合計で正規化します。
特性
- 長所: SMAよりも最新データへの追従が速い
- 短所: SMAと同様に窓幅分のバッファが必要。重みが線形のため、古いデータの影響が急に0になる(窓の端での不連続)
Python実装
def wma(x, window):
"""加重移動平均フィルタ"""
weights = np.arange(1, window + 1, dtype=float)
weights /= weights.sum()
return np.convolve(x, weights[::-1], mode='same')
指数移動平均 (Exponential Moving Average, EMA)
定義
EMAは再帰的に計算される移動平均で、過去のすべてのデータに指数関数的に減衰する重みを与えます。
\[ y_t = \alpha \cdot x_t + (1 - \alpha) \cdot y_{t-1} \tag{3} \]\(\alpha\)(\(0 < \alpha \le 1\))は平滑化係数です。\(\alpha\) が小さいほど平滑化が強くなります。
SMAの窓幅 \(N\) と対応させる場合、\(\alpha = \frac{2}{N + 1}\) がよく使われます。
特性
- 長所: 過去データのバッファが不要(1ステップ前の値だけ保持すればよい)。メモリ効率が良い。新旧データの重みが滑らかに減衰する
- 短所: パラメータ \(\alpha\) の選択が直感的でない場合がある
Python実装
def ema(x, alpha):
"""指数移動平均フィルタ"""
y = np.zeros_like(x, dtype=float)
y[0] = x[0]
for t in range(1, len(x)):
y[t] = alpha * x[t] + (1 - alpha) * y[t - 1]
return y
EMAの周波数特性(ゲイン・位相特性)の詳細な解析は指数移動平均(EMA)フィルタの周波数特性を参照してください。
3つのフィルタの比較
比較実験
ノイズを含む信号に対して、3つのフィルタを適用して比較します。
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
n = 200
t = np.linspace(0, 4 * np.pi, n)
# 元の信号 + ノイズ
signal = np.sin(t) + 0.5 * np.sin(5 * t)
noise = np.random.normal(0, 0.5, n)
observed = signal + noise
# フィルタ適用
window = 15
alpha = 2 / (window + 1)
y_sma = sma(observed, window)
y_wma = wma(observed, window)
y_ema = ema(observed, alpha)
# プロット
fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True)
axes[0, 0].plot(t, observed, alpha=0.4, label='Observed')
axes[0, 0].plot(t, signal, 'k--', label='True signal')
axes[0, 0].set_title('Original')
axes[0, 0].legend()
axes[0, 1].plot(t, observed, alpha=0.3)
axes[0, 1].plot(t, y_sma, label=f'SMA (N={window})')
axes[0, 1].set_title('SMA')
axes[0, 1].legend()
axes[1, 0].plot(t, observed, alpha=0.3)
axes[1, 0].plot(t, y_wma, label=f'WMA (N={window})')
axes[1, 0].set_title('WMA')
axes[1, 0].legend()
axes[1, 1].plot(t, observed, alpha=0.3)
axes[1, 1].plot(t, y_ema, label=f'EMA (α={alpha:.3f})')
axes[1, 1].set_title('EMA')
axes[1, 1].legend()
for ax in axes.flat:
ax.grid(True, alpha=0.3)
ax.set_ylabel('Amplitude')
axes[1, 0].set_xlabel('Time')
axes[1, 1].set_xlabel('Time')
plt.tight_layout()
plt.show()
特性比較表
| 特性 | SMA | WMA | EMA |
|---|---|---|---|
| 重みの分布 | 均一 | 線形減衰 | 指数減衰 |
| メモリ使用 | \(O(N)\)(窓幅分のバッファ) | \(O(N)\) | \(O(1)\)(前回値のみ) |
| 計算量(1ステップ) | \(O(N)\)(差分法で\(O(1)\)) | \(O(N)\) | \(O(1)\) |
| 過去データの影響 | 窓外で完全に0 | 窓外で完全に0 | 指数的に減衰するが0にはならない |
| 遅延 | \((N-1)/2\) サンプル | SMAより小さい | \(\alpha\)依存、最も小さい |
| ステップ応答 | 直線的に上昇 | 曲線的に上昇 | 指数的に収束 |
周波数特性の比較
SMAの伝達関数は以下のとおりです。
\[ G_{SMA}(z) = \frac{1}{N} \sum_{i=0}^{N-1} z^{-i} = \frac{1}{N} \cdot \frac{1 - z^{-N}}{1 - z^{-1}} \tag{4} \]SMAのゲイン特性にはゼロ点(特定周波数でゲインが0になる点)が存在するのに対し、EMAのゲインは単調に減少します。この違いにより、SMAは特定周波数の成分を完全に除去でき、EMAは全体的に滑らかな減衰特性を持ちます。
各フィルタの周波数特性の詳細な比較はローパスフィルタの設計と比較も参照してください。
用途別の選択ガイド
| 用途 | 推奨フィルタ | 理由 |
|---|---|---|
| センサデータの前処理 | EMA | メモリ効率が良く、リアルタイム処理に適している |
| 株価・金融データのトレンド分析 | SMA / EMA | SMAはテクニカル分析の標準指標、EMAは短期トレンドの追従に優れる |
| 組み込みシステム | EMA | \(O(1)\)のメモリと計算量で実装可能 |
| オフライン信号処理 | SMA / WMA | バッチ処理なのでメモリ制約が緩く、窓幅ベースの制御が直感的 |
| ノイズの周波数が既知の場合 | SMA | 窓幅を調整してゼロ点を目的周波数に合わせられる |
関連記事
- 指数移動平均(EMA)フィルタの周波数特性 - EMAのZ変換による伝達関数の導出と、ゲイン・位相特性の詳細な解析を行っています。
- 信号処理におけるフィルタリング手法の基礎 - カルマンフィルタ、EKF、UKF、粒子フィルタなど、確率的フィルタリング手法を体系的に解説しています。
- ローパスフィルタの設計と比較 - 移動平均・バターワース・チェビシェフフィルタの周波数応答を比較しています。
- 適応フィルタの基礎理論とデジタル信号処理への応用 - FIR/IIRフィルタの基礎からLMSアルゴリズムまで解説しています。
- Matplotlib実践Tips:論文品質のグラフを作る - 比較グラフをより高品質に仕上げるための設定やカラーマップの選択方法を紹介しています。
参考文献
- Smith, S. W. (1997). The Scientist and Engineer’s Guide to Digital Signal Processing. California Technical Publishing.
- Oppenheim, A. V., & Schafer, R. W. (2009). Discrete-Time Signal Processing (3rd ed.). Prentice Hall.