【Pandas入門 第3回】データ分析は準備が8割!欠損値・データ型・重複のクリーニング術 🧹

「CSVを読み込んだけど、ところどころ値が入っていない(NaN)…。どうすればいい?」
「数字のはずの列がなぜか文字列になっていて、計算ができない!」
「よく見たら、同じデータが何行も重複して入っている…。」

こんにちは! Pandas探検隊、隊長のPythonistaです! 前回の第2回では、lociloc、ブールインデックス参照を駆使して、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()を使った強力なグループ別集計に挑戦します。「部署ごとの平均給与は?」「各科目の最高点は?」といった、より深い洞察を得るためのデータ集計テクニックをマスターしましょう。お楽しみに!

その他の投稿はこちら

コメント

このブログの人気の投稿

タイトルまとめ

これで迷わない!WindowsでPython環境構築する一番やさしい方法 #0

【Python標準ライブラリ完結!】11の冒険をありがとう!君のPython力が飛躍する「次の一歩」とは? 🚀