scipyとかscikit-learnとかに機能があるのに気づかずに独自実装して無駄に時間を使ってたみたいなことをしなくて済むように、整形したデータを分類器とかに回す前段階でやる処理でお手頃そうなものをまとめておく。
文字列をIDに変換したい場合に利用できる。
from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(['tokyo', 'osaka', 'nagoya', 'tokyo', 'yokohama', 'osaka']) le.classes_ #=> array(['nagoya', 'osaka', 'tokyo', 'yokohama'], dtype='<U8') le.transform(['tokyo', 'osaka', 'nagoya', 'tokyo', 'yokohama', 'osaka']) #=> array([2, 1, 0, 2, 3, 1]) le.inverse_transform([2, 1, 0, 2, 3, 1]) #=> array(['tokyo', 'osaka', 'nagoya', 'tokyo', 'yokohama', 'osaka'], dtype='<U8')
生成したLabelEncoderを一旦保存して他でも使いまわしたい場合は、StackOverFlowに載ってるように一部を保存しておくか、下記のように単純にpickleで取っておくか。
pickle.dump(le, 'foo.txt')
with open('foo.p', 'wb') as f:
pickle.dump(le, f)
with open('foo.p', 'rb') as f:
le2 = pickle.load(f)
le2.inverse_transform([2, 1, 0, 2, 3, 1])
#=> array(['tokyo', 'osaka', 'nagoya', 'tokyo', 'yokohama', 'osaka'], dtype='<U8')
LabelEncoderでは文字列を数値に直したけど、こうした値はたいてい1つの要素としてではなく複数の要素として扱われる。
例えば[tokyo, osaka, nagoya]という3つの値が入っていた場合、[1, 2, 3]ではなく[[1, 0, 0], [0, 1, 0]. [0, 0, 1]]になって欲しい。
下記、イメージ。
# こうなって欲しいのではなく。
pd.DataFrame([{'prefecture': 1}, {'prefecture': 2}, {'prefecture': 3}])
#=> prefecture
#=> 0 1
#=> 1 2
#=> 2 3
# こうなって欲しい。
pd.DataFrame([
{'tokyo': 1, 'osaka': 0, 'nagoya': 0},
{'tokyo': 0, 'osaka': 1, 'nagoya': 0},
{'tokyo': 0, 'osaka': 0, 'nagoya': 1}])
#=> nagoya osaka tokyo
#=> 0 0 0 1
#=> 1 0 1 0
#=> 2 1 0 0
こうした変換はLabelBinarizerを使うと可能。
from sklearn.preprocessing import LabelBinarizer lb = LabelBinarizer() lb.fit(['tokyo', 'osaka', 'nagoya']) lb.transform(['tokyo', 'nagoya', 'osaka']) #=> array([[0, 0, 1], #=> [1, 0, 0], #=> [0, 1, 0]])
書いてる途中で気づいたけど、nagoyaはprefectureではなかった。まあいいや、1871年のデータですとか書いとけば。
pandasのDataFrameのカラムに対して実行して、結果を同じDataFrameに入れたい場合。
df = pd.DataFrame([{'prefecture': 'tokyo'}, {'prefecture': 'osaka'}, {'prefecture': 'nagoya'}, {'prefecture': 'tokyo'}])
lb.fit(df.prefecture)
pd.concat([df, pd.DataFrame(lb.transform(df.prefecture), columns=lb.classes_)], axis=1)
#=> prefecture nagoya osaka tokyo
#=> 0 tokyo 0 0 1
#=> 1 osaka 0 1 0
#=> 2 nagoya 1 0 0
#=> 3 tokyo 0 0 1
上記では3つしかカラムがないのでdenseなDataFrameでも問題ないけど、これが値が増えてくるとsparseにしないと持たなくなる。
LabelBinarizerの初期化時にsparse_outputを設定しておくと、出力結果がsparseになる。
from sklearn.preprocessing import LabelBinarizer lb = LabelBinarizer(sparse_output=True) lb.fit(['tokyo', 'osaka', 'nagoya']) lb.transform(['tokyo', 'nagoya', 'osaka']) #=> <3x3 sparse matrix of type '' with 3 stored elements in Compressed Sparse Row format>
LabelBinarizerだと1つのカラムに対して1つしか1が立たない。
例えば映画のジャンルを利用したい場合に、[action, horror]とか[romance, commedy]のように複数のジャンルが紐づく場合がある。
そんな時はMultiLabelBinarizerで配列を渡すと、良い感じに変換してくれる。
from sklearn.preprocessing import MultiLabelBinarizer mlb = MultiLabelBinarizer() mlb.fit_transform([['action', 'adventure'], ['action'], ['action', 'war', 'commedy'], []]) #=> array([[1, 1, 0, 0], #=> [1, 0, 0, 0], #=> [1, 0, 1, 1], #=> [0, 0, 0, 0]])