拍拍貸違約預測案例(GBDT)

該案例為拍拍貸在“魔鏡杯” 風控算法大賽,比賽公開了國內網絡借貸行業的貸款風險數據,本著保護借款?人隱私以及拍拍貸知識產權的目的,數據字段已經過脫敏處理。數據及來源可自行在網上下載

讀取數據&了解數據

import numpy as np
import pandas as pd
%matplotlib inline
# 用戶行為數據
train = pd.read_csv('PPD_Training_Master_GBK_3_1_Training_Set.csv', encoding='gbk')
train.head()
train.shape  # (30000, 228)
# 用戶登錄數據
train_log = pd.read_csv('PPD_LogInfo_3_1_Training_Set.csv')
train_log.head()
# 用戶修改信息數據
train_update = pd.read_csv('/Users/henrywongo/Desktop/Code_Python/PPD-RiskContral/data/first round train data/PPD_Userupdate_Info_3_1_Training_Set.csv')
train_update.head()

1. 數據處理

1.1 缺失值處理

import matplotlib.pyplot as plt
# 統計每一個字段的缺失值比率
train_isnull = train.isnull().mean()
train_isnull = train_isnull[train_isnull > 0].sort_values(ascending=False)
train_isnull.plot.bar(figsize=(12, 8))

有兩個字段確實值達到了97%, 三個字段達到60%, 部分字段缺失值在10%以下,對缺失值比率不同的字段,根據業務情況,進行處理

# 統計每一條記錄的缺失值個數
plt.figure(figsize=(12, 8))
plt.scatter(np.arange(train.shape[0]),
            train.isnull().sum(axis=1).sort_values().values)
plt.show()

有部分記錄的缺失值達25個以上,最大不超過40個,該數據集共228字段,最大缺失值比例不超過25%,在能容忍的范圍內,在這里,就不對又確實值得字段進行處理

# 通過觀察原數據,對于缺失值達90%以上的字段,無法知曉其業務的實際含義,在這里直接刪除
train = train.loc[:, train.isnull().mean() < 0.9]

通過觀察,對于缺失值在60%左右的字段,都為二分類型的數值,使用0填補

# 通過觀察,對于缺失值在60%左右的字段,都為二分類型的數值,使用0填補
col_6 = []
for col in train.columns:
    if train[col].isnull().mean() > 0.6:
        col_6.append(col)

col_6  # 缺失值在0.6以上的字段名稱列表

train.loc[:, col_6].info()  

train.loc[:, col_6] = train.loc[:, col_6].fillna(0)

還未處理的有缺失值的字段

# 統計余下字段的缺失值比率
train_isnull2 = train.isnull().mean()
train_isnull2 = train_isnull2[train_isnull2 > 0].sort_values(ascending=False)
train_isnull2.plot.bar(figsize=(12, 8))

由于無了解到以上字段的實際業務含義,在這里對數值型的字段,統一使用-1填補,把其歸為一類

# 由于無了解到以上字段的實際業務含義,在這里對數值型的字段,統一使用-1填補
for col in train_isnull2.index:
    if train[col].dtype in ['float', 'int']:
        train[col] = train[col].fillna(-1)

還剩余未處理的字段,這些字段均為字符型字段

# 統計余下字段的缺失值比率
train_isnull3 = train.isnull().mean()
train_isnull3 = train_isnull3[train_isnull3 > 0].sort_values(ascending=False)
train_isnull3.plot.bar(figsize=(12, 8))
# 采用'Unkonw'填補
train['WeblogInfo_20'] = train['WeblogInfo_20'].fillna('Unknow')
train['WeblogInfo_21'] = train['WeblogInfo_21'].fillna('Unknow')
train['WeblogInfo_19'] = train['WeblogInfo_19'].fillna('Unknow')
train['UserInfo_2'] = train['UserInfo_2'].fillna('Unknow')
train['UserInfo_4'] = train['UserInfo_4'].fillna('Unknow')

1.2 異常值處理

本數據僅未發現異常值點,故不作處理

1.3 文本處理

Userupdate_Info 表中的 UserupdateInfo1 字段,屬性取值為英?文字符, 包含了?大小寫,如 “QQ”和“qQ”,很明顯是同?一種取值,我們將所有 字符統?一轉換為小寫。

train_update['UserupdateInfo1'] = train_update['UserupdateInfo1'].apply(lambda x: np.char.lower(x))

train中 UserInfo_9 字段的取值包含了空格字符,如“中國移 動”和“中國移動 ”, 它們是同?一種取值,需要將空格符去除。

train['UserInfo_9'] = train['UserInfo_9'].apply(lambda x: x.strip())

UserInfo_8 包含有“重慶”、“重慶市”等取值,它們實際上是同?一個城 市,需要把 字符中的“市”全部去掉。去掉“市”之后,城市數由 600 多下 降到 400 多

train['UserInfo_8'] = train['UserInfo_8'].apply(lambda x: x[:-1] if x[-1] == '市' else x)

2. 特征工程

2.1 成交時間

# 將時間轉換為時間型數據
train['ListingInfo'] = pd.to_datetime(train['ListingInfo'])
# 獲取日其所在的周數,周數為所在年份的第幾周
train['Week'] = train['ListingInfo'].dt.week

以數據集起始時間為第一周,本數據集起始時間為2013年的第44周,所以2013年周數減去43,2014年周數加上9,即可把日期變量按周離散化

week = []
for i in range(train.shape[0]):
    if train['ListingInfo'].dt.year[i] == 2013:
        if train['ListingInfo'][i] in pd.to_datetime(['2013-12-30', '2013-12-31']):
            # 2013-12-30,2013-12-31為2014年第一周
            week.append(9)
        else:
            week.append(-43)    
    else:
        week.append(9)

train['Weeks'] = week + train['Week']
train.drop(['Week'], axis=1, inplace=True)
train.drop(['ListingInfo'], axis=1, inplace=True)
# 以周為維度,計算每周違約人數以及未違約人數
train_by_week = train.groupby('Weeks')
plt.figure(figsize=(12, 8))
plt.plot(train_by_week.target.sum().index, train_by_week.target.sum().values)
plt.plot(train_by_week.target.sum().index, train_by_week.target.count().values - train_by_week.target.sum().values)
plt.xlabel('Weeks(20131101-20141109)')
plt.ylabel('Count')
plt.legend(['Count_1', 'Count_0'], loc='upper left')
plt.show()

從圖中估計可看出,隨著時間的移動,違約人數在一定方位內浮動,未違約人數穩定增長,浮動均呈規律性變化,可能與該金融機構的繳款日有關

2.2 衍生特生

統計Log_info、Updat_info表中的用戶登錄次數以及用戶更新信息的次數,并命名為Log_count和Updat_count加入到數據中

# 統計Log登錄次數
log_count = train_log.pivot_table(values=['LogInfo3'], index=['Idx'], aggfunc=['count'])
log_count = log_count.reset_index()
log_count.columns = log_count.columns.droplevel(1)
log_count.rename(columns={'count':'Log_count'}, inplace=True)

# 統計Update更改次數
updat_count = train_update.pivot_table(values='UserupdateInfo1', index='Idx', aggfunc=['count'])
updat_count = updat_count.reset_index()
updat_count.columns = updat_count.columns.droplevel(1)
updat_count.rename(columns={'count':'Updat_count'}, inplace=True)

# 將新的衍生字段加入到數據中
train = pd.merge(train, log_count, how='left', on=['Idx'])
train = pd.merge(train, updat_count, how='left', on=['Idx'])
# 用0天不信的字段的缺失值
train['Log_count'] = train['Log_count'].fillna(0)
train['Updat_count'] = train['Updat_count'].fillna(0)

3. 特征選擇

3.1 方差分析

# 通過計算每個數值型特征的標準差,刪除方差很小的字段,尤其是只有唯一值的字段
train_var = train.var().sort_values()
train_var_index = train_var[train_var < 0.1].index[:-1]  # 保留target,因為target是因變量
train.drop(train_var_index, axis=1, inplace=True)

3.2 GBDT重要度排序

X = train.drop('target', axis=1).copy()
y = train['target'].copy()
X = pd.get_dummies(X)  # 對X進行獨熱編碼

from sklearn.ensemble import GradientBoostingClassifier
clf = GradientBoostingClassifier()
clf.fit(X, y)
print(clf.feature_importances_) 

[0.03835858 0.00768559 0.00235331 ... 0. 0. 0. ]
刪除重要度為0的字段

X_new = X.loc[:, clf.feature_importances_ > 0]
X_new.shape  # (30000, 259)

4. 類別不均衡處理

from collections import Counter
Counter(y)  # 正負樣本比例接近13:1

Counter({0: 27802, 1: 2198})

# 采取過采樣的方法解決類別不均衡問題,使用SMOTE
from imblearn.over_sampling import SMOTE
X_resampled, y_resampled = SMOTE().fit_sample(X_new, y)

sorted(Counter(y_resampled).items())

[(0, 27802), (1, 27802)]

5. 模型優化設計

# 實例化一個GBDT分類器
from sklearn import metrics
clf2 = GradientBoostingClassifier()
clf2.fit(X_resampled, y_resampled)
clf2.predict(X_resampled)
metrics.accuracy_score(y_resampled, clf2.predict(X_resampled))

調參

# n_estimators
n_estimators = np.arange(20, 200, 20)

for n in n_estimators:
    clf = GradientBoostingClassifier(n_estimators=n)
    clf.fit(X_resampled, y_resampled)
    print('n_estimators為{}, AUC為{}'.format(n, metrics.accuracy_score(y_resampled, clf.predict(X_resampled))))

運行發現n_estimators參數基本在92%-96%之間,n_estimators為60后,AUC提升基本不明顯

learning_rate = np.arange(0.1, 1, 0.1)
for n in learning_rate:
    clf4 = GradientBoostingClassifier(n_estimators=60, learning_rate=n)
    clf4.fit(X_resampled, y_resampled)
    print('learning_rate為{}, AUC為{}'.format(n, metrics.accuracy_score(y_resampled, clf4.predict(X_resampled))))

在不同的learning_rate取值下,AUC的變化不大,之才采用默認參數
使用交叉驗證評估模型穩定性

from sklearn.model_selection import cross_val_score
clf3 = GradientBoostingClassifier(n_estimators=60)
clf3.fit(X_resampled, y_resampled)
scores = cross_val_score(clf3, X_resampled, y_resampled, cv=10)
scores

經過交叉驗證的評估,模型AUC基本穩定在98%左右

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容