🎓 レベル:基礎 | 重要度:A(必須)
📎 数理:確率過程(マルコフ連鎖・ポアソン過程)(統計)
要点(BLUF)
- 時系列は順序と依存が本質のデータ(過去が未来に効く)。まず**トレンド・季節性・残差(不規則)**の3成分に分解して構造をつかみます。
- 加法分解 が基本。変動の大きさが水準に比例するなら乗法分解 (対数を取れば加法に戻る)。
- 真の構造を仕込んだ擬似系列を分解すると、季節の振幅やトレンドをほぼ正確に復元できます(本ノートのコードで季節振幅 4.0 を 4.01 と復元)。
1. 時系列データとは
時系列は、時刻順に並んだ観測 です。普通の表データと決定的に違うのは、順序に意味があり、近い時点が相関すること(自己相関、定常性と自己相関)。だから「行をシャッフルしてよい」前提のふつうの手法(k-fold交差検証など)はそのままでは使えません(予測の評価指標と時系列CV)。まずは系列を眺め、規則的な動き(トレンド・季節)と不規則な動き(残差)に分けて理解します。
2. 3成分への分解
- トレンド :長期的な上昇・下降。
- 季節性 :一定周期(12ヶ月・7日など)で繰り返す変動。
- 残差 :トレンド・季節で説明できない不規則変動。
組み合わせ方は2通り:
季節変動の幅が水準とともに大きくなるなら乗法。対数を取ると と加法に戻るので、実務では「対数変換して加法分解」がよく使われます。
古典的分解の手順はシンプルです:トレンドを中心化移動平均(窓=周期)で推定 → 元系列から引いた残りを季節ごとに平均して季節成分 → 残りが残差。
3. コード:既知の構造を分解して復元する
真のトレンド・季節・ノイズを自分で仕込んだ系列を作り、分解がそれを復元するか確かめます。
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
# 真の構造を仕込んだ擬似系列:水準20 + トレンド + 季節(振幅4) + ノイズ(SD0.7)
rng = np.random.default_rng(0)
n, period = 144, 12 # 12年×月次のイメージ
t = np.arange(n)
trend_true = 0.08 * t
seasonal_true = 4 * np.sin(2*np.pi*t/period)
y = 20 + trend_true + seasonal_true + rng.normal(0, 0.7, n)
# 加法分解(周期12)
res = seasonal_decompose(y, period=period, model="additive")
mask = ~np.isnan(res.trend)
print(f"季節成分の相関(復元 vs 真)= {np.corrcoef(res.seasonal, seasonal_true)[0,1]:.3f}")
print(f"トレンドの相関(復元 vs 真)= {np.corrcoef(res.trend[mask], trend_true[mask])[0,1]:.3f}")
print(f"季節の振幅(復元)= {(res.seasonal.max()-res.seasonal.min())/2:.2f} (真=4.0)")
print(f"残差のSD = {np.nanstd(res.resid):.3f} (真のノイズSD=0.7)")
出力:
季節成分の相関(復元 vs 真)= 0.997
トレンドの相関(復元 vs 真)= 0.998
季節の振幅(復元)= 4.01 (真=4.0)
残差のSD = 0.577 (真のノイズSD=0.7)
出力の意味:分解した季節成分・トレンドが、仕込んだ真の成分と相関 0.997/0.998 でほぼ完全に一致。季節の振幅も 4.0 → 4.01 と正確に復元できました。残差 SD は 0.58 で真の 0.7 よりやや小さい——これは古典分解の移動平均トレンドや季節平均が、ノイズの一部を吸収してしまうためで、分解は「記述(構造を見る)には強いが、残差を厳密なノイズ推定とみなしすぎない」のが正しい使い方です。
4. 図:4パネルで分解を見る
原系列・トレンド・季節・残差を並べると、構造が一目で分かります。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib # 日本語ラベル用
from statsmodels.tsa.seasonal import seasonal_decompose
rng = np.random.default_rng(0)
n, period = 144, 12; t = np.arange(n)
y = 20 + 0.08*t + 4*np.sin(2*np.pi*t/period) + rng.normal(0, 0.7, n)
res = seasonal_decompose(y, period=period, model="additive")
fig, ax = plt.subplots(4, 1, figsize=(9, 7), sharex=True)
for a, data, name in zip(ax, [y, res.trend, res.seasonal, res.resid],
["原系列", "トレンド", "季節性", "残差"]):
a.plot(t, data, lw=1.2); a.set_ylabel(name)
ax[0].set_title("時系列の加法分解(トレンド+季節+残差)")
ax[-1].set_xlabel("時点 t"); plt.tight_layout(); plt.show()
グラフの意味:原系列(最上段)の右肩上がり+ギザギザが、トレンド(直線的な上昇)・季節(周期12の波)・残差(不規則な揺れ)に分かれます。これで「上昇はトレンドのせい、毎年の波は季節のせい」と読め、次に何をモデル化すべきか(トレンド除去・季節調整)が見えてきます。
5. 加法か乗法か
- 加法:季節変動の幅が水準によらず一定(例:気温の年周期)。
- 乗法:季節変動の幅が水準とともに拡大(例:売上が伸びるほど年末の山も大きくなる)。
- 迷ったら対数変換して加法分解すれば、乗法構造を加法として扱えます。
より頑健な分解として STL(Seasonal-Trend decomposition using Loess、STL分解)があり、外れ値や季節の緩やかな変化に強いです。
⚠️ よくある誤解
- 「分解できたら予測も済み」ではない:分解は記述(構造を見る)の道具。予測には別途モデル(ARIMA・指数平滑・状態空間…)が要ります。
- 「残差=真のノイズ」ではない:古典分解の残差はトレンド・季節推定の誤差を含み、ノイズ SD をやや過小評価します(§3)。
- 「移動平均トレンドは端まで出る」ではない:中心化移動平均は系列の両端で計算できず、トレンドの先頭・末尾が欠損します(コードの
NaN)。 - 「季節の周期は自動で分かる」ではない:周期(12・7・24 など)は分析者がドメイン知識で与えます。誤った周期だと分解が崩れます。
関連ノート
- 定常性と自己相関(次のトピック・依存構造の測り方)
- 予測の評価指標と時系列CV(順序を保つ検証)
- STL分解(頑健な分解)
- 確率過程(マルコフ連鎖・ポアソン過程)(統計・時系列の確率的土台)
- 第1章 時系列の基礎 目次
- 時系列分析・予測テキスト 全体目次