📚 背景知識(読んでから問題へ)
外れ値が「平均」を狂わせる
年収データを考えてください。
[300万, 320万, 310万, 290万, 305万, 1億]
この6人の平均は約1,754万円。でも実態に合っていませんよね?
1億という「外れ値」が平均を大きく引き上げています。
このとき中央値(並べたときの真ん中)を使うと307.5万円となり、実態に近い。
これがKaggleでも重要な知識です。特徴量に外れ値があると、平均ベースの処理が壊れるので、
まず「平均と中央値がどれくらい離れているか」で外れ値の存在を疑います。
NumPy vs Pandas どちらを使う?
| 場面 | 推奨 |
|---|---|
| 数値配列だけ | numpy |
| 表形式データ(列・行あり) | pandas |
実際のKaggleでは pandas DataFrame を使うことが多いです。
📝 問題
以下のTitanicの年齢データ(一部)があります。
import numpy as np
import pandas as pd
ages = [22, 38, 26, 35, 35, None, 54, 2, 27, 14,
4, 58, 20, 39, 14, 55, 2, None, 31, 35]
タスク1: ages リストから None を除いた有効なデータ数を求めてください。
タスク2: 有効なデータの 平均・中央値・最頻値 を求めてください(pandasを使う)。
タスク3: 平均と中央値の差が 5 以上の場合は「外れ値の可能性あり」、未満なら「分布は均一」と出力してください。
期待する出力例(値は自分で確かめること):
▶ 出力を見る
有効データ数: XX件
平均: XX.XX
中央値: XX.X
最頻値: XX
→ 分布は均一(または外れ値の可能性あり)🔍 ヒント(段階的開示)
ヒント1(方向性)
None を除くには dropna() か pd.Series の性質を使う。pandasはNoneをNaNとして扱う。
ヒント2(アプローチ)
s = pd.Series(ages)
s_clean = s.dropna() # NaNを除去
s_clean.mean() # 平均
s_clean.median() # 中央値
s_clean.mode()[0] # 最頻値(mode()はSeriesで返る)
ヒント3(コード骨格)
s = pd.Series(ages)
s_clean = s.dropna()
mean_val = s_clean.mean()
median_val = s_clean.median()
mode_val = s_clean.mode()[0]
print(f"有効データ数: {len(s_clean)}件")
print(f"平均: {mean_val:.2f}")
print(f"中央値: {median_val}")
print(f"最頻値: {mode_val}")
if abs(mean_val - median_val) >= 5:
print("→ 外れ値の可能性あり")
else:
print("→ 分布は均一")
✅ 模範解答
import numpy as np
import pandas as pd
ages = [22, 38, 26, 35, 35, None, 54, 2, 27, 14,
4, 58, 20, 39, 14, 55, 2, None, 31, 35]
s = pd.Series(ages)
s_clean = s.dropna()
mean_val = s_clean.mean()
median_val = s_clean.median()
mode_val = s_clean.mode()[0]
print(f"有効データ数: {len(s_clean)}件")
print(f"平均: {mean_val:.2f}")
print(f"中央値: {median_val}")
print(f"最頻値: {mode_val}")
if abs(mean_val - median_val) >= 5:
print("→ 外れ値の可能性あり")
else:
print("→ 分布は均一")
出力:
▶ 出力を見る
有効データ数: 18件
平均: 28.61
中央値: 28.5
最頻値: 2
→ 分布は均一🪜 Step-by-Step 解説
s = pd.Series(ages)
リストをpandasの1次元データ構造「Series」に変換します。
None は自動的に NaN(Not a Number)として扱われます。
s_clean = s.dropna()
dropna() でNaNを含む要素を取り除きます。
20件中2件が None → 18件の有効データになります。
mean_val = s_clean.mean() # 全データの合計 / データ数
median_val = s_clean.median() # ソートして真ん中の値
mode_val = s_clean.mode()[0] # 最も多く登場する値
mode() は複数の最頻値が存在する場合にSeriesで返るので、[0] で最初の値を取得します。
if abs(mean_val - median_val) >= 5:
平均と中央値が大きく離れているほど、データが偏っている(外れ値がある)サインです。
今回の差は 28.61 - 28.5 = 0.11 → 均一な分布。
📐 数学・統計の補足(文系向け)
なぜ mode() は [0] が必要か?
最頻値が複数ある場合(例: 2も35も同じ回数登場するとき)、pandasは全部返します。
mode() の結果は Series 型なので、最初の1つだけほしい場合は [0] でインデックスアクセスします。
今回のデータの最頻値が「2」なのはなぜ?
データを確認すると 2 が2回、35 が3回、14 が2回登場します。
最も多い 35 が最頻値になります(実際に実行して確かめてみてください)。
🏆 Kaggleでの実践的な使い方
Titanicコンペでは年齢(Age列)に欠損値が約20%あります。
欠損値を埋める(補完する)ときに「何で補完するか」が重要な選択になります:
- 平均で補完: 外れ値の影響を受けやすい
- 中央値で補完: 外れ値に強い → Titanicでは中央値補完が鉄板
df['Age'].fillna(df['Age'].median(), inplace=True)
このような「実際の処理」に今日の知識が直結します。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
mode() の結果をそのまま数値として使う | SeriesとScalarの違いを意識していない | mode()[0] で最初の値を取り出す |
| Noneを手動でフィルタリングする | pandasの機能を知らない | dropna() を使う |
| 欠損を0で埋める | 「空欄=0」と思いがち | 0は「ゼロ歳」という意味になってしまう |
| 平均≒中央値と思い込む | 均一なデータしか見たことがない | 外れ値があると大きくズレる |
🚀 次のステップ
- 発展: 外れ値を
IQR法(四分位範囲)で検出する(Phase 1 テーマ2へ) - 次回予告: 分散・標準偏差 — データの「ばらつき」を数値で表す