Day 048 — Phase 1 総復習(記述統計 〜 相関係数)

2026-05-29 白 / Phase 1 分析 総復習 🎓 Phase 1 最終回

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

今日は Phase 1 の最終回・総復習です。Day 001 から Day 047 まで学んできたすべてのテーマを1つの問題セットで整理します。

🎓
Phase 1 完走まであと1問! 今日を終えると Phase 2「線形回帰・評価指標・CV」へ進みます。

総復習のポイント: 「各ツールが何のためにあるか」「どの場面で使うか」 を意識して解きましょう。手を動かす前に「なぜこれを使うか」を自問するのが上級者の習慣です。

🗺️ Phase 1 カバーテーマ一覧

📊 記述統計

平均・中央値・最頻値・分散・標準偏差・外れ値
mean(), median(), std()

🎲 確率・確率分布

条件付き確率・ベイズの定理・正規分布・二項分布
scipy.stats.norm

🔢 numpy

配列操作・数値計算・ufunc・乱数生成
np.array, np.random

🐼 pandas

DataFrame・Series・groupby・loc/iloc・apply
df.groupby, df.loc

🔍 前処理

shape/dtypes/describe・欠損値処理
isnull(), fillna(), dropna()

📈 可視化

hist・boxplot・heatmap・pairplot
sns.heatmap, sns.pairplot

🔗 相関係数

ピアソン相関・散布図・多重共線性
df.corr(), sns.regplot

🗂️ データスキーマ(今回使用:総合サンプル 300件)

列名説明生成方法 / 注意点
agefloat64年齢(5〜80)N(38,14)・外れ値混入: 200 と -5
incomefloat64年収(右裾が長い)対数正規分布 LogN(10.5, 0.6)
scorefloat64テスト得点(0〜100)N(65,15)・正規分布に近い
categoryobjectグループ (A/B/C)30%/40%/30%
flagfloat64二値フラグ(0/1)45%/45%/10%欠損 → 欠損補完が必要
⚠️
意図的なデータ品質問題: age に外れ値 (200, -5)、flag に約10%の欠損値が含まれています。EDAの「落とし穴」を体験するための設計です。

📝 問題(問1〜問6)

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

np.random.seed(0)
n = 300

df = pd.DataFrame({
    "age":      np.random.normal(38, 14, n).clip(5, 80).round(0),
    "income":   np.random.lognormal(10.5, 0.6, n).round(0),
    "score":    np.random.normal(65, 15, n).clip(0, 100).round(1),
    "category": np.random.choice(["A", "B", "C"], n, p=[0.3, 0.4, 0.3]),
    "flag":     np.random.choice([0, 1, np.nan], n, p=[0.45, 0.45, 0.1]),
})
# age の外れ値を意図的に混入
df.loc[0, "age"] = 200
df.loc[1, "age"] = -5

問1 — 基本確認と記述統計

データの形状・型・欠損値数を確認し、各数値列(age, income, score)の平均・中央値・標準偏差を求めて表示する。外れ値のある age平均と中央値のどちらが影響を受けるか コメントで説明する。

問2 — 欠損値の処理

flag 列の欠損値を 中央値で補完 する。補完前後の flag の平均値を表示して変化を確認する。

問3 — 外れ値の除去と条件フィルタ

age の外れ値(0 未満または 100 超)を除去した新しいDataFrame df_clean を作成する。除去前後の行数と age の平均・標準偏差を表示する。

問4 — グループ集計

df_cleancategory 別に income の平均・中央値・最大値を集計して表示する。

問5 — 相関分析

df_clean の数値列(age, income, score, flag)の相関行列を計算し、score との相関を絶対値の大きい順に表示する。さらに相関行列を sns.heatmap で可視化する。

問6 — 確率の計算(統計理論)

df_cleanscore が正規分布 N(μ, σ²) に従うと仮定し、scipy.stats.norm を使って以下を計算する:

  • P(score ≥ 80) — 高得点(80点以上)の確率
  • P(50 ≤ score ≤ 80) — 中間帯の確率
  • 下位10%のスコアの閾値(第10パーセンタイル)

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: df.info(), df.describe(), df.isnull().sum()。外れ値は平均を引っ張る(中央値は頑健)
  • 問2: df["flag"].fillna(df["flag"].median())
  • 問3: df[(df["age"] >= 0) & (df["age"] <= 100)]
  • 問4: df.groupby("category")["income"].agg(["mean","median","max"])
  • 問5: df.corr()["score"].drop("score").abs().sort_values(ascending=False) + sns.heatmap(df.corr())
  • 問6: from scipy.stats import norm; norm.sf(80, loc=mu, scale=sigma)
ヒント2 — アプローチ
# 問1 — 外れ値の影響確認
print(df["age"].mean())    # 外れ値に引っ張られる
print(df["age"].median())  # 外れ値の影響を受けにくい

# 問3
df_clean = df[(df["age"] >= 0) & (df["age"] <= 100)].copy()

# 問6
from scipy.stats import norm
mu = df_clean["score"].mean()
sigma = df_clean["score"].std()
p_high = norm.sf(80, loc=mu, scale=sigma)      # P(X >= 80)
p_mid  = norm.cdf(80, loc=mu, scale=sigma) - norm.cdf(50, loc=mu, scale=sigma)
p10    = norm.ppf(0.10, loc=mu, scale=sigma)   # 第10パーセンタイル
ヒント3 — コード骨格(ほぼ答え)
# 問1
print(df.shape)
print(df.dtypes)
print(df.isnull().sum())
for col in ["age", "income", "score"]:
    print(f"{col}: mean={df[col].mean():.1f}, median={df[col].median():.1f}, std={df[col].std():.1f}")

# 問2
median_flag = df["flag"].___()
df["flag"] = df["flag"].fillna(___)

# 問3
df_clean = df[(df["age"] >= ___) & (df["age"] <= ___)].copy()

# 問5
corr_score = df_clean[["age","income","score","flag"]].corr()["score"].drop("score")
print(corr_score.___().sort_values(ascending=False))

模範解答

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm

np.random.seed(0)
n = 300

df = pd.DataFrame({
    "age":      np.random.normal(38, 14, n).clip(5, 80).round(0),
    "income":   np.random.lognormal(10.5, 0.6, n).round(0),
    "score":    np.random.normal(65, 15, n).clip(0, 100).round(1),
    "category": np.random.choice(["A", "B", "C"], n, p=[0.3, 0.4, 0.3]),
    "flag":     np.random.choice([0, 1, np.nan], n, p=[0.45, 0.45, 0.1]),
})
df.loc[0, "age"] = 200
df.loc[1, "age"] = -5

# ── 問1: 基本確認と記述統計 ──
print("=== 問1: データ基本確認 ===")
print(f"Shape: {df.shape}")
print(f"欠損値:\n{df.isnull().sum()}")
for col in ["age", "income", "score"]:
    print(f"{col}: mean={df[col].mean():.2f}, median={df[col].median():.2f}, std={df[col].std():.2f}")
# age の平均は外れ値(200, -5)に引っ張られるが、中央値はほぼ変わらない

# ── 問2: 欠損値の処理 ──
print("\n=== 問2: 欠損値補完 ===")
print(f"補完前 flag 平均: {df['flag'].mean():.4f}")
df["flag"] = df["flag"].fillna(df["flag"].median())
print(f"補完後 flag 平均: {df['flag'].mean():.4f}")

# ── 問3: 外れ値の除去 ──
print("\n=== 問3: 外れ値除去 ===")
print(f"除去前: {len(df)}行, age mean={df['age'].mean():.2f}, std={df['age'].std():.2f}")
df_clean = df[(df["age"] >= 0) & (df["age"] <= 100)].copy()
print(f"除去後: {len(df_clean)}行, age mean={df_clean['age'].mean():.2f}, std={df_clean['age'].std():.2f}")

# ── 問4: グループ集計 ──
print("\n=== 問4: category 別 income 集計 ===")
grp = df_clean.groupby("category")["income"].agg(["mean", "median", "max"]).round(0)
print(grp)

# ── 問5: 相関分析 ──
print("\n=== 問5: score との相関ランキング ===")
num_cols = ["age", "income", "score", "flag"]
corr_matrix = df_clean[num_cols].corr()
corr_score = corr_matrix["score"].drop("score")
print(corr_score.abs().sort_values(ascending=False))

plt.figure(figsize=(6, 5))
sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="coolwarm",
            vmin=-1, vmax=1, linewidths=0.5)
plt.title("相関行列ヒートマップ")
plt.tight_layout()
plt.show()

# ── 問6: 確率計算 ──
print("\n=== 問6: 正規分布による確率計算 ===")
mu = df_clean["score"].mean()
sigma = df_clean["score"].std()
print(f"score: μ={mu:.2f}, σ={sigma:.2f}")

p_high = norm.sf(80, loc=mu, scale=sigma)
p_mid  = norm.cdf(80, loc=mu, scale=sigma) - norm.cdf(50, loc=mu, scale=sigma)
p10    = norm.ppf(0.10, loc=mu, scale=sigma)

print(f"P(score >= 80) = {p_high:.4f} ({p_high*100:.1f}%)")
print(f"P(50 <= score <= 80) = {p_mid:.4f} ({p_mid*100:.1f}%)")
print(f"下位10%の閾値 = {p10:.1f}点")

📊 問6: 正規分布と確率の可視化

score の分布を正規分布 N(μ≈65, σ≈15) と仮定したときの確率イメージ:

P(score ≥ 80) ≈ 16%
16%
P(50 ≤ score ≤ 80) ≈ 68%
68%
P(score < 50) ≈ 16%
16%
score の正規分布 N(μ=65, σ=15) 20 35 50 65(μ) 80 95 μ score < 50 (≈16%) 50〜80 (≈68%) score ≥ 80 (≈16%) P10≈46点
💡
68-95-99.7 ルール: μ±1σ に約68%、μ±2σ に約95%、μ±3σ に約99.7% が含まれる。score の場合: 50〜80 = μ±1σ ≈ 68%、35〜95 = μ±2σ ≈ 95%

🪜 Step-by-Step 解説

1 データ確認は「形状 → 型 → 欠損 → 統計量」の順番

EDA は必ずこの順番で進めます。df.shape でデータ規模を把握し、df.dtypes で文字列/数値の混在に気づき、df.isnull().sum() で欠損パターンを確認してから統計量を見ます。

print(df.shape)           # (行数, 列数)
print(df.dtypes)          # 各列の型
print(df.isnull().sum())  # 列ごとの欠損数

2 外れ値は平均を引っ張るが中央値は頑健

age に 200 と -5 を混入したとき、平均は実際の値から大きくずれますが、中央値(並べた真ん中の値)はほぼ変わりません。これが「ロバスト(頑健)な統計量」の意味です。

# 外れ値あり: mean ≈ 38.9 (引っ張られる), median ≈ 38.0 (ほぼ変わらない)
# 除去後:     mean ≈ 38.1 (正常に戻る)

3 欠損値補完は「なぜその値で埋めるか」が重要

flag は 0/1 の二値変数です。中央値(0か1)で補完するより、最頻値(mode) で補完するほうが分布を保ちやすいです。ただし今回は中央値補完の練習として実施します。

df["flag"] = df["flag"].fillna(df["flag"].median())
# より一般的: df["flag"] = df["flag"].fillna(df["flag"].mode()[0])

4 groupby + agg で複数統計量を一発計算

df_clean.groupby("category")["income"].agg(["mean", "median", "max"])

agg に関数名のリストを渡すと複数の統計量を1度に計算できます。実際のKaggleでは名前付き集計 agg(mean_income=("income","mean")) も便利です。

5 相関ヒートマップで全体の関係を俯瞰

sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1)
  • annot=True: セルに数値を表示
  • cmap="coolwarm": 赤(正)〜白(0)〜青(負)のカラーマップ
  • vmin=-1, vmax=1: -1〜+1 にスケールを固定(必須!)

6 scipy.stats.norm の3つのメソッドを使い分ける

from scipy.stats import norm
mu, sigma = df_clean["score"].mean(), df_clean["score"].std()

norm.sf(80, loc=mu, scale=sigma)    # P(X >= 80) = 1 - CDF(80) — 右側確率
norm.cdf(x, loc=mu, scale=sigma)    # P(X <= x) — 左側確率(累積分布関数)
norm.ppf(0.10, loc=mu, scale=sigma) # 下位10%の点(パーセンタイル点関数)

sf = Survival Function(右側確率)、cdf = Cumulative Distribution Function(左側確率)、ppf = Percent Point Function(CDFの逆関数)

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

「正規分布を仮定する」とはどういう意味?

データが正規分布に従うと仮定すると、「平均 μ と標準偏差 σ の2つのパラメータで、そのデータ全体の形を記述できる」ということです。身近な例: 身長・体重・テストの点数などは正規分布に近い形になることが多い。

範囲含まれる確率score の例(μ=65, σ=15)
μ ± 1σ約 68%50〜80
μ ± 2σ約 95%35〜95
μ ± 3σ約 99.7%20〜110(クリップで100以下)

外れ値の「影響を受けにくい統計量」= ロバスト統計

平均はすべてのデータを足して割るので外れ値の影響を受けます。中央値は「並べた真ん中の値」なので、端の値(外れ値)が変わっても真ん中は動きません。Kaggleでは外れ値が多いデータに対してロバストな集計(中央値・パーセンタイル)を優先します。

なぜ正規分布が重要か?

機械学習では「残差(予測誤差)が正規分布に従う」という仮定を置くモデルが多い(線形回帰など)。また「μ ± 3σ の外にあるデータ = 外れ値」という定義も正規分布仮定から来ています。

🏆 Kaggleでの実践的な使い方 — Phase 1 総まとめEDAテンプレート

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm

# Kaggle EDA テンプレート(Phase 1 総まとめ)
train = pd.read_csv("train.csv")

# ① 基本確認
print(train.shape, "\n")
print(train.dtypes, "\n")
print(train.isnull().sum().sort_values(ascending=False), "\n")

# ② 数値列の統計量 + 外れ値チェック
num_df = train.select_dtypes(include="number")
print(num_df.describe().T)

# ③ 欠損値補完(数値: 中央値、カテゴリ: 最頻値)
for col in num_df.columns:
    train[col] = train[col].fillna(train[col].median())

# ④ 目的変数との相関ランキング
target = "SalePrice"  # House Prices コンペの例
corr = num_df.corr()[target].drop(target).abs().sort_values(ascending=False)
print(corr.head(10))

# ⑤ 相関ヒートマップ(上位10特徴量)
top10 = corr.head(10).index.tolist()
sns.heatmap(train[top10 + [target]].corr(), annot=True, fmt=".2f", cmap="coolwarm")
plt.show()
Phase 1 のスキルKaggleでの活用場面
記述統計・外れ値除去EDA: データクリーニング
欠損値処理前処理: 欠損補完
groupby集計EDA: カテゴリ別分布確認
相関係数・ヒートマップ特徴量選択・多重共線性チェック
正規分布・確率計算外れ値定義 (μ±3σ) / 評価指標理解

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
外れ値を除去せず平均で比較する 外れ値の影響を見落とす 除去前後で mean/median を必ず比較する
欠損値を 0 で埋める 処理が簡単だから 0は「値が0」という意味になる。median/modeが安全
df.corr() に object 列が混ざる dtypes を確認しない select_dtypes(include="number") で数値のみに絞る
確率と割合を混同する P(X≥80) を「実データの80点以上の割合」と思い込む 正規分布仮定下での理論確率。実データの割合とは異なる
groupby後にindexがリセットされないと気づかない reset_index() を忘れる groupby(...).agg(...).reset_index() が安全
Phase 1を完了したら全て覚えた気になる 知識と実践は別 Kaggleコンペに実際に参加してEDAを繰り返すことで定着する

🚀 次のステップ

🎓
Phase 1 完走おめでとうございます!
Day 001(記述統計入門)から Day 048(Phase 1 総復習)まで、文系エンジニアとして データサイエンスの基礎を48日間積み上げてきました。
  • 発展: Kaggle の Titanic コンペ に登録し、今日学んだEDAテンプレートを使って自分でEDAノートブックを作成してみる
  • 次回予告 (Day 049): Phase 2 スタート — 線形回帰(Linear Regression)入門。今まで「データを見る」スキルを学んできたが、Phase 2 からは「モデルを作る」フェーズへ。目的変数を実際に予測する初めての体験!

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

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