Day 047 — 相関係数と散布図(散布図行列・回帰直線)

2026-05-28 白 / Phase 1 コーディング 相関係数

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

前回(Day 046)は「相関係数 r とは何か」を理論で学びました。今日はそれを手を動かして可視化するコーディング回です。

Kaggle の EDA では「数値(相関係数)」と「絵(散布図)」を必ずセットで見ます。相関係数は -1〜+1 の1つの数値に情報を圧縮するため、外れ値や非線形の関係を見落とすからです(前回学んだアンスコムの quartet)。

💡
身近な例:相関係数は「テストの点数(数字1個)」、散布図は「答案そのもの(中身)」。点数だけ見て満足せず、答案を開いて確認するイメージです。

🧰 今日の3つの武器

① 散布図 + 回帰直線

2変数の関係を1枚の絵に。sns.regplot が点と直線を同時に描く。

② 散布図行列

全ペアの散布図を一気に俯瞰。sns.pairplot + hue で色分け。

③ 相関ランキング

目的変数との相関を corr()["target"] で一覧。効く特徴量を発見。

🗂️ データスキーマ(今回使用:Titanic風 200件)

列名説明生成方法
agefloat64年齢(1〜80)np.random.normal(35, 12) をクリップ
farefloat64運賃(5〜300)np.random.exponential(30) をクリップ
pclassint64客室等級(1/2/3)np.random.choice、p=[.25,.25,.5]
sibspint64同乗の兄弟・配偶者数np.random.poisson(0.5)
survivedint64生存フラグ(0/1)fare↑・pclass↓ で生存しやすく生成

survived は 0.01·fare − 0.8·pclass + ノイズ から生成 → fare と正、pclass と負の相関を持つ。

📝 問題

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(42)
n = 200

df = pd.DataFrame({
    "age":     np.random.normal(35, 12, n).clip(1, 80).round(0),
    "fare":    np.random.exponential(30, n).clip(5, 300).round(2),
    "pclass":  np.random.choice([1, 2, 3], size=n, p=[0.25, 0.25, 0.5]),
    "sibsp":   np.random.poisson(0.5, n),
})
# survived は fare が高く pclass が小さい(上等席)ほど生存しやすいように生成
logit = 0.01 * df["fare"] - 0.8 * df["pclass"] + np.random.normal(0, 1, n)
df["survived"] = (logit > logit.mean()).astype(int)

問1 — 目的変数との相関ランキング

survived と各数値特徴量(age, fare, pclass, sibsp)の相関係数を計算し、絶対値の大きい順に並べて表示する。

問2 — 散布図 + 回帰直線

fare(x軸)と survived(y軸)の関係を sns.regplot で散布図 + 回帰直線として描画する。タイトルに相関係数 r を表示する。

問3 — 散布図行列(pairplot)

age, fare, pclass, survived の4列で sns.pairplot を描画し、hue="survived" で生存/非生存を色分けする。

問4 — 多重共線性チェック

全数値列の相関行列を計算し、特徴量同士(survived を除く)で絶対値 0.5 以上の相関ペアがあるか確認する。なければ「多重共線性なし」と表示する。

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: df.corr()["survived"] で一発取得。並べ替えは .abs().sort_values()
  • 問2: sns.regplot(data=df, x="fare", y="survived")。タイトルは plt.title(f"...{r:.2f}")
  • 問3: sns.pairplot(df[[...]], hue="survived")
  • 問4: 相関行列の上三角だけ見て、絶対値 0.5 以上を探す
ヒント2 — アプローチ
# 問1
corr_target = df.corr()["survived"].drop("survived")
ranking = corr_target.abs().sort_values(ascending=False)

# 問2
r = df["fare"].corr(df["survived"])
sns.regplot(data=df, x="fare", y="survived",
            scatter_kws={"alpha": 0.4}, line_kws={"color": "red"})
plt.title(f"fare vs survived (r = {r:.2f})")
plt.show()

# 問3
sns.pairplot(df[["age", "fare", "pclass", "survived"]], hue="survived")
plt.show()
ヒント3 — コード骨格(ほぼ答え)
# 問1
corr_target = df.corr()["___"].drop("___")
ranking = corr_target.___().sort_values(ascending=False)
print(ranking)

# 問4
feat_corr = df.drop(columns="survived").corr().abs()
upper = feat_corr.where(np.triu(np.ones(feat_corr.shape), k=1).astype(bool))
high = upper.stack()
high = high[high >= 0.5]
print(high if len(high) else "多重共線性なし")

模範解答

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(42)
n = 200

df = pd.DataFrame({
    "age":     np.random.normal(35, 12, n).clip(1, 80).round(0),
    "fare":    np.random.exponential(30, n).clip(5, 300).round(2),
    "pclass":  np.random.choice([1, 2, 3], size=n, p=[0.25, 0.25, 0.5]),
    "sibsp":   np.random.poisson(0.5, n),
})
logit = 0.01 * df["fare"] - 0.8 * df["pclass"] + np.random.normal(0, 1, n)
df["survived"] = (logit > logit.mean()).astype(int)

# ── 問1: 目的変数との相関ランキング ──
corr_target = df.corr()["survived"].drop("survived")
ranking = corr_target.abs().sort_values(ascending=False)
print("=== survived との相関(絶対値が大きい順)===")
for col in ranking.index:
    print(f"{col:8s}: {corr_target[col]:+.3f}")

# ── 問2: 散布図 + 回帰直線 ──
r = df["fare"].corr(df["survived"])
plt.figure(figsize=(7, 4))
sns.regplot(data=df, x="fare", y="survived",
            scatter_kws={"alpha": 0.4, "color": "steelblue"},
            line_kws={"color": "red"},
            logistic=False)
plt.title(f"fare vs survived (r = {r:.2f})")
plt.tight_layout()
plt.show()

# ── 問3: 散布図行列(pairplot) ──
sns.pairplot(df[["age", "fare", "pclass", "survived"]],
             hue="survived", palette="coolwarm",
             plot_kws={"alpha": 0.5})
plt.suptitle("Pairplot (hue = survived)", y=1.02)
plt.show()

# ── 問4: 多重共線性チェック(特徴量同士)──
feat_corr = df.drop(columns="survived").corr().abs()
upper = feat_corr.where(np.triu(np.ones(feat_corr.shape), k=1).astype(bool))
high = upper.stack()
high = high[high >= 0.5]

print("\n=== 多重共線性チェック(特徴量同士 |r| >= 0.5)===")
if len(high) == 0:
    print("多重共線性なし")
else:
    for (a, b), v in high.items():
        print(f"{a} - {b}: {v:.3f}")
📌
実行結果(seed=42 固定で再現)
survived との相関 → fare: +0.45 / pclass: -0.37 / age: +0.02 / sibsp: -0.01
多重共線性チェック → 多重共線性なし

📊 survived との相関ランキング(可視化)

絶対値が大きいほど「予測に効きそうな特徴量」。赤=正の相関、青=負の相関。

fare +0.45
+0.45 (正・中程度)
pclass −0.37
−0.37 (負・中程度)
age +0.02
~0
sibsp −0.01
~0

問2のイメージ — fare vs survived(散布図 + 回帰直線)

y は 0/1 の二値 → 点は上下2本の帯に並ぶ fare → 1 0 survived 回帰直線 (右上がり) fare↑ → survived=1 が増える (r=+0.45)

🪜 Step-by-Step 解説

1 df.corr()["survived"] で目的変数との相関を一発取得

相関行列(全列×全列)を作ってから ["survived"] 列だけ抜き出すと、survived と各列の相関がSeriesになります。.drop("survived") で「自分自身との相関 = 1.0」を除外します。

corr_target = df.corr()["survived"].drop("survived")
ranking = corr_target.abs().sort_values(ascending=False)

並べ替えは abs(絶対値)で行い、表示する値は元の corr_target から取るのがポイント(符号 +/- を失わない)。

2 sns.regplot は散布図と回帰直線を同時に描く

sns.regplot(data=df, x="fare", y="survived",
            scatter_kws={"alpha": 0.4},   # 点の透明度(重なりを見やすく)
            line_kws={"color": "red"})    # 回帰直線の色

scatter_kws は点、line_kws は直線への設定。y が 0/1 の二値なので点は上下2本の帯に並びます。直線が右上がりなら「fare が高いほど survived=1 が多い」と読めます。

3 sns.pairplot で全ペアを一気に俯瞰

sns.pairplot(df[["age", "fare", "pclass", "survived"]], hue="survived")

対角線にヒストグラム、それ以外のマスに散布図を自動で並べます。hue="survived" で2グループを色分けすると、「どの特徴量で生存/非生存が分かれるか」が一目でわかります。

4 上三角行列で多重共線性ペアを重複なく抽出

upper = feat_corr.where(np.triu(np.ones(feat_corr.shape), k=1).astype(bool))
high = upper.stack()          # NaN を除いて (行,列)->値 のSeriesに
high = high[high >= 0.5]      # 閾値でフィルタ
  • np.triu(M, k=1): 対角線(k=0)を含まず1つ上から上三角を取る → 自己相関 1.0 を自動除外
  • .where(mask): mask が False の場所を NaN にする
  • .stack(): NaN を落としつつ多階層インデックスのSeriesに変換

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

なぜ「上三角だけ」を見るのか?

相関行列は「九九の表」をイメージしてください。九九では「3×4」と「4×3」が同じ値(12)です。相関でも「age と fare」と「fare と age」は完全に同じ値。だから片方(上三角)だけ見れば十分で、対角線(自分×自分=1.0)は意味がないので除外します。

np.triu の triu って何の略?

tri-upper(三角・上)の略。対になる np.tril は tri-lower(三角・下)。k=1 は「対角線から1つ上にずらす」で、対角線そのものを含めたくないときに使います。

二値(0/1)の目的変数とピアソン相関

survived は 0 か 1 しかありませんが、ピアソン相関係数は計算できます(このとき「点双列相関係数」と呼ばれます)。値が +0.45 なら「fare が高いと survived=1 になりやすい中程度の傾向」と読めます。

🏆 Kaggleでの実践的な使い方

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

train = pd.read_csv("train.csv")

# ① 目的変数との相関ランキング = 特徴量の効きそう度
num_df = train.select_dtypes(include="number")
corr_target = num_df.corr()["Survived"].drop("Survived")
print(corr_target.abs().sort_values(ascending=False))

# ② 効きそうな上位特徴量だけ pairplot で深掘り
top3 = corr_target.abs().nlargest(3).index.tolist()
sns.pairplot(train[top3 + ["Survived"]].dropna(),
             hue="Survived", palette="coolwarm")
plt.show()

# ③ 多重共線性チェック → 片方を削るか特徴量合成を検討
feat_corr = num_df.drop(columns="Survived").corr().abs()
upper = feat_corr.where(np.triu(np.ones(feat_corr.shape), k=1).astype(bool))
print(upper.stack()[upper.stack() > 0.7])  # 0.7 以上は要注意
場面使うコード判断
特徴量の選別corr()["target"] ランキング相関が弱すぎる列は後回し
関係の目視確認sns.regplot / pairplot非線形・外れ値を発見したら変換を検討
多重共線性上三角 |r| > 0.7片方削除 or PCA。線形モデルで特に重要
データリーク疑い|r| > 0.95ターゲットと異常に高い相関は生成過程を疑う

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
df.corr() に文字列列が混ざってエラー object列を含めたまま呼ぶ select_dtypes(include="number") で数値だけに絞る
相関行列を全部見て同じペアを2回数える 対称行列だと気づかない np.triu(..., k=1) で上三角だけ抽出
ランキングを絶対値で並べたら符号を失う .abs() で上書きしてしまう 並べ替えは abs、表示する値は元のSeriesから取る
pairplot が遅い・重い 全列で実行している 効きそうな上位3〜5列に絞ってから描く
regplot の直線が右上がり=因果 と解釈 相関と因果の混同 直線は傾向を示すだけ。因果は別途検証

🚀 次のステップ

  • 発展: sns.regplot(..., logistic=True) で二値ターゲットにロジスティック回帰曲線を引く。また df.corr(method="spearman") と比較し外れ値の影響を確認する
  • 次回予告: Phase 1 総復習 — 記述統計・確率・numpy・pandas・可視化・相関係数を総合問題で整理(Phase 1 最終回)

📋 自己評価(解いた後に記入)

自分の回答・気づき・メモ: