📚 背景知識(読んでから問題へ)
今日は Phase 1 の最終回・総復習です。Day 001 から Day 047 まで学んできたすべてのテーマを1つの問題セットで整理します。
総復習のポイント: 「各ツールが何のためにあるか」「どの場面で使うか」 を意識して解きましょう。手を動かす前に「なぜこれを使うか」を自問するのが上級者の習慣です。
🗺️ Phase 1 カバーテーマ一覧
平均・中央値・最頻値・分散・標準偏差・外れ値mean(), median(), std()
条件付き確率・ベイズの定理・正規分布・二項分布scipy.stats.norm
配列操作・数値計算・ufunc・乱数生成np.array, np.random
DataFrame・Series・groupby・loc/iloc・applydf.groupby, df.loc
shape/dtypes/describe・欠損値処理isnull(), fillna(), dropna()
hist・boxplot・heatmap・pairplotsns.heatmap, sns.pairplot
ピアソン相関・散布図・多重共線性df.corr(), sns.regplot
🗂️ データスキーマ(今回使用:総合サンプル 300件)
| 列名 | 型 | 説明 | 生成方法 / 注意点 |
|---|---|---|---|
age | float64 | 年齢(5〜80) | N(38,14)・外れ値混入: 200 と -5 |
income | float64 | 年収(右裾が長い) | 対数正規分布 LogN(10.5, 0.6) |
score | float64 | テスト得点(0〜100) | N(65,15)・正規分布に近い |
category | object | グループ (A/B/C) | 30%/40%/30% |
flag | float64 | 二値フラグ(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_clean で category 別に income の平均・中央値・最大値を集計して表示する。
問5 — 相関分析
df_clean の数値列(age, income, score, flag)の相関行列を計算し、score との相関を絶対値の大きい順に表示する。さらに相関行列を sns.heatmap で可視化する。
問6 — 確率の計算(統計理論)
df_clean の score が正規分布 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) と仮定したときの確率イメージ:
🪜 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を繰り返すことで定着する |
🚀 次のステップ
Day 001(記述統計入門)から Day 048(Phase 1 総復習)まで、文系エンジニアとして データサイエンスの基礎を48日間積み上げてきました。
- 発展: Kaggle の Titanic コンペ に登録し、今日学んだEDAテンプレートを使って自分でEDAノートブックを作成してみる
- 次回予告 (Day 049): Phase 2 スタート — 線形回帰(Linear Regression)入門。今まで「データを見る」スキルを学んできたが、Phase 2 からは「モデルを作る」フェーズへ。目的変数を実際に予測する初めての体験!