LESSON

時系列データの分解

「時系列データをそのまま眺めていても、何が起きているかは分からない。分解して初めて構造が見える。」

田中VPoEがStore Salesの売上グラフを表示する。波打つような複雑なパターンが見える。

「トレンド、季節性、残差。この3つに分けることで、需要の本質的な動きが浮かび上がる。STL分解を使おう。」

時系列分解の基本

時系列データは、以下の成分に分解できる。

加法モデル: Y(t) = T(t) + S(t) + R(t)
乗法モデル: Y(t) = T(t) × S(t) × R(t)

T(t): トレンド成分(長期的な方向性)
S(t): 季節成分(周期的な変動)
R(t): 残差成分(ノイズ)

加法モデル vs 乗法モデル

特徴加法モデル乗法モデル
季節変動一定の振幅トレンドに比例して振幅が変化
使用場面売上が安定している場合売上が成長している場合
変換そのまま使用対数変換で加法モデルに変換可能

STL分解

STL(Seasonal and Trend decomposition using Loess)は、局所回帰(LOESS)を使った柔軟な時系列分解手法である。

from statsmodels.tsa.seasonal import STL
import pandas as pd
import matplotlib.pyplot as plt

# GROCERY Iカテゴリの全店舗合計売上
grocery = train[train['family'] == 'GROCERY I'].groupby('date')['sales'].sum()
grocery = grocery.asfreq('D').fillna(method='ffill')

# STL分解
stl = STL(grocery, period=7, robust=True)  # 週次季節性
result = stl.fit()

# 可視化
fig = result.plot()
fig.set_size_inches(14, 10)
plt.tight_layout()
plt.show()

# 各成分の確認
print("トレンド(最初の5日):")
print(result.trend.head())
print("\n季節成分(最初の7日):")
print(result.seasonal.head(7))
print("\n残差の標準偏差:")
print(result.resid.std())

STLのパラメータ

パラメータ説明推奨値
period季節周期日次→7(週)or 365(年)
seasonal季節成分の平滑度奇数、大きいほど滑らか
trendトレンドの平滑度Noneで自動設定
robust外れ値への頑健性Trueを推奨

古典的分解との比較

from statsmodels.tsa.seasonal import seasonal_decompose

# 古典的分解(移動平均ベース)
classic_result = seasonal_decompose(grocery, model='additive', period=7)
classic_result.plot()

# 比較
# 古典的分解の問題点:
# 1. 端部(最初と最後のperiod/2期間)が欠損になる
# 2. 季節成分が年間を通じて一定と仮定される
# 3. 外れ値に弱い
#
# STLの利点:
# 1. 端部も推定可能
# 2. 季節成分が時間とともに変化できる
# 3. robust=Trueで外れ値に頑健

複数の季節性

Store Salesデータには、週次と年次の両方の季節性がある。

# 週次パターンの確認
weekly = train.groupby(train['date'].dt.dayofweek)['sales'].mean()
print("曜日別平均売上:")
print(weekly)
# 日曜(0)が最も高い → エクアドルでは日曜が買い物日

# 月次パターンの確認
monthly = train.groupby(train['date'].dt.month)['sales'].mean()
print("\n月別平均売上:")
print(monthly)
# 12月がピーク(クリスマス需要)
# 4月も高い(Semana Santa: 聖週間)

MSTL: 複数季節性の分解

from statsmodels.tsa.seasonal import MSTL

# 週次(7)と年次(365)の2つの季節性を同時に分解
mstl = MSTL(grocery, periods=[7, 365])
result = mstl.fit()

# 成分: trend, seasonal(7), seasonal(365), resid
print("週次季節成分の振幅:", result.seasonal['seasonal_7'].std())
print("年次季節成分の振幅:", result.seasonal['seasonal_365'].std())

トレンドの分析

# トレンド成分から成長率を計算
trend = result.trend
yearly_growth = (trend.iloc[-365:].mean() / trend.iloc[:365].mean() - 1) * 100
print(f"年間成長率: {yearly_growth:.1f}%")

# トレンドの変化点検出
# 2016年4月の地震の影響を確認
earthquake_date = pd.Timestamp('2016-04-16')
before_eq = trend[trend.index < earthquake_date].tail(30).mean()
after_eq = trend[trend.index >= earthquake_date].head(30).mean()
print(f"地震前30日平均: {before_eq:.0f}")
print(f"地震後30日平均: {after_eq:.0f}")
print(f"変化率: {(after_eq / before_eq - 1) * 100:.1f}%")

まとめ

項目ポイント
時系列分解トレンド + 季節性 + 残差に分離
STL分解LOESSベースの柔軟な分解手法
加法 vs 乗法季節振幅が一定なら加法、比例なら乗法
複数季節性Store Salesでは週次と年次の2層

チェックリスト

  • 時系列分解の3成分を説明できる
  • 加法モデルと乗法モデルの違いを理解した
  • STL分解を実装し結果を解釈できる
  • Store Salesデータの週次・年次季節性を確認した

次のステップへ

時系列の分解ができたところで、次は自己相関分析(ACF/PACF)と定常性検定を学び、ARIMAモデルの前準備を行おう。

推定読了時間: 30分