機械学習のためのPandasデータ前処理:欠損値処理(fillna/dropna)と並び替え(sort_values)

機械学習を始める前に!Pandasでデータの前処理をマスターしよう

本記事では、機械学習、特に教師あり学習に取り組む上で不可欠な「データの前処理」について解説します。

Pythonの強力なライブラリである「Pandas」を使用し、データを分析に適した形式に整形する方法を、プログラミング経験が浅い方にも理解しやすいように説明を進めていきます。

機械学習プロジェクトの成功は、使用するデータの質に大きく依存します。しかし、実際のデータセットには欠損値が含まれていたり、形式が統一されていなかったりすることが一般的です。このような課題に対処するため、Pandasはデータの前処理に役立つ多くの関数を提供しています。

今回は、データの前処理において頻繁に使用される基本的な関数の中から、以下の3つを取り上げ、その仕様や利用目的、注意点について詳しく見ていきます。

  • 欠損値の処理: fillna(), dropna()
  • データの並び替え: sort_values()

これらの関数を習得することで、データの前処理における重要な第一歩を踏み出すことができます。

Pandasとは?

まず、Pandasについて簡単にご説明します。Pandasは、Pythonプログラミング言語で使用される、データ操作と分析のための高性能なライブラリです。特に、表形式のデータ(スプレッドシートやデータベースのテーブルのようなデータ)を効率的に扱う機能に優れており、データの読み込み、加工、集計などを容易に行うことができます。

Pandasを利用するには、まずPythonスクリプトの冒頭でライブラリをインポートする必要があります。慣例的にpdという別名をつけてインポートします。

import pandas as pd

この記述により、以降のコードでPandasの機能をpdという名前で呼び出すことが可能になります。

1. 欠損値の処理

データ分析を行う際、データの一部が欠落している状態、すなわち「欠損値」に遭遇することは珍しくありません。例えば、アンケート調査における無回答の項目や、センサーデータの収集時における一時的な記録漏れなどが原因で発生します。

欠損値が存在すると、機械学習モデルの学習プロセスに悪影響を与えたり、分析結果の信頼性を損なったりする可能性があります。したがって、これらの欠損値に対して適切な処理を施すことが重要です。Pandasには、欠損値を効果的に扱うための関数が用意されています。

1-1. 欠損値を特定の値で補完する:fillna()

fillna()関数は、データフレーム内の欠損値(多くの場合、NaN – Not a Number として表現されます)を指定した値で置き換える機能を提供します。

基本的な使い方:

df.fillna(value)
  • df: 操作対象のPandas DataFrame。
  • value: 欠損値を置き換えるための値(単一のスカラー値、辞書、Series、DataFrameなど)。

具体的な例:

以下のようなデータフレームを例に考えます。

import pandas as pd
import numpy as np # NaNを表現するためにnumpyもインポートします


data = {'名前': ['山田', '佐藤', '田中', '鈴木'],
'年齢': [25, np.nan, 32, 28],
'職業': ['エンジニア', 'デザイナー', None, '営業']}
df = pd.DataFrame(data)
print(df)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
1 佐藤 NaN デザイナー
2 田中 32.0 None
3 鈴木 28.0 営業

このデータフレームには、「年齢」列と「職業」列に欠損値(NaN および None)が含まれています。例えば、「年齢」列の欠損値を全体の平均年齢で補完する場合、まず平均年齢を算出します。

mean_age = df['年齢'].mean()
print(f"年齢の平均値: {mean_age}")

実行結果:

年齢の平均値: 28.333333333333332

次に、fillna()を使用して欠損値を補完します。

df_filled_age = df.fillna({'年齢': mean_age})
print(df_filled_age)

実行結果:

 名前 年齢 職業
0 山田 25.000000 エンジニア
1 佐藤 28.333333 デザイナー
2 田中 32.000000 None
3 鈴木 28.000000 営業

fillna()に関数の引数として辞書を渡すことで、列ごとに異なる値で欠損値を補完できます。例えば、「職業」列の欠損値を「不明」という文字列で補完するには、以下のように記述します。

df_filled = df.fillna({'年齢': mean_age, '職業': '不明'})
print(df_filled)

実行結果:

 名前 年齢 職業
0 山田 25.000000 エンジニア
1 佐藤 28.333333 デザイナー
2 田中 32.000000 不明
3 鈴木 28.000000 営業

主な引数:

  • value: 欠損値を補完する値を指定します。スカラー値、辞書、Series、DataFrameが利用可能です。辞書形式で指定する場合、キーに列名、バリューに補完値を指定します。
  • method: 欠損値の補完方法を指定する文字列です。
    • 'ffill' または 'pad': 直前の有効な値で補完します(前方補完)。
    • 'bfill' または 'backfill': 直後の有効な値で補完します(後方補完)。
  • inplace: 元のDataFrameを直接変更するかどうかを指定するブール値です。Trueに設定すると元のDataFrameが変更され、戻り値はNoneになります。デフォルトはFalseで、変更が適用された新しいDataFrameが返されます。

戻り値:

欠損値が補完された新しいDataFrame(inplace=Falseの場合)、またはNone(inplace=Trueの場合)。

注意点:

  • 欠損値を単一の値(例えば平均値)で一律に補完すると、データの本来の分布が変化する可能性があります。特に数値データの場合、平均値、中央値、最頻値など、どの代表値で補完するかはデータの特性を考慮して慎重に選択する必要があります。
  • method引数を用いた補完は、データの順序が意味を持つ場合に有効です。時系列データなどが代表的な例です。

1-2. 欠損値を含む行や列を除去する:dropna()

dropna()関数は、欠損値を含む行または列をデータフレームから削除するために使用されます。

基本的な使い方:

df.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
  • df: 操作対象のPandas DataFrame。
  • axis: 削除対象の軸を指定します。
    • 0 または 'index': 欠損値を含む行を削除します(デフォルト)。
    • 1 または 'columns': 欠損値を含む列を削除します。
  • how: 削除の条件を指定します。
    • 'any': 少なくとも1つの欠損値が含まれる行または列を削除します(デフォルト)。
    • 'all': すべての値が欠損値である行または列を削除します。
  • thresh: 削除せずに残すために必要な非欠損値の最小数を指定する整数。この閾値を下回る非欠損値の数しか持たない行または列が削除されます。
  • subset: 欠損値のチェック対象とする列(axis=0の場合)または行(axis=1の場合)のラベルのリスト。指定した範囲内でのみ欠損値がチェックされます。
  • inplace: 元のDataFrameを直接変更するかどうかを指定するブール値(fillna()と同様)。

具体的な例:

先ほどのデータフレームdfを用いて、欠損値を含む行を削除します。

df_dropped = df.dropna()
print(df_dropped)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
3 鈴木 28.0 営業

インデックス1(佐藤さん)とインデックス2(田中さん)の行に欠損値が含まれていたため、これらの行が削除されました。

次に、全ての値が欠損値である行を追加し、how='all'の動作を確認します。

df_with_na_row = df.copy()
# locを使って新しい行を追加(行ラベルを指定)
df_with_na_row.loc['新しい行'] = [np.nan, np.nan, np.nan]
print(df_with_na_row)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
1 佐藤 NaN デザイナー
2 田中 32.0 None
3 鈴木 28.0 営業
新しい行 NaN NaN NaN

ここでhow='all'を指定してdropna()を実行すると、「新しい行」のみが削除されます。

df_dropped_all = df_with_na_row.dropna(how='all')
print(df_dropped_all)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
1 佐藤 NaN デザイナー
2 田中 32.0 None
3 鈴木 28.0 営業

特定の列に欠損値が存在する場合にのみ行を削除したい場合は、subset引数を使用します。例えば、「職業」列に欠損値がある行を削除するには、以下のようにします。

df_dropped_subset = df.dropna(subset=['職業'])
print(df_dropped_subset)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
1 佐藤 NaN デザイナー
3 鈴木 28.0 営業

インデックス2(田中さん)の行が、「職業」列に欠損値(None)を持つため削除されました。

戻り値:

欠損値を含む行または列が削除された新しいDataFrame(inplace=Falseの場合)、またはNone(inplace=Trueの場合)。

注意点:

  • 欠損値を含むデータを安易に削除すると、分析に有用な情報まで失われる可能性があります。削除を実行する前に、欠損値の割合や、削除がデータセット全体に与える影響を十分に評価することが重要です。
  • thresh引数を利用すると、一定数以上の有効なデータを持つ行(または列)を残すことができます。例えば、多数の質問項目を持つアンケートデータで、回答数が極端に少ない回答者を分析対象から除外したい場合などに有効です。

2. データの並び替え:sort_values()

sort_values()関数は、データフレームを特定の列(または複数の列)の値に基づいて昇順または降順に並び替える機能を提供します。

基本的な使い方:

df.sort_values(by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last')
  • df: 操作対象のPandas DataFrame。
  • by: 並び替えの基準となる列名(文字列)、または列名のリスト。リストで指定した場合、リストの先頭の列から順に並び替えが行われます(多重ソート)。
  • axis: 並び替えを行う軸を指定します。
    • 0 または 'index': 行を並び替えます(デフォルト)。
    • 1 または 'columns': 列を並び替えます(列名に基づいてソートする場合など)。
  • ascending: 並び替えの順序を指定するブール値、またはブール値のリスト。Trueで昇順(小さい方から大きい方へ、デフォルト)、Falseで降順(大きい方から小さい方へ)。byにリストを指定した場合、各列に対応する昇順/降順をリストで指定できます。
  • inplace: 元のDataFrameを直接変更するかどうかを指定するブール値(fillna()dropna()と同様)。
  • kind: 使用するソートアルゴリズムを指定します(通常はデフォルトの'quicksort'で問題ありません)。
  • na_position: 欠損値(NaN)をソート結果のどこに配置するかを指定します。
    • 'last': 末尾に配置します(デフォルト)。
    • 'first': 先頭に配置します。

具体的な例:

元のデータフレームdfを「年齢」列の値に基づいて昇順で並び替えます。

df_sorted_age = df.sort_values(by='年齢')
print(df_sorted_age)

実行結果:

 名前 年齢 職業
0 山田 25.0 エンジニア
3 鈴木 28.0 営業
2 田中 32.0 None
1 佐藤 NaN デザイナー

年齢が小さい順に並び替えられました。欠損値(NaN)はデフォルトで最後に配置されます。

年齢で降順に並び替える場合は、ascending=Falseを指定します。

df_sorted_age_desc = df.sort_values(by='年齢', ascending=False)
print(df_sorted_age_desc)

実行結果:

 名前 年齢 職業
2 田中 32.0 None
3 鈴木 28.0 営業
0 山田 25.0 エンジニア
1 佐藤 NaN デザイナー

複数の列を基準に並び替えることも可能です。by引数に列名のリストを渡します。例えば、「職業」で昇順、同じ職業内では「年齢」で降順に並び替える場合は、以下のように記述します。

# 職業列のNoneを一時的に文字列に変換してソートの挙動を明確にする
df_temp = df.fillna({'職業': '未定義'}) # Noneを文字列に置き換える


df_sorted_multi = df_temp.sort_values(by=['職業', '年齢'], ascending=[True, False])
print(df_sorted_multi)

実行結果:

 名前 年齢 職業
1 佐藤 NaN デザイナー
0 山田 25.0 エンジニア
3 鈴木 28.0 営業
2 田中 32.0 未定義

まず「職業」のアルファベット(辞書)順で昇順にソートされ(この例では’デザイナー’, ‘エンジニア’, ‘営業’, ‘未定義’ の順)、もし同じ職業の人が複数存在すれば、その中で「年齢」の降順に並び替えられます。この例では各職業は1名ずつですが、複数いた場合の動作を示しています。(元のNaNやNoneの扱いはデータ型やPandasのバージョンにより若干挙動が異なる場合がありますので注意が必要です)。

欠損値の位置を変更したい場合は、na_position引数を使用します。例えば、欠損値を先頭に表示するには以下のようにします。

df_sorted_na_first = df.sort_values(by='年齢', na_position='first')
print(df_sorted_na_first)

実行結果:

 名前 年齢 職業
1 佐藤 NaN デザイナー
0 山田 25.0 エンジニア
3 鈴木 28.0 営業
2 田中 32.0 None

戻り値:

指定された基準で並び替えられた新しいDataFrame(inplace=Falseの場合)、またはNone(inplace=Trueの場合)。

注意点:

  • 並び替えの基準列に欠損値が含まれる場合、na_position引数によってその配置を制御できます。分析の目的によっては、欠損値を先頭または末尾のどちらかに意図的に配置することが有効な場合があります。
  • inplace=Trueオプションを使用すると、元のデータフレームが直接上書きされます。元のデータを保持したい場合は、このオプションをFalse(デフォルト)のままにして、結果を新しい変数に代入することをお勧めします。

まとめ

本記事では、Pandasを用いたデータの前処理における基本的ながら非常に重要な関数として、以下の3つをご紹介しました。

  • fillna(): 欠損値を指定した方法で補完する関数。
  • dropna(): 欠損値を含む行または列を削除する関数。
  • sort_values(): 指定した列の値に基づいてデータを並び替える関数。

これらの関数を適切に使いこなすことで、機械学習モデルの学習に適した、整然としたデータセットを準備する作業が効率化されます。ぜひ、お手元のデータでこれらの関数を実際に適用し、その効果を体験してみてください。

データの前処理は、地道な作業に見えるかもしれませんが、最終的な機械学習モデルの性能や分析結果の質を大きく左右する極めて重要な工程です。今回解説した内容が、皆様のデータ分析および機械学習への取り組みの一助となれば幸いです。

最後までお読みいただき、誠にありがとうございました。今後もデータの前処理に関連する様々なトピックを取り上げていく予定です。