Python pandas のデータを scikit-learn でうまいこと処理したい
はじめに
Python で機械学習する場合、pandas
で前処理したデータを scikit-learn
で処理する、というケースが多いと思う。pandas
, scikit-learn
には それぞれ 簡単にできる / できない処理があるので、うまいこと連携できるとうれしい。
scikit-learn
の各メソッドは numpy.ndarray
に対する処理を前提にしているため、pandas
のデータ形式 (DataFrame
や Series
) を渡すと 内部で ndarray
に変換して結果を返してくる。そのため、結果に対して 直接 pandas
の処理を続けることはできない。
ndarray
で処理すりゃいいじゃん、、というむきもあるが、自分はどうしても pandas
で処理がしたいんだ。とりあえず、pandas
のデータをできるだけ維持したい、というモチベーションがあるものとして処理のサンプルを書く。上述のとおり scikit-learn
で何か処理をするたびに ndarray
が返ってくるので、これを 都度 pandas
のデータ形式に戻してやる必要がある。
pandas
/ scikit-learn
標準での処理
このサンプルで行う処理の流れは、
- データの準備:
sklearn.datasets
のデータセットを読み込み - 前処理:
sklearn.preprocessing
で説明変数を二値化し、sklearn.cross_validation
でデータを訓練用 / テスト用に分割 - 分類 / 評価:
sklearn.svm
で 分類し、sklearn.metrics
で評価
# おまじない import numpy as np import pandas as pd pd.options.display.max_rows = 10 pd.options.display.max_columns = 15 state = np.random.RandomState(1)
データの準備
scikit-learn
付属の手書き数字のデータセットを datasets.load_digits
で読み込む。
import sklearn.datasets as datasets digits = datasets.load_digits() type(digits) # <class 'sklearn.datasets.base.Bunch'> # 説明変数 digits.data.shape # (1797, 64) # 目的変数 digits.target.shape # (1797,) # 目的変数を Series に変換 target = pd.Series(digits.target) target # 0 0 # 1 1 # 2 2 # ... # 1794 8 # 1795 9 # 1796 8 # Length: 1797, dtype: int64 # 説明変数を DataFrame に変換 data = pd.DataFrame(digits.data) data # 0 1 2 3 4 5 6 ... 57 58 59 60 61 62 63 # 0 0 0 5 13 9 1 0 ... 0 6 13 10 0 0 0 # 1 0 0 0 12 13 5 0 ... 0 0 11 16 10 0 0 # 2 0 0 0 4 15 12 0 ... 0 0 3 11 16 9 0 # 3 0 0 7 15 13 1 0 ... 0 7 13 13 9 0 0 # 4 0 0 0 1 11 0 0 ... 0 0 2 16 4 0 0 # ... .. .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 1792 0 0 4 10 13 6 0 ... 0 2 14 15 9 0 0 # 1793 0 0 6 16 13 11 1 ... 0 6 16 14 6 0 0 # 1794 0 0 1 11 15 1 0 ... 0 2 9 13 6 0 0 # 1795 0 0 2 10 7 0 0 ... 0 5 12 16 12 0 0 # 1796 0 0 10 14 8 1 0 ... 1 8 12 14 12 1 0 # # [1797 rows x 64 columns]
前処理
まずは 説明変数を preprocessing.binarize
で二値化して
import sklearn.preprocessing as pp data = pd.DataFrame(pp.binarize(data.values, threshold=5)) data # 0 1 2 3 4 5 6 ... 57 58 59 60 61 62 63 # 0 0 0 0 1 1 0 0 ... 0 1 1 1 0 0 0 # 1 0 0 0 1 1 0 0 ... 0 0 1 1 1 0 0 # 2 0 0 0 0 1 1 0 ... 0 0 0 1 1 1 0 # 3 0 0 1 1 1 0 0 ... 0 1 1 1 1 0 0 # 4 0 0 0 0 1 0 0 ... 0 0 0 1 0 0 0 # ... .. .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 1792 0 0 0 1 1 1 0 ... 0 0 1 1 1 0 0 # 1793 0 0 1 1 1 1 0 ... 0 1 1 1 1 0 0 # 1794 0 0 0 1 1 0 0 ... 0 0 1 1 1 0 0 # 1795 0 0 0 1 1 0 0 ... 0 0 1 1 1 0 0 # 1796 0 0 1 1 1 0 0 ... 0 1 1 1 1 0 0 # # [1797 rows x 64 columns]
cross_validation.train_test_split
で 訓練用とテスト用のデータに分割する。
import sklearn.cross_validation as crv train_data, test_data, train_target, test_target = crv.train_test_split(data.values, target.values, random_state=state) train_data = pd.DataFrame(train_data) test_data = pd.DataFrame(test_data) train_target = pd.Series(train_target) test_target = pd.Series(test_target) train_data # 0 1 2 3 4 5 6 ... 57 58 59 60 61 62 63 # 0 0 0 1 1 1 0 0 ... 0 1 1 1 0 0 0 # 1 0 0 0 1 1 0 0 ... 0 0 1 1 1 1 0 # 2 0 0 0 0 1 0 0 ... 0 0 0 1 1 0 0 # 3 0 0 0 1 1 1 1 ... 0 0 1 0 0 0 0 # 4 0 0 1 1 1 0 0 ... 0 0 1 1 1 0 0 # ... .. .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 1342 0 0 0 1 1 0 0 ... 0 0 1 1 1 0 0 # 1343 0 0 0 0 1 0 0 ... 0 0 0 1 0 0 0 # 1344 0 0 1 1 1 0 0 ... 0 1 1 1 1 0 0 # 1345 0 0 0 0 1 1 1 ... 0 0 0 1 1 0 0 # 1346 0 0 1 1 1 1 1 ... 0 1 1 1 0 0 0 # # [1347 rows x 64 columns] test_data # 0 1 2 3 4 5 6 ... 57 58 59 60 61 62 63 # 0 0 0 1 1 1 0 0 ... 0 0 1 1 0 0 0 # 1 0 1 1 1 1 1 1 ... 1 1 1 0 0 0 0 # 2 0 0 0 1 1 0 0 ... 0 0 1 1 1 0 0 # 3 0 0 0 1 1 1 1 ... 0 0 1 0 0 0 0 # 4 0 0 0 0 1 1 0 ... 0 0 0 1 1 0 0 # .. .. .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 445 0 0 1 1 1 0 0 ... 0 1 1 1 1 0 0 # 446 0 0 1 1 1 0 0 ... 0 1 1 1 1 0 0 # 447 0 0 1 1 1 0 0 ... 0 1 1 0 0 0 0 # 448 0 0 1 1 1 0 0 ... 0 1 1 1 0 0 0 # 449 0 0 1 1 1 0 0 ... 0 1 1 1 0 0 0 # # [450 rows x 64 columns]
分類 / 評価
svm.LinearSVC
で分類する。
import sklearn.svm as svm svc = svm.LinearSVC(random_state=state) # 訓練データをフィット svc.fit(train_data, train_target) # LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True, # intercept_scaling=1, loss='l2', multi_class='ovr', penalty='l2', # random_state=<mtrand.RandomState object at 0x103f28350>, tol=0.0001, # verbose=0) # テストデータを分類 predicted = svc.predict(test_data) predicted = pd.Series(predicted) predicted # 0 1 # 1 5 # 2 0 # ... # 447 7 # 448 8 # 449 5 # Length: 450, dtype: int64
最後に、得られた結果を metrics
で評価する。
metrics.confusion_matrix
: 混同行列metrics.accuracy_score
: 正答率
import sklearn.metrics as metrics metrics.confusion_matrix(test_target, predicted) # [[51 0 0 0 1 0 0 0 1 0] # [ 0 38 0 0 0 1 0 0 3 0] # [ 0 0 38 1 0 0 1 1 0 0] # [ 0 1 1 45 0 2 1 1 1 0] # [ 0 0 0 0 45 0 1 1 0 0] # [ 0 0 2 0 0 36 0 0 0 1] # [ 0 0 0 0 0 0 43 0 0 0] # [ 0 0 0 0 1 0 0 47 0 0] # [ 0 2 1 1 1 0 0 0 31 1] # [ 0 2 0 1 1 3 0 1 1 39]] metrics.accuracy_score(test_target, predicted) # 0.917777777778
うん、、、めんどくさい。この めんどくささは 以下 3 点に起因していると思う。
scikit-learn
返り値のndarray
を 都度pandas
のデータ形式に変換する必要がある ( このとき、データの属性、たとえばカラム名を維持したい場合は毎回columns
を引数として渡す必要がある )scikit-learn
の個々のモジュールを都度 ロードする必要がある (これはpandas
云々は関係ないが)scikit-learn
にいちいち似たような引数を渡さなければならない ( 目的変数が変わることはないはずなので、一度 定義したものが使いまわせるとうれしい )
というわけで
作りました。
ドキュメントはこちら。
こちらを使うと 上のサンプルは以下のように書ける。
state = np.random.RandomState(1) import pandas_ml as pdml import sklearn.datasets as datasets # データの読み込み / データ型の作成 (ModelFrame は DataFrame を継承したクラス) df = pdml.ModelFrame(datasets.load_digits()) # 説明変数 df.data # 0 1 2 3 4 5 6 ... 57 58 59 60 61 62 63 # 0 0 0 5 13 9 1 0 ... 0 6 13 10 0 0 0 # 1 0 0 0 12 13 5 0 ... 0 0 11 16 10 0 0 # 2 0 0 0 4 15 12 0 ... 0 0 3 11 16 9 0 # 3 0 0 7 15 13 1 0 ... 0 7 13 13 9 0 0 # 4 0 0 0 1 11 0 0 ... 0 0 2 16 4 0 0 # ... .. .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 1792 0 0 4 10 13 6 0 ... 0 2 14 15 9 0 0 # 1793 0 0 6 16 13 11 1 ... 0 6 16 14 6 0 0 # 1794 0 0 1 11 15 1 0 ... 0 2 9 13 6 0 0 # 1795 0 0 2 10 7 0 0 ... 0 5 12 16 12 0 0 # 1796 0 0 10 14 8 1 0 ... 1 8 12 14 12 1 0 # # [1797 rows x 64 columns] # 目的変数 df.target # 0 0 # 1 1 # 2 2 # ... # 1794 8 # 1795 9 # 1796 8 # Name: .target, Length: 1797, dtype: int64 # 二値化 df.data = df.data.preprocessing.binarize(threshold=5) # 訓練用とテスト用のデータに分割 train_df, test_df = df.cross_validation.train_test_split(random_state=state) train_df # .target 0 1 2 3 4 5 ... 57 58 59 60 61 62 63 # 0 2 0 0 1 1 1 0 ... 0 1 1 1 0 0 0 # 1 6 0 0 0 1 1 0 ... 0 0 1 1 1 1 0 # 2 6 0 0 0 0 1 0 ... 0 0 0 1 1 0 0 # 3 7 0 0 0 1 1 1 ... 0 0 1 0 0 0 0 # 4 1 0 0 1 1 1 0 ... 0 0 1 1 1 0 0 # ... ... .. .. .. .. .. .. ... .. .. .. .. .. .. .. # 1342 8 0 0 0 1 1 0 ... 0 0 1 1 1 0 0 # 1343 4 0 0 0 0 1 0 ... 0 0 0 1 0 0 0 # 1344 9 0 0 1 1 1 0 ... 0 1 1 1 1 0 0 # 1345 1 0 0 0 0 1 1 ... 0 0 0 1 1 0 0 # 1346 5 0 0 1 1 1 1 ... 0 1 1 1 0 0 0 # # [1347 rows x 65 columns] # 訓練データをフィット svc = train_df.svm.LinearSVC(random_state=state) train_df.fit(svc) # LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True, # intercept_scaling=1, loss='l2', multi_class='ovr', penalty='l2', # random_state=<mtrand.RandomState object at 0x103f3e350>, tol=0.0001, # verbose=0) # テストデータを分類 predicted = test_df.predict(svc) predicted # 0 1 # 1 5 # 2 0 # ... # 447 7 # 448 8 # 449 5 # Length: 450, dtype: int64 # 評価 test_df.metrics.confusion_matrix() # Predicted 0 1 2 3 4 5 6 7 8 9 # Target # 0 51 0 0 0 1 0 0 0 1 0 # 1 0 38 0 0 0 1 0 0 3 0 # 2 0 0 38 1 0 0 1 1 0 0 # 3 0 1 1 45 0 2 1 1 1 0 # 4 0 0 0 0 45 0 1 1 0 0 # 5 0 0 2 0 0 36 0 0 0 1 # 6 0 0 0 0 0 0 43 0 0 0 # 7 0 0 0 0 1 0 0 47 0 0 # 8 0 2 1 1 1 0 0 0 31 1 # 9 0 2 0 1 1 3 0 1 1 39 test_df.metrics.accuracy_score() # 0.9177777777777778
先に記載の課題は以下のように改善される。
scikit-learn
の処理をデータに対して適用することで、pandas
のデータ形式 (を継承したデータ構造) を維持できる- データのプロパティから
scikit-learn
の各モジュールへアクセスできる。モジュールは必要になったときにロードされる - メソッド呼び出しの際、目的変数 / 説明変数を省略できる
もう少しまともな解説を別の記事で書きます。
3/19追記 続きはこちら。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (10件) を見る
9/12追記 パッケージ名変更。