Mímisbrunnr知恵の泉

← 時系列分析 一覧

🎓 レベル:発展 | 重要度:A(必須)

📎 前提:ランダムウォークと単位根(単位根・見せかけの回帰)・VAR(ベクトル自己回帰)(多変量) | 数理:確率過程(マルコフ連鎖・ポアソン過程)(統計)

要点(BLUF)

1. 共和分:非定常どうしの定常な結合

2 系列 xt,ytx_t,y_t がともに I(1)I(1)(1 階差分で定常)だとします。一般に I(1)I(1) どうしの和や差も I(1)I(1) のままですが、特別な係数 β\beta では ytβxty_t-\beta x_tI(0)I(0)(定常)になることがあります。これが共和分で、β\beta共和分ベクトルytβxty_t-\beta x_t均衡誤差と呼びます。

直観は「2 つの酔っ払いが鎖でつながれて歩く」像。各自はランダムウォークで好き勝手に動く(非定常)が、鎖の長さ(差)は一定以上に開かない(差は定常)。為替と物価、現物と先物、ペア取引の 2 銘柄などが典型例です。

共通の確率トレンド wtw_t(ランダムウォーク)を共有すると共和分が生まれます:

xt=wt+ut,yt=βwt+vt,wt=wt1+etx_t=w_t+u_t,\qquad y_t=\beta\,w_t+v_t,\qquad w_t=w_{t-1}+e_t

このとき ytβxt=vtβuty_t-\beta x_t=v_t-\beta u_t は共通トレンド wtw_t消えて定常になります。x,yx,y 単体は wtw_t を含むので I(1)I(1)(非定常)のままです。

コード①:各々は I(1)・線形結合は定常を確かめ、共和分を検定

共通トレンド ww を共有する x,yx,y(真の共和分ベクトルは y:x=1:2y:x=1:-2)を生成し、(a) 各系列が ADF で非定常、(b) 線形結合 y2xy-2x が ADF で定常、を確かめます。さらに Engle-Grangercoint)と Johansencoint_johansen)で共和分を検定し、回帰で長期係数を復元します。

import numpy as np
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller, coint
from statsmodels.tsa.vector_ar.vecm import coint_johansen

# 共通の確率トレンド w(ランダムウォーク)を 2 系列で共有 → 共和分
#   w_t = w_{t-1} + e_w            (共通トレンド・I(1))
#   x_t = w_t + e_x                (x は w にノイズ)
#   y_t = 2 w_t + e_y              (y は 2w にノイズ)
# → x も y も I(1) で非定常。だが y - 2x = e_y - 2 e_x は定常(共和分ベクトル [1,-2])
np.random.seed(0)
n = 600
w = np.cumsum(np.random.normal(0, 1, n))
x = w + np.random.normal(0, 0.5, n)
y = 2.0 * w + np.random.normal(0, 0.5, n)

# (a) 各系列は単位根(非定常)か:ADF(p>=0.05 で非定常)
print("各系列の ADF(p>=0.05 で非定常・単位根あり)")
print(f"  x: p={adfuller(x)[1]:.3f}   y: p={adfuller(y)[1]:.3f}")

# (b) 真の線形結合 y - 2x は定常か
combo = y - 2.0 * x
print(f"  y-2x(線形結合): p={adfuller(combo)[1]:.3f}{'定常' if adfuller(combo)[1]<0.05 else '非定常'}")

# (c) Engle-Granger 共和分検定(帰無=共和分なし。p<0.05 で共和分あり)
t_stat, p_eg, _ = coint(y, x)
print(f"\nEngle-Granger 共和分検定: p={p_eg:.3f}{'共和分あり' if p_eg<0.05 else '共和分なし'}")

# 回帰で共和分ベクトルを推定(y を x に回帰した傾きは およそ 2)
beta = sm.OLS(y, sm.add_constant(x)).fit().params[1]
print(f"回帰で推定した長期係数(真値 2.0): {beta:.3f}")

# (d) Johansen 検定:共和分ランク(トレース統計量 > 95%臨界値 の数)
joh = coint_johansen(np.column_stack([y, x]), det_order=0, k_ar_diff=1)
trace = joh.lr1                  # トレース統計量
cv95 = joh.cvt[:, 1]             # 95% 臨界値
rank = int(np.sum(trace > cv95))
print(f"\nJohansen トレース統計量={np.round(trace,2)}  95%臨界値={np.round(cv95,2)}")
print(f"→ 共和分ランク = {rank}(2系列で 1 本の長期均衡関係)")

出力:

各系列の ADF(p>=0.05 で非定常・単位根あり)
  x: p=0.990   y: p=0.986
  y-2x(線形結合): p=0.000  → 定常

Engle-Granger 共和分検定: p=0.000  → 共和分あり
回帰で推定した長期係数(真値 2.0): 2.003

Johansen トレース統計量=[258.99   0.4 ]  95%臨界値=[15.49  3.84]
→ 共和分ランク = 1(2系列で 1 本の長期均衡関係)

出力の意味x,yx,y 単体は ADF で p=0.990, 0.986p=0.990,\ 0.986非定常(単位根あり)。ところが線形結合 y2xy-2xp=0.000p=0.000定常——共通トレンドが打ち消されました。Engle-Granger 検定も p=0.000p=0.000 で共和分ありと判定し、回帰の長期係数は 2.0032.003 と真値 2.0 を復元。Johansen のトレース統計量は、ランク 0 の帰無で 258.9915.49258.99\gg15.49(棄却)、ランク 1 の帰無で 0.40<3.840.40<3.84(棄却せず)なので共和分ランク=1——2 系列の間に長期均衡関係が 1 本ある、と正しく結論します。これは「非定常どうしの回帰は見せかけ」(ランダムウォークと単位根)の例外=本物の長期関係です。残差が定常か否かが分かれ目。

2. 誤差修正モデル(VECM):均衡へ引き戻す力

共和分があるなら、差分 VAR ではなく VECM(Vector Error Correction Model) を使います。VECM は差分 VAR に誤差修正項を足した形です:

Δyt=α(βyt1)+i=1p1ΓiΔyti+εt\Delta\mathbf{y}_t=\boldsymbol\alpha\,(\beta^\top\mathbf{y}_{t-1})+\sum_{i=1}^{p-1}\Gamma_i\,\Delta\mathbf{y}_{t-i}+\boldsymbol\varepsilon_t
flowchart LR
    A["2系列 x_t, y_t(各々 I(1)・非定常)"] --> B["共和分検定: 線形結合は定常か"]
    B -->|"共和分あり"| C["VECM = 差分VAR + 誤差修正項"]
    B -->|"共和分なし"| D["差分してから VAR"]
    C --> E["β: 長期均衡 / α: 均衡へ戻す速度"]
    E --> F["予測(誤差修正で均衡へ引き戻す)"]

コード②:VECM で誤差修正項を推定・解釈し予測

共和分する x,yx,yVECM(共和分ランク 1)を当て、共和分ベクトル β\beta調整係数 α\alpha(誤差修正項)を推定・解釈します。末尾 20 期はホールドアウトして予測区間つきで予測します。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from statsmodels.tsa.vector_ar.vecm import VECM

# 同じ共和分する 2 系列(共通トレンド w を共有)
np.random.seed(0)
n = 600
w = np.cumsum(np.random.normal(0, 1, n))
x = w + np.random.normal(0, 0.5, n)
y = 2.0 * w + np.random.normal(0, 0.5, n)
data = np.column_stack([y, x])             # 列順 [y, x]

h = 20
train = data[:-h]

# VECM:差分VAR + 誤差修正項。共和分ランク1、deterministic='ci'(共和分式に定数)
model = VECM(train, k_ar_diff=1, coint_rank=1, deterministic="ci")
res = model.fit()

# 共和分ベクトル β(正規化:y の係数=1)と 調整係数 α(誤差修正速度)
beta = res.beta[:, 0]
beta_norm = beta / beta[0]
print("共和分ベクトル β([y, x] を正規化, 真の比は y:x = 1:-2):")
print(f"  y 係数={beta_norm[0]:.3f}   x 係数={beta_norm[1]:.3f}")
print("\n調整係数 α(均衡からのズレを次期にどれだけ戻すか・誤差修正項):")
print(f"  Δy への α = {res.alpha[0,0]:.3f}")
print(f"  Δx への α = {res.alpha[1,0]:.3f}")

# 予測(点予測 + 95% 予測区間)
point, lower, upper = res.predict(steps=h, alpha=0.05)
test = data[-h:]
for j, name in enumerate(["y", "x"]):
    rmse = np.sqrt(np.mean((point[:, j] - test[:, j])**2))
    w1 = upper[0, j] - lower[0, j]
    wH = upper[-1, j] - lower[-1, j]
    print(f"{name}: RMSE={rmse:.3f}  区間幅 1期={w1:.2f}{h}期={wH:.2f}(水準はI(1)で広がる)")

# 図示(y:実測・点予測・区間)
t_axis = np.arange(n)
plt.figure(figsize=(9, 4.5))
plt.plot(t_axis[-80:-h], train[-80+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="C2", lw=2, label="VECM 点予測")
plt.fill_between(t_axis[-h:], lower[:, 0], upper[:, 0], color="C2", alpha=0.25, label="95%予測区間")
plt.axvline(n-h-1, ls=":", color="k")
plt.xlabel("時点 t"); plt.ylabel("y"); plt.legend()
plt.title("VECM による予測(誤差修正で長期均衡へ引き戻す)")
plt.tight_layout(); plt.show()

出力:

共和分ベクトル β([y, x] を正規化, 真の比は y:x = 1:-2):
  y 係数=1.000   x 係数=-2.005

調整係数 α(均衡からのズレを次期にどれだけ戻すか・誤差修正項):
  Δy への α = -0.209
  Δx への α = 0.422
y: RMSE=12.015  区間幅 1期=8.33→20期=34.70(水準はI(1)で広がる)
x: RMSE=5.986  区間幅 1期=4.42→20期=17.37(水準はI(1)で広がる)

出力の意味:共和分ベクトルは正規化して β=[1,2.005]\beta=[1,\,-2.005]——均衡関係 y2xy-2x を復元しました(真の [1,2][1,-2])。調整係数は αΔy=0.209, αΔx=0.422\alpha_{\Delta y}=-0.209,\ \alpha_{\Delta x}=0.422。解釈は「均衡誤差 y2xy-2x正に大きい(yy が均衡より高い)とき、Δy\Delta y0.209×(ズレ)-0.209\times(\text{ズレ})下がりΔx\Delta x+0.422×(ズレ)+0.422\times(\text{ズレ})上がる。どちらも y2xy-2x を縮める向き」——両変数が協調して均衡へ引き戻します。これが誤差修正のメカニズムです。

予測の水準 RMSE が大きい(yy で 12.0)のは、x,yx,y の**水準が共通の確率トレンド(ランダムウォーク)を含み I(1)I(1) だから——20 期先の水準は本質的に不確実で、区間も 8.3334.708.33\to34.70 と大きく広がります。共和分が縛るのは水準そのものではなく 2 系列の差(スプレッド)**です。次のコード③で「差分だけ取るより VECM が均衡スプレッドをよく予測する」を確かめます。

コード③:共和分を捨てる(差分だけ)と長期情報を失う

「共和分があるのに差分 VAR で済ます」と何を失うか。均衡誤差 y2xy-2x を持続的な AR(1) にした共和分 DGP を 100 本生成し、VECM(共和分を使う)と差分 VAR(共和分を捨てる)で 15 期先の均衡スプレッドを予測、out-of-sample の RMSE を比べます。

import numpy as np
import warnings
warnings.simplefilter("ignore")
from statsmodels.tsa.api import VAR
from statsmodels.tsa.vector_ar.vecm import VECM

# 均衡誤差 s を「持続的な AR(1)」にした共和分DGP
#   w_t = w_{t-1} + e_w                 (共通の確率トレンド・I(1))
#   s_t = 0.85 s_{t-1} + e_s            (均衡からのズレ・定常だが持続的)
#   x_t = w_t + 0.3 n_x,  y_t = 2 w_t + s_t   → y-2x はほぼ s (平均回帰する均衡誤差)
def simulate(seed, n=400):
    rng = np.random.default_rng(seed)
    w = np.cumsum(rng.normal(0, 1, n))
    s = np.zeros(n)
    for t in range(1, n):
        s[t] = 0.85 * s[t-1] + rng.normal(0, 0.6)
    x = w + 0.3 * rng.normal(0, 1, n)
    y = 2.0 * w + s
    return np.column_stack([y, x])

h = 15
rmse_vecm, rmse_diff = [], []
for seed in range(100):                       # 100 本のパスで out-of-sample 評価
    data = simulate(seed)
    train, test = data[:-h], data[-h:]
    spread_true = test[:, 0] - 2.0 * test[:, 1]
    # VECM(共和分を使う=差分VAR+誤差修正項)
    v = VECM(train, k_ar_diff=1, coint_rank=1, deterministic="ci").fit()
    sv = v.predict(steps=h)
    rmse_vecm.append(np.sqrt(np.mean((sv[:,0]-2*sv[:,1] - spread_true)**2)))
    # 差分だけ取った VAR(共和分=長期情報を捨てる)
    d = np.diff(train, axis=0)
    vd = VAR(d).fit(1).forecast(d[-1:], steps=h)
    lev = train[-1] + np.cumsum(vd, axis=0)
    rmse_diff.append(np.sqrt(np.mean((lev[:,0]-2*lev[:,1] - spread_true)**2)))

mv, md = np.mean(rmse_vecm), np.mean(rmse_diff)
print(f"均衡スプレッド y-2x の {h}期予測 RMSE(100本平均, out-of-sample)")
print(f"  VECM(共和分を使う)      = {mv:.3f}")
print(f"  差分VAR(共和分を捨てる)  = {md:.3f}")
print(f"  改善率 = {(1-mv/md)*100:.1f}%(VECM が均衡へ引き戻す分だけ有利)")
win = np.mean(np.array(rmse_vecm) < np.array(rmse_diff))
print(f"  100本中 VECM が勝った割合 = {win*100:.0f}%")

出力:

均衡スプレッド y-2x の 15期予測 RMSE(100本平均, out-of-sample)
  VECM(共和分を使う)      = 1.183
  差分VAR(共和分を捨てる)  = 1.458
  改善率 = 18.8%(VECM が均衡へ引き戻す分だけ有利)
  100本中 VECM が勝った割合 = 76%

出力の意味:均衡スプレッドの 15 期先予測 RMSE は、VECM 1.1831.183 vs 差分 VAR 1.4581.458VECM が 18.8% 良く、100 本中 76% で VECM が勝ちました。理由は明快——差分 VAR はスプレッドを「行ったきり」(ランダムウォーク的)に予測し、均衡へ戻る力を最初から持たない。一方 VECM は誤差修正項 α(βy)\alpha(\beta^\top\mathbf{y}) で「ズレていれば戻す」を組み込むので、ズレが大きいほど予測が効きます。共和分があるのに差分だけ取ると、この長期均衡の情報を丸ごと捨てる——それがコスト(過剰差分の一種)です。

3. 数式の直観

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

関連ノート