データクリーニング — 質の悪いデータを活用できるようにする
このトピックを修了すると
実際のデータでよく発生する欠損値、重複、外れ値の問題を特定し、処理できるようになります。
なぜデータクリーニングが必要なのか
現実世界のデータはきれいでありません。アンケートの回答が欠けていたり、同じ人が二重に登録されていたり、年齢が-5歳になっている行があったりします。このようなデータをそのまま分析すると、結果を信頼できません。
データ分析業務の60〜80%はクリーニングに費やされると言われています。派手な分析手法よりも、この段階の方がはるかに重要です。
1段階:データの全体像を把握する
import pandas as pd
df = pd.read_csv("patients.csv")
# 基本情報print(df.shape) # (1000, 8) — 1000行、8列print(df.dtypes) # 各列のデータ型print(df.info()) # 欠損値の状況を含む概要
# 最初/最後を確認print(df.head()) # 上位5行print(df.tail()) # 下位5行
# 統計概要print(df.describe()) # 数値型の列の平均、標準偏差、最小/最大などdf.info()を実行すると、Non-Null Countが表示されます。全体の行数よりも少ない場合、欠損値があることを意味します。df.describe()でmin/maxが常識的な範囲から外れている場合、外れ値を疑います。
2段階:欠損値の処理
# 欠損値の確認print(df.isnull().sum())# name 0# age 15# blood_type 3# weight 42
# 戦略1:削除 — 欠損行が少数の場合df_clean = df.dropna(subset=["blood_type"]) # blood_typeがない3行を削除
# 戦略2:補完 — 数値型の列df["age"] = df["age"].fillna(df["age"].median()) # 中央値でdf["weight"] = df["weight"].fillna(df["weight"].mean()) # 平均で
# 戦略3:特定の値で補完df["blood_type"] = df["blood_type"].fillna("未確認")どの戦略を使用するかは、状況によって異なります。
- 欠損率が5%未満であれば、削除が最も安全です。
- 数値型は、中央値(外れ値に強い)または平均で補完します。
- カテゴリ型(血液型、性別)は、最頻値または「未確認」で補完します。
3段階:重複の削除
# 重複の確認print(df.duplicated().sum()) # 23個の重複行
# 重複の内容を表示print(df[df.duplicated(keep=False)]) # 元の行 + 重複する行すべてを表示
# 重複の削除(最初の行を保持)df = df.drop_duplicates()
# 特定の列を基準に重複を削除df = df.drop_duplicates(subset=["patient_id"], keep="last")keep="first"は、最初の行を保持し、残りを削除します。keep="last"は、最後の行を保持します。データが時間順であれば、lastが最新のレコードを残します。
4段階:外れ値の検出
# 基本的な統計で確認print(df["age"].describe())# min: -5 ← 異常# max: 200 ← 異常
# 範囲フィルタリングdf = df[(df["age"] >= 0) & (df["age"] <= 120)]
# IQR法 — 統計的な外れ値の検出Q1 = df["weight"].quantile(0.25)Q3 = df["weight"].quantile(0.75)IQR = Q3 - Q1lower = Q1 - 1.5 * IQRupper = Q3 + 1.5 * IQR
outliers = df[(df["weight"] < lower) | (df["weight"] > upper)]print(f"外れ値 {len(outliers)}個を検出")
# 外れ値の削除df = df[(df["weight"] >= lower) & (df["weight"] <= upper)]外れ値を無条件に削除すべきではありません。年齢-5は明らかなエラーなので削除しますが、体重150kgは実際に存在する可能性があります。ドメイン知識が外れ値の判断の鍵です。
5段階:型の変換と形式の統一
# 文字列で表現された数字 → 数値型に変換df["age"] = pd.to_numeric(df["age"], errors="coerce")
# 日付文字列 → datetime型に変換df["visit_date"] = pd.to_datetime(df["visit_date"])
# 文字列の整理 — 前後の空白、大文字小文字の統一df["name"] = df["name"].str.strip()df["blood_type"] = df["blood_type"].str.upper()errors="coerce"は、変換できない値(例:"N/A")をNaNで処理します。エラーを出さずに処理できるため、大量のデータに便利です。
重要なまとめ
| 段階 | 確認事項 | 主要なツール |
|---|---|---|
| 全体像の把握 | 行/列数、型、欠損状況 | info()、describe() |
| 欠損値 | 割合の確認 → 削除 or 補完 | isnull()、fillna()、dropna() |
| 重複 | 同じ行またはキーを基準とした重複 | duplicated()、drop_duplicates() |
| 外れ値 | 常識的な範囲 + IQR | describe()、範囲フィルタリング |
| 型/形式 | 数字、日付、文字列の統一 | to_numeric()、to_datetime()、str.strip() |
データクリーニングは反復的で退屈な作業ですが、この段階を省略すると、その後のすべての分析結果が揺らぎます。「Garbage in, garbage out」 — 入力がゴミであれば、出力もゴミになります。