Mímisbrunnr知恵の泉

← 時系列分析 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:AR・MAモデル(単変量AR)・定常性と自己相関 | 数理:確率過程(マルコフ連鎖・ポアソン過程)(統計)

要点(BLUF)

1. VAR(pp):各変数を全変数の過去で説明する

KK 本の系列 yt=(y1t,,yKt)\mathbf{y}_t=(y_{1t},\dots,y_{Kt})^\top を、互いの過去ラグで同時に説明します。

yt=c+A1yt1+A2yt2++Apytp+εt\mathbf{y}_t=\mathbf{c}+A_1\mathbf{y}_{t-1}+A_2\mathbf{y}_{t-2}+\cdots+A_p\mathbf{y}_{t-p}+\boldsymbol\varepsilon_t

2 変数 VAR(1) を成分で書くと相互依存が見えます:

(y1ty2t)=(c1c2)+(a11a12a21a22)(y1,t1y2,t1)+(ε1tε2t)\begin{pmatrix}y_{1t}\\ y_{2t}\end{pmatrix} =\begin{pmatrix}c_1\\ c_2\end{pmatrix} +\begin{pmatrix}a_{11}&a_{12}\\ a_{21}&a_{22}\end{pmatrix} \begin{pmatrix}y_{1,t-1}\\ y_{2,t-1}\end{pmatrix} +\begin{pmatrix}\varepsilon_{1t}\\ \varepsilon_{2t}\end{pmatrix}

a120a_{12}\ne0 なら「y2y_2 の過去が y1y_1 に効く」、a210a_{21}\ne0 なら逆向き。両方非ゼロなら相互フィードバックです。単変量 AR(AR・MAモデル)は K=1K=1 の特殊ケース。

安定性条件(コンパニオン行列の固有値)

VAR(pp) は、ラグを状態に畳み込んだ VAR(1) 形(コンパニオン形) に書けます。Kp×KpKp\times Kp のコンパニオン行列

A=(A1A2ApI0000I0)\mathbf{A}= \begin{pmatrix} A_1&A_2&\cdots&A_p\\ I&0&\cdots&0\\ 0&\ddots&&\vdots\\ 0&\cdots&I&0 \end{pmatrix}

固有値がすべて単位円内(絶対値 < 1)なら定常(安定)。VAR(1) のときはこれが A1A_1 そのものなので、A1A_1 の固有値を見ればよい。単変量 AR の「特性根が単位円外」条件(AR・MAモデル)の行列版です。

コード①:真の係数行列を仕込んで VAR で復元

真の A1A_1(固有値 0.6, 0.3 で安定)と定数 c\mathbf{c} を仕込んだ 2 変数 VAR(1) を生成し、statsmodelsVAR係数行列を復元します。次数 ppselect_order(AIC)で選ばせます。

import numpy as np
from statsmodels.tsa.api import VAR

# 真の 2変数 VAR(1):  y_t = c + A1 y_{t-1} + ε_t
# A1 の固有値が単位円内 → 安定(固有値 0.6, 0.3)
c_true = np.array([1.0, 0.5])
A1_true = np.array([[0.5, 0.2],
                    [0.1, 0.4]])
Sigma = np.array([[1.0, 0.3],
                  [0.3, 1.0]])   # 残差の同時相関

np.random.seed(3)
n = 2000
eps = np.random.multivariate_normal([0, 0], Sigma, size=n)
Y = np.zeros((n, 2))
for t in range(1, n):
    Y[t] = c_true + A1_true @ Y[t-1] + eps[t]

# 安定性:A1 の固有値(絶対値)がすべて 1 未満か
eig = np.abs(np.linalg.eigvals(A1_true))
print("A1 固有値の絶対値 =", np.round(eig, 3), "→", "安定" if np.all(eig < 1) else "不安定")

# VAR を当てて次数選択(AIC)
model = VAR(Y)
sel = model.select_order(maxlags=5)
print("AIC が選ぶ次数 p =", sel.aic)

res = model.fit(1)            # VAR(1) を推定
A1_hat = res.coefs[0]         # 推定された係数行列(ラグ1)
print("\n真の A1:\n", np.round(A1_true, 3))
print("推定 A1:\n", np.round(A1_hat, 3))
print("\n真の c =", np.round(c_true, 3), " 推定 c =", np.round(res.params[0], 3))

出力:

A1 固有値の絶対値 = [0.6 0.3] → 安定
AIC が選ぶ次数 p = 1

真の A1:
 [[0.5 0.2]
 [0.1 0.4]]
推定 A1:
 [[0.494 0.247]
 [0.081 0.451]]

真の c = [1.  0.5]  推定 c = [0.999 0.514]

出力の意味A1A_1 の固有値の絶対値は 0.6, 0.30.6,\ 0.3 でともに 1 未満——安定(定常)。AIC は正しく p=1p=1 を選びました。推定行列 A^1\hat A_1 は真値 A1A_1 を成分ごとに復元(0.494,0.247,0.081,0.4510.494,\,0.247,\,0.081,\,0.451 vs 真 0.5,0.2,0.1,0.40.5,\,0.2,\,0.1,\,0.4)、定数も 0.999,0.5140.999,\,0.514 と当たっています。**非対角成分 a120.25, a210.08a_{12}\approx0.25,\ a_{21}\approx0.08 が「変数間の波及」**で、これがあるからこそ単変量 AR では捉えられない相互作用を VAR が表せます。

2. 多変量予測と予測区間

VAR の hh 期先予測は、推定した係数行列で漸化的に回します(y^t+1=c^+A^1yt+\hat{\mathbf{y}}_{t+1}=\hat{\mathbf{c}}+\hat A_1\mathbf{y}_t+\cdots、次は予測値を代入)。予測誤差の分散も漸化式で積み上がり、各変数に予測区間が付きます。安定 VAR では区間幅が有限値(無条件分散)へ収束する点が、非定常(ARIMA の和分)で青天井に広がるのと対照的です(ARMA・ARIMAモデル)。

flowchart LR
    A["多変量系列 y_t(K本)"] --> B["VAR(p) を各式 OLS で推定"]
    B --> C["次数 p を AIC/BIC で選択"]
    C --> D["forecast_interval で h 期先を予測"]
    D --> E["点予測 + 95%予測区間(各変数)"]
    B --> F["IRF: ショックの波及を追跡(任意)"]

コード②:予測区間つき多変量予測をホールドアウトで評価

末尾 20 期を検証に回し(時間順分割・シャッフルなし)、forecast_interval点予測+95% 予測区間を出して評価・図示します。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from statsmodels.tsa.api import VAR

# 同じ真の VAR(1) を生成(安定)
c_true = np.array([1.0, 0.5])
A1_true = np.array([[0.5, 0.2],
                    [0.1, 0.4]])
Sigma = np.array([[1.0, 0.3], [0.3, 1.0]])
np.random.seed(3)
n = 600
eps = np.random.multivariate_normal([0, 0], Sigma, size=n)
Y = np.zeros((n, 2))
for t in range(1, n):
    Y[t] = c_true + A1_true @ Y[t-1] + eps[t]

# ホールドアウト:末尾 20 期を検証に回し、過去だけで推定
h = 20
train, test = Y[:-h], Y[-h:]
res = VAR(train).fit(1)

# forecast_interval:点予測 + 95% 予測区間(直近 p 期を渡す)
point, lower, upper = res.forecast_interval(train[-res.k_ar:], steps=h, alpha=0.05)

# 検証:各変数の RMSE と区間カバレッジ
for j, name in enumerate(["y1", "y2"]):
    rmse = np.sqrt(np.mean((point[:, j] - test[:, j])**2))
    cover = np.mean((lower[:, j] <= test[:, j]) & (test[:, j] <= upper[:, j]))
    w1 = upper[0, j] - lower[0, j]
    wH = upper[-1, j] - lower[-1, j]
    print(f"{name}: RMSE={rmse:.3f}  95%被覆={cover*100:.0f}%  区間幅 1期={w1:.2f}{h}期={wH:.2f}")

# 図示(y1 のみ:実測・点予測・予測区間)
t_axis = np.arange(n)
plt.figure(figsize=(9, 4.5))
plt.plot(t_axis[-60:-h], train[-60+h:, 0], color="gray", lw=1, label="訓練(実測)")
plt.plot(t_axis[-h:], test[:, 0], color="k", lw=1.5, marker="o", ms=3, label="検証(実測)")
plt.plot(t_axis[-h:], point[:, 0], color="C1", lw=2, label="点予測")
plt.fill_between(t_axis[-h:], lower[:, 0], upper[:, 0], color="C1", alpha=0.25, label="95%予測区間")
plt.axvline(n-h-1, ls=":", color="k")
plt.xlabel("時点 t"); plt.ylabel("y1"); plt.legend()
plt.title("VAR(1) による多変量予測(y1・予測区間つき)")
plt.tight_layout(); plt.show()

出力:

y1: RMSE=1.047  95%被覆=100%  区間幅 1期=3.97→20期=5.09
y2: RMSE=1.065  95%被覆=100%  区間幅 1期=3.86→20期=4.49

出力の意味:各変数の RMSE は約 1.0(残差の標準偏差 1.0 と整合)で、検証 20 点はすべて 95% 区間に収まりました。注目は区間幅が(y1 で)3.975.093.97\to5.09 と少し広がってから頭打ちになること——安定 VAR の予測分散は無条件分散へ収束するので、ARIMA の和分系列のように h\sqrt{h} で青天井に広がりません(ARMA・ARIMAモデル)。図では橙の点予測がやがて無条件平均へ落ち着き、帯の幅も一定に近づきます。検証 20 点全被覆は標本が 1 本のため目安で、較正は本来ウォークフォワードで複数回確かめます(予測の評価指標と時系列CV)。

3. インパルス応答(IRF):ショックの波及を追う(任意)

VAR の魅力のひとつがインパルス応答関数(IRF)。「ある変数に大きさ 1 のショックを与えたら、各変数が何期にわたってどう反応するか」を係数行列の累乗で追います。安定系ならショックの影響は時間とともに 0 へ減衰します。

import numpy as np
from statsmodels.tsa.api import VAR

c_true = np.array([1.0, 0.5])
A1_true = np.array([[0.5, 0.2],
                    [0.1, 0.4]])
Sigma = np.array([[1.0, 0.3], [0.3, 1.0]])
np.random.seed(3)
n = 2000
eps = np.random.multivariate_normal([0, 0], Sigma, size=n)
Y = np.zeros((n, 2))
for t in range(1, n):
    Y[t] = c_true + A1_true @ Y[t-1] + eps[t]

res = VAR(Y).fit(1)
irf = res.irf(8)                 # 8 期先までのインパルス応答
resp = irf.irfs               # shape (steps+1, 2, 2): [期, 応答変数, ショック変数]

print("y1 に大きさ1のショック → 各変数の反応(直交化なし)")
print(f"{'h':>2}{'→y1':>8}{'→y2':>8}")
for hh in [0, 1, 2, 4, 8]:
    print(f"{hh:>2}{resp[hh,0,0]:>8.3f}{resp[hh,1,0]:>8.3f}")
print("反応は時間とともに 0 へ減衰 →", "安定系" if abs(resp[8,0,0]) < abs(resp[0,0,0]) else "発散")

出力:

y1 に大きさ1のショック → 各変数の反応(直交化なし)
 h     →y1     →y2
 0   1.000   0.000
 1   0.494   0.081
 2   0.264   0.076
 4   0.087   0.037
 8   0.012   0.006

出力の意味y1y_1 への 1 単位ショックは、即時(h=0h=0)に y1y_1 を 1、y2y_2 を 0 動かし、翌期(h=1h=1)には y10.494, y20.081y_1\to0.494,\ y_2\to0.081——これはちょうど A1A_1 の 1 列目です(係数行列がそのまま 1 期波及を決める)。以降は累乗で減衰し、h=8h=8 では 0.012,0.0060.012,\,0.006 とほぼ消えます。安定系ではショックが永続しないことが固有値 < 1 から保証されます。なお実務の IRF は誤差の同時相関 Σ\Sigma を考慮した直交化 IRF(コレスキー分解)を使い、変数の順序付けに結果が依存する——構造的な解釈には注意が要ります(後述)。

4. 数式の直観

⚠️ よくある誤解・落とし穴

関連ノート