Pythonで前処理する

| Topic path: Top / 機械学習 / Pythonで前処理する

*目次 [#f71521a0]
#contents


*はじめに [#c718b344]

Pythonの機械学習ライブラリーscikit-learnを用いて機械学習を行うには、入力をNumPy行列にする必要があります。

scikit-learnを使うととても簡単に機械学習ができますが、実際にscikit-learnを使って機械学習をしようとすると、scikit-learnに入力するNumPy行列を作る前処理の方が大変です。

ここでは、前処理(カテゴリー変数の変換、欠損値の処理、標準化)について説明します。

環境はこれまでと同じです。

例として、銀行マーケティングのデータセットを用います。
-[[Bank Marketing Data Set>https://archive.ics.uci.edu/ml/datasets/bank+marketing]]


*データの読み込み [#we2a356e]

データは、Excelなどの表計算ソフトを使って用意し、CSVファイルとして書き出します。

このCSVファイルを、Pandasを使って読み込みます。

#geshi(python){{
import pandas as pd
df_bank = pd.read_csv('bank.csv')
}}
ここでは、bank.csvがCSVファイルの名前です。

最初の列が事例IDの場合は、index_col オプションに列番号を指定します。(列番号は 0 から始まります。)
#geshi(python){{
df_bank = pd.read_csv('bank.csv', index_col=0)
}}




*カテゴリー変数の変換 [#l3d47ab3]

scikit-learnは、NumPy行列を入力とします。
このため、scikit-learnには数値しか入力できません。

一般的なカテゴリー変数は文字列で表されているため、これを変換する必要があります。

ここでは、二つの方法を説明します。
-カテゴリー変数を数値に置換する
-カテゴリー変数をダミー変数に変換する


**数値に置換 [#yf14b6d4]
カテゴリー変数の値を数値に置き換えるには、map()メソッドやreplace()メソッドを使います。

例えば、銀行マーケティングのデータセットには month という名前のカテゴリー変数があり、値として jan, feb, ... という月の名前を取っています。

map()メソッドを用いて、月を表す名前を数値に置き換えます。
#geshi(python){{
df_bank['month'] = df_bank['month'].map({'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12})
}}



**ダミー変数に変換 [#zacb704e]
カテゴリー変数の値ごと変数を作り、その値なら 1、そうでないなら 0 とする変数に変換します。
このようにして作成された変数を''ダミー変数''といいます。

例えば、銀行マーケティングのデータセットには、maritalという名前の説明変数があり、値として married, single, divorced という値を取っています。
|age|marital|h
|39|married|
|51|married|
|36|single|
|34|divorced|

maritalをダミー変数にすると次のようになります。
|age|marital_married|marital_single|marital_divorced|h
|39|1|0|0|
|51|1|0|0|
|36|0|1|0|
|34|0|0|1|

実際には、married でも single でもなければ divorced であることがわかるので、一つを取り除き、[math]k[/math] 種類の値を取るカテゴリー変数から [math]k - 1[/math] 個のダミー変数を作成します。
|age|marital_married|marital_single|h
|39|1|0|
|51|1|0|
|36|0|1|
|34|0|0|


ダミー変数は、Pandasの get_dummies 関数を用いて作成します。
#geshi(python){{
dummy = pd.get_dummies(df_bank[['marital']], drop_first=True)
}}
drop_first オプションは、ダミー変数の数を一つ減らすためのものです。
(get_dummiesの結果はデータフレームになります。)

get_dummiesメソッドには複数のカテゴリー変数を指定できるので、実際には、全てのカテゴリー変数をまとめて変換します。
get_dummiesメソッドにデータフレームを渡すと、全てのカテゴリー変数をまとめて変換します。
#geshi(python){{
dummies = pd.get_dummies(df_bank[['marital', 'education', 'default', 'housing', 'loan', 'contact',  'duration', 'poutcome']], drop_first=True)
df_bank = pd.get_dummies(df_bank, drop_first=True)
}}

ダミー変数を作成したら、データフレームにダミー変数をマージして、カテゴリー変数を取り除きます。
#geshi(python){{
df_bank = df_bank.merge(dummies, left_index=True, right_index=True)
df_bank = df_bank.drop(['marital', 'education', 'default', 'housing', 'loan', 'contact',  'duration', 'poutcome'], axis=1)
}}





*欠損値の処理 [#d65c7b01]

一般的なデータには欠損値(値がない項目)があります。

欠損値に対応していない機械学習手法を使うときには、欠損値を処理しておく必要があります。

ここでは、三つの方法を説明します。
-欠損値が多い説明変数の除去
-欠損値を含むデータの除去
-値の補完


欠損値を含むデータをPandasのread.csv()メソッドで読み込むと、numpy.NaN になります。

今回、例に用いている銀行マーケティングのデータセットでは、欠損値が 'unknown' となっています。
そこで、まず初めに、CSVファイルを読み込んだ直後に 'unknown' を numpy.NaN に置換し、欠損値とします。

#geshi(python){{
import numpy as np
import pandas as pd
df_bank = pd.read_csv('bank.csv')
df_bank = df.bank.replace('unknown', np.NaN)
}}


**欠損値が多い説明変数の除去 [#qe87fd79]

そもそも、欠損値が多く、重要でない説明変数を使わないようにすれば、欠損値の問題を回避できます。

説明変数を取り除くには、pandas.DataFrameのdrop()メソッドを使います。
#geshi(python){{
df_bank = df_bank.drop('poutcome', axis=1)
}}
オプションの axis=1 は、列を削除します(行を削除するときは axis=0)。



**欠損値を含むデータの除去 [#c8404d77]

データの数が十分に多く、かつ、欠損値を含むデータが少ないときは、欠損値を含むデータを取り除いても学習される予測モデルには大きな影響がないことが期待できます。

欠損値を含むデータを取り除くには、pandas.DataFrameのdropna()メソッドを使います。
#geshi(python){{
df_bank = df_bank.dropna(how='any')
}}
オプションの how='any' は、NumPy.NaN を一つでも含む行を削除します。




**値の補完 [#h2ccbc69]

データの数が少ないときや欠損値を含むデータが多いときは、欠損値を含むデータを取り除いてしまうと、学習される予測モデルが悪くなってしまいます。

そこで、このような場合には、欠損値に平均値、中央値、最頻値などを補完します。

今回の銀行マーケティングのデータセットでは、欠損値は全てカテゴリー変数に含まれているので、例として、poutcomeのsuccessを 1、failureを 0 にカテゴリー変数を変換します。
#geshi(python){{
df_bank['poutcome'] = df_bank['poutcome'].map({'success':1, 'failure':0})
}}

欠損値に値を補完するには、sklearn.imputeのSimpleImputerを使います。
#geshi(python){{
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(missing_values=np.nan, strategy='median')
df_bank['poutcome'] = imputer.fit_transform(df_bank['poutcome'].values.reshape(-1, 1))
}}

SimpleImputerを作成するときの missing_valueオプションには欠損値の値を指定します。
strategyオプションには補完する値を指定し、平均値を補完するときは 'mean'、中央値を補完するときは 'median'、最頻値を補完するときは 'most_frequent' とします。

SimpleImputerによる補完は、fit()メソッドで補完する値を求め、transform()メソッドで欠損値を補完する、という手順で行います。
上の例では、この手順を一度に行うfit_transform()メソッドを用いています。




*スケーリング [#hbce840d]

説明変数の値域のスケールが大きく異なると、学習された予測モデルのパラメーター(例えば、線形回帰分析の係数)を比較することが難しくなります。

そこで、説明変数の値が同じくらいになるように変換します。
これをスケーリングといいます。


**標準化 [#m2976eb1]

統計学の分野でよく用いられるスケーリングが、説明変数が平均 0、標準偏差 1 となるように説明変数の値 [math]x[/math] をzスコア [math]z[/math] に変換する、標準化 (Standardize) です。
\[
z = \frac{x - \mu}{\sigma}
\]
ここで、[math]\mu[/math] は平均値、[math]\sigma[/math]は標準偏差を表します。

zスコアに標準化することによって、説明変数が正規分布にしたがっているとき、[math][-1, 1][/math] の範囲に約68.3%のデータが、[math][-2, 2][/math] の範囲に約95.5%のデータが、[math][-3, 3][/math] の範囲に約99.7%のデータが含まれるようになります。

標準化するには、sklearn.preprocessingのStandardScalerを使います。
#geshi(python){{
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
df_bank['age'] = sc.fit_transform(df_bank['age'].values.reshape(-1, 1))
}}

StandardScalerによる標準化も、Imputerによる補完と同じように、fit()メソッドを用いて平均と標準偏差を求め、transform()メソッドを用いてzスコアに変換します。
この例でも、fitとtransformを一度に行うfit_transform()メソッドを用いています。

ageは整数なので、実際に標準化すると int64 が float64 になっちゃったよという警告が出ます。


**min-maxスケーリング [#zbaae243]

zスコアによる標準化は、データが正規分布にしたがっていることを前提としています。
しかし、例えば誕生月は正規分布にはなりませんので、zスコアによる標準化は適していません。

そこで、データを一様分布と仮定して、最小値が 0、最大値が 1 となるように変換するスケーリングが、min-maxスケーリングです。
\[
x' = \frac{x - \min}{\max - \min}
\]
ここで、[math]\min[/math] は最小値、[math]\max[/math] は最大値を表します。

min-maxスケーリングを行うには、sklearn.preprocessingのMinMaxScalerを使います。
#geshi(python){{
from sklearn.preprocessing import MinMaxScaler
sc = minmax_scale()
df_bank['age'] = sc.fit_transform(df_bank['age'].values.reshape(-1, 1))
}}


*前処理の注意点 [#e42095cb]

上の例では、fit_transform()メソッドを用いて、fitとtransformを同時に行っていますが、訓練データ以外のデータに対する評価をするときには、注意が必要になります。

まず、sklearn.model_selectionのtrain_test_split()メソッドを用いて、データを訓練データとテストデータに分けます。
#geshi(python){{
from sklearn.model_selection import train_test_split
(X_train, X_test, y_train, y_test) = train_test_split(X, y)
}}

テストデータ X_test に対して、fit_transform()メソッドを用いると、テストデータに対してfitさせた前処理になってしまいます。
たとえば、min-maxスケーリングによる標準化をする場合、訓練データにおける最小値が0で最大値が100で、テストデータにおける最小値が30で最大値が70の場合、標準化後の0の意味が大きく異なることになってしまいます。

このような問題が生じないように、訓練データに対してfはit_transform()メソッドを用いてfitとtransformを行い、テストデータに対してはtransform()メソッドを用いてfitしないでtransformだけを行うようにします。
#geshi(python){{
from sklearn.preprocessing import MinMaxScaler
sc = minimax_scale()
X_train = sc.fit_transform(X_train)
X_test = sc.fit(X_test)
}}
訓練データに合わせて標準化されることに注意が必要です。
min-maxスケーリングの場合、テストデータに訓練データの最小値より小さい値が含まれていると0より小さい値になり、訓練データの最大値より大きい値が含まれていると1より大きい値になります。
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS