Day 012 — 分散・標準偏差・外れ値(コーディング実践)

2026-04-21 白 / Phase 1 コーディング 分散・標準偏差・外れ値

📚 背景知識(読んでから問題へ)

外れ値とは?

「他のデータからかけ離れた値」のこと。例えば、社員10人の月収が全員30万円なのに1人だけ300万円だったら、その1人が外れ値。

外れ値を放置すると機械学習モデルの精度が大きく下がるため、データサイエンスでは「外れ値の発見と処理」は必須スキル

IQR法(四分位範囲を使った外れ値検出)

最もよく使われる外れ値検出の方法:

第1四分位数 (Q1) = データの下から25%の値
第3四分位数 (Q3) = データの上から75%の値
IQR = Q3 - Q1  ← データの「真ん中50%の幅」

外れ値の判定:
  Q1 - 1.5 × IQR より小さい → 外れ値
  Q3 + 1.5 × IQR より大きい → 外れ値

箱ひげ図(boxplot)の「ひげ」はこの範囲を表している。

Zスコア法

「平均から何標準偏差離れているか」で外れ値を判定:

Zスコア = (データ - 平均) / 標準偏差
|Zスコア| > 3 → 外れ値(正規分布の99.7%の範囲外)

📝 問題

以下のタスクをPythonで実装してください。

データ: 架空の不動産価格データ(20件)

import numpy as np
import pandas as pd

np.random.seed(42)
prices = [300, 280, 320, 295, 310, 305, 290, 315, 285, 300,
          298, 308, 312, 295, 302, 288, 5000, 299, 307, 293]
# ↑ 5000万円が外れ値
df = pd.DataFrame({'price': prices})

実装タスク:

  1. price 列の平均・中央値・標準偏差を求める(外れ値を含んだまま)
  2. IQR法で外れ値を検出し、外れ値のインデックスと値を出力する
  3. Zスコア法(閾値: |Z| > 2)で外れ値を検出する
  4. 外れ値を除いた場合の平均・中央値を求め、外れ値ありと比較する

期待する出力例:

▶ 出力を見る
=== 外れ値あり ===
平均: 549.85
中央値: 300.5
標準偏差: 1046.34

=== IQR法 外れ値検出 ===
外れ値インデックス: [16]
外れ値: [5000]
下限: 247.25, 上限: 347.75

=== Zスコア法 外れ値検出 ===
外れ値インデックス: [16]
外れ値: [5000]

=== 外れ値除去後 ===
平均: 299.05
中央値: 299.5
平均の変化: +250.80(外れ値による影響)

🔍 ヒント(段階的開示)

ヒント1(方向性)

numpypandas を組み合わせて使う。Q1/Q3は np.percentile() または df.quantile() で求められる。

ヒント2(アプローチ)
  • IQR法: Q1 = df['price'].quantile(0.25), Q3 = df['price'].quantile(0.75), IQR = Q3 - Q1
  • Zスコア: z = (df['price'] - df['price'].mean()) / df['price'].std()
  • 外れ値フィルタ: ブール型マスクを使う(例: df[df['price'] < lower_bound]
ヒント3(コード骨格)
# IQR法
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers_iqr = df[(df['price'] < lower) | (df['price'] > upper)]

# Zスコア法
z_scores = (df['price'] - df['price'].mean()) / df['price'].std()
outliers_z = df[abs(z_scores) > 2]

# 外れ値除去
df_clean = df[(df['price'] >= lower) & (df['price'] <= upper)]

模範解答

import numpy as np
import pandas as pd

np.random.seed(42)
prices = [300, 280, 320, 295, 310, 305, 290, 315, 285, 300,
          298, 308, 312, 295, 302, 288, 5000, 299, 307, 293]
df = pd.DataFrame({'price': prices})

# 1. 外れ値ありの基本統計
print("=== 外れ値あり ===")
print(f"平均: {df['price'].mean():.2f}")
print(f"中央値: {df['price'].median():.1f}")
print(f"標準偏差: {df['price'].std():.2f}")

# 2. IQR法
print("\n=== IQR法 外れ値検出 ===")
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

outliers_iqr = df[(df['price'] < lower) | (df['price'] > upper)]
print(f"外れ値インデックス: {outliers_iqr.index.tolist()}")
print(f"外れ値: {outliers_iqr['price'].tolist()}")
print(f"下限: {lower:.2f}, 上限: {upper:.2f}")

# 3. Zスコア法
print("\n=== Zスコア法 外れ値検出 ===")
z_scores = (df['price'] - df['price'].mean()) / df['price'].std()
outliers_z = df[abs(z_scores) > 2]
print(f"外れ値インデックス: {outliers_z.index.tolist()}")
print(f"外れ値: {outliers_z['price'].tolist()}")

# 4. 外れ値除去後の統計
print("\n=== 外れ値除去後 ===")
df_clean = df[(df['price'] >= lower) & (df['price'] <= upper)]
mean_before = df['price'].mean()
mean_after = df_clean['price'].mean()
print(f"平均: {mean_after:.2f}")
print(f"中央値: {df_clean['price'].median():.1f}")
print(f"平均の変化: +{mean_before - mean_after:.2f}(外れ値による影響)")

🪜 Step-by-Step 解説

1
基本統計の確認

外れ値がある状態での平均は約550万円。でも実際の価格はほぼ300万円台。

中央値は300.5万円で、外れ値の影響をほとんど受けていない。

「外れ値があるときは平均より中央値の方が実態に近い」

2
IQR法で外れ値を検出

Q1(下25%)≈ 291.25万、Q3(上25%)≈ 310.75万

IQR = 310.75 - 291.25 = 19.5

下限 = 291.25 - 29.25 = 262万、上限 = 310.75 + 29.25 = 340万

5000万円は上限(340万)を大幅に超える → 外れ値として検出

3
Zスコア法で外れ値を検出

平均: 550万、標準偏差: 1046万

5000万のZスコア = (5000 - 550) / 1046 ≈ 4.25 → |Z| > 2 なので外れ値

4
外れ値除去の効果を比較

外れ値除去後の平均は299万円 → 実態に即した値になった。

平均値が250万円以上「盛られていた」ことがわかる。


📐 数学・統計の補足(文系向け)

  • 四分位数: データを4等分したときの区切り値。Q1=下から25%地点、Q2=50%(中央値)、Q3=75%地点
  • IQRの「1.5倍」はなぜ1.5か: 統計学者のJohn Tukeyが正規分布に基づいて経験的に決めた値。ほぼ慣習。
  • Zスコア > 3 vs > 2: 正規分布で|Z|>3は全体の0.3%しかない(より厳しい基準)。データが少ないときは|Z|>2を使うことも多い

🏆 Kaggleでの実践的な使い方

# Kaggleコンペでよくある前処理パターン
# House Prices コンペ: 価格データの外れ値除去
train = pd.read_csv('train.csv')

# 目的変数の外れ値を可視化してから除去
Q1 = train['SalePrice'].quantile(0.25)
Q3 = train['SalePrice'].quantile(0.75)
IQR = Q3 - Q1
train_clean = train[
    (train['SalePrice'] >= Q1 - 1.5 * IQR) &
    (train['SalePrice'] <= Q3 + 1.5 * IQR)
]
print(f"除去前: {len(train)}件 → 除去後: {len(train_clean)}件")

重要: 外れ値の「除去」が常に正解ではない。ビジネス的に正当な高額物件が外れ値として除去されることもある。コンペでは除去前後でCV(交差検証)スコアを比較して判断する。


⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
外れ値は必ず除去すべき「汚いデータ=除去」という思い込み除去せずクリッピング(上限値に置換)する方が良い場合もある
Zスコア法の閾値は常に3教科書通りにやってしまうデータ量・分布によって2〜3を柔軟に選ぶ
df.std() の分母デフォルトは標本標準偏差(n-1)scipy.stats.zscore は別の実装なので混同注意

🚀 次のステップ

  • 発展: 外れ値の「クリッピング」(df.clip(lower, upper) でIQR範囲内に収める)
  • 次回予告: 確率の基礎(確率とは何か)

🎯 自己評価

自分の回答

気づき・メモ