データ分析において、カテゴリ変数は何らかの数値に変換する必要がある。
その変換方法とコードについてのまとめ。
エンコーディングの種類(概要)
- 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.0 | setosa |
6.5 | virginica |
4.7 | setosa |
5.1 | setosa |
6.3 | virginica |
5.7 | versicolor |
6.1 | virginica |
5.1 | setosa |
NaN | NaN |
NaN | NaN |
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) | species | setosa | versicolor | virginica |
---|---|---|---|---|
5.0 | setosa | 1 | 0 | 0 |
6.5 | virginica | 0 | 0 | 1 |
4.7 | setosa | 1 | 0 | 0 |
5.1 | setosa | 1 | 0 | 0 |
6.3 | virginica | 0 | 0 | 1 |
5.7 | versicolor | 0 | 1 | 0 |
6.1 | virginica | 0 | 0 | 1 |
5.1 | setosa | 1 | 0 | 0 |
NaN | NaN | 0 | 0 | 0 |
NaN | NaN | 0 | 0 | 0 |
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) | species | spcies_setosa | spcies_unknown | spcies_versicolor | spcies_virginica |
---|---|---|---|---|---|
5.0 | setosa | 1 | 0 | 0 | 0 |
6.5 | virginica | 0 | 0 | 0 | 1 |
4.7 | setosa | 1 | 0 | 0 | 0 |
5.1 | setosa | 1 | 0 | 0 | 0 |
6.3 | virginica | 0 | 0 | 0 | 1 |
5.7 | versicolor | 0 | 0 | 1 | 0 |
6.1 | virginica | 0 | 0 | 0 | 1 |
5.1 | setosa | 1 | 0 | 0 | 0 |
NaN | NaN | 0 | 1 | 0 | 0 |
NaN | NaN | 0 | 1 | 0 | 0 |
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)
結果
species | sepal length (cm) | species_1 | species_2 | species_3 | species_4 |
---|---|---|---|---|---|
setosa | 5.0 | 1 | 0 | 0 | 0 |
virginica | 6.5 | 0 | 1 | 0 | 0 |
setosa | 4.7 | 1 | 0 | 0 | 0 |
setosa | 5.1 | 1 | 0 | 0 | 0 |
virginica | 6.3 | 0 | 1 | 0 | 0 |
versicolor | 5.7 | 0 | 0 | 1 | 0 |
virginica | 6.1 | 0 | 1 | 0 | 0 |
setosa | 5.1 | 1 | 0 | 0 | 0 |
NaN | NaN | 0 | 0 | 0 | 1 |
NaN | NaN | 0 | 0 | 0 | 1 |
ラベルエンコーディング
各ラベルを数値(整数)に変換する。
数値の大小には意味がないため、回帰分析には使えないことに注意。
決定木ベースの予測機には使用可能
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) | species | label |
---|---|---|
5.0 | setosa | 0 |
6.5 | virginica | 3 |
4.7 | setosa | 0 |
5.1 | setosa | 0 |
6.3 | virginica | 3 |
5.7 | versicolor | 2 |
6.1 | virginica | 3 |
5.1 | setosa | 0 |
unknown | unknown | 1 |
unknown | unknown | 1 |
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)
結果
species | sepal length (cm) | species |
---|---|---|
setosa | 5.0 | 1 |
virginica | 6.5 | 2 |
setosa | 4.7 | 1 |
setosa | 5.1 | 1 |
virginica | 6.3 | 2 |
versicolor | 5.7 | 3 |
virginica | 6.1 | 2 |
setosa | 5.1 | 1 |
NaN | NaN | 4 |
NaN | NaN | 4 |
カウントエンコーディング
データ何に各ラベルが登場する回数を変数として使用する。
pandasのメソッド(groupby)を使う方法
欠損値はカウントされず、欠損値のまま
df_encoding = df.copy()
df_encoding['count'] = df.groupby('species')['species'].transform('count')
結果
sepal length (cm) | species | count |
---|---|---|
5.0 | setosa | 4.0 |
6.5 | virginica | 3.0 |
4.7 | setosa | 4.0 |
5.1 | setosa | 4.0 |
6.3 | virginica | 3.0 |
5.7 | versicolor | 1.0 |
6.1 | virginica | 3.0 |
5.1 | setosa | 4.0 |
NaN | NaN | NaN |
NaN | NaN | NaN |
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)
結果
species | sepal length (cm) | species |
---|---|---|
setosa | 5.0 | 4 |
virginica | 6.5 | 3 |
setosa | 4.7 | 4 |
setosa | 5.1 | 4 |
virginica | 6.3 | 3 |
versicolor | 5.7 | 1 |
virginica | 6.1 | 3 |
setosa | 5.1 | 4 |
NaN | NaN | 2 |
NaN | NaN | 2 |
ラベルカウントエンコーディング
カウントランクエンコーディングともいう。
各変数をその出現回数が多い順にランク付けし、そのランクを変数とする。
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) | species | countLabel |
---|---|---|
5.0 | setosa | 1.0 |
6.5 | virginica | 2.0 |
4.7 | setosa | 1.0 |
5.1 | setosa | 1.0 |
6.3 | virginica | 2.0 |
5.7 | versicolor | 3.0 |
6.1 | virginica | 2.0 |
5.1 | setosa | 1.0 |
NaN | NaN | NaN |
NaN | NaN | NaN |
ターゲットエンコーディング
ラベルごとに目的変数の統計量(通常は平均値を使用)を計算しその値を変数として使用する。
目的変数の情報を用いており リークが起きやすいためことに注意する。
基本的に使用を避けるか、リークが起きにくいように工夫した方法(以下)を用いるのが良い。
(*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) | species | GreedyTS |
---|---|---|
5.0 | setosa | 5.002863 |
6.5 | virginica | 6.212088 |
4.7 | setosa | 5.002863 |
5.1 | setosa | 5.002863 |
6.3 | virginica | 6.212088 |
5.7 | versicolor | 5.562500 |
6.1 | virginica | 6.212088 |
5.1 | setosa | 5.002863 |
NaN | NaN | NaN |
NaN | NaN | NaN |
結果(Leave one-out TS)
sepal length (cm) | species | LooTS |
---|---|---|
5.0 | setosa | 4.966667 |
6.5 | virginica | 6.200000 |
4.7 | setosa | 5.066667 |
5.1 | setosa | 4.933333 |
6.3 | virginica | 6.300000 |
5.7 | versicolor | 5.562500 |
6.1 | virginica | 6.400000 |
5.1 | setosa | 4.933333 |
NaN | NaN | 5.562500 |
NaN | NaN | 5.562500 |
結果(Ordered TS)
sepal length (cm) | species | OrderedTS |
---|---|---|
5.0 | setosa | 5.562500 |
6.5 | virginica | 5.562500 |
4.7 | setosa | 5.281250 |
5.1 | setosa | 5.087500 |
6.3 | virginica | 6.031250 |
5.7 | versicolor | 5.562500 |
6.1 | virginica | 6.120833 |
5.1 | setosa | 5.090625 |
NaN | NaN | NaN |
NaN | NaN | NaN |
参考
- カテゴリ変数のエンコーディング|qiita
- カテゴリ変数系特徴量の前処理(scikit-learnとcategory_encoders)|qiita
- Python: Target Encoding のやり方について
- Category Encoders 公式ドキュメント
関連書籍
関連書籍
リンク
リンク
リンク