📚 背景知識(読んでから問題へ)
「2つの変数がどれくらい一緒に動くか」を -1〜+1 の1つの数値で表したものが相関係数(ピアソンの相関係数)です。Kaggle EDA の必須スキルであり、特徴量選択・多重共線性検出・リーク発見に直接使われます。
📏 相関係数の目安
※ 分野・サンプルサイズによって基準は変わります。Kaggle では特徴量選択の参考値として使います。
⚠️ アンスコムの落とし穴 — 相関係数だけを信じるな
アンスコムの quartet は「統計値が同じでも散布図の形は全く異なる」ことを示す有名な例です。以下の4データセットはすべて相関係数 r ≈ 0.82、平均・分散もほぼ同じです。
きれいな直線状の散布図。相関係数が正しく機能するケース。
放物線(二次関数)の形。線形相関係数では曲線の関係を捉えられない。
1点の外れ値が相関係数を大きく引き上げている。本来は低相関。
1点だけ外れた垂直なクラスタ。散布図を見ないと構造を見落とす。
🗂️ データスキーマ(今回使用)
| 列名 | 型 | 説明 | 生成方法 |
|---|---|---|---|
study_hours | float64 | 1日の勉強時間(1〜10時間) | np.random.uniform(1, 10, 60) |
score | float64 | 試験スコア(0〜100点) | study_hours × 8 + ノイズ |
temperature | float64 | 気温(15〜35℃) | np.random.uniform(15, 35, 60) |
ice_cream | float64 | アイスクリーム販売数 | temperature × 3 + ノイズ |
study_hours と ice_cream は独立した変数から生成 → 直接の相関はないはず(疑似相関の検証に使う)
📝 問題
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(0)
n = 60
df = pd.DataFrame({
"study_hours": np.random.uniform(1, 10, n).round(1),
"score": 0.0,
"temperature": np.random.uniform(15, 35, n).round(1),
"ice_cream": 0.0,
})
df["score"] = (df["study_hours"] * 8 + np.random.normal(0, 8, n)).clip(0, 100).round(1)
df["ice_cream"] = (df["temperature"] * 3 + np.random.normal(0, 5, n)).clip(0, 200).round(0)
問1 — 理論:正しい選択肢をすべて選んでください
問2 — コーディング
study_hoursとscoreの相関係数を計算して表示する(小数点2桁)temperatureとice_creamの相関係数を計算して表示する(小数点2桁)- 全数値列の相関行列を
df.corr()で計算し、sns.heatmapで可視化するannot=True、fmt=".2f"、cmap="coolwarm"
問3 — 分析
df.corr() の結果を見て、study_hours と ice_cream の相関係数はどの程度になるか予測し、その理由と「相関≠因果」について説明してください。
🔍 ヒント(段階的開示)
ヒント1 — 方向性
- 問1: 正しいのは (B) と (C)。(A) は相関0≠独立、(D) は相関≠因果
- 問2:
df["A"].corr(df["B"])で2列の相関係数。df.corr()で行列 - 問3: study_hours と ice_cream は独立した乱数から生成 → ほぼ0になるはず
ヒント2 — アプローチ
# 問2-1, 2-2
r1 = df["study_hours"].corr(df["score"])
print(f"study_hours vs score: {r1:.2f}")
r2 = df["temperature"].corr(df["ice_cream"])
print(f"temperature vs ice_cream: {r2:.2f}")
# 問2-3
corr_matrix = df.corr()
fig, ax = plt.subplots(figsize=(7, 5))
sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="coolwarm", ax=ax)
ax.set_title("Correlation Matrix")
plt.tight_layout()
plt.show()
ヒント3 — コード骨格(ほぼ答え)
r1 = df["___"].corr(df["___"])
print(f"study_hours vs score: {___:.2f}")
r2 = df["___"].corr(df["___"])
print(f"temperature vs ice_cream: {___:.2f}")
corr_matrix = df.___()
fig, ax = plt.subplots(figsize=(7, 5))
sns.___(corr_matrix, annot=___, fmt=___, cmap=___, ax=ax)
plt.show()
✅ 模範解答
問1 の答え: (B) と (C) が正しい
| 選択肢 | 正誤 | 理由 |
|---|---|---|
| (A) r=0 なら必ず独立 | 誤り | r=0 は線形の関係がないことを示す。y=x² のような非線形の関係でも r≈0 になりうる |
| (B) -1 ≤ r ≤ +1 | 正しい | コーシー・シュワルツの不等式から数学的に証明されている |
| (C) r が +1 に近い → 右上がりの直線に近い | 正しい | r=+1 は完全な正の線形関係。r=-1 は完全な負の線形関係 |
| (D) 相関は因果を証明する | 誤り | 「相関 ≠ 因果」。共通原因(交絡変数)による疑似相関の可能性がある |
問2・問3 の完全なコード
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(0)
n = 60
df = pd.DataFrame({
"study_hours": np.random.uniform(1, 10, n).round(1),
"score": 0.0,
"temperature": np.random.uniform(15, 35, n).round(1),
"ice_cream": 0.0,
})
df["score"] = (df["study_hours"] * 8 + np.random.normal(0, 8, n)).clip(0, 100).round(1)
df["ice_cream"] = (df["temperature"] * 3 + np.random.normal(0, 5, n)).clip(0, 200).round(0)
# ── 問2-1, 2-2: 相関係数の計算 ──
r1 = df["study_hours"].corr(df["score"])
r2 = df["temperature"].corr(df["ice_cream"])
print(f"study_hours vs score: {r1:.2f}") # 強い正の相関
print(f"temperature vs ice_cream: {r2:.2f}") # 強い正の相関
# ── 問2-3: 相関行列ヒートマップ ──
corr_matrix = df.corr()
fig, ax = plt.subplots(figsize=(7, 5))
sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="coolwarm",
linewidths=0.5, ax=ax)
ax.set_title("Correlation Matrix")
plt.tight_layout()
plt.show()
# ── 問3 の検証: study_hours と ice_cream ──
r3 = df["study_hours"].corr(df["ice_cream"])
print(f"\nstudy_hours vs ice_cream: {r3:.2f}") # ほぼ 0
# ── 散布図で視覚確認 ──
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].scatter(df["study_hours"], df["score"],
color="steelblue", alpha=0.6, edgecolor="white", s=60)
axes[0].set_xlabel("study_hours")
axes[0].set_ylabel("score")
axes[0].set_title(f"study_hours vs score\nr = {r1:.2f}")
axes[1].scatter(df["temperature"], df["ice_cream"],
color="tomato", alpha=0.6, edgecolor="white", s=60)
axes[1].set_xlabel("temperature")
axes[1].set_ylabel("ice_cream")
axes[1].set_title(f"temperature vs ice_cream\nr = {r2:.2f}")
axes[2].scatter(df["study_hours"], df["ice_cream"],
color="#a78bfa", alpha=0.6, edgecolor="white", s=60)
axes[2].set_xlabel("study_hours")
axes[2].set_ylabel("ice_cream")
axes[2].set_title(f"study_hours vs ice_cream\nr = {r3:.2f}")
plt.tight_layout()
plt.show()
🪜 Step-by-Step 解説
1 問1: (B) と (C) だけが正しい理由
(A) が誤り: ピアソン相関係数はあくまで線形の関係のみを測ります。y = x² のような放物線型の関係では、正の部分と負の部分が打ち消し合って r ≈ 0 になりますが、y は x に完全に依存しています。アンスコムの quartet のデータセット II がその典型例です。
(D) が誤り: これは統計で最も重要な注意点です。「アイスクリームの売上↑と溺死件数↑の相関」が有名な例ですが、実際には「夏(気温)」という共通原因があるため見かけ上の相関が生じています。
2 Series.corr() の使い方
r = df["study_hours"].corr(df["score"])
print(f"{r:.2f}") # :.2f で小数点2桁に丸め
Series.corr(other) は NaN を自動的に除外してピアソン相関係数を計算します。結果は float 型の -1〜+1 の値です。method="spearman" を指定するとスピアマン順位相関係数に変わります。
3 df.corr() — 全列の相関行列
corr_matrix = df.corr()
# → 4×4 の DataFrame(全数値列の全ペアの相関係数)
# 対角線は必ず 1.0(自分自身との相関)
結果は n列×n列の対称行列です。対角線(左上から右下)は常に 1.0。上半分と下半分は鏡写しになります。
4 sns.heatmap の主要パラメータ
sns.heatmap(corr_matrix,
annot=True, # セルに数値を表示
fmt=".2f", # 小数点2桁フォーマット
cmap="coolwarm", # 赤(正)→白(0)→青(負)のカラーマップ
linewidths=0.5, # セルの境界線を追加
vmin=-1, vmax=1) # カラースケールの範囲(-1〜+1 に固定)
vmin=-1, vmax=1 を明示的に指定すると、データの範囲に引きずられずに正しく色が割り当てられます。
5 問3: 疑似相関(spurious correlation)を理解する
study_hours と ice_cream の相関係数はほぼ 0 です。それぞれ独立な乱数(uniform, normal)から生成されており、直接の因果関係がないからです。
現実の Kaggle コンペでも同様のことが起きます:
- 特徴量 A と特徴量 B が相関していても、A が目的変数の原因とは限らない
- 特徴量同士の高相関(多重共線性)は、モデルの解釈を困難にする
- ターゲットと高相関な特徴量がある場合、データリーク(未来のデータが混入)の可能性を疑う
🌡️ 相関行列ヒートマップの読み方
対称行列なので、上三角と下三角は同じ値のミラーです。実務では片方だけ見ればOK。
🔢 数学・統計の補足(文系向け)
ピアソン相関係数の直感的な導き方
数式を使わずに考えると:
- 各変数から平均を引く(「ズレ」を計算)
- x のズレと y のズレを掛け合わせる
- 両方が平均より大きい → プラス × プラス = プラス
- 片方が大きく片方が小さい → プラス × マイナス = マイナス
- 掛け算の平均を取る(共分散)
- それぞれの標準偏差で割って -1〜+1 に正規化
疑似相関の古典的な例
| 変数A | 変数B | 実際の原因 |
|---|---|---|
| アイスクリーム売上 ↑ | 溺死件数 ↑ | 夏(気温)が共通原因 |
| 靴のサイズ ↑ | 読書能力 ↑ | 年齢が共通原因 |
| コウノトリの数 ↑ | 出生率 ↑ | 農村部の人口密度が共通原因 |
スピアマン相関係数 — ピアソンの限界を補う
ピアソン相関係数は外れ値に敏感です。スピアマン相関係数は値の順位(ランク)を使うため、外れ値の影響を受けにくいです:
df.corr(method="spearman") # スピアマン順位相関係数
🏆 Kaggleでの実践的な使い方
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
train = pd.read_csv("train.csv")
# ① 目的変数との相関を数値で一覧(上位・下位の特徴量を把握)
corr_with_target = train.corr()["SalePrice"].sort_values(ascending=False)
print("正の相関 上位10:")
print(corr_with_target.head(10))
print("\n負の相関 上位5:")
print(corr_with_target.tail(5))
# ② 相関行列ヒートマップ(上位10変数のみに絞る)
top_cols = corr_with_target.abs().nlargest(10).index
corr_top = train[top_cols].corr()
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(corr_top, annot=True, fmt=".2f",
cmap="coolwarm", linewidths=0.5,
vmin=-1, vmax=1, ax=ax)
ax.set_title("Top 10 Features Correlation Heatmap")
plt.tight_layout()
plt.show()
# ③ 多重共線性チェック(相関が 0.9 以上のペアを検出)
num_df = train.select_dtypes(include="number")
corr_matrix = num_df.corr().abs()
# 上三角行列のみで重複を除く
upper = corr_matrix.where(
pd.DataFrame([[i < j for j in range(len(corr_matrix.columns))]
for i in range(len(corr_matrix.columns))],
index=corr_matrix.index, columns=corr_matrix.columns)
)
high_corr = upper.stack()
print("\n高相関ペア(候補):")
print(high_corr[high_corr > 0.9])
| 用途 | 何を見るか | アクション |
|---|---|---|
| 重要特徴量の発見 | 目的変数との相関 上位 | 特徴量エンジニアリングで拡張・深掘り |
| 多重共線性の検出 | 特徴量同士の相関 > 0.9 | 片方を削除 or PCAで次元圧縮 |
| リークの疑い | 相関係数が 0.99 以上 | データ生成プロセスを確認。リークなら除外 |
| EDA の起点 | 相関が弱い変数の発見 | 特徴量変換(log, sqrt, 二乗)を試みる |
Kaggle EDA における相関分析の位置づけ
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 相関が高いから因果がある | 「数値が高い = 関係がある」という直感 | 相関は関係の強さ。因果を示すには実験設計や因果推論が必要 |
| 相関行列に object 列が混入してエラー | df.corr() は数値列のみ対応(古いpandas: 警告 / 新しいpandas: エラー) | df.select_dtypes(include="number").corr() で数値のみに絞る |
| 外れ値1点で相関係数が大きく変わる | ピアソン係数は外れ値に敏感 | method="spearman" も併用して頑健性を確認 |
| 散布図を見ずに係数だけ信頼する | 計算が楽で視覚化を省きがち | アンスコムの quartet を思い出して必ず目視確認 |
| r = 0 を「絶対に無関係」と判断する | 線形相関 = 独立という誤解 | r=0 は線形の関係がないだけ。非線形の関係は別途確認が必要 |
🚀 次のステップ
- 発展: スピアマンの順位相関係数(
method="spearman")で外れ値に頑健な相関を確認する。またseaborn.pairplot(df, hue="target")で全変数ペアの散布図を一括生成する - 次回予告: Phase 1 総復習 — 記述統計・確率・numpy・pandas・可視化・相関係数を総合問題で整理(Phase 1 最終回)