🎓 レベル:標準 | 重要度:A(必須)
📎 前提:VAR(ベクトル自己回帰)(多変量回帰)・ランダムウォークと単位根(定常性) | 関連:相関と因果の違い(因果)
要点(BLUF)
- グレンジャー因果: の過去を加えると の予測が( 自身の過去だけのときより)有意に改善するなら「 は をグレンジャー因果する」。本質は予測の改善であって、メカニズムとしての因果ではありません。
- 検定は VAR の枠で「 のラグ係数がすべて 0」を 検定(または尤度比)で棄却するかどうか。 で因果ありと読みます。
- ⚠️ グレンジャー因果 ≠ 真の因果:交絡(共通原因)・先行指標・選択でも有意に出ます。構造的因果は別の枠組み(因果サイト 相関と因果の違い・構造的因果モデルとdo演算子)。
1. 定義:予測を改善するか
2 変数で考えます。 を「自分の過去だけ」で予測するモデル(制限モデル)と、「自分の過去+ の過去」で予測するモデル(非制限モデル)を比べます。
帰無仮説は ( のラグは効かない)。これを残差平方和の減少を見る 検定で調べ、棄却()なら「 は をグレンジャー因果する」。直観は「 の過去を知ると の予測誤差が有意に減るか?」です。
向きは非対称: と は別々に検定します。両方有意なら双方向フィードバック、片方だけなら一方向。これは VAR(ベクトル自己回帰) の係数行列で言えば「非対角ブロックがゼロか」を検定していることに相当します。
⚠️ 検定の前提は定常性。非定常(単位根)系列にそのままかけると見せかけの有意が出ます(ランダムウォークと単位根)。まず ADF などで定常を確認し、必要なら差分してから検定します。
コード①:一方向 X→Y を仕込んで向きを検出
は自分だけで進み、 は の過去に依存する——という一方向の構造を仕込みます。grangercausalitytests で両向きを検定し、** だけが有意( 小)・ は非有意( 大)**と正しく出るかを見ます。検定前に 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 で =定常で、検定の前提を満たします。 は (実質ゼロ)で強く有意—— の過去を加えると の予測が劇的に改善します。逆向き は で全く有意でない—— の過去は の予測を改善しない。仕込んだ一方向の依存()を、向きまで正しく検出できました。注意:grangercausalitytests(data, maxlag) は「第2列が第1列を因果するか」を調べるので、列順を [被説明, 説明] にするのが鉄則です(逆にすると向きを取り違える)。
なお、逆向きが「ちょうど 0.05 を少し下回る」程度の境界的な偽陽性は、標本サイズが小さいと珍しくありません(5% は本来 20 回に 1 回出る)。ラグ数を増やすほど偶然の有意も増えるので、ラグは AIC などで抑制し、複数ラグで頑健か確認します。
2. 見せかけのグレンジャー因果:共通原因
グレンジャー因果の最大の注意点は、直接のリンクが無くても有意に出ることです。典型は共通原因(交絡)。背後の駆動源 が と を別々のラグで動かすと、 に早く反応する が を「予測」してしまい、 が有意になります。
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 が背後にいるだけ)
→ グレンジャー因果は『予測の改善』であって構造的因果ではない
出力の意味: は と桁外れに有意です。しかし真の生成過程に の直接リンクは一切ありません—— も も共通原因 に反応しているだけで、 が に早く(ラグ 1)、 が遅く(ラグ 2)反応するため、 が の「先行指標」になっているのです。これは典型的な交絡で、「 を操作しても は動かない」(構造的因果はゼロ)にもかかわらず、グレンジャー検定は強く有意を返します。グレンジャー因果が答えるのは『 の過去は の予測に役立つか』であって、『 を変えたら が変わるか』ではない——この区別が決定的です(因果サイト 相関と因果の違い)。
3. 数式の直観と「予測的因果」の位置づけ
- 帰無=『追加ラグは無価値』: 検定は「 のラグを足しても残差平方和が(自由度の罰則を超えて)有意に減らない」を帰無に置きます。減れば棄却=予測に役立つ=グレンジャー因果あり。
- 時間の順序を使っているだけ:グレンジャー因果の根拠は「原因は結果に先行する」という時間の矢。過去 が未来 を予測する、という非対称性を利用します。だが先行は因果の必要条件であって十分条件ではない(先行指標・共通原因でも先行は起きる)。
- 予測 vs 介入:因果推論(構造的因果モデルとdo演算子)が問うのは介入 の効果。グレンジャー因果は観測データの予測関係で、介入なしには構造的因果へ橋渡しできません。時系列の文脈で構造的に踏み込むなら、SVAR(構造 VAR)や外生ショックの利用が必要です。
⚠️ よくある誤解・落とし穴
- 「グレンジャー因果=因果」ではない(最重要):交絡・共通原因・先行指標・選択バイアスでも有意に出ます(コード②)。名前に『因果』とあるが、中身は予測の改善。介入効果を知りたいなら因果推論の枠組みへ(相関と因果の違い・構造的因果モデルとdo演算子)。
- 「非定常でもかけてよい」ではない:単位根系列にそのまま検定すると、見せかけの回帰と同じ理屈で偽の有意が頻発します(ランダムウォークと単位根)。まず ADF で定常を確認し、非定常なら差分、共和分があるなら VECM の枠で扱う(共和分と誤差修正モデル(VECM))。
- 「ラグ数は適当でよい」ではない:結果はラグ次数に敏感。ラグを増やすほど偶然の有意が増え、減らすと真の依存を見落とす。AIC/BIC でラグを選び、複数ラグで頑健性を確認します。
- 「第三の変数は無視してよい」ではない:2 変数だけで検定すると、本当の駆動変数を落として見せかけの因果を拾います。関連しそうな変数は VAR に入れて多変量で条件付ける(それでも未観測交絡は残る)。
- 「有意でない=因果なし」ではない:検出力不足(標本不足・非線形・ラグ誤指定)で見逃すこともあります。非有意は「予測改善の証拠が無い」であって「因果が無い証明」ではありません。