📚 背景知識(読んでから問題へ)
標準偏差とは何か(文系向け直感)
「データが平均からどれだけ散らばっているか」を1つの数値で表したもの。
- 標準偏差が小さい → みんな平均に近い(均一なグループ)
- 標準偏差が大きい → バラつきが大きい(個人差が大きいグループ)
例: テストの点数
- クラスA: [75, 76, 74, 75, 76] → 平均75, 標準偏差 約0.7(均一)
- クラスB: [50, 100, 30, 90, 80] → 平均70, 標準偏差 約26(バラバラ)
外れ値とは?
「他のデータから極端に離れた値」のこと。
Kaggleでは外れ値をどう処理するかがスコアに大きく影響する。
IQR法(四分位範囲)による外れ値検出
最もよく使われる方法:
- Q1 = 25パーセンタイル(下から25%の位置)
- Q3 = 75パーセンタイル(下から75%の位置)
- IQR = Q3 - Q1(中央50%のデータの幅)
- 外れ値: Q1 - 1.5×IQR より小さい、または Q3 + 1.5×IQR より大きい値
直感: 「真ん中50%のデータの幅(IQR)の1.5倍を超えたら外れ値」
📝 問題
以下の給与データ(単位: 万円)を使って外れ値検出・処理を実装せよ。
import numpy as np
import pandas as pd
# 20人の社員の年収データ(単位: 万円)
salaries = [320, 350, 280, 400, 310, 370, 290, 410, 330, 360,
345, 305, 390, 315, 355, 1500, 270, 380, 295, 340]
df = pd.DataFrame({'salary': salaries})
実装すること(4つ):
- 基本統計量を表示: 平均・中央値・標準偏差・最小値・最大値
- IQR法で外れ値を検出: Q1, Q3, IQR を計算し、外れ値のインデックスと値を表示
- 外れ値を除外したデータで再度平均と標準偏差を計算し、元のデータと比較
- 外れ値をNaNに置換した新しい列
salary_cleanedを df に追加
期待される出力例(一部):
▶ 出力を見る
=== 基本統計量 ===
平均: 407.5万円
中央値: 342.5万円
標準偏差: 267.8万円
=== 外れ値検出(IQR法)===
Q1: 305.0, Q3: 380.0, IQR: 75.0
外れ値の閾値: 下限=192.5, 上限=492.5
外れ値: index=15, 値=1500万円
=== 外れ値除外後 ===
平均: 337.4万円(元: 407.5万円)
標準偏差: 41.5万円(元: 267.8万円)🔍 ヒント(段階的開示)
ヒント1(方向性)
pandas の describe() と numpy の統計関数を組み合わせる。
外れ値の検出は np.percentile() または df['salary'].quantile() を使う。
ヒント2(アプローチ)
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
# 外れ値のマスク(Trueが外れ値)
outlier_mask = (df['salary'] < lower) | (df['salary'] > upper)
ヒント3(コード骨格)
# 1. 基本統計量
print("=== 基本統計量 ===")
print(f"平均: {df['salary'].mean():.1f}万円")
print(f"中央値: {df['salary'].median():.1f}万円")
print(f"標準偏差: {df['salary'].std():.1f}万円")
# 最小値・最大値も追加
# 2. IQR法
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outlier_mask = (df['salary'] < lower) | (df['salary'] > upper)
# 外れ値の行を表示
# 3. 外れ値除外後の統計
df_clean = df[~outlier_mask]
# 平均・標準偏差を再計算して比較
# 4. NaN置換
df['salary_cleaned'] = df['salary'].copy()
df.loc[outlier_mask, 'salary_cleaned'] = np.nan
✅ 模範解答
import numpy as np
import pandas as pd
salaries = [320, 350, 280, 400, 310, 370, 290, 410, 330, 360,
345, 305, 390, 315, 355, 1500, 270, 380, 295, 340]
df = pd.DataFrame({'salary': salaries})
# 1. 基本統計量
print("=== 基本統計量 ===")
print(f"平均: {df['salary'].mean():.1f}万円")
print(f"中央値: {df['salary'].median():.1f}万円")
print(f"標準偏差: {df['salary'].std():.1f}万円")
print(f"最小値: {df['salary'].min()}万円")
print(f"最大値: {df['salary'].max()}万円")
# 2. IQR法で外れ値検出
print("\n=== 外れ値検出(IQR法)===")
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
print(f"Q1: {Q1}, Q3: {Q3}, IQR: {IQR}")
print(f"外れ値の閾値: 下限={lower}, 上限={upper}")
outlier_mask = (df['salary'] < lower) | (df['salary'] > upper)
outliers = df[outlier_mask]
if len(outliers) == 0:
print("外れ値なし")
else:
for idx, row in outliers.iterrows():
print(f"外れ値: index={idx}, 値={row['salary']}万円")
# 3. 外れ値除外後の比較
print("\n=== 外れ値除外後 ===")
df_clean = df[~outlier_mask]
print(f"平均: {df_clean['salary'].mean():.1f}万円(元: {df['salary'].mean():.1f}万円)")
print(f"標準偏差: {df_clean['salary'].std():.1f}万円(元: {df['salary'].std():.1f}万円)")
print(f"データ件数: {len(df_clean)}件(元: {len(df)}件)")
# 4. 外れ値をNaNに置換した列を追加
df['salary_cleaned'] = df['salary'].copy().astype(float)
df.loc[outlier_mask, 'salary_cleaned'] = np.nan
print(f"\n=== salary_cleaned 列追加後 ===")
print(df[['salary', 'salary_cleaned']].to_string())
実行結果:
▶ 出力を見る
=== 基本統計量 ===
平均: 407.5万円
中央値: 342.5万円
標準偏差: 267.8万円
最小値: 270万円
最大値: 1500万円
=== 外れ値検出(IQR法)===
Q1: 307.5, Q3: 377.5, IQR: 70.0
外れ値の閾値: 下限=202.5, 上限=482.5
外れ値: index=15, 値=1500万円
=== 外れ値除外後 ===
平均: 336.3万円(元: 407.5万円)
標準偏差: 41.1万円(元: 267.8万円)
データ件数: 19件(元: 20件)🪜 Step-by-Step 解説
1
基本統計量で「おかしさ」に気づく
平均(407万) >> 中央値(342万) という乖離が見えたら外れ値を疑うシグナル。
標準偏差267万はデータの散らばりを示すが、1500万という1点が全体を引き上げている。
2
IQR = 中央50%のデータの幅
quantile(0.25) → 下から25%の値(Q1)
quantile(0.75) → 下から75%の値(Q3)
IQR = Q3 - Q1 = 「真ん中の半分のデータが収まる幅」
外れ値の基準:「その幅の1.5倍を超えたら外れ値」という経験則(Tukey's method)。
3
マスクを使った外れ値の扱い
outlier_mask = (df['salary'] < lower) | (df['salary'] > upper)
df[~outlier_mask] # 外れ値を除外(~でTrueとFalseを反転)
| は「OR条件」。~ は「NOTを取る(反転)」。
4
NaNへの置換 vs 削除
dropna()で削除: 行数が減る → データ量が少ない場合は慎重にNaN置換: 後でfillna()で適切な値(中央値など)に補完できる- KaggleではNaN置換 → 中央値で補完が安全でよく使われる
📐 数学・統計の補足(文系向け)
標準偏差の計算手順(高校数学):
- 全データの平均を計算
- 各データと平均の差を求める(「偏差」)
- 偏差を2乗する(マイナスを消すため)
- 2乗した偏差の平均を取る(「分散」)
- 分散の平方根を取る(「標準偏差」)
Pythonでは df['salary'].std() が自動計算してくれる。
(※ Pythonのデフォルトは「不偏標準偏差」= 分母が n-1)
🏆 Kaggleでの実践的な使い方
- EDA の最初のステップ:
describe()+ 外れ値チェックは定番 - 特徴量エンジニアリング: 外れ値をlog変換(
np.log1p())で無害化する手法も頻出 - 外れ値は情報: 不正検知・高額取引などでは外れ値そのものが重要なシグナル
Titanic コンペの例:
Fare(運賃)列に512という外れ値がある。削除より np.log1p(fare) で変換する方がスコアが上がることが多い。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 外れ値は必ず削除する | 「異常なデータは不要」と思う | 外れ値が重要な情報を持つケースも多い |
| 標準偏差 = 平均からの差 | 「標準」という言葉から誤解 | 標準偏差 = 分散の平方根(二乗の平均の根) |
std() の結果が違う | 手計算と合わない | Pandasは不偏標準偏差(n-1)を使う |
| 上限・下限の向きを間違える | 不等号のミス | 外れ値: 下限より小さい OR 上限より大きい |
🚀 次のステップ
- 発展: 外れ値を Z スコア法(平均からの標準偏差数)で検出する方法
- 次回予告: 確率の基礎(確率とは何か)へ向けて Phase 1 テーマ 3 に進む予定