カテゴリ変数を数値に置き換える【Python】

データをモデル化する際に、「男性 / 女性」のようなカテゴリ変数はそのままではモデルに組み込むことができません。モデル化するには何らかの方法で数値化する必要があります。

ここではデータ解析の前処理としてカテゴリ変数を数値化する方法を説明していきます。

開発環境

  • pandas 1.2.4
  • Python 3.8.8
  • scikit-learn 0.24.1

カテゴリ変数の数値化手法

カテゴリ変数とは名義変数や尺度変数による離散量のデータのことで、その値が集合{A, B, C, D, … }の中の任意の1つの値であるような変数のことを言います。例えば代表的なカテゴリ変数である性別は{男性, 女性}のいずれかの値(カテゴリ)を持つ変数といえます。しかし、機械学習のモデルを作成するときには変数を数式に組み込む必要があるので、例えば「男性 / 女性」のような値をそのままモデルに組み込むことはできません。そこで「男性 → 0、女性 → 1」のように数値に変換する必要が出てきます。

ここでは、カテゴリ変数をモデル化に使える数値変数に変換する方法を説明していきます。なお、サンプルのデータセットとしてseabornのサンプルデータに含まれるタイタニック号の沈没事故の生存/死亡者のデータを用います。

import seaborn as sns
titanic_data = sns.load_dataset('titanic')
print(titanic_data)

このデータセットには多くのデータが含まれているので、ここでは特に、「survived / class / sex / age / embark_town」に絞ってみていきましょう。なお、欠損値はあらかじめ除外しておきます。

titanic_data = titanic_data[['survived', 'class', 'sex', 'age', 'embark_town']]
titanic_data = titanic_data.dropna()
print(titanic_data)
     survived   class     sex   age  embark_town
0           0   Third    male  22.0  Southampton
1           1   First  female  38.0    Cherbourg
2           1   Third  female  26.0  Southampton
3           1   First  female  35.0  Southampton
4           0   Third    male  35.0  Southampton
..        ...     ...     ...   ...          ...
885         0   Third  female  39.0   Queenstown
886         0  Second    male  27.0  Southampton
887         1   First  female  19.0  Southampton
889         1   First    male  26.0    Cherbourg
890         0   Third    male  32.0   Queenstown

[712 rows x 5 columns]

上記のうちclass(船室階級)、sex(性別)、embark_town(出港地)がカテゴリ変数に該当します。なお、survivedについても本来はカテゴリ変数ですが、「死亡 → 0、生存 → 1」として数値化された状態です。

「生存 / 死亡」「男性 / 女性」のような2値のみのカテゴリ変数はどのような手法で数値化しても同じ結果になるのですが、3値以上のカテゴリ変数については「その順番に意味があるのか、どの程度の重みづけが必要なのか」によって様々な数値化の手法があります。ここでは代表的な数値化手法について説明していきます。

均等にカテゴリを数値化する:Label Encoding

まず最初に考えられる方法は、それぞれのカテゴリを順番に並べて「0, 1, 2, 3, …」と番号を振っていく方法です。このように順番にカテゴリを数値化する方法をLabel Encodingと呼びます。

カテゴリの順序を自動で決める方法

scikit-learnのLabelEncoderを用いて、それぞれのカテゴリに自動で数値を割り当てることができます。なお、この時の数値はABC順で自動的に決められ、以下のように割り当てられます。

  • class(船室階級):「First → 0、Second → 1、Third → 2」
  • sex(性別):「female → 0、male → 1」
  • embark_town(出港地):「Cherbourg → 0、Queenstown → 1、Southampton → 2」
from sklearn.preprocessing import LabelEncoder

# 船室階級の数値化
class_le = LabelEncoder()
titanic_data['class'] = class_le.fit_transform(titanic_data['class'])

# 性別の数値化
sex_le = LabelEncoder()
titanic_data['sex'] = sex_le.fit_transform(titanic_data['sex'])

# 出港地の数値化
town_le = LabelEncoder()
titanic_data['embark_town'] = town_le.fit_transform(titanic_data['embark_town'])

print(titanic_data)
     survived  class  sex   age  embark_town
0           0      2    1  22.0            2
1           1      0    0  38.0            0
2           1      2    0  26.0            2
3           1      0    0  35.0            2
4           0      2    1  35.0            2
..        ...    ...  ...   ...          ...
885         0      2    0  39.0            1
886         0      1    1  27.0            2
887         1      0    0  19.0            2
889         1      0    1  26.0            0
890         0      2    1  32.0            1

[712 rows x 5 columns]

数値化されたカテゴリーの一覧はLabelEncoderインスタンスのclasses_属性に格納されています。例えば出港地については

print(town_le.classes_)
['Cherbourg' 'Queenstown' 'Southampton']

となり、左から順番に0, 1, 2となっていることが分かります。

カテゴリの順序を自分で指定する方法

モデルによってはカテゴリの順番も重要な要素になってくるので、そのような場合は自分で順番を指定する必要があります。ここでは先ほどとは反対に以下のように数値を割り当てます。

  • class(船室階級):「First → 2、Second → 1、Third → 0」
  • sex(性別):「female → 1、male → 0」
  • embark_town(出港地):「Cherbourg → 2、Queenstown → 1、Southampton → 0」

LabelEncoderを用いる方法

LabelEncoderで数値化するカテゴリの順番を変更したい場合は、classes_属性にカテゴリ名のリストで指定します。

from sklearn.preprocessing import LabelEncoder

# 船室階級の数値化
class_le = LabelEncoder()
class_le.classes_ = ['Third', 'Second', 'First']
titanic_data['class'] = class_le.transform(titanic_data['class'])

# 性別の数値化
sex_le = LabelEncoder()
sex_le.classes_ = ['male', 'female']
titanic_data['sex'] = sex_le.transform(titanic_data['sex'])

# 出港地の数値化
town_le = LabelEncoder()
town_le.classes_ = ['Southampton', 'Queenstown', 'Cherbourg']
titanic_data['embark_town'] = town_le.transform(titanic_data['embark_town'])

print(titanic_data)
     survived  class  sex   age  embark_town
0           0      0    0  22.0            0
1           1      2    1  38.0            2
2           1      0    1  26.0            0
3           1      2    1  35.0            0
4           0      0    0  35.0            0
..        ...    ...  ...   ...          ...
885         0      0    1  39.0            1
886         0      1    0  27.0            0
887         1      2    1  19.0            0
889         1      2    0  26.0            2
890         0      0    0  32.0            1

[712 rows x 5 columns]

Series.mapメソッドを用いる方法

pandasのSeries.mapメソッドではカテゴリを対応する数値を明示的に指定することができるので、それを用いてカテゴリの順序を自分で指定することができます。

# 船室階級の数値化
class_mapping = {'First':2, 'Second':1, 'Third':0}
titanic_data['class'] = titanic_data['class'].map(class_mapping)

# 性別の数値化
sex_mapping = {'female':1, 'male':0}
titanic_data['sex'] = titanic_data['sex'].map(sex_mapping)

# 出港地の数値化
town_mapping = {'Cherbourg':2, 'Queenstown':1, 'Southampton':0}
titanic_data['embark_town'] = titanic_data['embark_town'].map(town_mapping)

print(titanic_data)
     survived class  sex   age  embark_town
0           0     0    0  22.0            0
1           1     2    1  38.0            2
2           1     0    1  26.0            0
3           1     2    1  35.0            0
4           0     0    0  35.0            0
..        ...   ...  ...   ...          ...
885         0     0    1  39.0            1
886         0     1    0  27.0            0
887         1     2    1  19.0            0
889         1     2    0  26.0            2
890         0     0    0  32.0            1

[712 rows x 5 columns]

これでそれぞれのカテゴリを指定した数値に変換することができました。

重みづけをしてカテゴリを数値化する

3つ以上のカテゴリがある場合は、回帰モデルにおいてはどのような順序で数値化するかに加えて、どのように重みづけして数値化するかは非常に重要な要素です。例えばロジスティック回帰モデルでは、説明変数\(x_1, x_2, x_3, … ,x_n\)を用いて

$$logit(p)=a_1*x_1+a_2*x_2+a_3*x_3+…+a_n*x_n+b$$

と表し、数値化したカテゴリの値を\(x_1, x_2, x_3, … ,x_n\)に代入するので、3つ以上のカテゴリの場合はその順序と重みづけが結果に直接かかわってきます。

先ほどのSeries.mapメソッドを用いてカテゴリを数値化する方法では、それぞれのカテゴリの数値を直接指定することができるので、ここではそれを用いてカテゴリに重みづけした数値を指定する方法を見ていきましょう。

Count Encoding

Count Encodingはそれぞれのカテゴリのデータの出現回数によってそのカテゴリを数値化する方法です。

# 船室階級の数値化
class_mapping = titanic_data['class'].value_counts().to_dict()
titanic_data['class'] = titanic_data['class'].map(class_mapping)

# 性別の数値化
sex_mapping = titanic_data['sex'].value_counts().to_dict()
titanic_data['sex'] = titanic_data['sex'].map(sex_mapping)

# 出港地の数値化
town_mapping = titanic_data['embark_town'].value_counts().to_dict()
titanic_data['embark_town'] = titanic_data['embark_town'].map(town_mapping)

print(titanic_data)
     survived class  sex   age  embark_town
0           0   355  453  22.0          554
1           1   184  259  38.0          130
2           1   355  259  26.0          554
3           1   184  259  35.0          554
4           0   355  453  35.0          554
..        ...   ...  ...   ...          ...
885         0   355  259  39.0           28
886         0   173  453  27.0          554
887         1   184  259  19.0          554
889         1   184  453  26.0          130
890         0   355  453  32.0           28

[712 rows x 5 columns]

ここではそれぞれのカテゴリの出現回数に応じて以下のように数値化しています。

  • class(船室階級):「First → 184、Second → 173、Third → 355」
  • sex(性別):「female → 259、male → 453」
  • embark_town(出港地):「Cherbourg → 130、Queenstown → 28、Southampton → 554」

なお、データの出現回数によってカテゴリを並べ替えて、0, 1, 2, … という数値に置き換える方法もあり、そちらはLabel-Count Encodingと呼ばれています。

Target Encoding

Target Encodingはそのカテゴリにおける目的変数の平均値によってそのカテゴリを数値化する方法です。そのカテゴリが結果に与える影響の重みづけは、それぞれのカテゴリの目的変数の平均を考えるのが直感的にも分かりやすく、シンプルな方法です。

# 船室階級の数値化
class_mapping = titanic_data[['class', 'survived']].groupby(['class'])['survived'].mean().to_dict()
titanic_data['class'] = titanic_data['class'].map(class_mapping)

# 性別の数値化
sex_mapping = titanic_data[['sex', 'survived']].groupby(['sex'])['survived'].mean().to_dict()
titanic_data['sex'] = titanic_data['sex'].map(sex_mapping)

# 出港地の数値化
town_mapping = titanic_data[['embark_town', 'survived']].groupby(['embark_town'])['survived'].mean().to_dict()
titanic_data['embark_town'] = titanic_data['embark_town'].map(town_mapping)

print(titanic_data)
     survived     class       sex   age  embark_town
0           0  0.239437  0.205298  22.0     0.362816
1           1  0.652174  0.752896  38.0     0.607692
2           1  0.239437  0.752896  26.0     0.362816
3           1  0.652174  0.752896  35.0     0.362816
4           0  0.239437  0.205298  35.0     0.362816
..        ...       ...       ...   ...          ...
885         0  0.239437  0.752896  39.0     0.285714
886         0  0.479769  0.205298  27.0     0.362816
887         1  0.652174  0.752896  19.0     0.362816
889         1  0.652174  0.205298  26.0     0.607692
890         0  0.239437  0.205298  32.0     0.285714

[712 rows x 5 columns]

ここではそれぞれのカテゴリのsurvivedの平均値に応じて以下のように数値化しています。

  • class(船室階級):「First → 0.6521…、Second → 0.4797…、Third → 0.2394…」
  • sex(性別):「female → 0.7528…、male → 0.2052…」
  • embark_town(出港地):「Cherbourg→0.607…、Queenstown→0.285…、Southampton→0.362…」

ただし、この方法では目的変数の値が説明変数に含まれている(=リークしている)ので、見かけ上の予測精度が高くなってしまうことに注意が必要です。リークの影響を最小限にするように工夫する必要があり、実際には上記のような単純な平均値ではなく様々なテクニックを駆使した方法が用いられることになります

One-Hot Encoding

ここまで説明してきた方法はいずれもカテゴリ変数のカテゴリーを何らかの数値に置き換える方法でした。単純に0, 1, 2, … という数値に置き換えるのがLabel Encodingで、何らかの方法で重みづけを行うのがCount EncodingやTarget Encodingです。しかし、限られた情報からデータの前処理の段階で正しい重みづけを行うことは難しく、またTarget Encodingではリークの問題もあります。そこでそのような問題を解決する方法として、カテゴリ変数をそれぞれのカテゴリごとに変数(ダミー変数)に分割する方法をOne-Hot Encodingと言います。

今回の例ではembark_townのカテゴリは「Cherbourg / Queenstown / Southampton」なので、その3つのカテゴリを変数として、Yes(=1)かNo(=0)かで元のembark_townの要素を表します。

この方法ではそれぞれのカテゴリの重みづけをモデルの最適化の際に行うことができるので、今までの問題をすべて解決することができます。ただし、大量のカテゴリが含まれているカテゴリ変数の場合は、ダミー変数の数が膨大になってしまうのでこの方法を用いるのは難しい場合もあります。

なお、上記の例でいえば、embark_townが「Cherbourgか否か」とQueestownか否か」が分かれば「Southamptonか否か」は自動的に定まります。そのような場合にすべての変数をモデルに組み込んでしまうと多重共線性の問題が発生してしまうので、ダミー変数のうち1つをモデルから外すことがよく行われます。

Pythonでは、pandasのget_dummies関数を用いれば簡単にOne-Hot Encodingを行うことができます。その際にdrop_first引数をTrueに設定することで1つのダミー変数を自動で削除することができるので、多重共線性の問題を回避できます。

import pandas as pd
titanic_data = pd.get_dummies(titanic_data, drop_first=True)

print(titanic_data)

上記の例ではすべてのカテゴリ変数をダミー変数化しましたが、get_dummies関数のcolumns引数に列ラベルを指定することでそのカテゴリ変数のみをダミー変数にすることもできます。

import pandas as pd
titanic_data = pd.get_dummies(titanic_data, columns=['embark_town'], drop_first=True)

print(titanic_data)

columns引数にembark_townのみを渡すことで、sexはそのままでembark_townのみをダミー変数化しました。

なお、PythonでOne-Hot Encodingを行う方法はscikit-learnやcategory_encodersなどのライブラリでも実装されています。ここに示したget_dummies関数を用いる方法ではデータセットに含まれているデータ次第で列ラベルが変化してしまうので、訓練データの「行 x 列」の形状がデータ次第で変化してしまうというデメリットがあります。データ形状を一定にする必要がある場合はscikit-learnのOneHotEncoderやcategory_encodersのOneHotEncoderを使用してください。

関連記事・スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)