📚 背景知識(読んでから問題へ)
前回(Day 046)は「相関係数 r とは何か」を理論で学びました。今日はそれを手を動かして可視化するコーディング回です。
Kaggle の EDA では「数値(相関係数)」と「絵(散布図)」を必ずセットで見ます。相関係数は -1〜+1 の1つの数値に情報を圧縮するため、外れ値や非線形の関係を見落とすからです(前回学んだアンスコムの quartet)。
🧰 今日の3つの武器
2変数の関係を1枚の絵に。sns.regplot が点と直線を同時に描く。
全ペアの散布図を一気に俯瞰。sns.pairplot + hue で色分け。
目的変数との相関を corr()["target"] で一覧。効く特徴量を発見。
🗂️ データスキーマ(今回使用:Titanic風 200件)
| 列名 | 型 | 説明 | 生成方法 |
|---|---|---|---|
age | float64 | 年齢(1〜80) | np.random.normal(35, 12) をクリップ |
fare | float64 | 運賃(5〜300) | np.random.exponential(30) をクリップ |
pclass | int64 | 客室等級(1/2/3) | np.random.choice、p=[.25,.25,.5] |
sibsp | int64 | 同乗の兄弟・配偶者数 | np.random.poisson(0.5) |
survived | int64 | 生存フラグ(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}")
survived との相関 →
fare: +0.45 / pclass: -0.37 / age: +0.02 / sibsp: -0.01多重共線性チェック → 多重共線性なし
📊 survived との相関ランキング(可視化)
絶対値が大きいほど「予測に効きそうな特徴量」。赤=正の相関、青=負の相関。
問2のイメージ — fare vs survived(散布図 + 回帰直線)
🪜 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 最終回)