📚 背景知識(読んでから問題へ)
「ばらつき」を数値で表す
前回は「データの広がり(分散・標準偏差)」の計算を学んだ。今日は 「外れ値」 を実際にデータから見つける分析スキルを身につける。
外れ値(Outlier)とは?
→ 他のデータと比べて極端に大きい/小さい値のこと。
例: [1, 2, 2, 3, 3, 3, 4, 100] の「100」が外れ値。
なぜ外れ値が問題になるか?
- 平均値が大きく引っ張られる(中央値は影響を受けにくい)
- 機械学習モデルが誤学習する
- そのデータが「本物のデータ」か「入力ミス」かを判断する必要がある
外れ値の検出方法 2つ:
| 方法 | 考え方 | コード |
|---|---|---|
| IQR法 | 四分位数から±1.5倍の範囲外を外れ値とする | Q1 - 1.5×IQR, Q3 + 1.5×IQR |
| Zスコア法 | 平均から±3標準偏差を超えたら外れ値 | (x - mean) / std > 3 |
IQR(四分位範囲)とは?
- Q1 = データを小さい順に並べた時の「25%地点の値」
- Q3 = 「75%地点の値」
- IQR = Q3 - Q1(データの「真ん中50%の広がり」)
箱ひげ図(Box Plot)は IQR をそのまま可視化したグラフ。
📝 問題
以下のデータは、あるECサイトの 1日の注文金額(円) のサンプルデータです。
import numpy as np
import pandas as pd
orders = [1200, 3400, 2800, 4100, 3200, 1500, 2200, 98000, 2900, 3100,
1800, 2600, 4300, 3700, 1100, 2400, 3600, 2000, 89000, 2700,
3300, 1900, 2500, 3800, 4200, 1600, 2100, 3000, 4500, 2300]
df = pd.DataFrame({'order_amount': orders})
タスク
以下をすべて Python で実装し、分析結果を理解してください。
- 基本統計量を確認する:
mean,median,std,min,max,Q1,Q3を計算して表示する - IQR法で外れ値を検出する: 外れ値と判断されるデータを抽出して表示する
- Zスコア法で外れ値を検出する: |Zスコア| > 3 のデータを抽出して表示する
- 外れ値を除いた平均を計算する: IQR法で外れ値を除いた後の平均値を、除く前の平均と比較する
- 箱ひげ図を描く:
seabornまたはmatplotlibで外れ値が見えるように可視化する
制限時間: 20分
🔍 ヒント(段階的開示)
ヒント1(方向性)
外れ値の検出は「閾値を計算する」→「その閾値を超えるデータを取り出す」という2ステップ。pandas の Boolean Indexing が使える。
ヒント2(アプローチ)
IQR法:
Q1 = df['order_amount'].quantile(0.25)
Q3 = df['order_amount'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
Zスコア法:
mean = df['order_amount'].mean()
std = df['order_amount'].std()
df['z_score'] = (df['order_amount'] - mean) / std
ヒント3(コード骨格)
# 1. 基本統計量
print(df['order_amount'].describe())
print("Q1:", df['order_amount'].quantile(0.25))
print("Q3:", df['order_amount'].quantile(0.75))
# 2. IQR法
Q1 = df['order_amount'].quantile(0.25)
Q3 = df['order_amount'].quantile(0.75)
IQR = Q3 - Q1
outliers_iqr = df[(df['order_amount'] < Q1 - 1.5 * IQR) | (df['order_amount'] > Q3 + 1.5 * IQR)]
print("外れ値 (IQR):\n", outliers_iqr)
# 3. Zスコア法
df['z_score'] = (df['order_amount'] - df['order_amount'].mean()) / df['order_amount'].std()
outliers_z = df[df['z_score'].abs() > 3]
print("外れ値 (Z):\n", outliers_z)
✅ 模範解答
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
orders = [1200, 3400, 2800, 4100, 3200, 1500, 2200, 98000, 2900, 3100,
1800, 2600, 4300, 3700, 1100, 2400, 3600, 2000, 89000, 2700,
3300, 1900, 2500, 3800, 4200, 1600, 2100, 3000, 4500, 2300]
df = pd.DataFrame({'order_amount': orders})
# ─────────────────────────────────────────
# 1. 基本統計量
# ─────────────────────────────────────────
Q1 = df['order_amount'].quantile(0.25)
Q3 = df['order_amount'].quantile(0.75)
IQR = Q3 - Q1
print("=== 基本統計量 ===")
print(f"平均値 : {df['order_amount'].mean():,.0f} 円")
print(f"中央値 : {df['order_amount'].median():,.0f} 円")
print(f"標準偏差 : {df['order_amount'].std():,.0f} 円")
print(f"最小値 : {df['order_amount'].min():,.0f} 円")
print(f"最大値 : {df['order_amount'].max():,.0f} 円")
print(f"Q1 (25%): {Q1:,.0f} 円")
print(f"Q3 (75%): {Q3:,.0f} 円")
print(f"IQR : {IQR:,.0f} 円")
# ─────────────────────────────────────────
# 2. IQR法による外れ値検出
# ─────────────────────────────────────────
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = df[(df['order_amount'] < lower_bound) | (df['order_amount'] > upper_bound)]
normal_iqr = df[(df['order_amount'] >= lower_bound) & (df['order_amount'] <= upper_bound)]
print(f"\n=== IQR法 ===")
print(f"下限閾値: {lower_bound:,.0f} 円")
print(f"上限閾値: {upper_bound:,.0f} 円")
print(f"外れ値:\n{outliers_iqr}")
# ─────────────────────────────────────────
# 3. Zスコア法による外れ値検出
# ─────────────────────────────────────────
df['z_score'] = (df['order_amount'] - df['order_amount'].mean()) / df['order_amount'].std()
outliers_z = df[df['z_score'].abs() > 3]
print(f"\n=== Zスコア法(|Z| > 3)===")
print(outliers_z[['order_amount', 'z_score']])
# ─────────────────────────────────────────
# 4. 外れ値を除いた平均との比較
# ─────────────────────────────────────────
mean_with_outliers = df['order_amount'].mean()
mean_without_outliers = normal_iqr['order_amount'].mean()
print(f"\n=== 平均値の比較 ===")
print(f"外れ値あり: {mean_with_outliers:,.0f} 円")
print(f"外れ値なし: {mean_without_outliers:,.0f} 円")
print(f"差 : {mean_with_outliers - mean_without_outliers:,.0f} 円")
# ─────────────────────────────────────────
# 5. 箱ひげ図
# ─────────────────────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 外れ値あり
sns.boxplot(y=df['order_amount'], ax=axes[0])
axes[0].set_title("外れ値あり(全データ)")
axes[0].set_ylabel("注文金額(円)")
# 外れ値なし
sns.boxplot(y=normal_iqr['order_amount'], ax=axes[1])
axes[1].set_title("外れ値なし(IQR法で除去後)")
axes[1].set_ylabel("注文金額(円)")
plt.tight_layout()
plt.savefig("boxplot_orders.png", dpi=100)
plt.show()
print("\n箱ひげ図を保存しました: boxplot_orders.png")
🪜 Step-by-Step 解説
まず describe() または個別に mean() / median() / std() を計算する。今回のデータでは 平均が約 12,000 円近く になるはずだが、中央値は約 2,800 円前後 になる。この「平均と中央値の大きな差」が外れ値の存在を示すシグナル。
Q1 ≒ 1,975 円
Q3 ≒ 3,875 円
IQR = 1,900 円
下限 = 1,975 - 2,850 = -875 円(実質 0 以上なので下限は問題なし)
上限 = 3,875 + 2,850 = 6,725 円
→ 98,000 円と 89,000 円が外れ値として検出される。
平均が約 12,000 円、標準偏差が約 20,000 円になる(外れ値が平均・stdを押し上げるため)。
Z スコア: (98000 - 12000) / 20000 ≒ 4.3 → |Z| > 3 → 外れ値として検出。
- 外れ値あり平均: ≒ 12,000 円
- 外れ値なし平均: ≒ 2,800 円
- この差(約 9,000 円)が「外れ値が平均をどれだけ歪めるか」を示す
外れ値あり/なしの2つを並べると、外れ値がいかに箱の形を歪めているかが一目でわかる。
📐 数学・統計の補足(文系向け)
IQR の「1.5倍」はどこから来たの?
→ 統計学者 John Tukey が 1977 年に提唱した経験則。「正規分布に近いデータなら、この範囲外のデータは全体の約 0.7% しかない」という根拠がある。絶対ルールではなく「まず試してみる基準」として使われる。
Zスコアの「3」も経験則?
→ 正規分布では、平均±3σ の範囲に 99.7% のデータが収まる(3シグマ規則)。つまり Z > 3 になるのは理論上 0.3% 以下のレアケース → 外れ値と判断する。
🏆 Kaggleでの実践的な使い方
- 特徴量エンジニアリング: 外れ値をそのまま使うとモデルが歪む → log変換 (
np.log1p) で影響を軽減することが多い - Notebook の EDA セクション: コンペの上位解法では必ず箱ひげ図 or 外れ値確認をしている
- クリーニング判断: 外れ値が「入力ミス」か「本物の極端なケース」かを判断する →コンペ説明文・データ定義書を必ず読む
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 外れ値は常に削除すべき | 「ノイズ = 削除」という思い込み | 本物の極端なデータの場合は残すべき。コンペでは削除より変換(log)が多い |
| IQR法で全部の外れ値が見つかる | 1つの方法で完結すると思っている | IQR と Z スコアで結果が異なる場合がある。両方確認が推奨 |
std() がゼロ除算エラー | データが全部同じ値の場合 | 実際のKaggleデータでは定数列に注意。事前確認が必要 |
| 箱ひげ図の「ひげ」の意味を誤解 | 最大・最小と混同 | ひげ端は「Q1/Q3 ± 1.5×IQR」であり、最大・最小ではない |
🚀 次のステップ
- 発展: 外れ値を log 変換 (
np.log1p) して正規分布に近づける実装 - 次回予告: 「確率の基礎(確率とは何か)」 — 機械学習での確率の直感的理解