データ分析ガチ勉強アドベントカレンダー 8日目。
Day-7の記事で、データを取り扱えるようになりました。 しかし、データがいつもきれいで美しいものだとは限りません。なかには絶望的なデータもたくさんあります。
機械学習等の学習器に投げ入れるには、もうひと工夫いることのほうが多いです。
pandas
とsklearn
で、できる工夫、前処理についてまとめて行きます
前処理とは
学習の流れを簡単な図にまとめてみる。
データ分析の労力の7~8割は、↑図の赤の部分、前処理といわれている。 適当に学習器に投げ入れたデータよりも、きちんと温かみをもって処理をすることが大事。*1
データをうまく整形することで精度が大きく変わってくるので、試行錯誤で調整を繰り返すことが多い。
ひとくちに前処理といってもデータの質、型(数値、カテゴリ変数、画像、自然言語)、仮定する分布などで大きく変わるので、一概には言えない。下記スライド(英語)は前処理について詳しく書かれていて、大変勉強になった。
絶望的なデータの入手
たとえば、avent-calendar-2017リポジトリにある、day8-sample.csv
を読み込んでみる。
import pandas as pd df = pd.read_csv("day8-sample.csv", index_col="name") df
すると、絶望的なデータを手に入れることができる。
height weight name A 160cm 49kg B 170cm 37kg C 1.81m 80 D NaN 50kg E 1.90m 80 F 170cm NaN G 150cm 40
単位が違う、、単位がない、、、NaN。。。 実際、こんなデータがたくさん入っていたりする。本当はオペレーションのレベルで何とかしておけばこんなことは起こらないはずだが、データを集めているヒトが必ずしも情報技術者というわけではない。めんどくさいが、せむかたなし。
データを統一的な型(数値等)に変換(df.apply)
こういうデータを、学習器に投げるための形にもっていくのが、まず始めに行う前処理である。pandasを使えば、比較的楽に処理が可能。
【Day-4】都道府県のデータをいじりながら、pandasを学ぶにて、pandasについて触れた。
ここで、緯度経度を60度法から数値に変換するという処理をしたが、これも前処理のひとつである。
自分で関数を書いてpandasのapply
関数を使うと、処理が簡単に行えるので良い。(詳しくはDay-4)
データを眺めると、単位で場合わけしながら、cmとkgに統一した数値データにするのがよさげ。なので、関数を作る。少し雑だが、cm,m,kgなどをうまく除去するように場合わけをする。
def height_to_num(height): if type(height)==float: return height if "cm" in height: height = float(height[:-2]) if (type(height)!=float) and ("m" in height): height = float(height[:-1]) height *= 100 return height def weight_to_num(weight): if type(weight)==float: return weight if (type(weight)!=float) and ("kg" in weight): weight = weight[:-2] return float(weight)
この関数を全部のindexに適用するのが、apply
関数である。次のようにすると、各カラムにさっき作った関数が実行される
df["height"] = df.height.apply(height_to_num) df["weight"] = df.weight.apply(weight_to_num) print(df)
出力
height weight name A 160.0 49 B 170.0 37 C 181.0 80 D NaN 50 E 190.0 80 F 170.0 NaN G 150.0 40
数字がきれいに扱えるようになった。
NaNの除去
これで全部統一的なデータ形式にできた。あと厄介なのがNaN
(空白)の取り扱い。これをそのまま学習器に代入するとエラーが出る。sklearn
でもpandas
でも可能である。両方やってみる
pandasの場合、下記の三種類がある
df.dropna()
: NaNのある行を除去df.fillna()
: NaNをある値で埋めるdf.interpolate()
: NaNを前後の値から補間
df.dropna()
print(df.dropna(how="any"))
実行すると、
height weight name A 160.0 49.0 B 170.0 37.0 C 181.0 80.0 E 190.0 80.0 G 150.0 40.0
このように、ひとつでもNaNが入っているindexは消える。
dropna()
内の引数で、消し方を決められる。how="all"
だと、すべてNaNのindexだけ消すといった具合になる。
詳しくはこちら
df.fillna()
値を埋める。0で埋めてもいいが、たとえばそのほかの値の統計値を使って埋める。なども可能。今回は平均(mean)で埋めてみる
df.height = df.height.fillna(df.height.mean())
df.weight = df.weight.fillna(df.weight.mean())
print(df)
出力は
name A 160.000000 49.0 B 170.000000 37.0 C 181.000000 80.0 D 170.166667 50.0 E 190.000000 80.0 F 170.000000 56.0 G 150.000000 40.0
ふむふむ、無事平均値で埋まったようだ。ほかにも中央値で埋めるなど、無難な値で埋めることができる。
df.interpolate()
値を近似して補間してしまう方法。indexが意味を持つとき(たとえば時系列などにはよく用いられるが、名前のような、カテゴリカルなときは用いてもあまり意味を成さない。けど、練習のためやってみる。
print("orig\n",df.height.values) print("interpolate\n",df.height.interpolate(method='linear').values)
interpolateを行った後は、
orig [ 160. 170. 181. nan 190. 170. 150.] interpolate [ 160. 170. 181. 185.5 190. 170. 150. ]
となり、method="linear"
では線形補間が行われたことがわかる。
なお、methodには、いろんな補間形式がある。公式ページに補間の方法とまとめが載っているので、参考にするといいだろう。
また、sklearn.preprocessing.imputer
でも、欠損値補間はできるようだ。(Imputer)
その他
そのほかにも、回帰代入法など、NaNを埋める方法はたくさんある。データの特徴をみながら、どういう埋め方が良いのかを判断すると良い。
スケーリング
これですべて数値で埋まった値が帰ってきたが、身長と体重で絶対値が違う。こういうデータの大きさ、形状を調節して同じような大きさにするほうが、うまくいく(ことが多い)。これをスケーリングという。
sklearn.preprpcessing
には、スケーリングを行う関数が多く実装されている。
sklearn.preprocessing(公式ページ)
StandardScaler
各カラムの値を、平均0、標準偏差1の分布に変えてしまう。データが普通の分布(正規分布っぽい分布)をしているときなどはコレを用いることが多い。skelarn.preprocessing.StandardScaler
を用いる。
# standard scaler # スケールの取り出し(新しいデータが来たときに、固定された正規化定数で対応が可能) scaler = preprocessing.StandardScaler().fit(df) # スケールを出す print("mean:{} std:{}".format(scaler.mean_,scaler.scale_)) # スケーリングを行う df_scaled = scaler.transform(df) print(df_scaled)
出力
mean:[ 170.16666667 56. ] std:[ 12.07614729 16.27443218] [[-0.84187998 -0.43012253] [-0.01380131 -1.16747545] [ 0.89708523 1.47470583] [ 0. -0.36867646] [ 1.64235603 1.47470583] [-0.01380131 0. ] [-1.66995865 -0.98313722]]
基本的には、
scaler = preprocessing.hogehoge().fit(df)
で、それぞれのアルゴリズムによるデータのスケーリング定数を算出scaler.hoge_
で、スケーリング定数を取り出すsceler.transform(df)
で、スケーリングした配列を出力
という設計になっている。hogehoge,hogeは各アルゴリズムによって変わってくる
MinMaxScaler
これも良く使うスケーリング方法で、最低が0、最高が1にスケーリングする。StandardScaler
とほとんど同じだが、係数が少し変わってくる
# min-max scaling scaler = preprocessing.MinMaxScaler().fit(df) print("max:{}, min:{}".format(scaler.data_max_, scaler.data_min_)) print("SCALED") print(scaler.transform(df))
出力
max:[ 190. 80.], min:[ 150. 37.] SCALED [[ 0.25 0.27906977] [ 0.5 0. ] [ 0.775 1. ] [ 0.50416667 0.30232558] [ 1. 1. ] [ 0.5 0.44186047] [ 0. 0.06976744]]
その他
preprocessing
には、カーネル向けの前処理(KernelCenterer
)や、多項式化(PolynomialFeatures
)、バイナリ化(Binarizer
)そのほかにもさまざまな前処理方法が用意されている。詳しくは公式ページを参照し、いろいろ試してみるのがいい。使い方は基本的に上記で示したとおり。
また、FunctionTransformer
という関数を用いれば、sklearnの設計どおりに自分で特徴量変換関数を作ることができる。たとえば、データにlogをつけることも結構あったりするので、そういう変換関数を作る。
from sklearn.preprocessing import FunctionTransformer # logを当てはめる transformer = FunctionTransformer(np.log1p) transformer.transform(df)
出力は
array([[ 5.08140436, 3.91202301], [ 5.14166356, 3.63758616], [ 5.20400669, 4.39444915], [ 5.14263774, 3.93182563], [ 5.25227343, 4.39444915], [ 5.14166356, 4.04305127], [ 5.01727984, 3.71357207]])
オレオレ特徴変換をしたいときは、こんなのもよさそう。
まとめ
手に入るデータがいつもきれいだとは限らない。というか、汚いぐちゃぐちゃなデータのほうが多い。 うまく変換する方法を知っておくことで、効率的に学習器にデータを投げることができる。
近年ではよく使うものは、前処理を済ませたデータを配ろうとする動きもあるみたいで、オープンソースとして提供しようとする試みもある。 このdataset.jpというところなどがそう。開発が滞っているっぽいので、活発になればうれしいな(面倒が減るな)とか思ってる。
データ分析や機械学習に欠かせない「前処理」の共通化を目指したオープンソースが国内で発足|アイザック株式会社のプレスリリース
以上、これで学習器に投げられるところまできた気がする。 明日は、良い学習器を選ぶためには?というお題で書きたい。