静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念


【python】sklearnでのカテゴリ変数の取り扱いまとめ LabelEncoder, OneHotEncoderなど

概要

 カテゴリ変数をダミー変数に変換したり、one-hot表現に変換したりして取り扱うという方法は、機械学習などでは一般的に行われます。

 しかし、最近まではそういった処理をsklearnで行うのが若干面倒くさい、という問題がありました。なので、やれpandasを使えだの、サードパーティ製ライブラリで凌げだのといった話題が乱立していました。

 sklearn 0.20からはOneHotEncoderが拡張され、sklearnの枠組みの中で簡単にカテゴリ変数をone-hot特徴量に変換できるようになりました。

 目次

スポンサーリンク


カテゴリ変数を取り扱うために使用するクラス

 sklearn.preprocessing以下のクラスで望む処理が行なえます。

 具体的によく使うであろうクラスとしては、

  • LabelEncoder
  • LabelBinarizer
  • OrdinalEncoder
  • OneHotEncoder

 があります。上の2つがラベルに対する処理、下の2つが説明変数(カテゴリ変数)に対する処理を担います。

 他にも、

  • MultiLabelBinarizer

 というものがあり、複数のラベルを複数の二値変数で表現する(a,b,cがすべてのラベルのとき、a,cは[1,0,1]とか)機能を持つようですが、あまり使う機会もないと思うので割愛します。

 詳細はリファレンスで確認することをおすすめします。

API Reference — scikit-learn 0.21.3 documentation

LabelEncoder

 これはラベルの変換に使います。教師ラベルを変換するのに使えます。教師ラベルはあえて変換しなくても、そのまま受け付けてくれるモデルがsklearnでは多い気がするので、なくても良いような気はします。

>>> from sklearn.preprocessing import LabelEncoder
>>> le = LabelEncoder()
>>> le.fit_transform(["hoge", "fuga", "piyo"])
array([1, 0, 2])

 インデックスの順番は、たぶんソートして昇順でしょう。

sklearn.preprocessing.LabelEncoder — scikit-learn 0.21.3 documentation

LabelBinarizer

 ラベルを俗に言うone-hot表現に変換します。

>>> from sklearn.preprocessing import LabelBinarizer
>>> lb = LabelBinarizer()
>>> lb.fit_transform(["hoge", "fuga", "piyo"])
array([[0, 1, 0],
       [1, 0, 0],
       [0, 0, 1]])

 LabelEncoderのときの変換されたラベルに対応するインデックスが1になる、という特徴があります。

OrdinalEncoder

 LabelEncoderに似ていますが、ラベルではなくデータを受け付けるもの、と認識しておけば良いと思います。

 要するに、我々が慣れ親しんだ[n_samples, n_features]の配列を受け取ります。受け取る配列は同じ列数でないといけません。

>>> from sklearn.preprocessing import OrdinalEncoder
>>> oe = OrdinalEncoder()
>>> oe.fit_transform([["h", "ho", "hoge"], ["f", "fu", "fuga"], ["p", "pi", "piyo"]])
array([[1., 1., 1.],
       [0., 0., 0.],
       [2., 2., 2.]])

sklearn.preprocessing.OrdinalEncoder — scikit-learn 0.21.3 documentation

 これで良いときはこれを使えば良いのですが、one-hot表現がほしいときが多いと思います。その場合は、次節で示すOneHotEncoderが使えます。

OneHotEncoder

 最初に書いた通り、sklearn 0.20からOneHotEncoderがOrdinalEncoder相応のデータを受け付けるようになりました(それ以前はそういう仕様ではなく、OrdinalEncoderを通して数値化したデータを入れる必要がありました)。

 なので、今後は事実上これだけ使えば良いということです。

>>> from sklearn.preprocessing import OneHotEncoder
>>> ohe = OneHotEncoder()
>>> ohe.fit_transform([["h", "ho", "hoge"], ["f", "fu", "fuga"], ["p", "pi", "piyo"]])
<3x9 sparse matrix of type '<class 'numpy.float64'>'
	with 9 stored elements in Compressed Sparse Row format>
>>> ohe = OneHotEncoder(sparse=False)
>>> ohe.fit_transform([["h", "ho", "hoge"], ["f", "fu", "fuga"], ["p", "pi", "piyo"]])
array([[0., 1., 0., 0., 1., 0., 0., 1., 0.],
       [1., 0., 0., 1., 0., 0., 1., 0., 0.],
       [0., 0., 1., 0., 0., 1., 0., 0., 1.]])

 注意点として、上でも示していますがデフォルトパラメータでは返り値がsparse matrixです。sparse=False一発でnumpy配列を返すようになります。sklearnのモデルの疎行列対応はモデルによって対応していたり、していなかったりという具合なので、後段のモデルに色々なものを使いたければそちらを指定することをおすすめします。

sklearn.preprocessing.OneHotEncoder — scikit-learn 0.21.3 documentation

pandasとの使い分けについて

 インターネット上では、このような用途にはpandasのget_dummiesを使う、という解説が多いようです。検索してもたくさんヒットします。

One-HotエンコーディングならPandasのget_dummiesを使おう | Shikoan's ML Blog
pandasでカテゴリ変数をダミー変数に変換(get_dummies) | note.nkmk.me

 ただ、この方法だと「1つのデータフレームに対して操作する分には良いけど、同じ条件で新データを変換するのには?」という問題がつきまといます。scikit-learnならそういう使い方を当然前提としているのでfit, transformで一発でできる操作が、ずいぶんややこしくなります。

 kaggleの解説notebookとかで、学習データと予測データをconcatして変換してから切り分けるみたいなのも見たことがありますが、普段leakageに気を配って作業している機械学習ユーザにとっては背筋の凍るような話だと思います。インデックスをミスっただけで死ぬわ……いや、それ以前に、実際の予測にどうやって適用するのか、と。

 逆に、そういうことを考えないで良い場合は、get_dummiesは便利です。なので、

  • 可視化や探索的データ解析など、学習データとテストデータ(あるいは実データ)を分離する必要がないときはpandasの枠組みで取り扱う
  • 機械学習のときはおとなしくscikit-learnでやろう

 という使い分けになるかと思います。目的の違うライブラリなので、用途に合わせて使いましょう。

 pandasとの棲み分けについては以下の記事も御覧ください。

www.haya-programming.com

まとめ

 このようにsklearnがカテゴリ変数の取扱にちゃんと対応したので、古いノウハウは役立たずというか「冗長なやり方」になっています。注意が必要です。

 現在は、大抵の場合はsklearnだけでシンプルにやりたいことが実現できます。Pipelineなどを使ってモデルに統合できるので、そちらの方が(機械学習モデルの構築という観点からすれば)冴えたやり方のはずです。こちらを活用しましょう。