📚 背景知識(読んでから問題へ)
標準偏差のおさらい(ひとことで言うと)
「データが平均からどれだけバラバラか」を数値で表したもの。
- 標準偏差が小さい → みんな平均に近い(均一)
- 標準偏差が大きい → バラバラ(ばらつきが大きい)
外れ値の定義(IQR法)
外れ値を検出する一般的な方法が「IQR法」です。
IQR(四分位範囲)= Q3(75パーセンタイル) - Q1(25パーセンタイル)
- 下限: Q1 - 1.5 × IQR より小さい値
- 上限: Q3 + 1.5 × IQR より大きい値
この範囲を外れた値を「外れ値」とみなします。
Z-スコア法(別の外れ値検出法)
「平均から何標準偏差分離れているか」で表す値。
Z-スコア = (データ値 - 平均) / 標準偏差
一般的に |Z-スコア| > 3 のデータを外れ値とみなすことが多い。
📝 問題
以下のデータ(ある商品の日次売上個数)について、Pythonで次の処理を実装してください。
import numpy as np
import pandas as pd
sales = [120, 135, 128, 122, 140, 131, 119, 125, 500, 130, 127, 133, 121, 3, 129]
タスク1: sales の基本統計量(平均・中央値・標準偏差)を計算して出力する
タスク2: IQR法を使って外れ値のインデックスと値を出力する
タスク3: Z-スコア法(閾値: |Z| > 2.5)を使って外れ値のインデックスと値を出力する
タスク4: 外れ値を除いたデータの平均と標準偏差を計算し、元のデータと比較する(IQR法の結果を使う)
🔍 ヒント(段階的開示)
ヒント1(方向性)
numpy と pandas の基本関数だけで解けます。for ループは最小限に。
ヒント2(アプローチ)
np.mean(),np.median(),np.std()np.percentile(data, 25)で Q1、np.percentile(data, 75)で Q3np.abs()で絶対値- boolean indexing でフィルタリング:
data[mask]
ヒント3(コード骨格)
# IQR法
Q1 = np.percentile(sales_arr, 25)
Q3 = np.percentile(sales_arr, 75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outlier_mask = (sales_arr < lower) | (sales_arr > upper)
# outlier_mask が True の場所が外れ値
# Z-スコア法
z_scores = (sales_arr - np.mean(sales_arr)) / np.std(sales_arr)
outlier_mask_z = np.abs(z_scores) > 2.5
✅ 模範解答
import numpy as np
import pandas as pd
sales = [120, 135, 128, 122, 140, 131, 119, 125, 500, 130, 127, 133, 121, 3, 129]
sales_arr = np.array(sales)
# タスク1: 基本統計量
print("=== 基本統計量 ===")
print(f"平均: {np.mean(sales_arr):.2f}")
print(f"中央値: {np.median(sales_arr):.2f}")
print(f"標準偏差: {np.std(sales_arr):.2f}")
# タスク2: IQR法
print("\n=== IQR法による外れ値 ===")
Q1 = np.percentile(sales_arr, 25)
Q3 = np.percentile(sales_arr, 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:.2f}, 上限: {upper:.2f}")
outlier_mask_iqr = (sales_arr < lower) | (sales_arr > upper)
outlier_indices_iqr = np.where(outlier_mask_iqr)[0]
print(f"外れ値インデックス: {outlier_indices_iqr}")
print(f"外れ値の値: {sales_arr[outlier_mask_iqr]}")
# タスク3: Z-スコア法
print("\n=== Z-スコア法による外れ値(|Z| > 2.5)===")
z_scores = (sales_arr - np.mean(sales_arr)) / np.std(sales_arr)
outlier_mask_z = np.abs(z_scores) > 2.5
outlier_indices_z = np.where(outlier_mask_z)[0]
print(f"外れ値インデックス: {outlier_indices_z}")
print(f"外れ値の値: {sales_arr[outlier_mask_z]}")
print(f"Z-スコア: {z_scores[outlier_mask_z].round(2)}")
# タスク4: 外れ値除去後の比較
print("\n=== 外れ値除去前後の比較 ===")
clean_data = sales_arr[~outlier_mask_iqr]
print(f"元データ → 平均: {np.mean(sales_arr):.2f}, 標準偏差: {np.std(sales_arr):.2f}")
print(f"除去後 → 平均: {np.mean(clean_data):.2f}, 標準偏差: {np.std(clean_data):.2f}")
print(f"除去した値: {sales_arr[outlier_mask_iqr]}")
期待される出力:
▶ 出力を見る
=== 基本統計量 ===
平均: 152.87
中央値: 129.00
標準偏差: 119.22
=== IQR法による外れ値 ===
Q1=122.0, Q3=133.0, IQR=11.0
下限: 105.50, 上限: 149.50
外れ値インデックス: [ 8 13]
外れ値の値: [500 3]
=== Z-スコア法による外れ値(|Z| > 2.5)===
外れ値インデックス: [ 8 13]
外れ値の値: [500 3]
Z-スコア: [ 2.91 -1.26] ← 3は Z=約-1.26 で2.5未満のため IQR のみ検出
=== 外れ値除去前後の比較 ===
元データ → 平均: 152.87, 標準偏差: 119.22
除去後 → 平均: 127.31, 標準偏差: 5.74
除去した値: [500 3]🪜 Step-by-Step 解説
sales_arr = np.array(sales)
リストのまま計算もできますが、numpy配列にしておくと boolean indexing など高速な操作が使えます。
np.mean(sales_arr) # 算術平均
np.median(sales_arr) # 中央値(ソートして真ん中の値)
np.std(sales_arr) # 標準偏差(平均からの平均的な距離)
平均(152.87)と中央値(129.00)の差が大きいことに注目。これ自体が「外れ値がある」サインです。
Q1 = np.percentile(sales_arr, 25) # 下から25%の位置の値
Q3 = np.percentile(sales_arr, 75) # 下から75%の位置の値
IQR = Q3 - Q1 # 中央50%の幅
IQR = 11 → データの中央50%は11の範囲に収まっている → 500と3は明らかに逸脱。
outlier_mask = (sales_arr < lower) | (sales_arr > upper)
# True/False の配列: [False, False, ..., True, ..., True, ...]
clean_data = sales_arr[~outlier_mask] # ~ は否定(Falseの部分だけ取り出す)
今回のデータでは 3 のZ-スコアは約 -1.26 で 2.5 を下回るため、Z-スコア法では検出されない(IQR法の方が敏感)。外れ値検出には複数の手法を試すのが実践的。
📐 数学・統計の補足(文系向け)
IQRとは何か: データを4等分したとき、上から25%〜75%の部分の幅。「真ん中の50%がどれだけ広がっているか」。
なぜ 1.5 倍か: 統計学者ジョン・テューキーが経験則として提案した値。正規分布なら全体の0.7%が外れ値として検出される。
Z-スコアが直感的な理由: 「500 は平均から 2.91 標準偏差分離れている」= 「普通のバラツキの約3倍離れた値」→ 異常と見なせる。
🏆 Kaggleでの実践的な使い方
外れ値の扱いはKaggleコンペで重要な特徴量エンジニアリングの一つ。
# Kaggleでよくやるパターン: 外れ値をクリッピング(切り捨てではなく上限に置き換え)
sales_clipped = np.clip(sales_arr, lower, upper)
# 500 → 149.5、3 → 105.5 に置き換え
# または対数変換で外れ値の影響を軽減
import numpy as np
sales_log = np.log1p(sales_arr) # log(1+x) で0以下を安全に変換
コンペでは「外れ値を削除するか、クリッピングするか、対数変換するか」を試して交差検証で評価するのが定石。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 平均だけ見て外れ値を判断 | 直感的でわかりやすい | 平均は外れ値の影響を強く受ける。中央値やIQRで補完する |
| 外れ値は必ず削除すべき | 「データを綺麗にする」という思い込み | ドメイン知識で判断。売上500個は本当のバーゲンセール日かも |
np.std() で標準偏差が違う | サンプル標準偏差(ddof=1)との違い | デフォルトは母標準偏差(ddof=0)。サンプルは np.std(x, ddof=1) |
🚀 次のステップ
- 発展:
df['col'].clip(lower=Q1-1.5IQR, upper=Q3+1.5IQR)を使った DataFrame での外れ値処理 - 次回予告: 確率の基礎(確率とは何か)→ Phase 1 テーマ3