Higu`s diary

新米データサイエンティストのブログ。技術についてゆるく書きます〜

Santanderコンペで学ぶ、EDA(探索的データ解析)の手法

お久しぶりです、ひぐです!

f:id:zerebom:20190412103328p:plain  

先日、Kaggleの Santander Customer Transaction Predictionコンペティション(以下、Santanderコンペ)
に参加したのですが、凄惨な結果に終わってしまいました。。。

  そこで今回の記事では自分の復習もかねて、上位の方の解法をまとめつつ
データ分析に必要なEDAの手法を紹介していきたいと思います!  

この記事を読むのに必要な前提知識

  • Kaggleとは?
    インターネット上で開催されているデータ分析コンペティション
    企業が公開したデータセットを前処理、モデル構築を行い、
    予測データを作成し、その精度で競います。

くわしくは↓
www.codexa.net

  • EDAとは?
    Explanatory Data Analysis の略。日本語で言うと、探索的データ解析。
    データの特徴や構造を理解するためにグラフを作成し、
    特徴量の相関やターゲットとの関係性を調べることです。

くわしくは↓

www.codexa.net

Santanderコンペってどんなコンペ?

f:id:zerebom:20190412105118p:plain

Santanderというアメリカの銀行が開いたコンペ。

レーニング・テストデータどちらも
20万行、かつ200列の特徴量から構成されている。
特定の取引を行った行にtarget==1が付与され、それ以外にtarget==0が付与されている。

全ての特徴量がfloat型かつvar_iと命名されていて、どんな特徴量か分からない様に匿名化されている。
f:id:zerebom:20190412105125p:plain

どんなことに気づけばスコアが伸びたのか

このコンペでは大きく分けて、

  • testデータにある、計算に用いられない偽データの存在
  • 逆向きになっている列の存在(0行目の値が200000行目の値、1行目→199999,2→199998...)
  • 値が重複しているかどうかの特徴量
  • 各列が独立であるということ

これらに気づけた場合、精度があげることが出来たそうです(どうやってわかるんだよ

これらを踏まえてEDAしていきましょう。

1位の方の解法はこちら↓
#1 Solution | Kaggle

EDAの手法紹介

基礎編(どんなコンペでも用いるEDA)

基礎編ではこの方のKernelを参考にしました。ありがとうございます。
https://www.kaggle.com/gpreda/santander-eda-and-prediction

0.ライブラリimport

import gc
import os
import logging
import datetime
import warnings
import numpy as np
import pandas as pd
import seaborn as sns
import lightgbm as lgb
from tqdm import tqdm_notebook
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error
from sklearn.metrics import roc_auc_score, roc_curve
from sklearn.model_selection import StratifiedKFold
warnings.filterwarnings('ignore')

1.データ読み込み

import feather as ftr
train = ftr.read_dataframe("../data/input/train.feather")
test = ftr.read_dataframe("../data/input/test.feather")

read_csvより早いので、feather形式で読み込みます。 こちらを参考にさせていただきました↓
Kaggleで使えるFeather形式を利用した特徴量管理法 - 天色グラフィティ

2.データの大きさ、最初の数行確認

print(train.shape)
train.head()

3.各列の欠損値を確認

def missing_data(data):
    #欠損値を含む行の合計数
    total = data.isnull().sum()
    #欠損値を含む行の合計(%表示)
    percent = (data.isnull().sum()/data.isnull().count()*100)
    tt = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
    types = []
    for col in data.columns:
        dtype = str(data[col].dtype)
        types.append(dtype)
    #各列の型を確認
    tt['Types'] = types
    #行列を入れ替える

missing_data(train)

結果↓
f:id:zerebom:20190412193310p:plain
欠損値は全くなく、すべての特徴量がfloat64です。

4.列ごとの統計量の確認

train.describe()

結果
f:id:zerebom:20190412193812p:plain

target=1が全体の10%ほどという、不均衡データであることを確認しておきましょう。

5.各特徴量のtarget==0の行,target==1の行の分布比較

def plot_feature_distribution(df1, df2, label1, label2, features):
    i = 0
    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(10,10,figsize=(18,22))

    for feature in features:
        i += 1
        plt.subplot(10,10,i)
        sns.kdeplot(df1[feature], bw=0.5,label=label1)
        sns.kdeplot(df2[feature], bw=0.5,label=label2)
        plt.xlabel(feature, fontsize=9)
        locs, labels = plt.xticks()
        plt.tick_params(axis='x', which='major', labelsize=6, pad=-6)
        plt.tick_params(axis='y', which='major', labelsize=6)
    plt.show();

t0 = train.loc[train['target'] == 0]
t1 = train.loc[train['target'] == 1]
features = train.columns.values[2:102]
plot_feature_distribution(t0, t1, '0', '1', features)

結果↓
f:id:zerebom:20190412194118p:plain

こちらで使っているseabornのkdeplotはカーネル密度推定と呼ばれるものです。
分布の密度関数推定に使われる、ノンパラメトリック推定の一種です。
簡単に説明すると、ヒストグラムと同じような分布の推定方法だけど、滑らかになるように操作を加えています(雑)
詳しくはこちらなどを参考にしてください。
http://www.ocw.titech.ac.jp/index.php?module=General&action=DownLoad&file=200927244-21-0-19.pdf&type=cal&JWC=200927244

6.trainとtestデータの各行の平均値の分布を比較

plt.figure(figsize=(16,6))
features = train_df.columns.values[2:202]
plt.title("Distribution of mean values per row in the train and test set")
sns.distplot(train_df[features].mean(axis=1),color="green", kde=True,bins=120, label='train')
sns.distplot(test_df[features].mean(axis=1),color="blue", kde=True,bins=120, label='test')
plt.legend()
plt.show()

結果↓
f:id:zerebom:20190412200923p:plain

train,testの分布がほとんど同じということが分かります。
.mean(axis=1)の部分を.mean(axis=0)にすれば列の平均値を集約し、分布の推定が行われます。
他にも、std,min,max,skew,kurtosisなどとすることで、標準偏差、最小値、最大値、尖度、歪度などの分布も確認できます。

train,とtestの分布が異なる場合は、適切にバリデーションを行わないと、
過学習を起こしてしまうので要注意です。

u++さんの記事が非常にわかりやすいのでよかったら参考にしてください。
upura.hatenablog.com

これを応用して、trainデータをtarget==0,target==1で分けて同様に分布を確認することが出来ます。

#分け方↓
t0 = train.loc[train['target'] == 0]
t1 = train.loc[train['target'] == 1]

7.重複の存在する特徴量の確認

features = train.columns.values[2:202]
unique_max_train = []
unique_max_test = []
for feature in features:
    values = train[feature].value_counts()
    unique_max_train.append([feature, values.max(), values.idxmax()])
    values = test[feature].value_counts()
    unique_max_test.append([feature, values.max(), values.idxmax()])

np.transpose((pd.DataFrame(unique_max_train, columns=['Feature', 'Max duplicates', 'Value'])).\
            sort_values(by = 'Max duplicates', ascending=False).head(15))

結果↓
f:id:zerebom:20190412202108p:plain

これが非常に大切だったのです...(のちに紹介)

8.各列ごとの相関係数確認

#各列の相関係数を求め、絶対値を取り、行列を1列に直し、相関係数の大きさで並べ替え、indexを再度付与
correlations = train[features].corr().abs().unstack().sort_values(kind="quicksort").reset_index()
#同じ列同士の行は消去
correlations = correlations[correlations['level_0'] != correlations['level_1']]
correlations.head(10)

結果↓
f:id:zerebom:20190412202851p:plain

非常に相関係数が小さいことが分かります。
このことから、それぞれの特徴量は独立に扱ってよいという考えに至れると、
新たな学習方法を行うことが出来ます。

9.各特徴量毎のtarget==0,target==1の散布図。

train.loc[train['target']==0]['var_180'].plot(figsize=(30,30),style='.')
train.loc[train['target']==1]['var_180'].plot(figsize=(30,30),style='.')

結果↓
f:id:zerebom:20190412203426p:plain

このKernelを参考にしました。
In Search of Weirdness | Kaggle

横軸が行番号、縦列が各要素の値です。 青がtarget==0,オレンジがtarget==1です。

自分は各特徴量毎にこの散布図を作成し、なめるように見ていました。
ここでは、0.48付近にtarget==1が多く含まれていて、効きそうな特徴量が作れそうですが、
この図だけでは青がどれだけ密集しているか詳しくはわからないので要注意です。

手法5で確認を行えばよいですが、

plt.figure(figsize=(30,30))
sns.kdeplot(train.loc[train['target']==1]['var_108'], bw=0.2)
sns.kdeplot(train.loc[train['target']==0]['var_108'], bw=0.2)

とすることで、特徴量毎に大きく表示し、確認することもできます。
f:id:zerebom:20190412204322p:plain

応用編(Santanderで役に立つEDA)

それぞれ長くなりそうなので、別の記事にしました。

  1. 各特徴量を独立と仮定した修正ナイーブベイズ法によるtarget確率分布推定

初めて、本気で参加したコンペについての感想

Santanderコンペについて

欠損値もなく、データも大きくなく、すべて数値型だったので取り組みやすい!
と思って始めたのですが、実際は人と差をつけることが難しく、スコアも伸びず大変でした…

LBスコアが非常に密集していて、
kernelで公開されている、0.901スコア代の人が3000人くらいいたのが特徴的でした。

自分の足りなかった部分

  • 着手し始めるのが遅すぎた。
  • NNライブラリを扱えず、アンサンブルができなかった
  • モデルの理解度が低く、LGBMでは効かない特徴量を作ってしまった。
  • 英語力が足りず、discussionから必要な情報を収集しきれなかった。
  • discussionに書いてあることをデータにうまく落とし込めなかった。
    EX)各列が無相関→各列を縦に連結し、(4000万行,2列)のデータセットにするなど

参加して得られたこと

  • Kaggleにしっかり参加するときの流れがつかめた。

u++さんが公開してくださった、ディレクトリを用いて
デバッグ、特徴量の追加削除を自由にできる環境でのKaggleを行うことが出来るようになった。

upura.hatenablog.com

  • のちのコンペに生かせそうな関数を知れた
    EDA等、dfを引数にした汎用的な関数をいくつか知ることが出来たので、今後のコンペに対してスムーズに動生きだせそう。

  • やるき
    何の成果も得られず悔しかったので、次はやってやるぞという気持ちになれた。

   

google-site-verification: google1c6f931fc8723fac.html