statsmodels不要？正則化回帰の活用で多重共線性を克服するGranger因果性検定

# **user**:

時系列の変数2つに対して、Granger因果性を計算しようと思ったら、多重共線性の影響で数値が不安定になることがあります。正則化回帰などを用いて、Granger因果性の計算における多重共線性の影響を解決する方法はありますでしょうか？Pythonを使って教えてください。

# **assistant**:

多重共線性の影響を緩和しながら **Granger因果性** を評価する方法の一つとして、**正則化回帰（リッジ回帰やLASSO回帰）を用いたGranger因果性検定** があります。  
通常のGranger因果性検定では、**ベクトル自己回帰（VAR）モデル** を用いますが、VARモデルの回帰式で**多重共線性**が問題となる場合、リッジ回帰やLASSOを使うと安定した推定が可能になります。

---

## **方法**
1. **データの準備**（2つの時系列変数）
2. **ラグ付き回帰モデルの作成**
   - 変数\(X\)と\(Y\)のラグを作成する
3. **正則化回帰（リッジ or LASSO）を適用**
   - 目標: 変数\(X\)の過去が\(Y\)を予測できるか
4. **Granger因果性の評価**
   - 係数の有意性を評価する

---

## **Pythonでの実装**
以下のコードでは、リッジ回帰を用いてGranger因果性を検定します。

### **1. データの準備**
まず、適当な時系列データを作成します。

```python
import numpy as np
import pandas as pd
from sklearn.linear_model import RidgeCV
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler

# 乱数シード設定
np.random.seed(42)

# 人工データの作成
n_samples = 100
X = np.cumsum(np.random.randn(n_samples))  # ランダムウォーク
Y = 0.5 * np.roll(X, 1) + np.random.randn(n_samples) * 0.5  # Xのラグを持つY

# データフレーム化
df = pd.DataFrame({'X': X, 'Y': Y})
print(df)
```

```
            X         Y
0    0.496714 -5.900011
1    0.358450  0.038034
2    1.006138  0.007868
3    2.529168  0.101931
4    2.295015  1.183941
..        ...       ...
95 -10.712354 -4.431761
96 -10.416233 -5.798106
97 -10.155178 -5.131254
98 -10.150065 -5.048485
99 -10.384652 -5.646517

[100 rows x 2 columns]
```

---

### **2. ラグ付き特徴量の作成**
Granger因果性の検定には、変数の過去の値（ラグ）を用いるので、例えば **最大3ラグまで** を作成します。

```python
def create_lagged_features(df, lag=3):
    """
    指定したラグ数までの遅延変数を作成
    """
    df_lagged = df.copy()
    for i in range(1, lag + 1):
        df_lagged[f'X_lag{i}'] = df['X'].shift(i)
        df_lagged[f'Y_lag{i}'] = df['Y'].shift(i)
    return df_lagged.dropna()

# 3ラグを作成
df_lagged = create_lagged_features(df, lag=3)
print(df_lagged)
```

```
            X         Y     X_lag1    Y_lag1     X_lag2    Y_lag2     X_lag3  \
3    2.529168  0.101931   1.006138  0.007868   0.358450  0.038034   0.496714   
4    2.295015  1.183941   2.529168  0.101931   1.006138  0.007868   0.358450   
5    2.060878  1.349533   2.295015  1.183941   2.529168  0.101931   1.006138   
6    3.640091  1.973532   2.060878  1.349533   2.295015  1.183941   2.529168   
7    4.407525  1.907334   3.640091  1.973532   2.060878  1.349533   2.295015   
..        ...       ...        ...       ...        ...       ...        ...   
95 -10.712354 -4.431761  -9.248839 -4.341775  -8.856731 -4.887404  -8.529068   
96 -10.416233 -5.798106 -10.712354 -4.431761  -9.248839 -4.341775  -8.856731   
97 -10.155178 -5.131254 -10.416233 -5.798106 -10.712354 -4.431761  -9.248839   
98 -10.150065 -5.048485 -10.155178 -5.131254 -10.416233 -5.798106 -10.712354   
99 -10.384652 -5.646517 -10.150065 -5.048485 -10.155178 -5.131254 -10.416233   

      Y_lag3  
3  -5.900011  
4   0.038034  
5   0.007868  
6   0.101931  
7   1.183941  
..       ...  
95 -3.806461  
96 -4.887404  
97 -4.341775  
98 -4.431761  
99 -5.798106  

[97 rows x 8 columns]
```

---

### **3. リッジ回帰を用いたGranger因果性検定**
- **Yを予測するモデル**
- **Xのラグ変数を説明変数に含める**
- **リッジ回帰（RidgeCV）を使用**

```python
# 説明変数と目的変数を定義
target = 'Y'  # 予測対象
lags = [col for col in df_lagged.columns if 'lag' in col]  # 全ラグ変数

X_train = df_lagged[lags]
y_train = df_lagged[target]

# 特徴量の標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)

# クロスバリデーション付きのリッジ回帰
ridge = RidgeCV(alphas=np.logspace(-3, 3, 10), store_cv_values=True)
ridge.fit(X_train_scaled, y_train)

# モデルの係数を取得
coef_df = pd.DataFrame({'Feature': lags, 'Coefficient': ridge.coef_})
print(coef_df)
```

```
  Feature  Coefficient
0  X_lag1     2.064536
1  Y_lag1    -0.202087
2  X_lag2     0.575275
3  Y_lag2    -0.157769
4  X_lag3    -0.068836
5  Y_lag3     0.089708
```

---

### **4. Granger因果性の判定**
Granger因果性の検定では、**Xのラグ変数の係数が統計的にゼロでないか** を確認します。  
ただし、リッジ回帰では標準的なp値は得られないため、**ゼロに近い係数は影響がない** と判断します。

```python
# Xのラグ変数に関する係数の合計
X_lag_coefs = coef_df[coef_df['Feature'].str.contains('X_lag')]['Coefficient']

# Granger因果性の判定
if np.any(np.abs(X_lag_coefs) > 0.1):  # しきい値0.1（経験的に調整）
    print("Granger因果性あり（X → Y）")
else:
    print("Granger因果性なし（X → Y）")
```

```
Granger因果性あり（X → Y）
```

---

## **結果の解釈**
- **Granger因果性あり** → \(X\) の過去の値が \(Y\) を予測するのに有意に寄与している  
- **Granger因果性なし** → \(X\) の過去の値が \(Y\) の予測に寄与していない（少なくともリッジ回帰の範囲では）

---

## **まとめ**
- **通常のGranger因果性検定はVARモデルを使用する** が、多重共線性があると不安定になる
- **リッジ回帰（またはLASSO）を用いると、正則化により数値が安定**
- **ラグ変数の係数を基にGranger因果性を判定**

この方法なら、**多重共線性があっても安定したGranger因果性の推定** が可能です！  
LASSOを使うと、より **スパースな（影響のある変数だけを選ぶ）** 推定もできますね。

# **user**:

ありがとうございます。LASSO回帰をする場合のコードも書いてください。

# **assistant**:

LASSO回帰を使った **Granger因果性の検定** も可能です。LASSOは **L1正則化** により不要なラグ変数の係数をゼロにするため、多重共線性を緩和しながら **本当に重要なラグだけを選択** できます。

---

## **LASSOを用いたGranger因果性検定**
以下のコードでは、  
- **LASSO回帰（L1正則化）を適用**
- **ラグ変数の係数がゼロかどうかを確認**
  - **ゼロでないラグ変数** があれば、Granger因果性があると判断
  - **すべてゼロ** なら、Granger因果性なしと判定

---

### **1. 必要なライブラリをインポート**
```python
import numpy as np
import pandas as pd
from sklearn.linear_model import LassoCV
from sklearn.preprocessing import StandardScaler

# 乱数シード設定
np.random.seed(42)

# 人工データの作成
n_samples = 100
X = np.cumsum(np.random.randn(n_samples))  # ランダムウォーク
Y = 0.5 * np.roll(X, 1) + np.random.randn(n_samples) * 0.5  # Xのラグを持つY

# データフレーム化
df = pd.DataFrame({'X': X, 'Y': Y})

# ラグ付きデータの作成（最大3ラグ）
def create_lagged_features(df, lag=3):
    df_lagged = df.copy()
    for i in range(1, lag + 1):
        df_lagged[f'X_lag{i}'] = df['X'].shift(i)
        df_lagged[f'Y_lag{i}'] = df['Y'].shift(i)
    return df_lagged.dropna()

df_lagged = create_lagged_features(df, lag=3)
```

---

### **2. LASSO回帰を適用**
```python
# 説明変数と目的変数を定義
target = 'Y'
lags = [col for col in df_lagged.columns if 'lag' in col]  # すべてのラグ変数

X_train = df_lagged[lags]
y_train = df_lagged[target]

# 特徴量の標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)

# クロスバリデーション付きのLASSO回帰
lasso = LassoCV(alphas=np.logspace(-3, 3, 100), cv=5)
lasso.fit(X_train_scaled, y_train)

# モデルの係数を取得
coef_df = pd.DataFrame({'Feature': lags, 'Coefficient': lasso.coef_})
print(coef_df)
```

```
  Feature  Coefficient
0  X_lag1     2.275388
1  Y_lag1    -0.000000
2  X_lag2     0.000000
3  Y_lag2    -0.000000
4  X_lag3    -0.000000
5  Y_lag3     0.030755
```
---

### **3. Granger因果性の判定**
```python
# Xのラグ変数に関する係数を取得
X_lag_coefs = coef_df[coef_df['Feature'].str.contains('X_lag')]['Coefficient']

# Granger因果性の判定
if np.any(np.abs(X_lag_coefs) > 0):
    print("Granger因果性あり（X → Y）")
else:
    print("Granger因果性なし（X → Y）")
```

```
Granger因果性あり（X → Y）
```
---

## **LASSOによる結果の解釈**
- **ゼロでない係数がある場合 → Granger因果性あり**
- **すべてゼロの場合 → Granger因果性なし**
  - LASSOは **関連が弱いラグ変数の係数をゼロにする** ため、不要な変数を自動選択

---

## **まとめ**
| **方法** | **特徴** |
|----------|---------|
| **リッジ回帰** | 多重共線性を解消するが、すべてのラグ変数に影響を分散 |
| **LASSO回帰** | **不要なラグ変数をゼロにし、重要なものだけを選択** |

Granger因果性を調べる際に **多重共線性の影響を抑えながら、重要な変数のみを選択したい** 場合は、LASSO回帰が特に有効です！

# **user**:

ありがとうございます。今までstatsmodels.tsa.stattools.grangercausalitytestsを使ってたんですが、使わなくても簡単にできるんですね。

# **assistant**:

そうですね！`statsmodels.tsa.stattools.grangercausalitytests` は便利ですが、**通常のF検定ベース** のため、多重共線性が強いと不安定になりやすいです。  

リッジ回帰やLASSOを使えば、**正則化による安定化** ＋ **不要なラグ変数の削除** まで一気にできるので、より頑健な因果判定が可能になります。  

もし **統計的な有意性（p値）が欲しい** 場合は、以下のようにブートストラップや擬似的な信頼区間を考慮する方法もあります：
1. **LASSOの選択頻度**（異なるデータサブセットでLASSOを繰り返し適用）
2. **リッジ回帰の係数分布**（交差検証でリッジ回帰を複数回適用して分布を得る）
3. **グリッドサーチで最適なラグを選択**

統計モデルに頼らずに因果性を評価する手法として、こうした **機械学習的なアプローチ** も面白いですよね！  

他にも「こんなデータでGranger因果性を試したい」などあれば、気軽に聞いてください 😊

# **user**:

ありがとうございます。今日の議論をブログにまとめたいのですが、タイトル案をいくつか提案していただけますか？

# **assistant**:

ブログのタイトル案をいくつか考えてみました！  

### **技術寄り（具体的な方法にフォーカス）**
1. **多重共線性を克服するGranger因果性検定：リッジ回帰とLASSOを活用**  
2. **Granger因果性 × 正則化回帰：LASSO・リッジで安定した因果分析を実現**  
3. **Granger因果性の新アプローチ：機械学習的手法で多重共線性を克服する**  
4. **statsmodels不要？リッジ・LASSO回帰でシンプルにGranger因果性を判定**  
5. **機械学習で時系列の因果推論：Granger因果性をLASSOで解析する**

### **応用寄り（より広い読者向け）**
6. **時系列データの因果関係を機械学習で解析！Granger因果性の新しいアプローチ**  
7. **リッジ回帰 vs LASSO回帰：多重共線性に強いGranger因果性の求め方**  
8. **Granger因果性をもっとシンプルに！Pythonで実装する正則化回帰の活用法**  
9. **時系列データの因果解析：正則化回帰を使うと何が変わるのか？**  
10. **Granger因果性の限界を超える？機械学習を使った安定した因果分析手法**  

ブログのターゲット（データサイエンス寄り、機械学習寄り、時系列解析専門家向けなど）によって、タイトルのトーンを変えるのもいいですね！  

気に入ったものがあれば、それに合わせてイントロや構成案もお手伝いできます 😊

# **user**:

ありがとうございます。「statsmodels不要？正則化回帰の活用で多重共線性を克服するGranger因果性検定」にしたいと思います。最後に、今回のまとめを書いてください。

# **assistant**:

いいタイトルですね！  
では、今回の議論のまとめを書きます。  

---

# **statsmodels不要？正則化回帰の活用で多重共線性を克服するGranger因果性検定**  

### **1. Granger因果性とは？**  
Granger因果性とは、「ある変数 \( X \) の過去の情報が、もう一つの変数 \( Y \) の将来の予測に役立つか？」を統計的に評価する方法です。通常、**ベクトル自己回帰（VAR）モデル** を用いたF検定で判定します。  

### **2. なぜ多重共線性が問題になるのか？**  
- VARモデルはラグ変数を多数含むため、多重共線性が発生しやすい  
- 多重共線性があると、係数の推定が不安定になり、Granger因果性の判定も不安定になる  

### **3. 正則化回帰（リッジ・LASSO）を活用する**  
**リッジ回帰**（L2正則化）：  
- 多重共線性を緩和しながら全ての変数の寄与を考慮  
- ただし、係数がゼロになることはない  

**LASSO回帰**（L1正則化）：  
- 影響の少ないラグ変数をゼロにしてスパースなモデルを構築  
- 本当に重要なラグだけを選択できる  

### **4. Pythonによる実装**  
- **ラグ付きデータの作成**（時系列変数を過去の値で特徴量化）  
- **リッジ回帰 / LASSO回帰で学習**  
- **ラグ変数の係数がゼロかどうかでGranger因果性を判定**  
  - ゼロでなければ「Granger因果性あり」  
  - すべてゼロなら「Granger因果性なし」  

### **5. まとめ**
- `statsmodels.tsa.stattools.grangercausalitytests` を使わずにGranger因果性を判定できる  
- 多重共線性があっても **リッジ回帰** で安定した推定が可能  
- **LASSO回帰** を使えば「本当に影響のあるラグ変数」だけを選択できる  
- 機械学習的な手法を取り入れることで、**従来の統計モデルよりも頑健な因果解析** が可能  

この方法を応用すれば、時系列データの因果関係をより実用的に分析できます！  
今後は **交差検証やブートストラップを活用した信頼性評価** も試してみると、より精度の高い解析ができるでしょう。