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

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

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

標準偏差とは何か(文系向け直感)

「データが平均からどれだけ散らばっているか」を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つ):

  1. 基本統計量を表示: 平均・中央値・標準偏差・最小値・最大値
  2. IQR法で外れ値を検出: Q1, Q3, IQR を計算し、外れ値のインデックスと値を表示
  3. 外れ値を除外したデータで再度平均と標準偏差を計算し、元のデータと比較
  4. 外れ値を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(方向性)

pandasdescribe()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置換 → 中央値で補完が安全でよく使われる

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

標準偏差の計算手順(高校数学):

  1. 全データの平均を計算
  2. 各データと平均の差を求める(「偏差」)
  3. 偏差を2乗する(マイナスを消すため)
  4. 2乗した偏差の平均を取る(「分散」)
  5. 分散の平方根を取る(「標準偏差」)

Pythonでは df['salary'].std() が自動計算してくれる。

(※ Pythonのデフォルトは「不偏標準偏差」= 分母が n-1)


🏆 Kaggleでの実践的な使い方

  1. EDA の最初のステップ: describe() + 外れ値チェックは定番
  2. 特徴量エンジニアリング: 外れ値をlog変換(np.log1p())で無害化する手法も頻出
  3. 外れ値は情報: 不正検知・高額取引などでは外れ値そのものが重要なシグナル

Titanic コンペの例:

Fare(運賃)列に512という外れ値がある。削除より np.log1p(fare) で変換する方がスコアが上がることが多い。


⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
外れ値は必ず削除する「異常なデータは不要」と思う外れ値が重要な情報を持つケースも多い
標準偏差 = 平均からの差「標準」という言葉から誤解標準偏差 = 分散の平方根(二乗の平均の根)
std() の結果が違う手計算と合わないPandasは不偏標準偏差(n-1)を使う
上限・下限の向きを間違える不等号のミス外れ値: 下限より小さい OR 上限より大きい

🚀 次のステップ

  • 発展: 外れ値を Z スコア法(平均からの標準偏差数)で検出する方法
  • 次回予告: 確率の基礎(確率とは何か)へ向けて Phase 1 テーマ 3 に進む予定

🎯 自己評価

自分の回答

気づき・メモ