Kaggle Case - Santander Customer Transaction Prediction (1)

在這一個 Kaggle Challenge中,Santander提供了20萬筆的Train Data以及20萬筆的Test Data,希望Kagglers可以建構出一個模型來預測他們客戶是否能夠跟Santander進行交易。

在這邊,我選擇以Gabriel Preda的Kernel來進行第一次的Kaggle Case研讀,也藉此了解一般在進行EDA以及Feature Engineering時可能會遇到的方法及解決的方式。

在這邊他大概以幾個大方向來依序做處理: 1. Prepare the data analysis 2. Data exploration * Check the data * Density plots of features * Distribution of mean and std * Distribution of min and max * Distribution of skew and kurtosis * Features correlations * Duplicate values 3. Feature engineering 4. Model


Prepare the data analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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')

從這邊我們大概可以看到在後面的Code裡面大概會用到那些模組、工具 ( 不過 Gabriel Preda的這一連串匯入中,有些本次似乎用不到。 )

會用 os 模組做一些系統操作 ( EX: 讀取檔案...)、numpypandas則是數據處理的好幫手、seabornmatplotlib處理視覺化、roc_auc_score用來評估、StratifiedKFold用來驗證,最後此次用的 Model 則是 lightgbm

而這一整份的模組或許也可以當作我們的一份必備清單,日後是否有需要增減再做調整。

1
2
3
%%time
train_df = pd.read_csv("train.csv")
test_df = pd.read_csv("test.csv")
這裡,我將kaggle提供的檔案跟這個ipynb檔放在一起,所以在讀取的時候就很簡單,直接讀取就可以。若不同資料夾存放則可使用os模組進行讀取 1

Data exploration

Check the data

1
train_df.shape, test_df.shape

1
train_df.head()

1
test_df.head()

以上這三個算是很基本的資料檢視過程,觀察筆數、看看Train & Test 的各個欄位。也可以在此時簡單的看看資料數據是否有很嚴重的缺失值。

從這個例子來看,我們可以瞭解幾件事情 : * Train data : 有20萬列,即為資料筆數 有202行,包含了ID_Code、target、以及200行的未知欄位資料 * Test data : 有20萬列,即為資料筆數 有201行,包含了ID_Code、以及200行的未知欄位資料

Train & Test資料差在 target,這的確可以理解,我們要利用訓練資料訓練出一套模型,再來對測試資料進行測試,看看準確度到多少,這便是我們要做的事情,而 Test 資料的 target 只有 Kaggle 才有,當我們最終答案上傳,他們會立刻進行比對,可以即時的告知我們結果。

1
2
3
4
5
6
7
8
9
10
11
12
def missing_data(data):
total = data.isnull().sum()
#Pandas通常使用.isnull()來找缺失值,.sum()則是計算缺失的數量
percent = (data.isnull().sum()/data.isnull().count()*100)
tt = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
# pd.concat(objs合併的東西,axis合併的方向,keys索引)
types = []
for col in data.columns:
dtype = str(data[col].dtype)
types.append(dtype)
tt['Types'] = types
return(np.transpose(tt))
1
missing_data(train_df)

1
missing_data(test_df)

檢查缺失值在整個資料處理的過程中,往往會是一個非常重要的步驟,缺失值數量過多,則跑出來的模型的擬合狀況會非常差,在這個Case裡面,很幸運地並沒有任何的缺失值在其中。 (這蠻可惜的,畢竟如何填補、修改、刪除缺失值也是需要很多的技巧,只能等之後的Case再來學習了)

Gabriel Preda的這個缺失值計算函數 (後面很多自訂函數亦然) 我認為是一個可以共通的方式,可以記下來以便未來處理不同Case可以使用

1
2
%%time
train_df.describe()

1
2
%time
test_df.describe()

DataFrame.describe() 是一個非常好用的工具之一,可以一次將整個表格的統計量呈現出來,而這些統計量可以很清楚的呈現這些資料的分布及特性。

在這邊我們可以知道幾個狀況 : * 無論 Training 還是 Test data,標準差都不小 * Training 及 Test data 的極值、均值、四分位數、標準差都很接近 * 各欄位平均值差異極大

1
2
3
4
5
6
7
8
9
10
11
12
def plot_feature_scatter(df1, df2, features):
i = 0
sns.set_style('whitegrid')
plt.figure()
fig, ax = plt.subplots(4,4,figsize=(14,14))

for feature in features:
i += 1
plt.subplot(4,4,i)
plt.scatter(df1[feature], df2[feature], marker='+')
plt.xlabel(feature, fontsize=9)
plt.show();
1
2
3
4
features = ['var_0', 'var_1','var_2','var_3', 'var_4', 'var_5', 'var_6', 'var_7', 
'var_8', 'var_9', 'var_10','var_11','var_12', 'var_13', 'var_14', 'var_15',
]
plot_feature_scatter(train_df[::20],test_df[::20], features)

這裡的部分,主要是把剛剛觀察的現象做一個視覺化的處理來確認我們的「觀察」是有根據的。從這裏的圖片看來,不管哪一個欄位,在兩個部分的資料的分布都算集中。

( 意即 : 在這40萬筆資料中,每一筆的資料的各個欄位/特徵大概都在某些範圍內不會有太大的異常值 )

有兩個問題大家可以思考一下 : 1. 圖片中每一個點代表的意思是什麼?2 2. 假如出現完全零散不集中、或是呈現特殊集中方式代表的意義是什麼 ?

1
2
3
4
5
features = ['var_0', 'var_1','var_2','var_3', 'var_4', 'var_5', 'var_6', 
'var_7', 'var_8', 'var_9', 'var_10','var_11','var_12', 'var_13', 'var_14', 'var_15',
]
plot_feature_scatter(train_df[0:1],test_df[0:1], features)

我們就只取一個點來看看會是什麼 ![](https://i.imgur.com/Wmyfy8P.png) 你看出來了嗎 ? 圖中的每一個點,代表的就是Training Data跟Test Data相對應index資料的特徵值。 拿第一個子圖來說,那一點代表的就是 index=0 時 Training Data var_0=8.9255 (X軸) ; index=0 時 Test Data var_0= 11.0656 (Y軸)
1
sns.countplot(train_df['target'])

1
2
3
4
print("There are {}% target values with 1".format(100 * train_df["target"].value_counts()[1]/train_df.shape[0]))

#train_df["target"].value_counts()[1] return "1" 有多少個
#train_df.shape[0] return 列數

遇到了一個大難題,這是一個 unbalence 的情況。 之所以會困難是因為,如果我們訓練出一個模型,只會把所有資料無差別分類成 target 0 (Dummy classifier),這樣的準確率也會接近90%,這會影響我們評估這個模型的好壞。(++這也是為什麼在最前面我們發現他選擇使用roc_auc_score, roc_curve來做為評估模型的指標++)

嗯,很順利的現在進行了1/3.....

To be Continue....


  1. 存放於不同資料夾的檔案讀取

    1
    2
    3
    dpath=os.path.abspath("待讀取資料的絕對路徑(不含檔案本身)") #資料夾位置
    fpath=os.path.join(dpath,"檔名(含副檔名)") #檔案位置
    a_train=pd.read_csv(fpath) #讀檔
    ↩︎

  2. 這個問題我其實思考了一陣子,我們稍微更改一下code你就能知道這代表什麼了:↩︎