📚 背景知識(読んでから問題へ)
復習: 分散と標準偏差
- 分散: データが平均からどれだけ「ばらついているか」を表す数値。各データの「平均からのズレ²」の平均
- 標準偏差: 分散の√。元のデータと「同じ単位」になる
- なぜ²を使うか: ズレにはプラスもマイナスもあるので、そのまま足すと打ち消しあってしまうから
外れ値とは
平均から「極端に離れたデータ」のこと。外れ値があるとモデルの精度が下がったり、統計量が歪んだりする。
IQR(四分位範囲)による外れ値検出
最もよく使われる外れ値検出の方法:
Q1 = 25パーセンタイル(下から25%の位置)
Q3 = 75パーセンタイル(下から75%の位置)
IQR = Q3 - Q1
外れ値 = Q1 - 1.5×IQR より小さい OR Q3 + 1.5×IQR より大きい
📝 問題
以下のコードを完成させ、Titanicデータの Age(年齢)列について:
- 平均・中央値・標準偏差 を計算して表示する
- IQR法 で外れ値を検出し、外れ値の数と値を表示する
- 外れ値を除外 したデータの平均・標準偏差を計算して比較する
- 箱ひげ図(boxplot) で外れ値を可視化する
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Titanicデータの読み込み(seabornのサンプルデータを使用)
df = sns.load_dataset('titanic')
# Age列(欠損値を除外)
age = df['age'].dropna()
# ===== ここから実装 =====
# 1. 基本統計量
mean_age = ___
median_age = ___
std_age = ___
print(f"平均: {mean_age:.2f}")
print(f"中央値: {median_age:.2f}")
print(f"標準偏差: {std_age:.2f}")
# 2. IQR法で外れ値を検出
Q1 = ___
Q3 = ___
IQR = ___
lower_bound = ___
upper_bound = ___
outliers = age[___ | ___]
print(f"\n外れ値の数: {len(outliers)}")
print(f"外れ値: {sorted(outliers.values)}")
# 3. 外れ値除外後の統計量
age_clean = age[___ & ___]
print(f"\n【外れ値除外後】")
print(f"平均: {age_clean.mean():.2f}")
print(f"標準偏差: {age_clean.std():.2f}")
# 4. 箱ひげ図
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].___ # 元データの boxplot
axes[0].set_title('Age(元データ)')
axes[1].___ # 外れ値除外後の boxplot
axes[1].set_title('Age(外れ値除外後)')
plt.tight_layout()
plt.show()
🔍 ヒント(段階的開示)
ヒント1(方向性)
pandas Series の .mean() .median() .std() .quantile() を使う。
外れ値のフィルタリングは |(OR)と &(AND)の論理演算子を使う。
ヒント2(アプローチ)
Q1 = age.quantile(0.25) # 25パーセンタイル
Q3 = age.quantile(0.75) # 75パーセンタイル
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 外れ値は lower_bound 未満 OR upper_bound 超
outliers = age[(age < lower_bound) | (age > upper_bound)]
ヒント3(コード骨格)
# 外れ値除外(both conditions must be TRUE)
age_clean = age[(age >= lower_bound) & (age <= upper_bound)]
# boxplot
axes[0].boxplot(age) # matplotlib の boxplot
axes[1].boxplot(age_clean)
✅ 模範解答
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
df = sns.load_dataset('titanic')
age = df['age'].dropna()
# 1. 基本統計量
mean_age = age.mean()
median_age = age.median()
std_age = age.std()
print(f"平均: {mean_age:.2f}")
print(f"中央値: {median_age:.2f}")
print(f"標準偏差: {std_age:.2f}")
# 2. IQR法で外れ値を検出
Q1 = age.quantile(0.25)
Q3 = age.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = age[(age < lower_bound) | (age > upper_bound)]
print(f"\n外れ値の数: {len(outliers)}")
print(f"外れ値: {sorted(outliers.values)}")
# 3. 外れ値除外後の統計量
age_clean = age[(age >= lower_bound) & (age <= upper_bound)]
print(f"\n【外れ値除外後】")
print(f"平均: {age_clean.mean():.2f}")
print(f"標準偏差: {age_clean.std():.2f}")
# 4. 箱ひげ図
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].boxplot(age)
axes[0].set_title('Age(元データ)')
axes[1].boxplot(age_clean)
axes[1].set_title('Age(外れ値除外後)')
plt.tight_layout()
plt.show()
期待される出力例(実際の数値は近似):
▶ 出力を見る
平均: 29.70
中央値: 28.00
標準偏差: 14.53
外れ値の数: 1
外れ値: [80.0]
【外れ値除外後】
平均: 29.61
標準偏差: 13.89🪜 Step-by-Step 解説
1
基本統計量の計算
mean_age = age.mean() # 全データの合計 ÷ データ数
median_age = age.median() # データを並べたとき真ん中の値
std_age = age.std() # 分散の√(平均からのバラつき)
ポイント: 平均 > 中央値 → 大きな値側に引っ張られている(右裾が重い分布)
2
IQR の計算
Q1 = age.quantile(0.25) # 下から25%の位置
Q3 = age.quantile(0.75) # 下から75%の位置
IQR = Q3 - Q1 # 中央50%のデータが収まる範囲
なぜ 1.5×IQR か: 統計学者 John Tukey が経験則として提案した値。正規分布では約99.3%のデータが収まる。
3
外れ値フィルタリングの論理
# 外れ値(除外したいもの): lower 未満 OR upper 超
outliers = age[(age < lower_bound) | (age > upper_bound)]
# 正常値(残したいもの): lower 以上 AND upper 以下
age_clean = age[(age >= lower_bound) & (age <= upper_bound)]
よく混同するポイント: 除外のときは |(OR)、残すときは &(AND)
4
箱ひげ図の読み方
|--[Q1]===中央値====[Q3]--| ← 箱の部分がIQR
↑箱ひげ図の「髭(ひげ)」= 1.5×IQR
箱から外れた点 = 外れ値(●で表示される)
📐 数学・統計の補足(文系向け)
IQR を使う理由(平均・標準偏差じゃダメなの?)
- 平均・標準偏差は外れ値に影響される → 「外れ値を検出するのに外れ値に影響される指標を使う」は矛盾
- IQR は「中央50%のデータ」だけを見るので、外れ値に頑健(ロバスト)
Kaggleでよく見る「ロバスト統計」
| 感度高い(外れ値に弱い) | ロバスト(外れ値に強い) |
|---|---|
| 平均 | 中央値 |
| 標準偏差 | IQR |
| 最小・最大値 | 5・95パーセンタイル |
🏆 Kaggleでの実践的な使い方
特徴量エンジニアリングでの外れ値処理(Phase 3で本格的に学ぶ)
# よく使われる3つの方法
# 1. 外れ値を除外(データ量が多い場合)
df_clean = df[(df['Age'] >= lower_bound) & (df['Age'] <= upper_bound)]
# 2. クリッピング(外れ値を上限・下限に丸める)
df['Age_clipped'] = df['Age'].clip(lower=lower_bound, upper=upper_bound)
# 3. 変換(対数変換で外れ値の影響を緩和)
df['Age_log'] = np.log1p(df['Age'])
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 | |
|---|---|---|---|
& と and を混同 | Python の and は論理演算子 | pandas フィルタでは必ず & と ` | を使う(and/or` はNG) |
| 外れ値は必ず除外すべき | 「外れ値 = ゴミ」という誤解 | 外れ値が重要な情報を持つケースもある(不正取引検出など) | |
.std() の デフォルト | ddof=1(不偏分散)vs. ddof=0(標本分散) | pandas はデフォルト ddof=1(母集団が未知のサンプルを分析するとき) |
🚀 次のステップ
- 発展: Zスコア法(
(x - mean) / stdが 3以上なら外れ値)で同じデータを検出し、IQR法と結果を比較する - 次回予告: 確率の基礎(確率とは何か、Kaggleで役に立つ確率の直感)