Day 015 — 分散・標準偏差・外れ値

2026-04-24 白 / Phase 1 分析 分散・標準偏差・外れ値

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

「ばらつき」を数値で表す

前回は「データの広がり(分散・標準偏差)」の計算を学んだ。今日は 「外れ値」 を実際にデータから見つける分析スキルを身につける。

外れ値(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 で実装し、分析結果を理解してください。

  1. 基本統計量を確認する: mean, median, std, min, max, Q1, Q3を計算して表示する
  2. IQR法で外れ値を検出する: 外れ値と判断されるデータを抽出して表示する
  3. Zスコア法で外れ値を検出する: |Zスコア| > 3 のデータを抽出して表示する
  4. 外れ値を除いた平均を計算する: IQR法で外れ値を除いた後の平均値を、除く前の平均と比較する
  5. 箱ひげ図を描く: 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 解説

1
基本統計量で全体像をつかむ

まず describe() または個別に mean() / median() / std() を計算する。今回のデータでは 平均が約 12,000 円近く になるはずだが、中央値は約 2,800 円前後 になる。この「平均と中央値の大きな差」が外れ値の存在を示すシグナル。

2
IQR法の閾値を計算する
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 円が外れ値として検出される。

3
Zスコア法で確認する

平均が約 12,000 円、標準偏差が約 20,000 円になる(外れ値が平均・stdを押し上げるため)。

Z スコア: (98000 - 12000) / 20000 ≒ 4.3 → |Z| > 3 → 外れ値として検出。

4
外れ値除去後の影響を確認
  • 外れ値あり平均: ≒ 12,000 円
  • 外れ値なし平均: ≒ 2,800 円
  • この差(約 9,000 円)が「外れ値が平均をどれだけ歪めるか」を示す
5
箱ひげ図で視覚確認

外れ値あり/なしの2つを並べると、外れ値がいかに箱の形を歪めているかが一目でわかる。


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

IQR の「1.5倍」はどこから来たの?

→ 統計学者 John Tukey が 1977 年に提唱した経験則。「正規分布に近いデータなら、この範囲外のデータは全体の約 0.7% しかない」という根拠がある。絶対ルールではなく「まず試してみる基準」として使われる。

Zスコアの「3」も経験則?

→ 正規分布では、平均±3σ の範囲に 99.7% のデータが収まる(3シグマ規則)。つまり Z > 3 になるのは理論上 0.3% 以下のレアケース → 外れ値と判断する。


🏆 Kaggleでの実践的な使い方

  1. 特徴量エンジニアリング: 外れ値をそのまま使うとモデルが歪む → log変換 (np.log1p) で影響を軽減することが多い
  2. Notebook の EDA セクション: コンペの上位解法では必ず箱ひげ図 or 外れ値確認をしている
  3. クリーニング判断: 外れ値が「入力ミス」か「本物の極端なケース」かを判断する →コンペ説明文・データ定義書を必ず読む

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
外れ値は常に削除すべき「ノイズ = 削除」という思い込み本物の極端なデータの場合は残すべき。コンペでは削除より変換(log)が多い
IQR法で全部の外れ値が見つかる1つの方法で完結すると思っているIQR と Z スコアで結果が異なる場合がある。両方確認が推奨
std() がゼロ除算エラーデータが全部同じ値の場合実際のKaggleデータでは定数列に注意。事前確認が必要
箱ひげ図の「ひげ」の意味を誤解最大・最小と混同ひげ端は「Q1/Q3 ± 1.5×IQR」であり、最大・最小ではない

🚀 次のステップ

  • 発展: 外れ値を log 変換 (np.log1p) して正規分布に近づける実装
  • 次回予告: 「確率の基礎(確率とは何か)」 — 機械学習での確率の直感的理解

🎯 自己評価

自分の回答

気づき・メモ