プロクラシスト

今日の寄り道 明日の近道

【Day-8】絶望的なデータを前処理で何とかする。(pandas/sklearn)


スポンサーリンク

f:id:imslotter:20171208163632p:plain データ分析ガチ勉強アドベントカレンダー 8日目。

Day-7の記事で、データを取り扱えるようになりました。 しかし、データがいつもきれいで美しいものだとは限りません。なかには絶望的なデータもたくさんあります。

機械学習等の学習器に投げ入れるには、もうひと工夫いることのほうが多いです。 pandassklearnで、できる工夫、前処理についてまとめて行きます

前処理とは

学習の流れを簡単な図にまとめてみる。

データ分析の労力の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]])

オレオレ特徴変換をしたいときは、こんなのもよさそう。

まとめ

手に入るデータがいつもきれいだとは限らない。というか、汚いぐちゃぐちゃなデータのほうが多い。 うまく変換する方法を知っておくことで、効率的に学習器にデータを投げることができる。

  • pandasのapply関数で、カラムごと一括処理
  • pandasのdropna, fillna, interpolateで、欠損値に対応
  • sklearn.preprocessingをうまく使って、データをいい具合に変換
  • 近年ではよく使うものは、前処理を済ませたデータを配ろうとする動きもあるみたいで、オープンソースとして提供しようとする試みもある。 このdataset.jpというところなどがそう。開発が滞っているっぽいので、活発になればうれしいな(面倒が減るな)とか思ってる。

    データ分析や機械学習に欠かせない「前処理」の共通化を目指したオープンソースが国内で発足|アイザック株式会社のプレスリリース

    以上、これで学習器に投げられるところまできた気がする。 明日は、良い学習器を選ぶためには?というお題で書きたい。

    *1:ディープラーニングの対等で、適当に投げてもかまわんという風潮もあるが、個人的には前処理は結果の説明のために必要だと思っている。

    PROCRASIST