📚 背景知識(読んでから問題へ)
昨日(Day007)は理論で分散・標準偏差・外れ値の概念を学んだ。
今日は Pythonで実際に計算・検出する コーディング問題。
よく使う関数
import numpy as np
data = [2, 4, 4, 4, 5, 5, 7, 9]
np.mean(data) # 平均
np.var(data) # 分散(標本分散、N で割る)
np.std(data) # 標準偏差(同上)
np.percentile(data, 25) # Q1(第1四分位数)
np.percentile(data, 75) # Q3(第3四分位数)
IQR法の実装フロー
1. Q1 = np.percentile(data, 25)
2. Q3 = np.percentile(data, 75)
3. IQR = Q3 - Q1
4. 下限 = Q1 - 1.5 * IQR
5. 上限 = Q3 + 1.5 * IQR
6. 範囲外のデータ = 外れ値
📝 問題
以下のコードを完成させてください。
import numpy as np
import pandas as pd
# 不動産価格データ(万円)
prices = [
1500, 1800, 2000, 1700, 1600,
1900, 2100, 1850, 1750, 1650,
2200, 1550, 1950, 8500, # 8500 は外れ値
1700, 1800, 2050, 1900, 1600, 1750
]
# --- タスク1: 基本統計量を計算 ---
mean_price = ___
std_price = ___
var_price = ___
print(f"平均: {mean_price:.0f}万円")
print(f"標準偏差: {std_price:.0f}万円")
print(f"分散: {var_price:.0f}万円²")
# --- タスク2: 3σルールで外れ値を検出 ---
lower_3sigma = ___
upper_3sigma = ___
outliers_3sigma = [p for p in prices if ___ or ___]
print(f"\n3σルール外れ値: {outliers_3sigma}")
# --- タスク3: IQR法で外れ値を検出 ---
q1 = ___
q3 = ___
iqr = ___
lower_iqr = ___
upper_iqr = ___
outliers_iqr = [p for p in prices if ___ or ___]
print(f"IQR法外れ値: {outliers_iqr}")
# --- タスク4: 外れ値を除いた平均を計算 ---
clean_prices = [p for p in prices if ___] # IQR法で外れ値除去
mean_clean = ___
print(f"\n外れ値除去後の平均: {mean_clean:.0f}万円")
print(f"外れ値による歪み: {mean_price - mean_clean:.0f}万円")
🔍 ヒント(段階的開示)
ヒント1(方向性)
np.mean(), np.std(), np.percentile() の基本的な使い方を確認する。外れ値検出は「閾値の計算」→「リスト内包表記でフィルタ」の2ステップ。
ヒント2(アプローチ)
- タスク2の閾値:
mean ± 3 * std - タスク3の閾値:
q1 - 1.5 iqrとq3 + 1.5 iqr - タスク4の
clean_pricesは「IQRの範囲内」だけを残す
ヒント3(コード骨格)
# タスク1
mean_price = np.mean(prices)
std_price = np.std(prices)
var_price = np.var(prices)
# タスク2
lower_3sigma = mean_price - 3 * std_price
upper_3sigma = mean_price + 3 * std_price
outliers_3sigma = [p for p in prices if p < lower_3sigma or p > upper_3sigma]
# タスク3
q1 = np.percentile(prices, 25)
q3 = np.percentile(prices, 75)
iqr = q3 - q1
lower_iqr = q1 - 1.5 * iqr
upper_iqr = q3 + 1.5 * iqr
✅ 模範解答
import numpy as np
prices = [
1500, 1800, 2000, 1700, 1600,
1900, 2100, 1850, 1750, 1650,
2200, 1550, 1950, 8500,
1700, 1800, 2050, 1900, 1600, 1750
]
# タスク1: 基本統計量
mean_price = np.mean(prices)
std_price = np.std(prices)
var_price = np.var(prices)
print(f"平均: {mean_price:.0f}万円")
print(f"標準偏差: {std_price:.0f}万円")
print(f"分散: {var_price:.0f}万円²")
# タスク2: 3σルール
lower_3sigma = mean_price - 3 * std_price
upper_3sigma = mean_price + 3 * std_price
outliers_3sigma = [p for p in prices if p < lower_3sigma or p > upper_3sigma]
print(f"\n3σルール外れ値: {outliers_3sigma}")
print(f" 下限: {lower_3sigma:.0f}万円, 上限: {upper_3sigma:.0f}万円")
# タスク3: IQR法
q1 = np.percentile(prices, 25)
q3 = np.percentile(prices, 75)
iqr = q3 - q1
lower_iqr = q1 - 1.5 * iqr
upper_iqr = q3 + 1.5 * iqr
outliers_iqr = [p for p in prices if p < lower_iqr or p > upper_iqr]
print(f"\nIQR法外れ値: {outliers_iqr}")
print(f" Q1: {q1:.0f}, Q3: {q3:.0f}, IQR: {iqr:.0f}")
print(f" 下限: {lower_iqr:.0f}万円, 上限: {upper_iqr:.0f}万円")
# タスク4: 外れ値除去後の平均
clean_prices = [p for p in prices if lower_iqr <= p <= upper_iqr]
mean_clean = np.mean(clean_prices)
print(f"\n外れ値除去後の平均: {mean_clean:.0f}万円")
print(f"外れ値による歪み: {mean_price - mean_clean:.0f}万円")
実行結果(近似値):
▶ 出力を見る
平均: 2278万円
標準偏差: 1505万円
分散: 2265025万円²
3σルール外れ値: [] ← 外れ値8500を検出できない!
下限: -3237万円, 上限: 7793万円
IQR法外れ値: [8500] ← IQRは正しく検出
Q1: 1650, Q3: 1925, IQR: 275
下限: 1237万円, 上限: 2337万円
外れ値除去後の平均: 1803万円
外れ値による歪み: 475万円🪜 Step-by-Step 解説
1
3σルールが外れ値 8500 を検出できない理由
mean = 2278 # 外れ値8500に引っ張られて平均が上昇
std = 1505 # 外れ値があるため標準偏差も大きくなる
upper = mean + 3 * std = 2278 + 4515 = 6793 # 閾値が7800に広がる
# → 8500 は超えるが、3σルールの信頼性が低下している
これが「右歪みデータに3σルールが弱い」理由。
2
IQR法が正しく検出できる理由
q1 = 1650 # 外れ値の影響を受けない(中央50%を使っているから)
q3 = 1925
iqr = 275
upper_iqr = 1925 + 1.5 * 275 = 2337 # 妥当な閾値
# → 8500 >> 2337 なので外れ値として検出
3
外れ値 1個が平均を 475万円 引き上げている
外れ値あり平均: 2278万円
外れ値なし平均: 1803万円
差: 475万円(約26%の歪み)
これが「外れ値を除去してから分析する」ことの重要性。
🏆 Kaggleでの実践的な使い方
import pandas as pd
import numpy as np
df = pd.read_csv('train.csv')
def remove_outliers_iqr(df, column):
"""指定カラムのIQR外れ値を除去したDataFrameを返す"""
q1 = df[column].quantile(0.25)
q3 = df[column].quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
mask = (df[column] >= lower) & (df[column] <= upper)
return df[mask], df[~mask]
# House Pricesコンペの例
clean_df, outlier_df = remove_outliers_iqr(df, 'SalePrice')
print(f"外れ値除去: {len(outlier_df)}件 / 全{len(df)}件")
Kaggleでの判断:
- 訓練データのみ外れ値を除去し、テストデータはそのまま使う
- 除去する前に、その物件が「入力ミスか本物の高級物件か」をEDAで確認する
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
np.std() と pd.Series.std() の結果が違う | 分母が N か N-1 か(標本 vs 不偏) | np.std はデフォルトN(標本)、pandas はN-1(不偏)。引数 ddof で調整可 |
| 外れ値を全部削除する | ノイズと思い込む | テストデータにも同様のデータがあれば残す必要がある |
percentile(25) と quantile(0.25) を混同 | 関数名の違い | 同じ意味。numpy は percentile、pandas は quantile |
🚀 次のステップ
- 発展: log変換で外れ値の影響を軽減する(
np.log1p(prices)で分布を正規化) - 次回予告: 確率の基礎(確率とは何か / 同様に確からしい)へ進む(テーマ3)