Day 046 — 相関係数と散布図

2026-05-27 白 / Phase 1 理論 相関係数

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

「2つの変数がどれくらい一緒に動くか」を -1〜+1 の1つの数値で表したものが相関係数(ピアソンの相関係数)です。Kaggle EDA の必須スキルであり、特徴量選択・多重共線性検出・リーク発見に直接使われます。

💡
身近な例:身長と体重は一緒に増える(正の相関)、気温とコート売上は逆に動く(負の相関)、天気と試験の点数は関係なし(無相関)。
相関係数 r の直感的なイメージ r = -1 r ≈ 0 r = +1 完全な負の相関 無相関 完全な正の相関

📏 相関係数の目安

+0.7 〜 +1.0(強い正の相関)
r = +0.7 〜 +1.0
+0.4 〜 +0.7(中程度の正)
r = +0.4 〜 +0.7
-0.4 〜 +0.4(ほぼ無相関)
無相関
-0.7 〜 -0.4(中程度の負)
r = -0.7 〜 -0.4
-1.0 〜 -0.7(強い負の相関)
r = -1.0 〜 -0.7

※ 分野・サンプルサイズによって基準は変わります。Kaggle では特徴量選択の参考値として使います。

⚠️ アンスコムの落とし穴 — 相関係数だけを信じるな

アンスコムの quartet は「統計値が同じでも散布図の形は全く異なる」ことを示す有名な例です。以下の4データセットはすべて相関係数 r ≈ 0.82、平均・分散もほぼ同じです。

データセット I

きれいな直線状の散布図。相関係数が正しく機能するケース。

データセット II

放物線(二次関数)の形。線形相関係数では曲線の関係を捉えられない。

データセット III

1点の外れ値が相関係数を大きく引き上げている。本来は低相関。

データセット IV

1点だけ外れた垂直なクラスタ。散布図を見ないと構造を見落とす。

🔑
鉄則: 相関係数を計算したら、必ず散布図も描いて目視確認すること。数値だけでは見えない構造がある。

🗂️ データスキーマ(今回使用)

列名説明生成方法
study_hoursfloat641日の勉強時間(1〜10時間)np.random.uniform(1, 10, 60)
scorefloat64試験スコア(0〜100点)study_hours × 8 + ノイズ
temperaturefloat64気温(15〜35℃)np.random.uniform(15, 35, 60)
ice_creamfloat64アイスクリーム販売数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 — 理論:正しい選択肢をすべて選んでください

(A) 相関係数が 0 なら、2変数は必ず独立(無関係)である
(B) 相関係数は -1 から +1 の間に必ず収まる
(C) 相関係数が +1 に近いほど、散布図の点が右上がりの直線に近く並ぶ
(D) 相関係数は「因果関係」を証明する

問2 — コーディング

  1. study_hoursscore の相関係数を計算して表示する(小数点2桁)
  2. temperatureice_cream の相関係数を計算して表示する(小数点2桁)
  3. 全数値列の相関行列を df.corr() で計算し、sns.heatmap で可視化する
    • annot=Truefmt=".2f"cmap="coolwarm"

問3 — 分析

df.corr() の結果を見て、study_hoursice_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()
📌
問3の解答: study_hours と ice_cream はどちらも独立した乱数から生成されているため、相関係数はほぼ 0 になります。これは「相関≠因果」の典型例で、temperature が ice_cream の原因ですが study_hours とは無関係です。

🪜 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 が目的変数の原因とは限らない
  • 特徴量同士の高相関(多重共線性)は、モデルの解釈を困難にする
  • ターゲットと高相関な特徴量がある場合、データリーク(未来のデータが混入)の可能性を疑う

🌡️ 相関行列ヒートマップの読み方

4×4 相関行列ヒートマップの構造(模式図) study_h score temp ice_cream study_h score temp ice_cream 1.00 1.00 1.00 1.00 ~0.82 ~0.82 ~0.89 ~0.89 ~0.05 ~0.05 ~-0.03 ~-0.03 ~0.07 ~0.07 ~0.01 ~0.01 ← 対角線: 1.0 ← 正の相関 ← ほぼ無相関

対称行列なので、上三角と下三角は同じ値のミラーです。実務では片方だけ見ればOK。

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

ピアソン相関係数の直感的な導き方

数式を使わずに考えると:

  1. 各変数から平均を引く(「ズレ」を計算)
  2. x のズレと y のズレを掛け合わせる
    • 両方が平均より大きい → プラス × プラス = プラス
    • 片方が大きく片方が小さい → プラス × マイナス = マイナス
  3. 掛け算の平均を取る(共分散)
  4. それぞれの標準偏差で割って -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 における相関分析の位置づけ

1
df.shape / df.dtypes / df.head() — データの全体像を把握
2
df.describe() / isna().sum() — 欠損値・分布・外れ値の確認
3
df.corr() + sns.heatmap — 特徴量と目的変数の関係を一覧把握 ← 今日のテーマ
4
散布図 / 箱ひげ図 — 上位特徴量を個別に深掘り
5
特徴量エンジニアリング — 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 最終回)

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

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