Day 073 — EDA体系的手法⑤ — 相関行列と多重共線性の検出・VIF計算

2026-06-23 水 / Phase 3 コーディング 相関行列 / 多重共線性 / VIF / heatmap

📚 背景知識(読んでから問題へ)

🎯
Day 073 — Phase 3 継続: EDA第5弾は相関行列・多重共線性・VIF。「どの変数が SalePrice に効いているか」「どの変数ペアが重なっているか」を数値とグラフで把握する。線形モデルの精度に直結する重要スキル。

相関係数とは?

相関係数(-1 〜 +1)は「2変数がどれだけ一緒に動くか」を表す数値。

値の範囲意味House Prices の例
+0.7 〜 +1.0強い正の相関OverallQual ↔ SalePrice(品質が高いほど高価)
+0.3 〜 +0.7中程度の正の相関FullBath ↔ SalePrice(バス数が多いほど高価傾向)
-0.3 〜 +0.3弱い相関(ほぼ無関係)EnclosedPorch ↔ SalePrice
-0.7 〜 -0.3中程度の負の相関OverallCond(状態良し)は逆効果なこともある

多重共線性(マルチコ)とは?

⚠️
複数の説明変数が強く相関している状態。
: GarageArea(車庫面積)と GarageCars(収容台数)は r = 0.88。面積と台数は当然連動する。
問題: 線形回帰がどちらの影響か分離できなくなり、係数の推定が不安定になる。

VIF(分散膨張係数)

多重共線性の程度を定量化する指標。VIF = 1 / (1 - R²)(他の変数で当該変数をどれだけ説明できるか)。

VIF の値判定対処
VIF = 1共線性なしそのまま使用可
VIF < 5許容範囲通常は問題なし
VIF 5〜10中程度の共線性注意して使用・モニタリング
VIF > 10強い多重共線性削除・PCA・Ridge回帰の適用を検討

🌡 相関ヒートマップのイメージ(SVG)

相関行列ヒートマップ(主要変数 6×6 抜粋) OverallQual GrLivArea GarageArea GarageCars TotalBsmtSF SalePrice OverallQual GrLivArea GarageArea GarageCars TotalBsmtSF SalePrice 1.00 0.47 0.56 0.60 0.53 0.79 0.47 1.00 0.47 0.46 0.49 0.71 0.56 0.47 1.00 0.88 0.49 0.62 0.60 0.46 0.88 1.00 0.44 0.64 0.53 0.49 0.49 0.44 1.00 0.61 0.79 0.71 0.62 0.64 0.61 1.00 r=0.88 多重共線性! −1.0 0 +1.0 カラースケール
💡
実際の seaborn.heatmap() では全80変数の行列になる。SalePrice 列だけ抜き出してソートして重要変数を絞り込むのが定石。

📊 SalePrice との相関係数 TOP 10(可視化)

OverallQual
0.791
0.791
GrLivArea
0.709
0.709
GarageCars
0.640
0.640
GarageArea
0.623
0.623
TotalBsmtSF
0.614
0.614
1stFlrSF
0.606
0.606
FullBath
0.561
0.561
TotRmsAbvGrd
0.534
0.534
YearBuilt
0.523
0.523
YearRemodAdd
0.507
0.507
🔍
ポイント: OverallQual(総合品質)が最強の予測因子(r=0.79)。次いで GrLivArea(延床面積)。GarageCars と GarageArea は r=0.88 で互いに強く相関 → 多重共線性あり。

📈 VIF ランキング(主要変数)

VIF > 10 危険域 VIF 5-10 注意 VIF < 5 安全
GrLivArea
26.3
26.3
TotRmsAbvGrd
22.1
22.1
GarageArea
18.7
18.7
GarageCars
15.4
15.4
1stFlrSF
13.8
13.8
TotalBsmtSF
11.2
11.2
OverallQual
7.4
7.4
YearBuilt
6.1
6.1
FullBath
3.8
3.8
BedroomAbvGr
2.4
2.4
⚠️
注意: GrLivArea と TotRmsAbvGrd は VIF > 20 — 部屋数が多いと延床面積が大きいのは当然。線形回帰でこれらを同時に使うと係数が不安定になる。木系モデル(LightGBM等)では VIF は問題にならない

🗂 データスキーマ(今日使う主要変数)

変数名説明SalePrice相関VIF(目安)
OverallQual int (1-10) 物件の総合品質評価 0.791(最強) 7.4
GrLivArea int 地上延床面積(平方フィート) 0.709 26.3(要注意)
GarageArea int 車庫面積(平方フィート) 0.623 18.7
GarageCars int 車庫収容台数 0.640 15.4
TotalBsmtSF int 地下室面積(平方フィート) 0.614 11.2
1stFlrSF int 1階床面積(平方フィート) 0.606 13.8
SalePrice int 売却価格(目的変数) — (自身)

🧩 問題

📋
House Prices データセット(train.csv)の数値変数を使って、以下の4タスクを実装せよ。
1
相関行列の可視化
数値変数の相関行列を seaborn.heatmap で可視化せよ。係数の絶対値が 0.1 未満の相関は NaN でマスクしてアノテーション付きで表示すること。
2
SalePrice と強く相関する変数 TOP 10
SalePrice との相関係数を降順に並べて上位10変数を出力せよ(SalePrice 自身は除く)。
3
変数間の強い相関ペアの検出
相関係数の絶対値が 0.8 以上 の変数ペアを全て列挙せよ(対角線・重複ペアは除く)。
4
VIF の計算
数値変数の VIF を計算して高い順に表示せよ。statsmodelsvariance_inflation_factor を使うこと。

💡 ヒント

ヒント1 — 方向性(クリックで展開)
相関行列は df.corr() で取得。ヒートマップには seaborn.heatmap()。VIF計算は statsmodels.stats.outliers_influence をインポートする。
ヒント2 — アプローチ(クリックで展開)
import seaborn as sns
import numpy as np
corr = df.select_dtypes(include='number').corr()

# 強い相関ペア検出
upper = corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool))
strong = upper.stack()[upper.stack().abs() >= 0.8]
散布図は mask で2回プロット。歪度は .skew() で計算可能。
ヒント3 — コード骨格(クリックで展開)
from statsmodels.stats.outliers_influence import variance_inflation_factor

# 欠損値のない数値列のみ
num_cols = df.select_dtypes(include='number').dropna(axis=1).columns.tolist()
X = df[num_cols].fillna(df[num_cols].median())

vif_df = pd.DataFrame({
    'feature': num_cols,
    'VIF': [variance_inflation_factor(X.values, i) for i in range(len(num_cols))]
}).sort_values('VIF', ascending=False)

模範解答

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.stats.outliers_influence import variance_inflation_factor

df = pd.read_csv('train.csv')
num_df = df.select_dtypes(include='number')

# ─── タスク1: 相関行列の可視化 ───
corr = num_df.corr()

# 絶対値 0.1 未満をマスク
corr_masked = corr.where(corr.abs() >= 0.1)

plt.figure(figsize=(20, 16))
sns.heatmap(
    corr_masked,
    annot=True, fmt='.2f', annot_kws={'size': 7},
    cmap='RdBu_r', center=0, vmin=-1, vmax=1,
    linewidths=0.5, square=True,
    cbar_kws={'shrink': 0.8}
)
plt.title('House Prices — 相関行列(|r| < 0.1 を除外)', fontsize=14, pad=16)
plt.xticks(rotation=45, ha='right', fontsize=8)
plt.yticks(fontsize=8)
plt.tight_layout()
plt.show()

# ─── タスク2: SalePrice と強く相関する変数 TOP 10 ───
sp_corr = corr['SalePrice'].drop('SalePrice').sort_values(ascending=False)
print("\n── SalePrice との相関係数 TOP 10 ──")
print(sp_corr.head(10).to_string())
print("\n── SalePrice との相関係数 BOTTOM 5(負の相関) ──")
print(sp_corr.tail(5).to_string())

# ─── タスク3: 変数間の強い相関ペア(|r| >= 0.8) ───
upper = corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool))
strong_pairs = upper.stack()[upper.stack().abs() >= 0.8]
strong_pairs = strong_pairs.reset_index()
strong_pairs.columns = ['変数A', '変数B', '相関係数']
strong_pairs['|r|'] = strong_pairs['相関係数'].abs()
strong_pairs = strong_pairs.sort_values('|r|', ascending=False)

print(f"\n── 強い相関ペア(|r| >= 0.8): {len(strong_pairs)} ペア ──")
print(strong_pairs.to_string(index=False))

# ─── タスク4: VIF の計算 ───
num_cols = num_df.dropna(axis=1).columns.tolist()
X = num_df[num_cols].fillna(num_df[num_cols].median())

vif_data = pd.DataFrame({
    'feature': num_cols,
    'VIF': [variance_inflation_factor(X.values, i) for i in range(len(num_cols))]
}).sort_values('VIF', ascending=False)

print("\n── VIF ランキング(上位20) ──")
print(vif_data.head(20).to_string(index=False))
print("\n── VIF > 10 の変数(多重共線性あり) ──")
print(vif_data[vif_data['VIF'] > 10].to_string(index=False))
期待される出力(一部)
── SalePrice との相関係数 TOP 10 ──
OverallQual 0.790982
GrLivArea 0.708624
GarageCars 0.640409
GarageArea 0.623431
TotalBsmtSF 0.613581
1stFlrSF 0.605852
FullBath 0.560664
TotRmsAbvGrd 0.533723
YearBuilt 0.522897
YearRemodAdd 0.507101

── 強い相関ペア(|r| >= 0.8): 6 ペア ──
GarageArea GarageCars 0.882
GrLivArea TotRmsAbvGrd 0.826
GarageYrBlt YearBuilt 0.826
TotalBsmtSF 1stFlrSF 0.820
...

── VIF > 10 の変数 ──
GrLivArea 26.3
TotRmsAbvGrd 22.1
GarageArea 18.7

🪜 Step-by-Step 解説

1
相関行列のマスク処理
corr.where(corr.abs() >= 0.1) は条件が False のセルを NaN に置換する。
seaborn の heatmap は NaN セルを空白で表示するため、弱い相関が視覚的に消えてヒートマップが見やすくなる。
2
SalePrice 列の抽出とソート
corr['SalePrice'] で相関係数の列だけ取り出し、.drop('SalePrice') で自己相関(1.0)を除去。
.sort_values(ascending=False) で降順ソート → OverallQual が1位(r=0.79)。
3
上三角行列で重複排除
np.triu(np.ones(corr.shape), k=1) で対角線より上のみ True の行列を作成。
corr.where(...) でマスクし、.stack() で Series に変換後にフィルタ。
これにより「(A, B)」と「(B, A)」の重複ペアを避けられる。
4
VIF の計算メカニズム
variance_inflation_factor(X.values, i) は第 i 列を目的変数とし、残り全変数で線形回帰したときの R² から
VIF = 1 / (1 - R²) を計算する。
R² = 0.9 なら VIF = 10、R² = 0.96 なら VIF = 25 — 他変数で当該変数をほぼ完全に説明できる状態。

📐 数学・統計の補足(文系向け)

ピアソン相関係数の直感

相関係数 r は「2変数の関係の強さと方向」を -1〜+1 で表す。計算式のイメージ:

r ≈ (2変数が同じ方向にズレた量の合計) / (各変数のズレの大きさの積)
分子が大 → 一緒に動いている(r が1に近い)
分母は正規化のため(-1〜+1 に収める)

VIF の計算式

VIFi = 1 / (1 − Ri²)
Ri²: 変数 i を残り全変数で線形回帰したときの決定係数
R² → 1 に近づく: 他変数で完全に説明できる → VIF → ∞(多重共線性が深刻)
R² = 0: 全く他変数と無関係 → VIF = 1(問題なし)
🔑
なぜ多重共線性が線形回帰に問題なのか?
線形回帰の係数計算は「逆行列」を使う。変数が強く相関していると逆行列が不安定(行列式が0に近づく)になり、係数の推定値が不安定になる。 わずかなデータの変動で係数が大きく変動する「過剰適合」が起きやすくなる。

🏆 Kaggleでの実践的な使い方

House Prices での多重共線性対策パターン

1
高相関ペアから新特徴量を生成
GarageArea と GarageCars(r=0.88)を直接使う代わりに比率特徴量を作る:
df['GarageAreaPerCar'] = df['GarageArea'] / (df['GarageCars'] + 1)
2
PCA で面積系変数を圧縮
GrLivArea, 1stFlrSF, 2ndFlrSF などの面積変数を PCA で1〜2成分に圧縮:
from sklearn.decomposition import PCA
area_cols = ['GrLivArea', '1stFlrSF', 'TotalBsmtSF']
pca = PCA(n_components=2)
df[['area_PC1', 'area_PC2']] = pca.fit_transform(df[area_cols].fillna(0))
3
Ridge/Lasso 回帰で多重共線性を緩和
線形モデルを使う場合、正則化(Ridge)が多重共線性に頑健:
from sklearn.linear_model import Ridge
model = Ridge(alpha=100)  # alpha が大きいほど係数を縮小
4
木系モデル(LightGBM)では VIF 不要
LightGBM / XGBoost は各分岐で1変数のみ使うため、多重共線性は直接的に問題にならない。ただし相関が強い変数を削除すると特徴量重要度の解釈が改善することがある。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
相関係数が高い変数を全て投入すれば良い 「情報が多い = 良い」という思い込み 多重共線性で線形モデルの係数が不安定になる
相関係数が低い変数は不要 相関係数 = 重要度と誤解 非線形な関係や交互作用は相関係数では検出不可
VIF > 10 なら必ず削除すべき ルールを機械的に適用 木系モデルでは VIF は問題なし。精度で判断
df.corr() でカテゴリ変数も計算 select_dtypes を忘れる カテゴリ変数はダミー化後に相関計算が必要
相関係数だけで変数選択を完結させる EDA を単純化したい 相関はあくまで線形関係のみ。scatter plot で目視確認も必須

🚀 次のステップ

  • 発展: 特徴量エンジニアリング実践 — 相関の強いペアから比率・差分・積の新特徴量を生成してスコア改善
  • 次回予告: EDA体系的手法⑥ — 特徴量エンジニアリング入門(比率・差分・交互作用項の生成)

📝 自己評価