カテゴリ変数のエンコーディングについて

データ分析において、カテゴリ変数は何らかの数値に変換する必要がある。
その変換方法とコードについてのまとめ。

エンコーディングの種類(概要)

  • One-Hot エンコーディング:変数におけるラベルの種類ごとに特徴量(列)を生成し、True(1), False(0)を割り当てる。
  • ラベルエンコーディング:各ラベルを数値(整数)に変換する。
  • カウントエンコーディング:データ何に各ラベルが登場する回数を変数として使用する。
  • ラベルカウントエンコーディング:各変数のラベルを、その出現回数が多い順にランク付けし、そのランクを変数とする。
  • ターゲットエンコーディング:ラベルごとに目的変数の統計量(通常は平均値を使用)を計算しその値を変数として使用する。

それぞれ、pandasのメソッドや、scikit-learnのpreprosessing、専用のライブラリ(category_encoding)を使う方法がある。各方法で欠損値の取扱などが微妙に異なる。

データの準備

scikit-learnのあやめのデータを利用する。

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris

data = load_iris()

df_X = pd.DataFrame(data.data,columns=data.feature_names)
df_y = pd.DataFrame(data.target,columns=['species'])
df = pd.concat([df_X.iloc[:,0],df_y],axis=1)

speciesDict = {k : v for k, v in zip([0,1,2],data.target_names)}
df['species'] = df['species'].replace(speciesDict)
df = df.sample(n=10,random_state=6)
df.reset_index(drop=True, inplace=True)

#欠損値を入れる
df.iloc[-2:,:] = np.nan
データの中身
sepal length (cm)species
5.0setosa
6.5virginica
4.7setosa
5.1setosa
6.3virginica
5.7versicolor
6.1virginica
5.1setosa
NaNNaN
NaNNaN

One-hot エンコーディング

変数におけるラベルの種類ごとに特徴量(列)を生成し、True(1), False(0)を割り当てる。

pandasのget_dummiesを使う方法

欠損値はエンコーディングされない(すべて0になる)

df_dummies = pd.get_dummies(df['species'])
df_encoding = pd.concat([df,df_dummies],axis=1)
結果
sepal length (cm)speciessetosaversicolorvirginica
5.0setosa100
6.5virginica001
4.7setosa100
5.1setosa100
6.3virginica001
5.7versicolor010
6.1virginica001
5.1setosa100
NaNNaN000
NaNNaN000

scikit-learn.preprocesssing を使う方法

欠損値があるとエンコーディングできないので何らかの値で補完する必要がある。

from sklearn.preprocessing import OneHotEncoder

#欠損値補完
df_temp = df.fillna('unknown')

#エンコーディング
ohe = OneHotEncoder(sparse=False)
encoded = ohe.fit_transform(df_temp['species'].values.reshape(-1, 1))

#ラベル名を取得し接頭語をつけたのちDataFrameを作成
label = ohe.get_feature_names(['spcies'])
df_encoding = pd.DataFrame(encoded, columns=label, dtype=int)

df_encoding = pd.concat([df, df_encoding], axis=1)
結果
sepal length (cm)speciesspcies_setosaspcies_unknownspcies_versicolorspcies_virginica
5.0setosa1000
6.5virginica0001
4.7setosa1000
5.1setosa1000
6.3virginica0001
5.7versicolor0010
6.1virginica0001
5.1setosa1000
NaNNaN0100
NaNNaN0100

category_encodersを使う方法

欠損値の取り扱いを指定することが可能

  • "value": 欠損値も変数の一つとして扱う
  • "return_nan": NaNで埋める
  • "error": エンコードせずにerrorを返す
import category_encoders as ce

ce_ohe = ce.OneHotEncoder(cols=['species'],handle_missing='value')
df_encoding = ce_ohe.fit_transform(df)
df_encoding = pd.concat([df['species'],df_encoding],axis=1)
結果
speciessepal length (cm)species_1species_2species_3species_4
setosa5.01000
virginica6.50100
setosa4.71000
setosa5.11000
virginica6.30100
versicolor5.70010
virginica6.10100
setosa5.11000
NaNNaN0001
NaNNaN0001

ラベルエンコーディング

各ラベルを数値(整数)に変換する。
数値の大小には意味がないため、回帰分析には使えないことに注意。
決定木ベースの予測機には使用可能

scikit-learn.preprocesssing を使う方法

欠損値があるとエラーになるので何らかの値で補完する。

#1種類の変数を変換したい時 ==========================
from sklearn.preprocessing import LabelEncoder

#欠損値補完
df_encoding = df.fillna('unknown')

#エンコーディング
le = LabelEncoder()
encoded = le.fit_transform(df_encoding['species'].values)
df_encoding['label'] = encoded

#元の変数に戻したい(デコード)時
decoded = le.inverse_transform(encoded)
#=================================================

#複数変数を一気にラベルエンコーディングしたい時 ==========
# from sklearn.preprocessing import OrdinalEncoder

# df_encoding = df.fillna('unknown')

# oe = preprocessing.OrdinalEncoder()
# encoded = oe.fit_transform(df[_encoding['hoge1', 'hoge2']].values)
# # decoded = oe.inverse_transform(encoded)
# df_encoding[['encoded1', 'encoded2']] = encoded
#=================================================
結果
sepal length (cm)specieslabel
5.0setosa0
6.5virginica3
4.7setosa0
5.1setosa0
6.3virginica3
5.7versicolor2
6.1virginica3
5.1setosa0
unknownunknown1
unknownunknown1

category_encodersを使う方法

欠損値の取り扱いを指定することが可能

  • "value": 欠損値も変数の一つとして扱う
  • "return_nan": NaNで埋める
  • "error": エンコードせずにerrorを返す
import category_encoders as ce

ce_oe = ce.ordinal.OrdinalEncoder(cols=['species'],handle_missing='value')
df_encoding = ce_oe.fit_transform(df)
df_encoding = pd.concat([df['species'],df_encoding],axis=1)
結果
speciessepal length (cm)species
setosa5.01
virginica6.52
setosa4.71
setosa5.11
virginica6.32
versicolor5.73
virginica6.12
setosa5.11
NaNNaN4
NaNNaN4

カウントエンコーディング

データ何に各ラベルが登場する回数を変数として使用する。

pandasのメソッド(groupby)を使う方法

欠損値はカウントされず、欠損値のまま

df_encoding = df.copy()
df_encoding['count'] = df.groupby('species')['species'].transform('count')
結果
sepal length (cm)speciescount
5.0setosa4.0
6.5virginica3.0
4.7setosa4.0
5.1setosa4.0
6.3virginica3.0
5.7versicolor1.0
6.1virginica3.0
5.1setosa4.0
NaNNaNNaN
NaNNaNNaN

category_encodersを使う方法

欠損値の取り扱いを指定することが可能

  • "count": 欠損値もカウントされる
  • "return_nan": NaNで埋める
  • "error": エンコードせずにerrorを返す
import category_encoders as ce

ce_ce = ce.count.CountEncoder(cols=['species'],handle_missing='count')
df_encoding = ce_ce.fit_transform(df)
df_encoding = pd.concat([df['species'],df_encoding],axis=1)
結果
speciessepal length (cm)species
setosa5.04
virginica6.53
setosa4.74
setosa5.14
virginica6.33
versicolor5.71
virginica6.13
setosa5.14
NaNNaN2
NaNNaN2

ラベルカウントエンコーディング

カウントランクエンコーディングともいう。
各変数をその出現回数が多い順にランク付けし、そのランクを変数とする。

pandasのメソッド(groupby)を使う方法

欠損値はカウント・ランク付けされず、欠損値のまま

df_encoding = df.copy()
count_rank = df.groupby('species')['species'].count().rank(ascending=False)
df_encoding['countLabel'] = df['species'].map(count_rank)
結果
sepal length (cm)speciescountLabel
5.0setosa1.0
6.5virginica2.0
4.7setosa1.0
5.1setosa1.0
6.3virginica2.0
5.7versicolor3.0
6.1virginica2.0
5.1setosa1.0
NaNNaNNaN
NaNNaNNaN

ターゲットエンコーディング

ラベルごとに目的変数の統計量(通常は平均値を使用)を計算しその値を変数として使用する。
目的変数の情報を用いており リークが起きやすいためことに注意する。
基本的に使用を避けるか、リークが起きにくいように工夫した方法(以下)を用いるのが良い。
(*TS:target Statistics)

  • Greedy TS: 最も基本的な方法、ラベルごとに統計量を計算する(リーク起きやすい)
  • Leave one-out TS: 自分自身を統計量の計算から除外することでリークを緩和している(Greedy TSよりはマシだが依然リークは起きる)
  • Holdout TS: k-Fold CVの容量でデータを分割し学習用データを使って統計量を計算し、ホールドアウトサンプルの変数として用いる。
    Leave one-out TSでは一つだった除外レコードを増しているイメージ。
    (Leave one-out TSよりもリークを起こしにくい)
  • Ordered TS: オンライン学習のコンセプトを取り入れることでリークを防いでいるらしい(詳しくはref参照)

category_encoderを用いたターゲットエンコーディング

  • "value": ターゲットの平均値が記載される
  • "return_nan" :NaNで埋める
  • "error": エンコードせずにerrorを返す
import category_encoders as ce

#Greedy Target Statistics
df_GreedyTS = df.copy()
te = ce.TargetEncoder(cols=['species'])
df_GreedyTS['GreedyTS']= te.fit_transform(df['species'], df['sepal length (cm)'])

#Leave one-out Target Statistics
df_LooTS = df.copy()
te = ce.LeaveOneOutEncoder(cols=['species'])
df_LooTS['LooTS']= te.fit_transform(df['species'], df['sepal length (cm)'])

#Ordered Target Statistics
df_OTS = df.copy()
te = ce.CatBoostEncoder(cols=['species'])
df_OTS['OrderedTS']= te.fit_transform(df['species'], df['sepal length (cm)'])
結果(Greedy TS)
sepal length (cm)speciesGreedyTS
5.0setosa5.002863
6.5virginica6.212088
4.7setosa5.002863
5.1setosa5.002863
6.3virginica6.212088
5.7versicolor5.562500
6.1virginica6.212088
5.1setosa5.002863
NaNNaNNaN
NaNNaNNaN
結果(Leave one-out TS)
sepal length (cm)speciesLooTS
5.0setosa4.966667
6.5virginica6.200000
4.7setosa5.066667
5.1setosa4.933333
6.3virginica6.300000
5.7versicolor5.562500
6.1virginica6.400000
5.1setosa4.933333
NaNNaN5.562500
NaNNaN5.562500
結果(Ordered TS)
sepal length (cm)speciesOrderedTS
5.0setosa5.562500
6.5virginica5.562500
4.7setosa5.281250
5.1setosa5.087500
6.3virginica6.031250
5.7versicolor5.562500
6.1virginica6.120833
5.1setosa5.090625
NaNNaNNaN
NaNNaNNaN

参考

関連書籍

関連書籍