📋 このステップで学ぶこと
- 異常値と外れ値の違い
- 異常値の検出方法(範囲チェック、論理チェック)
- 異常値の対処方法(削除、置換、補完、クリッピング)
- 外れ値の検出方法(IQR、Zスコア、箱ひげ図)
- 外れ値の処理方法
- データの正規化(Min-Max正規化、標準化、ログ変換)
- 実践演習:総合的なデータクリーニング
🎯 1. 異常値の検出と対処
1-1. STEP 7の復習
STEP 7では、欠損値(NaN)の処理と重複データの削除を学びました。
このSTEP 8では、さらに異常値・外れ値の検出と処理、データの正規化を学びます。
1-2. 異常値とは?
異常値(Anomaly)とは、明らかにおかしい値です。
入力ミス、システムエラー、不正データなどが原因で発生します。
🏥 例え話:健康診断の結果
健康診断で「身長:-170cm」という結果が出たら、明らかにおかしいですよね。
これが異常値です。機械の故障か、入力ミスか、何か問題があったはずです。
⚠️ 異常値の具体例
- 年齢が-5歳や150歳 → 人間としてありえない値
- 価格が0円や-1000円 → 通常の商品ではありえない
- 日付が未来(2050年) → データ入力ミス
- 郵便番号が12345678 → 桁数が違う
- メールアドレスに@がない → フォーマットエラー
1-3. 範囲チェックで異常値を検出
最も基本的な方法は、値の範囲をチェックすることです。
# ===== 基本的な異常値検出 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘name’: [‘山田’, ‘佐藤’, ‘鈴木’, ‘田中’, ‘高橋’, ‘渡辺’],
‘age’: [25, 30, -5, 28, 150, 35],
‘price’: [1000, 1500, -500, 2000, 0, 1800]
})
print(“=== 元のデータ ===”)
print(df)
print()
# 1. 年齢の異常値を検出(0未満または120より大きい)
print(“=== 異常な年齢(0未満 or 120超)===”)
invalid_age = df[(df[‘age’] < 0) | (df['age'] > 120)]
print(invalid_age)
print()
# 2. 価格の異常値を検出(0以下)
print(“=== 異常な価格(0以下)===”)
invalid_price = df[df[‘price’] <= 0]
print(invalid_price)
print()
# 3. 正常なデータだけを抽出
print("=== 正常なデータ ===")
valid_df = df[
(df['age'] >= 0) &
(df[‘age’] <= 120) &
(df['price'] > 0)
]
print(valid_df)
【実行結果】
=== 異常な年齢(0未満 or 120超)===
name age price
2 鈴木 -5 -500
4 高橋 150 0
=== 異常な価格(0以下)===
name age price
2 鈴木 -5 -500
4 高橋 150 0
=== 正常なデータ ===
name age price
0 山田 25 1000
1 佐藤 30 1500
3 田中 28 2000
5 渡辺 35 1800
1-4. 複数条件での異常値チェック
# ===== 複数条件での異常値検出 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘product_name’: [‘ノートPC’, ”, ‘マウス’, ‘キーボード’, ‘モニター’],
‘price’: [89800, 1500, 1980, -100, 25000],
‘stock’: [10, 50, 5, 30, -5],
‘category’: [‘PC’, ‘PC’, ‘アクセサリ’, ‘アクセサリ’, ‘PC’]
})
print(“=== 元のデータ ===”)
print(df)
# 異常値を検出するフラグを作成
df[‘is_invalid’] = False
# 条件1: 商品名が空
df.loc[df[‘product_name’] == ”, ‘is_invalid’] = True
# 条件2: 価格が異常(100円未満または100万円超)
df.loc[(df[‘price’] < 100) | (df['price'] > 1000000), ‘is_invalid’] = True
# 条件3: 在庫が負
df.loc[df[‘stock’] < 0, 'is_invalid'] = True
# 異常データを表示
print("\n=== 異常なデータ ===")
print(df[df['is_invalid']])
# 正常なデータだけを抽出
df_clean = df[~df['is_invalid']].drop('is_invalid', axis=1)
print("\n=== 正常なデータ ===")
print(df_clean)
1-5. 異常値の対処方法
1️⃣ 削除
異常値を含む行を削除
使いどころ:異常値が少ない場合
2️⃣ 置換
正しい値に修正
使いどころ:明らかな入力ミス
3️⃣ 補完
平均値や中央値で置換
使いどころ:データを残したい場合
4️⃣ クリッピング
上限・下限で切り詰め
使いどころ:極端な値を緩和
# ===== 異常値の対処方法 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘age’: [25, 30, -5, 28, 150, 35],
‘price’: [1000, 1500, -500, 2000, 0, 1800]
})
print(“=== 元のデータ ===”)
print(df)
# 方法1: 削除
df1 = df.copy()
df1 = df1[(df1[‘age’] >= 0) & (df1[‘age’] <= 120)]
print("\n=== 方法1: 削除 ===")
print(df1)
# 方法2: NaNに置換してから補完
df2 = df.copy()
df2.loc[(df2['age'] < 0) | (df2['age'] > 120), ‘age’] = np.nan
df2[‘age’] = df2[‘age’].fillna(df2[‘age’].median())
print(“\n=== 方法2: 中央値で補完 ===”)
print(df2)
# 方法3: クリッピング(上限・下限で切り詰め)
df3 = df.copy()
df3[‘age’] = df3[‘age’].clip(lower=0, upper=120)
print(“\n=== 方法3: クリッピング(0〜120)===”)
print(df3)
1-6. 実践例:ECサイトの注文データクリーニング
# ===== 実践的な異常値処理 =====
import pandas as pd
import numpy as np
# 注文データ(異常値あり)
df = pd.DataFrame({
‘order_id’: [1, 2, 3, 4, 5, 6],
‘product_name’: [‘ノートPC’, ”, ‘マウス’, ‘キーボード’, ‘モニター’, ‘ヘッドホン’],
‘quantity’: [2, 5, -1, 3, 1000, 1],
‘price’: [89800, 1500, 1980, -500, 25000, 8900],
‘order_date’: [‘2024-01-01’, ‘2050-12-31’, ‘2024-01-03’,
‘2024-01-04’, ‘2024-01-05’, ‘2024-01-06’]
})
print(“=== 元のデータ ===”)
print(df)
# クリーニング処理
df_clean = df.copy()
# 1. 商品名が空の行を削除
print(“\n【処理1】商品名が空の行を削除”)
before = len(df_clean)
df_clean = df_clean[df_clean[‘product_name’] != ”]
print(f” 削除数: {before – len(df_clean)}件”)
# 2. 数量が異常(0以下または100超)な行を削除
print(“\n【処理2】数量が異常な行を削除”)
before = len(df_clean)
df_clean = df_clean[(df_clean[‘quantity’] > 0) & (df_clean[‘quantity’] <= 100)]
print(f" 削除数: {before - len(df_clean)}件")
# 3. 価格が異常(0以下)なものを中央値で置換
print("\n【処理3】価格が異常なものを中央値で置換")
median_price = df_clean[df_clean['price'] > 0][‘price’].median()
invalid_price_count = (df_clean[‘price’] <= 0).sum()
df_clean.loc[df_clean['price'] <= 0, 'price'] = median_price
print(f" 置換数: {invalid_price_count}件(中央値: {median_price}円)")
# 4. 日付が未来のものを削除
print("\n【処理4】日付が未来の行を削除")
df_clean['order_date'] = pd.to_datetime(df_clean['order_date'])
before = len(df_clean)
df_clean = df_clean[df_clean['order_date'] <= pd.Timestamp.now()]
print(f" 削除数: {before - len(df_clean)}件")
# インデックスをリセット
df_clean = df_clean.reset_index(drop=True)
print("\n=== クリーニング後 ===")
print(df_clean)
📊 2. 外れ値の処理
2-1. 外れ値とは?
外れ値(Outlier)とは、他のデータから大きく離れた値です。
異常値とは違い、正しいデータだが極端な値を指します。
💰 例え話:クラスの年収
同窓会で年収を聞いたとき、ほとんどの人が400〜600万円なのに、一人だけ年収1億円の人がいた。
これは間違いではないけど極端な値=外れ値です。
平均を計算すると、この一人のせいで全体の平均が大きく歪んでしまいます。
2-2. 異常値と外れ値の違い
| 項目 |
異常値(Anomaly) |
外れ値(Outlier) |
| 定義 |
明らかにおかしい値 |
極端だが正しい値 |
| 例 |
年齢が-5歳、150歳 |
年収が1億円 |
| 原因 |
入力ミス、システムエラー |
自然なばらつき、特殊なケース |
| 対処 |
削除または修正 |
慎重に判断(残すことも多い) |
2-3. 外れ値の検出方法①:IQR(四分位範囲)
IQR(Interquartile Range)は、データの散らばりを測る指標です。
第1四分位数(Q1)と第3四分位数(Q3)の差を使って外れ値を検出します。
📐 IQRの計算方法
- Q1:データを小さい順に並べたとき、下から25%の位置の値
- Q3:データを小さい順に並べたとき、下から75%の位置の値
- IQR = Q3 – Q1
- 外れ値の範囲:Q1 – 1.5×IQR より小さい、または Q3 + 1.5×IQR より大きい
# ===== IQRで外れ値を検出 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘price’: [1000, 1200, 1100, 1300, 1150, 1250, 5000, 1180, 950, 1050]
})
print(“=== 元のデータ ===”)
print(df[‘price’].values)
# IQR(四分位範囲)を計算
Q1 = df[‘price’].quantile(0.25) # 第1四分位数(25%)
Q3 = df[‘price’].quantile(0.75) # 第3四分位数(75%)
IQR = Q3 – Q1
# 外れ値の範囲を計算
lower_bound = Q1 – 1.5 * IQR # 下限
upper_bound = Q3 + 1.5 * IQR # 上限
print(f”\n=== IQRの計算 ===”)
print(f”Q1(25%): {Q1}”)
print(f”Q3(75%): {Q3}”)
print(f”IQR: {IQR}”)
print(f”外れ値の下限: {lower_bound}”)
print(f”外れ値の上限: {upper_bound}”)
# 外れ値を検出
outliers = df[(df[‘price’] < lower_bound) | (df['price'] > upper_bound)]
print(f”\n=== 外れ値 ===”)
print(outliers)
# 外れ値を除外
df_clean = df[(df[‘price’] >= lower_bound) & (df[‘price’] <= upper_bound)]
print(f"\n=== 外れ値除外後 ===")
print(df_clean['price'].values)
【実行結果】
=== 元のデータ ===
[1000 1200 1100 1300 1150 1250 5000 1180 950 1050]
=== IQRの計算 ===
Q1(25%): 1037.5
Q3(75%): 1237.5
IQR: 200.0
外れ値の下限: 737.5
外れ値の上限: 1537.5
=== 外れ値 ===
price
6 5000
=== 外れ値除外後 ===
[1000 1200 1100 1300 1150 1250 1180 950 1050]
2-4. 外れ値の検出方法②:Zスコア(標準偏差)
Zスコアは、データが平均からどれだけ離れているかを標準偏差で測る指標です。
📐 Zスコアの計算方法
- Zスコア = (値 – 平均) / 標準偏差
- |Z| > 2:約95%の範囲外 → 外れ値の可能性
- |Z| > 3:約99.7%の範囲外 → ほぼ確実に外れ値
# ===== Zスコアで外れ値を検出 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘price’: [1000, 1200, 1100, 1300, 1150, 1250, 5000, 1180]
})
print(“=== 元のデータ ===”)
print(df[‘price’].values)
# Zスコアを計算
mean = df[‘price’].mean()
std = df[‘price’].std()
df[‘z_score’] = (df[‘price’] – mean) / std
print(f”\n平均: {mean:.2f}”)
print(f”標準偏差: {std:.2f}”)
print(“\n=== Zスコア ===”)
print(df)
# |Z| > 2 を外れ値とする
outliers_2 = df[np.abs(df[‘z_score’]) > 2]
print(f”\n=== 外れ値(|Z| > 2)===”)
print(outliers_2)
# |Z| > 3 を外れ値とする
outliers_3 = df[np.abs(df[‘z_score’]) > 3]
print(f”\n=== 外れ値(|Z| > 3)===”)
print(outliers_3 if len(outliers_3) > 0 else “なし”)
2-5. IQR vs Zスコア
| 項目 |
IQR |
Zスコア |
| 前提 |
分布を仮定しない |
正規分布を仮定 |
| 外れ値の影響 |
受けにくい(頑健) |
受けやすい |
| 使いどころ |
一般的なデータ |
正規分布に近いデータ |
| 推奨度 |
★★★(よく使われる) |
★★☆ |
2-6. 外れ値の処理方法
# ===== 外れ値の処理方法 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘price’: [1000, 1200, 1100, 1300, 1150, 1250, 5000, 1180]
})
# 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
print(f”外れ値の範囲: {lower:.0f} 〜 {upper:.0f}”)
print(f”元のデータ: {df[‘price’].values}”)
# 方法1: 削除
df1 = df[(df[‘price’] >= lower) & (df[‘price’] <= upper)].copy()
print(f"\n【方法1】削除後: {df1['price'].values}")
# 方法2: クリッピング(上限・下限で切り詰め)
df2 = df.copy()
df2['price'] = df2['price'].clip(lower=lower, upper=upper)
print(f"【方法2】クリッピング後: {df2['price'].values}")
# 方法3: 中央値で置換
df3 = df.copy()
median = df3[(df3['price'] >= lower) & (df3[‘price’] <= upper)]['price'].median()
df3.loc[(df3['price'] < lower) | (df3['price'] > upper), ‘price’] = median
print(f”【方法3】中央値({median:.0f})で置換後: {df3[‘price’].values}”)
⚠️ 外れ値処理の注意点
- 削除は慎重に:重要な情報が失われる可能性がある
- ビジネス知識が重要:年収1億円は外れ値だが、削除すべきではない場合もある
- 可視化してから判断:箱ひげ図やヒストグラムで確認してから処理
- 外れ値の原因を調査:本当に外れ値なのか、異常値なのかを確認
📏 3. データの正規化
3-1. 正規化とは?
正規化(Normalization)とは、データの範囲を揃える処理です。
機械学習や統計分析の前処理としてよく使われます。
📊 なぜ正規化が必要?
例:「年齢(20〜80)」と「年収(200万〜2000万)」を一緒に分析する場合
- 年齢:最大60の差(80 – 20)
- 年収:最大1800万の差(2000万 – 200万)
このまま機械学習にかけると、年収の影響が大きすぎて、年齢の影響が無視されてしまいます。
→ 正規化で0〜1の範囲に揃えれば、公平に比較できる!
3-2. Min-Max正規化(0〜1に変換)
データを0〜1の範囲に変換する方法です。
📐 Min-Max正規化の公式
正規化された値 = (元の値 – 最小値) / (最大値 – 最小値)
# ===== Min-Max正規化 =====
import pandas as pd
df = pd.DataFrame({
‘age’: [25, 30, 35, 40, 45],
‘salary’: [3000000, 4000000, 5000000, 6000000, 7000000]
})
print(“=== 元のデータ ===”)
print(df)
# 手動でMin-Max正規化
df[‘age_normalized’] = (df[‘age’] – df[‘age’].min()) / (df[‘age’].max() – df[‘age’].min())
df[‘salary_normalized’] = (df[‘salary’] – df[‘salary’].min()) / (df[‘salary’].max() – df[‘salary’].min())
print(“\n=== 手動で正規化 ===”)
print(df)
# scikit-learnを使う方法
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df[[‘age_sklearn’, ‘salary_sklearn’]] = scaler.fit_transform(df[[‘age’, ‘salary’]])
print(“\n=== scikit-learnで正規化 ===”)
print(df[[‘age’, ‘salary’, ‘age_sklearn’, ‘salary_sklearn’]])
【実行結果】
=== 手動で正規化 ===
age salary age_normalized salary_normalized
0 25 3000000 0.00 0.00
1 30 4000000 0.25 0.25
2 35 5000000 0.50 0.50
3 40 6000000 0.75 0.75
4 45 7000000 1.00 1.00
3-3. 標準化(Standardization)
データを平均0、標準偏差1に変換する方法です。
📐 標準化の公式
標準化された値 = (元の値 – 平均) / 標準偏差
# ===== 標準化 =====
import pandas as pd
from sklearn.preprocessing import StandardScaler
df = pd.DataFrame({
‘age’: [25, 30, 35, 40, 45],
‘salary’: [3000000, 4000000, 5000000, 6000000, 7000000]
})
print(“=== 元のデータ ===”)
print(df)
# 手動で標準化
df[‘age_standardized’] = (df[‘age’] – df[‘age’].mean()) / df[‘age’].std()
# scikit-learnで標準化
scaler = StandardScaler()
df[[‘age_std’, ‘salary_std’]] = scaler.fit_transform(df[[‘age’, ‘salary’]])
print(“\n=== 標準化後 ===”)
print(df)
print(“\n=== 確認:平均と標準偏差 ===”)
print(f”age_std の平均: {df[‘age_std’].mean():.10f}”)
print(f”age_std の標準偏差: {df[‘age_std’].std():.10f}”)
3-4. Min-Max正規化 vs 標準化
| 項目 |
Min-Max正規化 |
標準化 |
| 変換後の範囲 |
0〜1 |
平均0、標準偏差1 |
| 外れ値の影響 |
大きい(最大・最小を使うため) |
小さい |
| 使いどころ |
画像処理、ニューラルネットワーク |
統計分析、機械学習(SVM、回帰) |
| 元に戻す |
可能 |
可能 |
3-5. ログ変換(対数変換)
ログ変換は、右に歪んだ分布を正規分布に近づける方法です。
年収や売上など、大きな値に偏ったデータに有効です。
# ===== ログ変換 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘income’: [2000000, 3000000, 4000000, 8000000, 50000000]
})
print(“=== 元のデータ ===”)
print(df)
print(f”平均: {df[‘income’].mean():,.0f}円”)
print(f”中央値: {df[‘income’].median():,.0f}円”)
# ログ変換(自然対数)
df[‘income_log’] = np.log(df[‘income’])
# log1p(log(1+x)):0があってもOK
df[‘income_log1p’] = np.log1p(df[‘income’])
print(“\n=== ログ変換後 ===”)
print(df)
# 元に戻す
df[‘income_restored’] = np.exp(df[‘income_log’])
print(“\n=== 元に戻す(exp)===”)
print(df[[‘income’, ‘income_restored’]])
💡 正規化の使い分け
- Min-Max正規化:データを0〜1に収めたい場合、外れ値が少ない場合
- 標準化:外れ値がある場合、平均周辺に集めたい場合
- ログ変換:右に歪んだ分布(年収、売上など)を正規分布に近づけたい場合
🏋️ 4. 実践演習:総合的なデータクリーニング
4-1. 課題:顧客データの完全クリーニング
実際の現場を想定した汚いデータを、これまで学んだ技術を使って完全にクリーニングします。
# ===== 汚いデータの作成 =====
import pandas as pd
import numpy as np
# 汚いデータ(様々な問題を含む)
df = pd.DataFrame({
‘customer_id’: [1, 2, 3, 2, 4, 5, 6, 7, 8],
‘name’: [‘山田太郎’, ‘佐藤花子’, ”, ‘佐藤花子’, ‘鈴木一郎’,
‘田中美咲’, ‘高橋健太’, ‘渡辺由美’, ‘伊藤翔太’],
‘age’: [25, np.nan, 30, 28, -5, 150, 35, 40, 45],
‘email’: [‘yamada@ex.com’, ‘sato@ex.com’, ‘suzuki@ex.com’,
‘sato@ex.com’, ‘tanaka@ex.com’, ‘takahashi@ex.com’,
”, ‘watanabe@ex.com’, ‘ito@ex.com’],
‘purchase_amount’: [10000, ‘15000’, 20000, 18000, -5000,
50000, 8000, 12000, 0],
‘purchase_date’: [‘2024-01-01’, ‘2024-01-02’, ‘2024-01-03’,
‘2024-01-04’, ‘2050-12-31’, ‘2024-01-06’,
‘2024-01-07’, ‘2024-01-08’, ‘2024-01-09’]
})
print(“=== 汚いデータ ===”)
print(df)
print(f”\nデータ型:\n{df.dtypes}”)
print(f”\n欠損値:\n{df.isnull().sum()}”)
4-2. 完全なクリーニング処理
# ===== 完全なクリーニング処理 =====
import pandas as pd
import numpy as np
from datetime import datetime
# (上記の汚いデータを使用)
print(“\n” + “=” * 60)
print(“データクリーニング開始”)
print(“=” * 60)
# ステップ1: 重複を削除(emailで判定、最初を残す)
print(“\n【ステップ1】重複削除(emailで判定)”)
before_count = len(df)
df = df.drop_duplicates(subset=[‘email’], keep=’first’)
print(f” → 削除数: {before_count – len(df)}件”)
# ステップ2: 空の名前を削除
print(“\n【ステップ2】空の名前を削除”)
before_count = len(df)
df = df[df[‘name’] != ”]
print(f” → 削除数: {before_count – len(df)}件”)
# ステップ3: 年齢の欠損値を中央値で補完
print(“\n【ステップ3】年齢の欠損値を中央値で補完”)
# まず正常な範囲のデータだけで中央値を計算
valid_ages = df[(df[‘age’] >= 0) & (df[‘age’] <= 120)]['age']
median_age = valid_ages.median()
missing_count = df['age'].isnull().sum()
df['age'] = df['age'].fillna(median_age)
print(f" → 補完数: {missing_count}件(中央値: {median_age}歳)")
# ステップ4: 年齢の異常値を削除
print("\n【ステップ4】年齢の異常値を削除(0未満 or 120超)")
before_count = len(df)
df = df[(df['age'] >= 0) & (df[‘age’] <= 120)]
print(f" → 削除数: {before_count - len(df)}件")
# ステップ5: 購入金額のデータ型を変換
print("\n【ステップ5】購入金額のデータ型を数値に変換")
df['purchase_amount'] = pd.to_numeric(df['purchase_amount'], errors='coerce')
# ステップ6: 購入金額の異常値を削除(0以下)
print("\n【ステップ6】購入金額の異常値を削除(0以下)")
before_count = len(df)
df = df[df['purchase_amount'] > 0]
print(f” → 削除数: {before_count – len(df)}件”)
# ステップ7: 購入日をdatetime型に変換
print(“\n【ステップ7】購入日をdatetime型に変換”)
df[‘purchase_date’] = pd.to_datetime(df[‘purchase_date’])
# ステップ8: 未来の日付を削除
print(“\n【ステップ8】未来の日付を削除”)
before_count = len(df)
df = df[df[‘purchase_date’] <= pd.Timestamp.now()]
print(f" → 削除数: {before_count - len(df)}件")
# ステップ9: 空のメールアドレスを補完
print("\n【ステップ9】空のメールアドレスを補完")
empty_email_count = (df['email'] == '').sum()
df.loc[df['email'] == '', 'email'] = 'unknown@example.com'
print(f" → 補完数: {empty_email_count}件")
# ステップ10: 購入金額の外れ値処理(IQR)
print("\n【ステップ10】購入金額の外れ値処理(IQR)")
Q1 = df['purchase_amount'].quantile(0.25)
Q3 = df['purchase_amount'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
before_count = len(df)
df = df[(df['purchase_amount'] >= lower) & (df[‘purchase_amount’] <= upper)]
print(f" → 削除数: {before_count - len(df)}件(範囲: {lower:.0f}〜{upper:.0f})")
# ステップ11: インデックスをリセット
df = df.reset_index(drop=True)
# ステップ12: データ型を最適化
df['customer_id'] = df['customer_id'].astype('int32')
df['age'] = df['age'].astype('int32')
df['purchase_amount'] = df['purchase_amount'].astype('int32')
print("\n" + "=" * 60)
print("データクリーニング完了")
print("=" * 60)
print(f"\n=== クリーニング後のデータ ===")
print(df)
print(f"\n=== データ型 ===")
print(df.dtypes)
print(f"\n=== 基本統計量 ===")
print(df.describe())
✅ クリーニングのポイント
- 各ステップで何件処理したかを表示する
- 処理の順番に注意(重複削除→欠損値処理→異常値処理)
- 元のデータは保存しておく(いつでも戻れるように)
- 最後にデータ型を最適化してメモリを節約
📝 STEP 8 のまとめ
✅ このステップで学んだこと
- 異常値の検出:範囲チェック、複数条件での検証
- 異常値の対処:削除、置換、補完、クリッピング
- 外れ値の検出:IQR(四分位範囲)、Zスコア
- 外れ値の処理:削除、クリッピング、中央値置換
- Min-Max正規化:0〜1に変換
- 標準化:平均0、標準偏差1に変換
- ログ変換:歪んだ分布を正規分布に近づける
💡 データクリーニングのベストプラクティス
- 元データを保存:クリーニング前のデータは必ず残す
- 段階的に処理:一度にすべてやらず、1つずつ確認しながら
- 処理内容を記録:何をしたか必ずログに残す
- 削除は慎重に:本当に削除すべきか検討
- 可視化して確認:処理前後でグラフ確認
🎯 次のステップの予告
次のSTEP 9では、「データ変換の基本パターン」を学びます。
- カラムの追加・削除・変更
- 文字列操作(split、strip、replace)
- 日付データの処理
- カテゴリデータの変換
実務で頻出する変換技術を習得しましょう!
📝 練習問題
問題 1
基礎
ageカラムが0未満または120を超える行を抽出するコードを書いてください。
【解答例】
invalid_age = df[(df[‘age’] < 0) | (df['age'] > 120)]
print(invalid_age)
問題 2
基礎
priceカラムの値を0〜1000の範囲でクリッピングするコードを書いてください。
【解答例】
df[‘price’] = df[‘price’].clip(lower=0, upper=1000)
問題 3
基礎
IQRを使った外れ値の上限と下限を計算するコードを書いてください。
【解答例】
Q1 = df[‘price’].quantile(0.25)
Q3 = df[‘price’].quantile(0.75)
IQR = Q3 – Q1
lower_bound = Q1 – 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f”下限: {lower_bound}, 上限: {upper_bound}”)
問題 4
基礎
Min-Max正規化を手動で実装するコードを書いてください。
【解答例】
df[‘price_normalized’] = (df[‘price’] – df[‘price’].min()) / (df[‘price’].max() – df[‘price’].min())
問題 5
応用
Zスコアを計算し、|Z| > 2 の外れ値を抽出するコードを書いてください。
【解答例】
import numpy as np
mean = df[‘price’].mean()
std = df[‘price’].std()
df[‘z_score’] = (df[‘price’] – mean) / std
outliers = df[np.abs(df[‘z_score’]) > 2]
print(outliers)
問題 6
応用
scikit-learnのStandardScalerを使って標準化するコードを書いてください。
【解答例】
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[[‘price_std’, ‘quantity_std’]] = scaler.fit_transform(df[[‘price’, ‘quantity’]])
問題 7
応用
ログ変換を行い、元に戻すコードを書いてください。
【解答例】
import numpy as np
# ログ変換
df[‘price_log’] = np.log(df[‘price’])
# 元に戻す
df[‘price_restored’] = np.exp(df[‘price_log’])
問題 8
応用
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
df[‘price_clipped’] = df[‘price’].clip(lower=lower, upper=upper)
問題 9
発展
異常値を検出し、NaNに置換してから中央値で補完するコードを書いてください。(条件:priceが0以下または100000以上)
【解答例】
import numpy as np
# 異常値をNaNに置換
df.loc[(df[‘price’] <= 0) | (df['price'] >= 100000), ‘price’] = np.nan
# 中央値で補完
median_price = df[‘price’].median()
df[‘price’] = df[‘price’].fillna(median_price)
print(df)
問題 10
発展
以下の条件でデータクリーニングを行うコードを書いてください。
① ageの異常値(0未満 or 120超)をクリッピング
② priceの外れ値(IQR)を削除
③ 残ったデータをMin-Max正規化
【解答例】
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# ① ageの異常値をクリッピング
df[‘age’] = df[‘age’].clip(lower=0, upper=120)
# ② priceの外れ値を削除(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
df = df[(df[‘price’] >= lower) & (df[‘price’] <= upper)]
# ③ Min-Max正規化
scaler = MinMaxScaler()
df[['age_norm', 'price_norm']] = scaler.fit_transform(df[['age', 'price']])
print(df)