Pandasで学ぶ機械学習データ前処理入門:欠損値・外れ値・表記ゆれ対策と頻出関数

はじめに:機械学習とデータ前処理の重要な関係

こんにちは!これから機械学習、特に「教師あり学習」について学んでいきたいと考えている皆さん。このブログへようこそ!

機械学習と聞くと、なんだか難しそうな数式やプログラムがたくさん出てくるイメージがあるかもしれませんね。確かにそういう側面もありますが、実は機械学習のプロジェクトを成功させる上で、非常に重要でありながら、最初に取り組むべき基本的なステップがあります。それが今回ご紹介する「データの前処理」です。

教師あり学習は、簡単に言うと「過去のデータ(正解付きのデータ)からパターンを学習し、新しいデータに対して未来を予測する」技術です。例えば、過去の顧客データから、新しく来たお客さんが商品を買ってくれるかどうかを予測したり、過去の気温や天気データから明日の気温を予測したりします。

この「予測」の精度を高めるためには、機械学習モデル(予測を行うコンピュータープログラムのようなもの)に、質の高い、きれいなデータを学習させることが不可欠です。コンピューターの世界には「Garbage In, Garbage Out(ゴミを入力すれば、ゴミしか出てこない)」という有名な言葉があります。これは、いくら高性能な機械学習モデルを使っても、元となるデータが汚れていたり、整理されていなかったりすると、良い予測結果は得られない、という意味です。

そこで登場するのが「データの前処理」。これは、いわば料理でいう「下ごしらえ」のようなものです。美味しい料理を作るためには、野菜を洗ったり、皮をむいたり、適切な大きさに切ったりする必要がありますよね?データ分析や機械学習でも同様に、生のデータをモデルが理解しやすく、学習しやすい形に整える作業が必要なのです。

この記事では、データ分析の世界で非常に広く使われているPythonライブラリ「Pandas」を使いながら、データの前処理とは何か、なぜ重要なのか、そして具体的な手法について、初心者の方にも分かりやすく解説していきます。

この記事を読み終える頃には、あなたもデータの前処理の第一歩を踏み出し、Pandasを使って簡単なデータ操作ができるようになっているはずです!

データの前処理って何?なぜ必要なの?

データの前処理とは?

データの前処理とは、収集した生のデータを、機械学習モデルでの分析や学習に適した形式に変換・整形する一連の作業のことです。生のデータは、そのままでは機械学習モデルがうまく扱えないことが多いため、この「下ごしらえ」が必要になります。

なぜデータの前処理が必要なの?

大きく分けて、以下の3つの理由があります。

  1. 現実世界のデータは「汚い」ことが多いから:

    私たちが手に入れるデータは、残念ながら最初から完璧に整っていることは稀です。例えば、アンケート調査のデータでは、回答が抜けている「欠損値」があったり、年齢に「999歳」のような明らかに間違った値(外れ値)が含まれていたり、同じ意味なのに「男性」「男」「M」のように表記がバラバラ(表記ゆれ)だったりします。このような「汚い」データをそのまま使うと、分析結果が歪んだり、モデルがうまく学習できなかったりします。

  2. 機械学習モデルには「好み」があるから:

    多くの機械学習モデルは、数値データを扱うことを前提としています。そのため、「男性」「女性」のような文字列データ(カテゴリデータ)は、そのままでは扱えません。また、モデルによっては、データの尺度がバラバラだと(例えば、年齢は「歳」、収入は「万円」)、うまく学習できないこともあります。モデルが理解しやすい形式にデータを変換してあげる必要があるのです。

  3. モデルの予測精度を「向上」させるため:

    不要な情報を取り除き、重要な情報を強調し、データを整理することで、機械学習モデルはデータの中にあるパターンをより効率的に、そして正確に学習できるようになります。結果として、予測精度が向上する可能性が高まります。

汚いデータの具体例:架空の顧客データ

言葉だけだとイメージしにくいかもしれませんので、簡単な架空の顧客データを見てみましょう。

顧客ID 名前 年齢 性別 居住地 購入金額(円) 最終購入日
1 佐藤太郎 35 男性 東京都 5000 2023-10-20
2 鈴木花子 28 女性 大阪府 12000 2023-11-05
3 高橋一郎 42 8000 2023-09-15
4 田中さち 999 神奈川県 3000 2023-11-01
5 伊藤健太 31 男性 東京都 2023-08-30

このデータには、以下のような「前処理」が必要そうな点があります。

  • 欠損値: 顧客ID=3の「居住地」、顧客ID=5の「購入金額(円)」が空欄になっています。
  • 外れ値: 顧客ID=4の「年齢」が999歳になっています。これは入力ミスなどの可能性が高いです。
  • 表記ゆれ: 「性別」列に「男性」「女性」「男」「女」が混在しています。
  • 不要な情報: もし「購入金額を予測する」という目的の場合、「顧客ID」や「名前」は直接的な予測には使わないかもしれません。
  • データ型: 「最終購入日」は日付ですが、文字列として扱われている可能性があります。また、「性別」は文字列です。

このような問題を一つ一つ解決していくのが、データの前処理です。そして、この作業を効率的に行うための強力なツールが「Pandas」なのです。

Pandasとは? データ分析の頼れる相棒

Pandas(パンダス)は、Pythonプログラミング言語で使用される、データ分析を支援するための非常に人気のあるライブラリ(便利な機能をまとめたもの)です。特に、Excelの表のような「表形式データ」を扱うのが得意です。

Pandasを使うと、以下のようなことができます。

  • CSVファイルやExcelファイルなど、様々な形式のデータを簡単に読み込める。
  • データの確認、集計、並び替え、絞り込みなどが容易に行える。
  • 欠損値の処理やデータ型の変換など、データの前処理を効率的に実行できる。
  • 複数のデータを結合したり、変形したりできる。

Pandasの中心的な役割を担うのが「DataFrame(データフレーム)」というデータ構造です。これは、まさに行と列からなるExcelのシートのようなものです。各列は「Series(シリーズ)」という一次元のデータ構造で構成されています。

これからの説明では、このPandasを使って、先ほどの架空の顧客データのような問題に対処していく方法を見ていきましょう。

Pandasを使った基本的なデータ前処理ステップ

それでは、実際にPandasを使ってデータの前処理を行ってみましょう。ここでは、先ほどの架空の顧客データを少し簡略化したサンプルデータを使います。

1. ライブラリのインポートとデータの準備

まず、Pandasライブラリを使えるように読み込みます。import pandas as pd と書くのが一般的です(pd という短い名前でPandasの機能を使えるようにするおまじないだと思ってください)。

そして、サンプルデータを作成します。ここではPythonの辞書を使ってデータを作り、それをPandasのDataFrameに変換します。

import pandas as pd
import numpy as np # numpyも数値計算でよく使うライブラリなのでインポートしておきます

# サンプルデータの作成 (辞書形式)
data = {
    'Age': [35, 28, 42, 999, 31, np.nan], # np.nan は欠損値を表します
    'Gender': ['男性', '女性', '男', '女', '男性', '女性'],
    'Prefecture': ['東京都', '大阪府', None, '神奈川県', '東京都', '大阪府'], # None も欠損値です
    'PurchaseAmount': [5000, 12000, 8000, 3000, np.nan, 7500]
}

# 辞書からDataFrameを作成
df = pd.DataFrame(data)

print("元のデータ:")
print(df)

pd.DataFrame() 関数の解説

主な引数:

  • data: DataFrameの元になるデータ。辞書、リストのリスト、NumPy配列などを指定できます。辞書の場合、キーが列名になり、値(リストなど)がその列のデータになります。

戻り値:

作成されたDataFrameオブジェクト。

動作:

引数で与えられたデータ構造をもとに、表形式のDataFrameを作成します。

上記のコードを実行すると、以下のようなDataFrameが表示されるはずです。

元のデータ:
    Age Gender Prefecture  PurchaseAmount
0   35.0     男性       東京都          5000.0
1   28.0     女性       大阪府         12000.0
2   42.0      男      None          8000.0
3  999.0      女      神奈川県          3000.0
4   31.0     男性       東京都           NaN
5    NaN     女性       大阪府          7500.0

NaN は “Not a Number” の略で、欠損値を表します。

2. まずはデータを確認しよう

データが手に入ったら、いきなり処理を始めるのではなく、まずどんなデータなのかを確認することが大切です。

head(): 最初の数行を表示

データの全体像をざっくり掴むのに便利です。デフォルトでは最初の5行が表示されます。

print("\nデータの最初の5行:")
print(df.head())

info(): データ型の概要と欠損値の確認

各列の名前、非欠損値の数、データ型などを一覧表示してくれます。データ型が意図したものになっているか、どの列に欠損値が多いかなどを確認できます。

print("\nデータの情報:")
df.info()

実行結果(一部):


RangeIndex: 6 entries, 0 to 5
Data columns (total 4 columns):
 #   Column          Non-Null Count  Dtype
---  ------          --------------  -----
 0   Age             5 non-null      float64  # 年齢: 5つが非欠損、データ型はfloat64 (浮動小数点数)
 1   Gender          6 non-null      object   # 性別: 6つが非欠損、データ型はobject (主に文字列)
 2   Prefecture      5 non-null      object   # 居住地: 5つが非欠損、データ型はobject
 3   PurchaseAmount  5 non-null      float64  # 購入金額: 5つが非欠損、データ型はfloat64
dtypes: float64(2), object(2)
memory usage: 320.0+ bytes

Non-Null Count が全体の行数(ここでは6)より少ない列には、欠損値があることがわかります (Age, Prefecture, PurchaseAmount)。 Dtype はデータ型を示します。float64は小数点を含む数値、objectは主に文字列を表します。

describe(): 数値データの基本的な統計量の表示

数値データが含まれる列について、個数(count)、平均値(mean)、標準偏差(std)、最小値(min)、四分位数(25%, 50%, 75%)、最大値(max)などの基本的な統計量を計算して表示します。データのばらつき具合や、極端な値(外れ値)の存在を確認するのに役立ちます。

print("\n数値データの基本統計量:")
print(df.describe())

実行結果:

数値データの基本統計量:
              Age  PurchaseAmount
count    5.000000     5.000000
mean   227.000000  7100.000000
std    433.206650  3507.135583
min     28.000000  3000.000000
25%     31.000000  5000.000000
50%     35.000000  7500.000000 # 中央値 (Median)
75%     42.000000  8000.000000
max    999.000000 12000.000000

Age列を見ると、maxが999になっており、平均値(mean)が227と非常に大きくなっています。これは明らかに外れ値の影響を受けていることが推測できますね。

3. 欠損値の処理

データを確認した結果、いくつかの列に欠損値 (NaNNone) があることがわかりました。欠損値は計算エラーの原因になったり、分析結果を歪めたりするため、対処が必要です。

欠損値の数を確認

まずは、どの列にいくつ欠損値があるかを確認しましょう。

print("\n各列の欠損値の数:")
print(df.isnull().sum())

isnull() 関数の解説

引数:

なし

戻り値:

DataFrameの各要素が欠損値(NaN, Noneなど)かどうかを示す、同じ形状の真偽値(True/False)のDataFrame。

動作:

要素が欠損値であればTrue、そうでなければFalseを返します。

sum() 関数の解説 (DataFrame/Seriesに対して)

引数:

  • axis: 集計する軸を指定します。0または'index' (デフォルト) は列ごとに集計、1または'columns' は行ごとに集計します。

戻り値:

集計結果 (Seriesまたは数値)。

動作:

isnull() の結果 (True/FalseのDataFrame) に対して sum() を適用すると、Trueを1、Falseを0として列ごとに合計します。これにより、各列の欠損値(True)の数が計算されます。

実行結果:

各列の欠損値の数:
Age               1
Gender            0
Prefecture        1
PurchaseAmount    1
dtype: int64

Age, Prefecture, PurchaseAmount にそれぞれ1つずつ欠損値があることが確認できました。

欠損値の対処法

主な対処法は「削除」と「補完」の2つです。

対処法1: 削除 (dropna())

欠損値を含む行、または列全体を削除する方法です。データが大量にあり、欠損値のあるデータが比較的少ない場合に有効です。ただし、貴重な情報を失う可能性もあります。

# 欠損値を含む行を削除 (axis=0)
df_dropped_row = df.dropna(axis=0)
print("\n欠損値を含む行を削除したデータ:")
print(df_dropped_row)

# 欠損値を含む列を削除 (axis=1)
# df_dropped_col = df.dropna(axis=1)
# print("\n欠損値を含む列を削除したデータ:")
# print(df_dropped_col)

dropna() 関数の解説

主な引数:

  • axis: 0または'index' (デフォルト) は欠損値を含む行を削除、1または'columns' は欠損値を含む列を削除します。
  • how: 'any' (デフォルト) は一つでも欠損値があれば削除、'all' はすべての値が欠損値の場合に削除します。
  • thresh: 整数を指定すると、非欠損値がその数未満である行/列を削除します。
  • subset: 欠損値をチェックする列/行のラベルを指定します。
  • inplace: Trueにすると、元のDataFrameを直接変更します (注意して使用)。デフォルトはFalseで、新しいDataFrameを返します。

戻り値:

欠損値が削除されたDataFrame (inplace=Falseの場合)。

動作:

指定された条件に基づいて、欠損値を含む行または列を削除します。

対処法2: 補完 (fillna())

欠損値を何らかの値で埋める方法です。データ量を減らさずに済みますが、どのような値で埋めるかが重要になります。

  • 数値データの場合:
    • 平均値 (mean()): データ全体の平均的な値で埋める。外れ値の影響を受けやすい。
    • 中央値 (median()): データを小さい順に並べたときの中央の値で埋める。外れ値の影響を受けにくい。
    • 最頻値 (mode()): 最も頻繁に出現する値で埋める。
    • 固定値: 0などの特定の値で埋める。
  • カテゴリデータの場合:
    • 最頻値 (mode()): 最もよく出現するカテゴリで埋める。
    • 固定のカテゴリ: 「不明」などの新しいカテゴリで埋める。

今回は、AgePurchaseAmount は中央値で、Prefecture は最頻値で補完してみましょう。

# Age の欠損値を中央値で補完
age_median = df['Age'].median()
print(f"\nAgeの中央値: {age_median}")
df['Age'].fillna(age_median, inplace=True) # inplace=True で元のDataFrameを直接変更

# PurchaseAmount の欠損値を中央値で補完
purchase_median = df['PurchaseAmount'].median()
print(f"PurchaseAmountの中央値: {purchase_median}")
df['PurchaseAmount'].fillna(purchase_median, inplace=True)

# Prefecture の欠損値を最頻値で補完
pref_mode = df['Prefecture'].mode()[0] # mode()は最頻値が複数ある場合Seriesを返すため[0]で最初の値を取得
print(f"Prefectureの最頻値: {pref_mode}")
df['Prefecture'].fillna(pref_mode, inplace=True)

print("\n欠損値を補完した後のデータ:")
print(df)
print("\n再度、欠損値の数を確認:")
print(df.isnull().sum())

fillna() 関数の解説

主な引数:

  • value: 欠損値を埋めるための値(スカラー値、辞書、Series、DataFrameなど)。
  • method: 補完方法を指定します。'ffill' (forward fill) は前の値で、'bfill' (backward fill) は後の値で埋めます。
  • axis: 補完を行う軸 (0 or 1)。
  • inplace: Trueにすると、元のDataFrame/Seriesを直接変更します。デフォルトはFalse

戻り値:

欠損値が補完されたDataFrame/Series (inplace=Falseの場合)。

動作:

欠損値(NaN)を指定された値や方法で置き換えます。

median() 関数の解説 (Seriesに対して)

引数:

  • skipna: True (デフォルト) の場合、欠損値を無視して計算します。

戻り値:

計算された中央値 (数値)。

動作:

Series内の中央値を計算します。

mode() 関数の解説 (Seriesに対して)

引数:

  • dropna: True (デフォルト) の場合、欠損値を無視して計算します。

戻り値:

最頻値を含むSeries (最頻値が複数存在する場合があるため)。

動作:

Series内で最も頻繁に出現する値を計算します。

実行後、欠損値がすべて0になっていることが確認できます。

4. 外れ値の処理 (今回は簡単な例として削除)

describe()の結果から、Age列に999という外れ値があることがわかっています。外れ値の処理方法は様々ですが(例: 適切な値で置き換える、別の値に丸めるなど)、今回は単純にこの外れ値を含む行を削除してみましょう。

# Age が現実的でない値 (例: 100より大きい) の行を特定
outlier_index = df[df['Age'] > 100].index
print(f"\nAgeの外れ値(>100)が含まれる行のインデックス: {outlier_index.tolist()}")

# 外れ値を含む行を削除
df.drop(outlier_index, inplace=True)

print("\nAgeの外れ値を削除した後のデータ:")
print(df)
print("\n再度、数値データの基本統計量を確認:")
print(df.describe())

drop() 関数の解説 (DataFrameに対して)

主な引数:

  • labels: 削除する行または列のラベル(単一ラベルまたはリスト)。
  • axis: 0または'index' (デフォルト) は行を削除、1または'columns' は列を削除します。
  • index: axis=0 のエイリアス。削除する行ラベルを指定。
  • columns: axis=1 のエイリアス。削除する列ラベルを指定。
  • inplace: Trueにすると、元のDataFrameを直接変更します。デフォルトはFalse

戻り値:

指定された行/列が削除されたDataFrame (inplace=Falseの場合)。

動作:

指定されたラベルを持つ行または列を削除します。

外れ値の行が削除され、Agemaxmeanが現実的な値になっていることが確認できます。

5. 表記ゆれの修正

Gender列には「男性」「女性」「男」「女」が混在しています。これを統一しましょう。例えば、「男」を「男性」に、「女」を「女性」に置き換えます。

# 表記ゆれを修正
replace_map = {'男': '男性', '女': '女性'}
df['Gender'] = df['Gender'].replace(replace_map)

print("\nGenderの表記ゆれを修正した後のデータ:")
print(df)
print("\nGender列のユニークな値を確認:")
print(df['Gender'].unique())

replace() 関数の解説 (Seriesに対して)

主な引数:

  • to_replace: 置き換えたい値(単一の値、リスト、辞書など)。
  • value: to_replaceを置き換える先の値。to_replaceが辞書の場合、この引数は不要。
  • inplace: Trueにすると、元のSeriesを直接変更します。デフォルトはFalse

戻り値:

値が置き換えられたSeries (inplace=Falseの場合)。

動作:

Series内の値を指定されたルールに基づいて置き換えます。辞書を使うと、複数の値を一度に効率よく置き換えることができます。

unique() 関数の解説 (Seriesに対して)

引数:

なし

戻り値:

Series内のユニークな値(重複を除いた値)を格納したNumPy配列。

動作:

Seriesに含まれる値の種類を確認するのに便利です。

実行後、Gender列の値が「男性」と「女性」に統一されていることが確認できます。

6. カテゴリ変数の数値化 (One-Hotエンコーディング)

機械学習モデルの多くは数値データしか扱えません。そのため、「男性」「女性」のようなカテゴリ変数や、「東京都」「大阪府」のような地域名も数値に変換する必要があります。

よく使われる手法に「One-Hotエンコーディング」があります。これは、カテゴリの各値に対して新しい列を作成し、該当するカテゴリの列には1を、それ以外の列には0を入れる方法です。

Pandasのget_dummies()関数を使うと簡単に実現できます。

# カテゴリ変数をOne-Hotエンコーディング
df_encoded = pd.get_dummies(df, columns=['Gender', 'Prefecture'], drop_first=True)

print("\nOne-Hotエンコーディング後のデータ:")
print(df_encoded)

pd.get_dummies() 関数の解説

主な引数:

  • data: One-Hotエンコーディングを適用するDataFrameまたはSeries。
  • columns: エンコーディング対象とする列名のリスト。指定しない場合、数値型や日付型以外のすべての列が対象になる可能性があります。
  • prefix: 新しく作成される列名の接頭辞を指定できます。
  • prefix_sep: 接頭辞と元のカテゴリ値を区切る文字(デフォルトは_)。
  • dummy_na: Trueにすると、欠損値(NaN)に対しても列を作成します。
  • drop_first: Trueにすると、各カテゴリの最初のレベル(例: ‘男性’と’女性’なら’男性’)の列を削除します。これは、多重共線性(列同士が強い相関を持つこと)を防ぐために行われることがあります。例えば、「Gender_女性」が0なら自動的に男性だとわかるため、片方の列は不要という考え方です。

戻り値:

One-Hotエンコーディングが適用されたDataFrame。

動作:

指定されたカテゴリ列を、そのカテゴリ値に対応する0と1の数値列(ダミー変数)に変換します。

実行結果を見ると、Gender列がGender_女性列に、Prefecture列がPrefecture_東京都Prefecture_神奈川県列に変換されていることがわかります。drop_first=Trueを指定したので、Gender_男性列とPrefecture_大阪府列は作成されていません(他のダミー列が0なら、それが該当カテゴリだとわかるため)。

これで、すべてのデータが数値になり、多くの機械学習モデルで扱える形式になりました!

まとめ:データ前処理は機械学習成功の鍵!

今回は、機械学習プロジェクトにおけるデータの前処理の重要性と、Pandasを使った基本的な前処理手法について解説しました。

  • データの前処理はなぜ必要か?
    • 生のデータは欠損値、外れ値、表記ゆれなどで「汚れている」ことが多い。
    • 機械学習モデルが扱いやすい形式(主に数値)に変換する必要がある。
    • モデルの予測精度を高めるため。
  • Pandasを使った基本的な前処理ステップ
    • データの読み込みと確認 (pd.DataFrame(), head(), info(), describe())
    • 欠損値の処理(削除 dropna() または 補完 fillna(), median(), mode())
    • 外れ値の処理(今回は削除 drop()
    • 表記ゆれの修正 (replace())
    • カテゴリ変数の数値化 (pd.get_dummies())

今回ご紹介したのは、データ前処理のほんの一部です。実際のプロジェクトでは、データの特性や目的に応じて、さらに多くの手法(例えば、特徴量の尺度を揃えるスケーリング、データから新しい特徴を作り出す特徴量エンジニアリングなど)を組み合わせて使うことになります。

しかし、まずは今回学んだ基本的な確認方法や処理方法をマスターすることが、機械学習の第一歩として非常に重要です。Pandasは非常に強力なツールですので、ぜひ実際に手を動かしながら使い方に慣れていってください。

きれいなデータは、良い予測モデルを作るための基礎となります。データ前処理のスキルを身につけて、機械学習プロジェクトを成功に導きましょう!