【Pandas入門 第3回】データ分析は準備が8割!欠損値・データ型・重複のクリーニング術 🧹
「CSVを読み込んだけど、ところどころ値が入っていない(NaN)…。どうすればいい?」
「数字のはずの列がなぜか文字列になっていて、計算ができない!」
「よく見たら、同じデータが何行も重複して入っている…。」
こんにちは! Pandas探検隊、隊長のPythonistaです! 前回の第2回では、loc
やiloc
、ブールインデックス参照を駆使して、DataFrameから必要なデータを自在に選択・抽出するテクニックを学びましたね。これで、巨大なデータの中からでもお目当ての情報を取り出せるようになりました。
しかし、現実世界のデータは、いつも綺麗に整っているとは限りません。むしろ、値が欠けていたり、データ型が間違っていたり、同じデータが重複していたり…といった「汚れた」データであることの方がほとんどです。不正確なデータで分析を行っても、得られる結果は信頼できません。「Garbage In, Garbage Out(ゴミを入れれば、ゴミしか出てこない)」という言葉があるほどです。
シリーズ第3回の今回は、信頼できる分析を行うための最も重要なステップ、「データクリーニングと前処理」を徹底解説します! 具体的には、
- 欠損値(NaN)の扱い(検出、削除、補完)
- 正しいデータ型への変換
- 重複データの検出と削除
といった、データラングリング(データ整形)の必須スキルを学んでいきます。この地道で重要な作業をマスターして、データ分析の信頼性を一気に高めましょう!
1. 準備:今回の相棒「汚れた」サンプルDataFrame
データクリーニングを学ぶために、今回は意図的に「汚れた」サンプルデータを用意します。欠損値(np.nan
)、間違ったデータ型、重複データを含んだDataFrameを作成しましょう。
import pandas as pd
import numpy as np
# 意図的に汚れたデータを作成
messy_data = {
'ID': [1, 2, 3, 4, 5, 2], # ID=2が重複
'名前': ['佐藤', '鈴木', '高橋', '田中', np.nan, '鈴木'], # 欠損値あり
'年齢': [28, 35, np.nan, 29, 41, 35], # 欠損値あり
'給与(年収)': ['5,000,000', '6,500,000', '4,800,000', '不明', '7,200,000', '6,500,000'], # 文字列、カンマ、不正な値
'入社日': ['2020-04-01', '2018-09-15', '2022-04-01', '2021-10-01', '2015-04-01', '2018-09-15']
}
df = pd.DataFrame(messy_data)
print("--- 汚れたサンプルDataFrame ---")
print(df)
print("\n--- データ情報の確認 (info) ---")
df.info()
df.info()
の結果を見ると、「名前」や「年齢」列に欠損値があり(Non-Null Count
が全体の行数より少ない)、「給与(年収)」列が数値ではなくobject
型(文字列)になっていることが分かります。また、ID=2の鈴木さんのデータは完全に重複していますね。このデータを綺麗にしていきましょう!
2. 欠損値 (NaN) との戦い:検出・削除・補完
欠損値は、データ分析においてノイズとなり、計算エラーの原因にもなります。Pandasでは、欠損値はNumPyのnp.nan
(Not a Number) として表現されます。
2.1. 欠損値の検出
どの列にどれだけ欠損値があるかを確認するのは、クリーニングの第一歩です。
.isnull()
(または.isna()
): DataFrameの各要素が欠損値かどうかをTrue
/False
で返します。.isnull().sum()
: 各列の欠損値の数を集計する、非常によく使われるテクニックです。
# 各列の欠損値の数を表示
print("\n--- 各列の欠損値の数 ---")
print(df.isnull().sum())
実行結果:
--- 各列の欠損値の数 ---
ID 0
名前 1
年齢 1
給与(年収) 0
入社日 0
dtype: int64
2.2. 欠損値の処理方法
欠損値の扱い方には、主に「削除」と「補完(埋める)」の2つのアプローチがあります。
アプローチ1:欠損値を含む行/列を削除 (.dropna()
)
欠損値を含む行や列を削除するのは簡単な方法ですが、貴重なデータを失う可能性もあるため、慎重に行う必要があります。
# 欠損値を含む全ての行を削除
df_dropped_any = df.dropna()
print("\n--- 欠損値を含む行を削除後 ---")
print(df_dropped_any)
# '年齢'列に欠損値がある行だけを削除
df_dropped_age = df.dropna(subset=['年齢'])
print("\n--- '年齢'が欠損している行を削除後 ---")
print(df_dropped_age)
アプローチ2:欠損値を特定の値で補完 (.fillna()
)
データを削除する代わりに、何らかの妥当な値で欠損値を埋める方法です。こちらの方が一般的に推奨されます。
# '年齢'の欠損値を、全体の平均年齢で補完する
mean_age = df['年齢'].mean() # まず平均値を計算
df_filled_age = df.copy() # 元のdfを変更しないようにコピー
df_filled_age['年齢'] = df_filled_age['年齢'].fillna(mean_age)
print("\n--- 年齢の欠損値を平均値で補完後 ---")
print(df_filled_age)
# '名前'の欠損値を、特定の文字列で補完する
df_filled_name = df.copy()
df_filled_name['名前'] = df_filled_name['名前'].fillna('不明')
print("\n--- 名前の欠損値を'不明'で補完後 ---")
print(df_filled_name)
3. データ型の修正:正しい形で計算できるように
df.info()
の結果から、「給与(年収)」列が数値ではなくobject
型(文字列)になっていることが分かりました。これでは平均給与などの計算ができません。カンマや「不明」といった文字列を取り除き、正しい数値型に変換しましょう。
3.1. 文字列操作とpd.to_numeric()
文字列の置換には.str.replace()
が便利です。そして、文字列を数値に変換する際にはpd.to_numeric()
を使います。この関数のerrors='coerce'
オプションは、数値に変換できない値をNaN
(欠損値)に置き換えてくれるため、非常に便利です。
# df_filled_age (年齢を補完済みのdf) を使う
df_cleaned_type = df_filled_age.copy()
# 1. 給与列のカンマ(,)を削除
df_cleaned_type['給与(年収)'] = df_cleaned_type['給与(年収)'].str.replace(',', '')
# 2. 数値に変換。変換できないものはNaNにする (例: '不明' -> NaN)
df_cleaned_type['給与(年収)'] = pd.to_numeric(df_cleaned_type['給与(年収)'], errors='coerce')
# 3. 給与の欠損値(NaN)を、全体の給与の中央値で補完
median_salary = df_cleaned_type['給与(年収)'].median()
df_cleaned_type['給与(年収)'] = df_cleaned_type['給与(年収)'].fillna(median_salary)
print("\n--- 給与列を数値型に変換し、欠損値を補完 ---")
df_cleaned_type.info() # 給与(年収)がfloat64になっていることを確認
print(df_cleaned_type)
3.2. 日付文字列をdatetime
型に変換 (pd.to_datetime()
)
「入社日」列も現在はただの文字列です。これをdatetime
型に変換することで、日付に基づいた計算や分析(例: 勤続年数の計算)が可能になります。
# df_cleaned_type を引き続き使う
df_cleaned_type['入社日'] = pd.to_datetime(df_cleaned_type['入社日'])
print("\n--- 入社日をdatetime型に変換 ---")
df_cleaned_type.info() # 入社日がdatetime64[ns]になっていることを確認
4. 重複データの処理:同じデータは一つだけあればいい
最後に、完全に同じ内容の行が複数存在する場合、それは分析のノイズになるため削除します。
4.1. 重複の検出 (.duplicated()
) と削除 (.drop_duplicates()
)
.duplicated()
: 各行が上の行と重複しているかどうかをTrue
/False
で返します。.drop_duplicates()
: 重複している行を削除した新しいDataFrameを返します。
# df_cleaned_type を引き続き使う
print("\n--- 重複削除前のデータ ---")
print(df_cleaned_type)
# 重複している行を確認
print("\n重複している行:")
print(df_cleaned_type[df_cleaned_type.duplicated()])
# 重複行を削除 (デフォルトでは最初の行が残る)
df_final = df_cleaned_type.drop_duplicates()
print("\n--- 重複削除後の最終データ ---")
print(df_final)
subset
引数で特定の列に基づいて重複を判断したり、keep
引数でどの重複行を残すか(最初、最後、全て残さない)を指定することもできます。
5. まとめ:データクリーニングは分析の土台作り
今回は、データ分析の成功を左右する重要なステップである「データクリーニングと前処理」について学びました。
- 欠損値(NaN)の扱い:
.isnull().sum()
で検出し、.dropna()
で削除するか、.fillna()
で適切な値(平均値、中央値など)を補完する。 - データ型の変換:
pd.to_numeric()
やpd.to_datetime()
を使って、文字列を計算可能な数値型や日付型に変換する。errors='coerce'
が非常に便利。 - 重複データの処理:
.duplicated()
で検出し、.drop_duplicates()
で削除する。
現実のデータは、今回扱ったサンプル以上に様々な形で「汚れて」います。しかし、ここで学んだ基本的なクリーニングテクニックを組み合わせることで、多くの問題に対処できるはずです。綺麗に整えられたデータは、信頼性の高い分析結果へと繋がります。
さて、これで私たちのデータは分析の準備が整いました! 次回、第4回では、この綺麗なデータを使って、groupby()
を使った強力なグループ別集計に挑戦します。「部署ごとの平均給与は?」「各科目の最高点は?」といった、より深い洞察を得るためのデータ集計テクニックをマスターしましょう。お楽しみに!
コメント
コメントを投稿