K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

ETS モデルでトレンドと季節性を分離

中級

04.7.6

ETS モデルでトレンドと季節性を分離

最終更新 2020-08-02 読了時間 3 分
まとめ
ETS は Error(誤差)・Trend(トレンド)・Seasonal(季節)の頭文字で、指数平滑法の仲間を「誤差は加法か乗法か」「トレンドは無し・加法・減衰か」「季節は無し・加法・乗法か」の 3 文字で分類する枠組みです。たとえば ETS(A,A,A) は加法誤差・加法トレンド・加法季節で、Holt-Winters 加法型と同じ更新式になります。このページでは、月 1.5 の上昇と振幅 8・周期 12 の季節波を持つ 60 か月の系列を先頭 48 か月で学習し、ExponentialSmoothing(trend="add", seasonal="add", seasonal_periods=12) で残り 12 か月を予測して実測と比べます。読み終えると、3 文字の組み合わせをデータの形から選び、fit() が返す α・β・γ を成分ごとの「記憶の長さ」として読めるようになります。

ホルト・ウィンター法 で水準・傾き・季節の 3 成分を α・β・γ で更新する仕組みを先に見ておくと、ETS はその分類表として読めます。

直感 #

月次の売上を「ふだんの水準」「毎月の増え方」「月ごとの季節のクセ」「それでも説明できない誤差」の 4 つに分けて考えるのが ETS の発想です。Holt-Winters 法が水準・傾き・季節の 3 つを更新するのは同じですが、ETS は「誤差が足し算で乗るのか(A)、水準に比例して大きくなるのか(M)」まで明示します。この 1 文字を決めると予測区間の広がり方が決まるので、点予測だけでなく「来年 8 月は 150 ± 10」のような幅を出せるのが ETS の利点です。このページのデータは季節の振幅が 8 で一定、ノイズの標準偏差が 2.5 で一定なので、3 文字とも A の ETS(A,A,A) が自然な選択になります。

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:水準 → 傾き → 季節 と要素を足すほど、未来への伸ばし方が豊かになる。ETS(A,A,A) はこの 3 つをすべて足し算で持った形で、3 段目の予測線に対応する。

式で書くと ETS(A,A,A) は何をしているか #

加法誤差・加法トレンド・加法季節の ETS(A,A,A) は、1 期先予測の誤差 \(e_t = y_t - (\ell_{t-1} + b_{t-1} + s_{t-12})\) を使って

$$ \begin{aligned} \ell_t &= \ell_{t-1} + b_{t-1} + \alpha\, e_t \\ b_t &= b_{t-1} + \beta\, e_t \\ s_t &= s_{t-12} + \gamma\, e_t \\ \hat{y}_{t+h} &= \ell_t + h\, b_t + s_{t+h-12} \end{aligned} $$

と書けます。3 つの成分がどれも「前の値 + 係数 × 同じ誤差 \(e_t\)」の形で更新される点が ETS の見やすさで、α・β・γ は「今月の外れをどの成分にどれだけ振り分けるか」を表します。乗法誤差 ETS(M,A,A) では \(e_t\) が比率 \(y_t / \hat{y}_t - 1\) に変わり、水準が大きいほど予測区間が広がります。このページのデータは \(y_t = 100 + 1.5t + 8\sin(2\pi t/12) + \varepsilon_t\) なので、正解の傾きは 1.5、季節の振幅は 8 です。

Python で ETS(A,A,A) を学習して 12 か月先を予測する #

次のコードは、60 か月の系列を先頭 48 か月で学習し、加法トレンド・加法季節のモデルで残り 12 か月を予測して、実測(薄い青)・学習区間(青)・予測(橙)を重ねます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.holtwinters import ExponentialSmoothing

rng = np.random.default_rng(23)
dates = pd.date_range("2018-01-01", periods=5 * 12, freq="M")
trend = 1.5 * np.arange(len(dates))
seasonal = 8 * np.sin(2 * np.pi * np.arange(len(dates)) / 12)
noise = rng.normal(0, 2.5, size=len(dates))
series = pd.Series(100 + trend + seasonal + noise, index=dates)

train = series.iloc[:-12]
test_index = series.index[-12:]

model = ExponentialSmoothing(
    train,
    trend="add",
    seasonal="add",
    seasonal_periods=12,
).fit()

forecast = model.forecast(steps=12)

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(series.index, series, color="#cbd5f5", label="実測値(全体)")
ax.plot(train.index, train, color="#2563eb", linewidth=1.2, label="学習区間")
ax.plot(test_index, forecast, color="#f97316", linewidth=1.6, label="ETS 予測")
ax.set_title("ETS モデルによる予測")
ax.set_xlabel("年月")
ax.set_ylabel("値")
ax.legend()
ax.grid(alpha=0.3)

fig.tight_layout()
fig.savefig("static/images/timeseries/ets_model.svg")

ETS モデルでトレンドと季節性を分離の図

実行結果の読み方 #

青の学習区間は 100 付近から 4 年で約 170(100 + 1.5 × 47)まで上がりながら、振幅 8 の季節波を 4 回繰り返します。橙の予測線はその続きとして、月 1.5 前後の傾きで上がりつつ 5 周期目の山と谷を描き、薄い青の実測とおおむね重なります。予測と実測のずれは標準偏差 2.5 のノイズが主で、特定の月だけ大きく外れるならその月の季節係数が 4 回分の平均ではうまく決まらなかった、と読みます。model.params を表示すると smoothing_level(α)、smoothing_trend(β)、smoothing_seasonal(γ)が確認でき、β と γ が 0 に近いのは傾きと季節が 4 年間ほぼ一定だったことの反映です。

使いどころと落とし穴 #

ETS の 3 文字は、実測の図から「季節の振幅が水準とともに広がるか(S = M)」「誤差の散らばりが水準とともに広がるか(E = M)」「傾きがどこまでも続くと信じるか(T = A か Ad)」を見て決めます。月次・週次の販売数のように季節が明確で 2 周期以上あるデータの中期予測に向き、周期がはっきりしないデータでは seasonal=None の Holt 法に落とします。ExponentialSmoothing は点予測しか返さないので、予測区間が必要な業務では ETSModel の get_prediction().summary_frame() を使ってください。Holt-Winters の発展形として α・β・γ を「水準・傾き・季節の記憶の短さ」と読む習慣をつけると、fit() の結果が妥当かを数値で判断できます。