Mímisbrunnr知恵の泉

← 時系列分析 一覧

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

📎 前提:VAR(ベクトル自己回帰)(多変量回帰)・ランダムウォークと単位根(定常性) | 関連:相関と因果の違い(因果)

要点(BLUF)

1. 定義:予測を改善するか

2 変数で考えます。YY を「自分の過去だけ」で予測するモデル(制限モデル)と、「自分の過去+XX の過去」で予測するモデル(非制限モデル)を比べます。

制限:yt=c+i=1pαiyti+ut\text{制限}:\quad y_t=c+\sum_{i=1}^{p}\alpha_i y_{t-i}+u_t 非制限:yt=c+i=1pαiyti+i=1pβixti+et\text{非制限}:\quad y_t=c+\sum_{i=1}^{p}\alpha_i y_{t-i}+\sum_{i=1}^{p}\beta_i x_{t-i}+e_t

帰無仮説は H0: β1=β2==βp=0H_0:\ \beta_1=\beta_2=\cdots=\beta_p=0XX のラグは効かない)。これを残差平方和の減少を見る FF 検定で調べ、棄却(p<0.05p<0.05)なら「XXYY をグレンジャー因果する」。直観は「XX の過去を知ると YY の予測誤差が有意に減るか?」です。

向きは非対称:XYX\to YYXY\to X は別々に検定します。両方有意なら双方向フィードバック、片方だけなら一方向。これは VAR(ベクトル自己回帰) の係数行列で言えば「非対角ブロックがゼロか」を検定していることに相当します。

⚠️ 検定の前提は定常性。非定常(単位根)系列にそのままかけると見せかけの有意が出ます(ランダムウォークと単位根)。まず ADF などで定常を確認し、必要なら差分してから検定します。

コード①:一方向 X→Y を仕込んで向きを検出

XX は自分だけで進み、YYXX の過去に依存する——という一方向の構造を仕込みます。grangercausalitytests で両向きを検定し、**XYX\to Y だけが有意(pp 小)・YXY\to X は非有意(pp 大)**と正しく出るかを見ます。検定前に ADF で定常を確認します。

import numpy as np
import warnings
warnings.simplefilter("ignore")
from statsmodels.tsa.stattools import grangercausalitytests, adfuller

# 真の構造:X → Y の一方向だけ依存させる
#   x_t = 0.5 x_{t-1} + e_x                    (X は自分だけで進む)
#   y_t = 0.4 y_{t-1} + 0.6 x_{t-1} + e_y      (Y は X の過去に依存)
np.random.seed(14)
n = 1200
ex = np.random.normal(0, 1, n)
ey = np.random.normal(0, 1, n)
x = np.zeros(n); y = np.zeros(n)
for t in range(1, n):
    x[t] = 0.5 * x[t-1] + ex[t]
    y[t] = 0.4 * y[t-1] + 0.6 * x[t-1] + ey[t]

# 検定の前提:両系列が定常か(ADF, p<0.05 で定常)
print("ADF  x: p=%.3f   y: p=%.3f (ともに p<0.05 で定常)"
      % (adfuller(x)[1], adfuller(y)[1]))

# grangercausalitytests(data, maxlag): 第2列が第1列を因果するか検定
maxlag = 2
def granger_p(cause, effect, lag):
    data = np.column_stack([effect, cause])     # [被説明=effect, 説明=cause]
    r = grangercausalitytests(data, maxlag=lag, verbose=False)
    return r[lag][0]["ssr_ftest"][1]            # F検定の p値

p_xy = granger_p(cause=x, effect=y, lag=maxlag)   # X→Y
p_yx = granger_p(cause=y, effect=x, lag=maxlag)   # Y→X
print(f"\nX → Y のグレンジャー因果: p={p_xy:.2e}{'有意(予測を改善)' if p_xy<0.05 else '非有意'}")
print(f"Y → X のグレンジャー因果: p={p_yx:.3f}{'有意' if p_yx<0.05 else '非有意(改善なし)'}")
print("\n仕込んだ向き X→Y だけが有意 → 向きを正しく検出")

出力:

ADF  x: p=0.000   y: p=0.000 (ともに p<0.05 で定常)

X → Y のグレンジャー因果: p=3.84e-98  → 有意(予測を改善)
Y → X のグレンジャー因果: p=0.980     → 非有意(改善なし)

仕込んだ向き X→Y だけが有意 → 向きを正しく検出

出力の意味:両系列とも ADF で p=0.000p=0.000=定常で、検定の前提を満たします。XYX\to Yp=3.84×1098p=3.84\times10^{-98}(実質ゼロ)で強く有意——XX の過去を加えると YY の予測が劇的に改善します。逆向き YXY\to Xp=0.980p=0.980全く有意でない——YY の過去は XX の予測を改善しない。仕込んだ一方向の依存(XYX\to Y)を、向きまで正しく検出できました。注意:grangercausalitytests(data, maxlag) は「第2列が第1列を因果するか」を調べるので、列順を [被説明, 説明] にするのが鉄則です(逆にすると向きを取り違える)。

なお、逆向きが「ちょうど 0.05 を少し下回る」程度の境界的な偽陽性は、標本サイズが小さいと珍しくありません(5% は本来 20 回に 1 回出る)。ラグ数を増やすほど偶然の有意も増えるので、ラグは AIC などで抑制し、複数ラグで頑健か確認します。

2. 見せかけのグレンジャー因果:共通原因

グレンジャー因果の最大の注意点は、直接のリンクが無くても有意に出ることです。典型は共通原因(交絡)。背後の駆動源 ZZXXYY別々のラグで動かすと、ZZ に早く反応する XXYY を「予測」してしまい、XYX\to Y が有意になります。

import numpy as np
import warnings
warnings.simplefilter("ignore")
from statsmodels.tsa.stattools import grangercausalitytests

# 共通原因 Z が X と Y を別々のラグで駆動。X と Y に直接リンクは無い
#   z_t = 0.6 z_{t-1} + e_z          (共通の駆動源)
#   x_t = z_{t-1} + e_x              (X は Z に 1 期遅れで反応)
#   y_t = z_{t-2} + e_y              (Y は Z に 2 期遅れで反応 → X より遅い)
np.random.seed(8)
n = 1200
ez = np.random.normal(0, 1, n)
ex = np.random.normal(0, 0.5, n)
ey = np.random.normal(0, 0.5, n)
z = np.zeros(n); x = np.zeros(n); y = np.zeros(n)
for t in range(2, n):
    z[t] = 0.6 * z[t-1] + ez[t]
    x[t] = z[t-1] + ex[t]
    y[t] = z[t-2] + ey[t]

def granger_p(cause, effect, lag=2):
    data = np.column_stack([effect, cause])
    r = grangercausalitytests(data, maxlag=lag, verbose=False)
    return r[lag][0]["ssr_ftest"][1]

p_xy = granger_p(x, y)     # X→Y
print(f"X → Y のグレンジャー因果: p={p_xy:.2e}{'有意' if p_xy<0.05 else '非有意'}")
print("だが真の構造に X→Y の直接リンクは無い(共通原因 Z が背後にいるだけ)")
print("→ グレンジャー因果は『予測の改善』であって構造的因果ではない")

出力:

X → Y のグレンジャー因果: p=1.68e-286  → 有意
だが真の構造に X→Y の直接リンクは無い(共通原因 Z が背後にいるだけ)
→ グレンジャー因果は『予測の改善』であって構造的因果ではない

出力の意味XYX\to Yp=1.68×10286p=1.68\times10^{-286} と桁外れに有意です。しかし真の生成過程に XYX\to Y の直接リンクは一切ありません——XXYY も共通原因 ZZ に反応しているだけで、XXZZ に早く(ラグ 1)、YY が遅く(ラグ 2)反応するため、XXYY の「先行指標」になっているのです。これは典型的な交絡で、「XX を操作しても YY は動かない」(構造的因果はゼロ)にもかかわらず、グレンジャー検定は強く有意を返します。グレンジャー因果が答えるのは『XX の過去は YY の予測に役立つか』であって、『XX を変えたら YY が変わるか』ではない——この区別が決定的です(因果サイト 相関と因果の違い)。

3. 数式の直観と「予測的因果」の位置づけ

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

関連ノート