第9回 時系列データ分析
はじめに
本シリーズ「Pythonで学ぶデータ分析実践」の第9回では、時系列データ分析について解説します。
株価、売上、気温、アクセス数など、時間の経過とともに測定されるデータは身の回りに多く存在します。こうした時系列データを分析し、将来の値を予測することはビジネスや研究において非常に重要です。
しかし、時系列データには「前の値が次の値に影響する」という時間依存性があるため、通常の回帰分析とは異なるアプローチが必要です。本記事では、時系列データの基本概念からARIMA・SARIMAモデルによる予測まで、実践的に解説します。
シリーズ全体の構成(全12回)
| 回 | テーマ |
|---|---|
| 第1回 | データ分析とは |
| 第2回 | データの前処理 |
| 第3回 | データの把握 |
| 第4回 | 相関分析 |
| 第5回 | 統計的推定 |
| 第6回 | 統計的検定 |
| 第7回 | 分散分析 |
| 第8回 | 回帰分析 |
| 第9回 | 時系列データ分析(本記事) |
| 第10回 | クラス分類 |
| 第11回 | クラスタリング |
| 第12回 | 次元削減 |
9-1. 時系列データとは
時系列データとは、時間の経過に沿って測定されたデータのことです。通常の回帰分析ではデータの独立性が前提ですが、時系列データでは「前の値が次の値に影響する」ため、専用の分析手法が必要です。
時系列データの例
| 分野 | データ例 | 時間単位 |
|---|---|---|
| 金融 | 株価、為替レート | 分、日 |
| 小売 | 売上、来店客数 | 日、月 |
| 気象 | 気温、降水量 | 時間、日 |
| Web | アクセス数、CVR | 時間、日 |
| 製造 | センサーデータ、生産量 | 秒、分 |
時系列データの4つの構成要素
時系列データは以下の4つの成分に分解できます。
| 成分 | 説明 | 例 |
|---|---|---|
| トレンド(T) | 長期的な上昇・下降の傾向 | 人口増加、GDP成長 |
| 季節変動(S) | 一定周期で繰り返すパターン | 夏にアイス売上↑、冬にコート売上↑ |
| 周期変動(C) | 季節より長い不規則な波 | 景気循環 |
| 不規則変動(I) | 予測不能なランダムな変動 | 突発的なイベント |
$$Y_t = T_t + S_t + C_t + I_t \quad(加法モデル)$$
時系列データの基本統計量
自己共分散(Autocovariance)
同じデータの異なる時点間の関係性を測る指標です。ラグkの自己共分散は以下で定義されます。
$$\gamma(k) = \frac{1}{N}\sum_{t=1}^{N-k}(x_t - \bar{x})(x_{t+k} - \bar{x})$$
自己相関係数(ACF: Autocorrelation Function)
自己共分散を標準化した指標で、-1〜1の値をとります。
$$\rho(k) = \frac{\gamma(k)}{\gamma(0)}$$
- ρ(k)が高い → k期前のデータと現在のデータが似ている
- ρ(k)が低い → k期前のデータと現在は無関係
Pythonでの時系列データの基本操作
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import japanize_matplotlib
# サンプル時系列データの生成(月次売上データ)
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', periods=72, freq='MS')
trend = np.linspace(100, 200, 72)
seasonal = 30 * np.sin(2 * np.pi * np.arange(72) / 12)
noise = np.random.normal(0, 10, 72)
sales = trend + seasonal + noise
ts = pd.Series(sales, index=dates, name="月次売上")
# 基本統計量
print("=== 時系列データの基本情報 ===")
print(f"期間: {ts.index[0].strftime('%Y-%m')} 〜 {ts.index[-1].strftime('%Y-%m')}")
print(f"データ数: {len(ts)}")
print(f"平均: {ts.mean():.2f}")
print(f"標準偏差: {ts.std():.2f}")
# 時系列プロット
plt.figure(figsize=(12, 4))
plt.plot(ts, color='steelblue', linewidth=1.5)
plt.xlabel("日付")
plt.ylabel("売上")
plt.title("月次売上データの推移")
plt.grid(alpha=0.3)
plt.show()
自己相関の可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
# 自己相関関数(ACF)
plot_acf(ts, lags=24, ax=axes[0])
axes[0].set_title("自己相関関数(ACF)")
# 偏自己相関関数(PACF)
plot_pacf(ts, lags=24, ax=axes[1])
axes[1].set_title("偏自己相関関数(PACF)")
plt.tight_layout()
plt.show()
定常性
時系列分析の多くのモデル(AR, MA, ARMA)は、データが定常であることを前提とします。
| 定常性 | 条件 | 例 |
|---|---|---|
| 定常 | 平均・分散が時間によらず一定 | ホワイトノイズ |
| 非定常 | 平均・分散が時間とともに変化 | トレンドのある売上データ |
定常性の確認: ADF検定(拡張ディッキー・フラー検定)
from statsmodels.tsa.stattools import adfuller
result = adfuller(ts)
print(f"=== ADF検定(定常性の検定) ===")
print(f"ADF統計量: {result[0]:.4f}")
print(f"p値: {result[1]:.6f}")
print(f"ラグ数: {result[2]}")
if result[1] < 0.05:
print("→ 定常である(帰無仮説「単位根あり」を棄却)")
else:
print("→ 非定常である(差分を取って定常化が必要)")
9-2. トレンド
トレンドは、データの長期的な上昇・下降傾向を表す成分です。トレンドを除去することで、季節性や短期的なパターンを分析しやすくなります。
トレンドの抽出方法
| 方法 | 特徴 |
|---|---|
| 移動平均 | 一定期間の平均でトレンドを推定 |
| 回帰分析 | 線形・多項式でトレンドをモデル化 |
| 季節分解 | STL分解でトレンド成分を抽出 |
| ローパスフィルタ | 高周波成分を除去 |
移動平均によるトレンド抽出
# 移動平均(12ヶ月)
ts_ma12 = ts.rolling(window=12, center=True).mean()
plt.figure(figsize=(12, 5))
plt.plot(ts, alpha=0.7, label='元データ', color='steelblue')
plt.plot(ts_ma12, linewidth=2.5, label='12ヶ月移動平均(トレンド)', color='red')
plt.xlabel("日付")
plt.ylabel("売上")
plt.title("移動平均によるトレンドの抽出")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
時系列分解(STL分解)
# 季節分解(加法モデル)
decomposition = seasonal_decompose(ts, model='additive', period=12)
fig, axes = plt.subplots(4, 1, figsize=(12, 10), sharex=True)
decomposition.observed.plot(ax=axes[0], title='元データ(Observed)')
decomposition.trend.plot(ax=axes[1], title='トレンド(Trend)')
decomposition.seasonal.plot(ax=axes[2], title='季節変動(Seasonal)')
decomposition.resid.plot(ax=axes[3], title='残差(Residual)')
for ax in axes:
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
9-3. 季節性
季節性とは、一定の周期で繰り返されるパターンのことです。週次、月次、年次などさまざまな周期がありえます。
季節性の確認方法
# 月ごとの平均を計算して季節パターンを確認
monthly_pattern = ts.groupby(ts.index.month).mean()
plt.figure(figsize=(8, 5))
plt.bar(monthly_pattern.index, monthly_pattern.values, color='steelblue', alpha=0.8)
plt.xlabel("月")
plt.ylabel("平均売上")
plt.title("月別平均売上(季節パターン)")
plt.xticks(range(1, 13))
plt.grid(axis='y', alpha=0.3)
plt.show()
差分による非定常性の除去
トレンドや季節性がある非定常データは、差分を取ることで定常化できます。
# 1次差分(トレンド除去)
ts_diff1 = ts.diff().dropna()
# 季節差分(季節性除去)
ts_seasonal_diff = ts.diff(12).dropna()
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=False)
axes[0].plot(ts)
axes[0].set_title("元データ(非定常)")
axes[0].grid(alpha=0.3)
axes[1].plot(ts_diff1)
axes[1].set_title("1次差分(トレンド除去)")
axes[1].grid(alpha=0.3)
axes[2].plot(ts_seasonal_diff)
axes[2].set_title("季節差分(周期12)")
axes[2].grid(alpha=0.3)
plt.tight_layout()
plt.show()
# 差分後の定常性検定
result = adfuller(ts_diff1)
print(f"1次差分のADF検定 p値: {result[1]:.6f}")
9-4. 自己回帰(AR)
自己回帰(AR: AutoRegressive)モデルは、過去の値そのものを用いて現在の値を予測するモデルです。「昨日の気温が高ければ、今日も高い可能性が高い」という直感に基づいています。
AR(p)モデルの数式
$$y_t = c + \alpha_1 y_{t-1} + \alpha_2 y_{t-2} + \cdots + \alpha_p y_{t-p} + \varepsilon_t$$
- $y_t$: 時点tの値
- $\alpha_1, \ldots, \alpha_p$: 自己回帰係数
- $c$: 定数項
- $\varepsilon_t$: ホワイトノイズ($N(0, \sigma^2)$に従う乱数)
- p: 次数(何期前まで使うか)
AR(1)モデルの例
$$y_t = c + \alpha_1 y_{t-1} + \varepsilon_t$$
1期前の値のみで現在を予測する最もシンプルなARモデルです。
ARモデルの特徴
- PACFが次数pで切断される(p期目以降で急に0に近づく)
- ACFは徐々に減衰する
- 過去のデータとの自己共分散が大きい場合に有効
Pythonでの実装
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt
import japanize_matplotlib
# AR(2)モデルのデータを生成
np.random.seed(42)
n = 200
y = np.zeros(n)
y[0], y[1] = 0, 0
for t in range(2, n):
y[t] = 0.6 * y[t-1] + 0.2 * y[t-2] + np.random.normal(0, 1)
ts_ar = pd.Series(y, name="AR(2)データ")
# ARモデルの推定(order=(p, d, q) = (2, 0, 0))
model_ar = ARIMA(ts_ar, order=(2, 0, 0))
result_ar = model_ar.fit()
print("=== AR(2)モデルの推定結果 ===")
print(result_ar.summary())
# 予測
forecast = result_ar.get_forecast(steps=30)
forecast_mean = forecast.predicted_mean
forecast_ci = forecast.conf_int()
# 可視化
plt.figure(figsize=(12, 5))
plt.plot(ts_ar[-50:], label='実測値', color='steelblue')
plt.plot(range(200, 230), forecast_mean, label='予測', color='red', linewidth=2)
plt.fill_between(range(200, 230), forecast_ci.iloc[:, 0], forecast_ci.iloc[:, 1],
alpha=0.2, color='red', label='95%信頼区間')
plt.xlabel("時点")
plt.ylabel("値")
plt.title("AR(2)モデルによる予測")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
9-5. 移動平均(MA)
移動平均(MA: Moving Average)モデルは、過去の予測誤差(残差)を用いて現在の値を予測するモデルです。
MA(q)モデルの数式
$$y_t = c + \varepsilon_t + \theta_1 \varepsilon_{t-1} + \theta_2 \varepsilon_{t-2} + \cdots + \theta_q \varepsilon_{t-q}$$
- $\theta_1, \ldots, \theta_q$: 移動平均係数
- $\varepsilon_t$: 時点tのホワイトノイズ
- q: 次数(何期前の誤差まで使うか)
MAモデルの特徴
- ACFが次数qで切断される(q期目以降で急に0に近づく)
- PACFは徐々に減衰する
- 短期的なショックの影響がデータに残る場合に有効
ARモデルとMAモデルの見分け方
| 特徴 | ARモデル | MAモデル |
|---|---|---|
| ACF | 徐々に減衰 | q期で切断 |
| PACF | p期で切断 | 徐々に減衰 |
| 意味 | 過去の値で予測 | 過去の誤差で予測 |
Pythonでの実装
# MA(2)モデルのデータを生成
np.random.seed(42)
n = 200
eps = np.random.normal(0, 1, n)
y_ma = np.zeros(n)
for t in range(2, n):
y_ma[t] = eps[t] + 0.7 * eps[t-1] + 0.3 * eps[t-2]
ts_ma = pd.Series(y_ma, name="MA(2)データ")
# MAモデルの推定(order=(0, 0, 2))
model_ma = ARIMA(ts_ma, order=(0, 0, 2))
result_ma = model_ma.fit()
print("=== MA(2)モデルの推定結果 ===")
print(f"θ1 = {result_ma.params[1]:.4f}")
print(f"θ2 = {result_ma.params[2]:.4f}")
print(f"AIC = {result_ma.aic:.2f}")
9-6. ARMA
ARMA(AutoRegressive Moving Average)モデルは、ARモデルとMAモデルを組み合わせたモデルです。過去のデータ(AR部分)と過去の誤差(MA部分)の両方を考慮して予測を行います。
ARMA(p, q)モデルの数式
$$y_t = c + \sum_{i=1}^{p}\alpha_i y_{t-i} + \varepsilon_t + \sum_{j=1}^{q}\theta_j \varepsilon_{t-j}$$
ARMAモデルの適用条件
- データが定常であること(平均・分散が時間によらず一定)
- 非定常データには差分を取ってから適用するか、ARIMAモデルを使用
次数(p, q)の決定方法
| 方法 | 説明 |
|---|---|
| ACF/PACF | ACFの切断→q、PACFの切断→pの手がかり |
| AIC/BIC | 複数の(p,q)を試し、AIC/BICが最小のモデルを選択 |
| auto_arima | 自動で最適な次数を探索 |
Pythonでの実装
# ARMA(2,1)モデル(定常データに適用)
# ARIMAのorder=(p, 0, q)として指定
model_arma = ARIMA(ts_ar, order=(2, 0, 1))
result_arma = model_arma.fit()
print("=== ARMA(2,1)モデルの推定結果 ===")
print(f"AR係数: {result_arma.arparams}")
print(f"MA係数: {result_arma.maparams}")
print(f"AIC: {result_arma.aic:.2f}")
print(f"BIC: {result_arma.bic:.2f}")
AICによるモデル選択
import warnings
warnings.filterwarnings('ignore')
# 複数のARMAモデルを比較
results = []
for p in range(0, 4):
for q in range(0, 4):
try:
model = ARIMA(ts_ar, order=(p, 0, q))
fit = model.fit()
results.append({"p": p, "q": q, "AIC": fit.aic, "BIC": fit.bic})
except:
continue
df_results = pd.DataFrame(results).sort_values("AIC")
print("=== AICによるモデル比較(上位5件) ===")
print(df_results.head().to_string(index=False))
9-7. ARIMA
ARIMA(AutoRegressive Integrated Moving Average)モデルは、ARMAモデルに**差分化(I: Integrated)**を加えたもので、非定常データ(トレンドのあるデータ)にも対応できます。
ARIMA(p, d, q)のパラメータ
| パラメータ | 意味 | 役割 |
|---|---|---|
| p | AR次数 | 何期前までの値を使うか |
| d | 差分次数 | 何回差分を取るか(非定常→定常化) |
| q | MA次数 | 何期前までの誤差を使うか |
ARIMAモデルの適用場面
- 非季節的トレンドのあるデータ
- 売上、株価、経済指標など長期的傾向を持つデータ
- 季節性がない(または弱い)時系列データ
Pythonでの実装
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
import japanize_matplotlib
# トレンドのある非定常データの生成
np.random.seed(42)
n = 120
trend = np.linspace(100, 250, n)
noise = np.random.normal(0, 10, n)
ts_nonstat = pd.Series(trend + noise,
index=pd.date_range('2015-01', periods=n, freq='MS'),
name="売上")
# 定常性の確認
result = adfuller(ts_nonstat)
print(f"=== 元データのADF検定 ===")
print(f"p値: {result[1]:.6f} → {'定常' if result[1] < 0.05 else '非定常'}")
# 1次差分後の定常性
ts_diff = ts_nonstat.diff().dropna()
result_diff = adfuller(ts_diff)
print(f"\n=== 1次差分後のADF検定 ===")
print(f"p値: {result_diff[1]:.6f} → {'定常' if result_diff[1] < 0.05 else '非定常'}")
# ARIMA(1,1,1)モデルの構築
model = ARIMA(ts_nonstat, order=(1, 1, 1))
result_arima = model.fit()
print(f"\n=== ARIMA(1,1,1)モデル ===")
print(f"AIC: {result_arima.aic:.2f}")
print(f"BIC: {result_arima.bic:.2f}")
print(result_arima.summary())
予測と可視化
# 12ヶ月先まで予測
forecast = result_arima.get_forecast(steps=12)
forecast_mean = forecast.predicted_mean
forecast_ci = forecast.conf_int()
plt.figure(figsize=(12, 5))
plt.plot(ts_nonstat, label='実測値', color='steelblue', linewidth=1.5)
plt.plot(forecast_mean, label='予測値', color='red', linewidth=2)
plt.fill_between(forecast_ci.index,
forecast_ci.iloc[:, 0], forecast_ci.iloc[:, 1],
alpha=0.2, color='red', label='95%信頼区間')
plt.xlabel("日付")
plt.ylabel("売上")
plt.title("ARIMA(1,1,1)モデルによる売上予測")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
パラメータの自動最適化
import itertools
import warnings
warnings.filterwarnings('ignore')
# グリッドサーチでAICが最小のパラメータを探索
best_aic = np.inf
best_order = None
for p in range(0, 4):
for d in range(0, 3):
for q in range(0, 4):
try:
model = ARIMA(ts_nonstat, order=(p, d, q))
fit = model.fit()
if fit.aic < best_aic:
best_aic = fit.aic
best_order = (p, d, q)
except:
continue
print(f"=== 最適パラメータ(AIC最小) ===")
print(f"order: {best_order}")
print(f"AIC: {best_aic:.2f}")
# 最適モデルで再推定
best_model = ARIMA(ts_nonstat, order=best_order).fit()
print(f"\n最適モデルのサマリー:")
print(best_model.summary())
pmdarima による自動ARIMA
pmdarimaライブラリのauto_arimaを使うと、最適なパラメータを自動で探索できます。
# pip install pmdarima
from pmdarima import auto_arima
# 自動ARIMAパラメータ選択
auto_model = auto_arima(ts_nonstat,
start_p=0, max_p=5,
start_q=0, max_q=5,
d=None, # 自動で差分次数を決定
seasonal=False,
stepwise=True,
trace=True)
print(f"\n=== auto_arima 結果 ===")
print(auto_model.summary())
9-8. SARIMA
SARIMA(Seasonal ARIMA)モデルは、ARIMAモデルに季節性の成分を加えたモデルです。季節的なパターンを持つ時系列データ(売上の年末増加、夏のビール消費増など)をモデル化する場合に使用します。
SARIMAのパラメータ
SARIMAは非季節性パラメータと季節性パラメータの2組を指定します。
$$SARIMA(p, d, q)(P, D, Q, s)$$
| パラメータ | 種類 | 意味 |
|---|---|---|
| p | 非季節性 | AR次数 |
| d | 非季節性 | 差分次数 |
| q | 非季節性 | MA次数 |
| P | 季節性 | 季節AR次数 |
| D | 季節性 | 季節差分次数 |
| Q | 季節性 | 季節MA次数 |
| s | 季節性 | 季節周期(12=月次、7=週次、4=四半期) |
SARIMAモデルの適用場面
- 明確な季節パターンがあるデータ
- 月次売上データ(12ヶ月周期)
- 日次アクセスデータ(7日周期)
- 四半期決算データ(4期周期)
Pythonでの実装
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
import matplotlib.pyplot as plt
import japanize_matplotlib
# 季節性のある時系列データの生成
np.random.seed(42)
n = 96 # 8年分の月次データ
dates = pd.date_range(start='2017-01-01', periods=n, freq='MS')
trend = np.linspace(100, 180, n)
seasonal = 25 * np.sin(2 * np.pi * np.arange(n) / 12) + 15 * np.cos(2 * np.pi * np.arange(n) / 12)
noise = np.random.normal(0, 8, n)
ts_seasonal = pd.Series(trend + seasonal + noise, index=dates, name="月次売上")
# データの可視化
plt.figure(figsize=(12, 5))
plt.plot(ts_seasonal, color='steelblue', linewidth=1.5)
plt.xlabel("日付")
plt.ylabel("売上")
plt.title("季節性のある月次売上データ")
plt.grid(alpha=0.3)
plt.show()
# 学習データとテストデータの分割
train = ts_seasonal[:'2023-12']
test = ts_seasonal['2024-01':]
print(f"学習データ: {train.index[0].strftime('%Y-%m')} 〜 {train.index[-1].strftime('%Y-%m')} ({len(train)}件)")
print(f"テストデータ: {test.index[0].strftime('%Y-%m')} 〜 {test.index[-1].strftime('%Y-%m')} ({len(test)}件)")
SARIMAモデルの構築
# SARIMAモデルの推定
# SARIMA(1,1,1)(1,1,1,12) - 月次データ(周期12)
model_sarima = SARIMAX(train,
order=(1, 1, 1),
seasonal_order=(1, 1, 1, 12),
enforce_stationarity=False,
enforce_invertibility=False)
result_sarima = model_sarima.fit(disp=False)
print("=== SARIMA(1,1,1)(1,1,1,12)モデル ===")
print(f"AIC: {result_sarima.aic:.2f}")
print(f"BIC: {result_sarima.bic:.2f}")
print(result_sarima.summary())
予測と評価
# テスト期間の予測
forecast = result_sarima.get_forecast(steps=len(test))
forecast_mean = forecast.predicted_mean
forecast_ci = forecast.conf_int()
# 可視化
plt.figure(figsize=(14, 6))
plt.plot(train[-24:], label='学習データ', color='steelblue', linewidth=1.5)
plt.plot(test, label='実測値(テスト)', color='green', linewidth=1.5, linestyle='--')
plt.plot(forecast_mean, label='予測値', color='red', linewidth=2)
plt.fill_between(forecast_ci.index,
forecast_ci.iloc[:, 0], forecast_ci.iloc[:, 1],
alpha=0.2, color='red', label='95%信頼区間')
plt.xlabel("日付")
plt.ylabel("売上")
plt.title("SARIMAモデルによる売上予測")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
# 予測精度の評価
from sklearn.metrics import mean_squared_error, mean_absolute_error
rmse = np.sqrt(mean_squared_error(test, forecast_mean))
mae = mean_absolute_error(test, forecast_mean)
mape = np.mean(np.abs((test - forecast_mean) / test)) * 100
print(f"\n=== 予測精度 ===")
print(f"RMSE: {rmse:.2f}")
print(f"MAE: {mae:.2f}")
print(f"MAPE: {mape:.2f}%")
auto_arimaによるSARIMAの自動最適化
from pmdarima import auto_arima
# 季節性ありの自動ARIMA
auto_model = auto_arima(train,
start_p=0, max_p=3,
start_q=0, max_q=3,
d=None,
start_P=0, max_P=2,
start_Q=0, max_Q=2,
D=None,
seasonal=True,
m=12, # 季節周期
stepwise=True,
trace=True)
print(f"\n=== auto_arima(季節性あり)の結果 ===")
print(auto_model.summary())
# 予測
forecast_auto = auto_model.predict(n_periods=len(test), return_conf_int=True)
補足:時系列分析の実践的なポイント
モデル選択のフロー
データに季節性はあるか?
├── あり → SARIMA(季節周期sを設定)
└── なし
├── データは定常か?
│ ├── はい → ARMA(d=0)
│ └── いいえ → ARIMA(差分dを設定)
└── トレンドの型は?
├── 線形 → d=1で十分なことが多い
└── 二次的 → d=2を検討
残差診断
良いモデルの残差はホワイトノイズ(ランダム)であるべきです。
# 残差診断
residuals = result_sarima.resid
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# 残差のプロット
axes[0,0].plot(residuals)
axes[0,0].set_title("残差の推移")
axes[0,0].axhline(0, color='red', linestyle='--')
# 残差のヒストグラム
axes[0,1].hist(residuals, bins=20, edgecolor='black', alpha=0.7, density=True)
axes[0,1].set_title("残差のヒストグラム")
# ACF
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuals, lags=24, ax=axes[1,0])
axes[1,0].set_title("残差のACF")
# Q-Qプロット
from scipy import stats
stats.probplot(residuals, plot=axes[1,1])
axes[1,1].set_title("残差のQ-Qプロット")
plt.tight_layout()
plt.show()
# Ljung-Box検定(残差が白色雑音かどうか)
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_result = acorr_ljungbox(residuals, lags=[12], return_df=True)
print(f"\n=== Ljung-Box検定 ===")
print(lb_result)
if lb_result['lb_pvalue'].values[0] > 0.05:
print("→ 残差はホワイトノイズ(モデルは適切)")
else:
print("→ 残差に自己相関あり(モデルの改善が必要)")
時系列分析でよくある注意点
| 注意点 | 説明 |
|---|---|
| 過学習 | パラメータを増やしすぎると過学習する。AIC/BICで抑制 |
| データ漏洩 | テストデータの情報が学習に混入しないようにする |
| 外れ値 | 突発的なイベント(コロナ禍など)の影響を考慮 |
| 構造変化 | 途中でデータの性質が変わった場合、分割してモデル化 |
| 予測の不確実性 | 予測期間が長くなるほど信頼区間は広がる |
まとめ
本記事では、時系列データ分析として以下の内容を解説しました。
| 項目 | ポイント |
|---|---|
| 時系列データとは | 時間依存性があり、特別な分析手法が必要 |
| トレンド | 長期的な傾向。移動平均やSTL分解で抽出 |
| 季節性 | 一定周期のパターン。差分で除去可能 |
| AR | 過去の値で予測。PACFで次数pを決定 |
| MA | 過去の誤差で予測。ACFで次数qを決定 |
| ARMA | AR+MA。定常データに適用 |
| ARIMA | ARMA+差分化。非定常データに対応 |
| SARIMA | ARIMA+季節成分。季節パターンのあるデータに対応 |
時系列分析のチェックリスト
- データの時間的な推移を可視化したか
- トレンドと季節性の有無を確認したか
- 定常性を検定したか(ADF検定)
- ACF/PACFでモデルの次数の見当をつけたか
- AIC/BICで複数モデルを比較したか
- 残差診断を行い、モデルの妥当性を確認したか
- テストデータで予測精度を評価したか
- 信頼区間を含めて予測結果を報告したか
シリーズ記事一覧
- 【第1回】Pythonで学ぶデータ分析実践 〜データ分析とは〜
- 【第2回】データの前処理(欠損値・外れ値・ダミー変数・データスケーリングなど)
- 【第3回】データの把握(基本統計量・ヒストグラム・箱ひげ図・散布図など)
- 【第4回】相関分析(共分散・相関係数・相関比・連関係数など)
- 【第5回】統計的推定(母集団と標本・点推定・区間推定・信頼区間)
- 【第6回】統計的検定(仮説検定・p値・t検定・カイ二乗検定など)
- 【第7回】分散分析(一元分散分析・二元分散分析・多重比較)
- 【第8回】回帰分析(単回帰・重回帰・一般化線形モデル・モデル評価)
- 【第9回】時系列データ分析(トレンド・季節性・ARIMA・SARIMAなど)
- 【第10回】クラス分類(決定木・ランダムフォレスト・ロジスティック回帰など)
- 【第11回】クラスタリング(k-means・階層クラスタリング・エルボー法など)
- 【第12回】次元削減(主成分分析・寄与率・可視化)
- 【発展編】分析精度の向上(特徴量エンジニアリング・ハイパーパラメータ調整・アンサンブル学習など)
- 【発展編】ニューラルネットワーク入門(パーセプトロン・誤差逆伝播法・TensorFlow・Kerasなど)
次回の第10回では、データを分類するクラス分類について解説します。決定木、ランダムフォレスト、ロジスティック回帰などの分類手法と、その評価方法を学びます。