DataCastle實戰---用戶貸款風險預測
本次分享的機器學習案例來自DataCastle競賽中的「用戶貸款風險預測」項目,小夥伴們有興趣的話可以點擊這個網站鏈接,報名參賽後就能獲取數據進行練習了.
用戶貸款風險預測-競賽信息-DC競賽因這個案例的數據量比較大,欄位數也多,在建模前的數據處理工作花了比較長的時間,所以這個前期工作內容也會佔文章的較多篇幅。
照例先說一下項目流程:
- 項目目標理解
- 表與欄位理解
- 數據預處理
- 特徵構建選擇
- 數據建模
【項目目標理解】
1. 項目任務:
通過舉辦商提供的關於用戶基本身份信息,消費行為,銀行還款等數據信息,以此建立準確的逾期預測模型,來預測用戶是否會逾期還款。
2. 目標變數解釋:
信用卡逾期還款是指用戶超過銀行規定的最後還款期限,未及時足額將所消費款項存入指定賬戶的情形。
在網上查了一下關於信用卡逾期的信息,發現在真實的業務場景中,發卡銀行會向持卡者提供還款的「容時容差」服務,「容時」指的是如果持卡人沒有及時還款,是有一個寬限期的,每個銀行的寬限期都不一樣,正常的在1-3天,「容差」指的是如果持卡人當期發生不足額還款時,且在到期還款日後賬戶中未清償部分小於或等於一定金額(這個金額一般在10元以內),應當視同持卡人全額還款。在本次案例中暫時不會考慮「容時容差」的條件,只要持卡人沒有按時還款,就視為「逾期」。
建議小夥伴們在做這個項目前也去了解一下關於信用卡貸款和逾期的相關知識,畢竟作數據分析最重要的是基於對業務的理解。
【表與欄位理解】
1. 表的組成
項目提供的數據包括這幾張表:
1)用戶的基本屬性表(user_info.txt),共6個欄位。
2)銀行流水記錄(bank_detail.txt),共5個欄位。
3)用戶瀏覽行為(browse_history.txt),共4個欄位。
4)信用卡賬單記錄(bill_detail.txt),共15個欄位。
5)放款時間信息(loan_time.txt),共2個欄位。
6)顧客是否發生逾期行為的記錄(overdue.txt),共2個欄位。
2. 欄位理解
在這裡重點提一下,因為項目提供的數據都經過「脫敏處理」,丟失了業務信息,同時舉辦商將用戶的屬性信息全部數字化處理,將時間戳和所有金額的值都做了函數變換。最麻煩的還不是這些,是舉辦商根本沒提供"欄位名的解釋"以及"數據值的含義",說白了要靠我們自己理解和猜測。
對於這6張表我自定義了一下英文欄位名並做了欄位解釋,如下所示:
--用戶信息表:
因為性別,職業這些屬性都提前做了「數字化處理」,根本不知道其含義是什麼,所以我也只能根據各項值的分布進行猜測了。。。。
性別:1--男, 2--女,0--未知
職業:0--國家機關事業單位人員 ,1--農、林、牧、漁、水利業生產人員 ,2--商業,辦事,服務類人員 ,3--生產、運輸設備操作人員及有關人員,4--技術從業人員
教育程度:0--未知,1--博士,2--碩士,3-本科,4--高中/大專
婚姻狀態:0--未知,1--未婚,2--離異,3--已婚,4--再婚,5--喪偶
戶口類型:0--未知,1--農業戶口,2--城鎮居民戶口,3--集體戶口,4--農村居民戶口
--銀行流水記錄:
時間戳:因為作了函數轉換,所以不是時間格式,但可以根據值的大小反映時間的遠近,0表示時間未知。
交易類型:1表示支出、0表示收入
交易金額:每次交易記錄的金額數量
工資收入:標記為1時,表示工資收入,0表示沒有
--用戶瀏覽行為:
--信用卡賬單記錄:
這張表的欄位很多,且都是專業性名詞,下面我寫的欄位解釋是網上查的,如有不對請小夥伴們指正。
上期賬單金額: 上月需要向信用卡還款的金額
上期還款金額:上月用戶已還款的金額
信用卡額度:信用額度(即信用卡最高可以透支使用的限額)+ 存入信用卡的金額。
本期賬單餘額:上期賬單金額 - 上期還款金額
本期賬單最低還款額:最低還款額=信用額度內消費款的10%+預借現金交易款的100%+前期最低還款額未還部分的100%+超過信用額度消費款的100%+費用和利息的100%
消費筆數:用戶在賬單期內的消費記錄總數
本期賬單金額:本期需要向信用卡還款的金額
調整金額:有可能是原先多還的款項, 在下期還款時會去掉這部分的金額
循環利息:當您償還的金額等於或高於當期帳單的最低還款額,但低於本期應還金額時,剩餘的延後還款的金額銀行會計算相應的利息。
可用餘額:信用額度-未還清的已出賬金額-已使用未入賬的累積金額
預借現金額度:是指持卡人使用信用卡通過ATM等自助終端提取現金的最高額度
還款狀態:0--未還款,1--已還款
--放款時間信息表:
--是否逾期表:
逾期狀態:本項目的目標預測變數,0--未逾期,1--逾期
【數據的預處理】
1.數據導入
先把6張表導入Python看看大致情況:
銀行流水記錄表:
columns = [user_id,time,loan_type,loan_amount,income]df_bank_train = pd.read_table(C:/Users/Administrator/Desktop/sklearn/loan/train/bank_detail_train.txt,names = columns, sep=,,encoding=utf_8 )df_bank_train.head()
df_bank_train.isnull().any()
查了一下缺失值,發現數據非常完整。再看一下用戶id的總數:
len(df_bank_train.user_id.unique())
結果為:9294,包含了9294條用戶在銀行的流水記錄。
同理我們也導入其他表,這裡就略過其它導入過程了,6張表導入之後,發現每張表的數據都很完整,看來舉辦商確實做好了數據的處理工作,不過我發現了這幾張表的一些不同之處,就是在於每張表的用戶id數目(去重後)。
用戶信息表,是否逾期表,放款時間表這三張表的id數目都是55,596,銀行流水表為9,294,瀏覽信息表為47,330,信用卡賬單表為53,174,這說明並非每一位用戶都有非常完整的記錄,如有些用戶並沒有信用卡賬單記錄,有些用戶沒有銀行流水記錄,而用戶信息,放款時間,是否逾期這三張表是完整的記錄。
所以如果這些表組合在一起,會導致很多用戶記錄存在缺失,為了解決這個問題,我打算篩選出這6張表所共同擁有的用戶id記錄,並將篩選後的結果組合在一起形成一張完整的數據表。
2. 數據篩選
在篩選前我們要考慮一個時間窗口的問題,根據上面的理解可以看出放款時間表和是否逾期是對應的,簡單講就是銀行給用戶放款後,在下一個還款期銀行再記錄用戶是否發生逾期,所以我們選取的記錄應當是放款時間之前的,根據放款時間來劃分時間窗口,構建每個表的放款前特徵。
我們以銀行流水記錄表為例:
首先將銀行流水表與放款時間表進行交叉,提取兩張表對應的時間:
df = pd.merge(left = df_bank_train, right= df_loantime_train, how = left,on = user_id)df.head()
time_x表示流水記錄時間,time_y表示放款時間,然後篩選出流水時間<放款時間的記錄:
t = df[(df[time_x]<=df[time_y])]t.head()
接著將用戶id進行去重處理:
bank_user = t[[user_id]]bank_user = bank_user.drop_duplicates(subset = user_id,keep=first)bank_user.info()
可以看到結果為9271,同理我們篩選信用卡賬單表及瀏覽行為表的用戶id,
信用卡賬單的id數為:46739,瀏覽行為表為:44945。
然後我們篩選出這6張表共有的用戶id:
user1 = pd.merge(left=bank_user,right=brows_user,how=inner,on = user_id)user2 = pd.merge(left=user1,right=bill_user,how=inner,on = user_id)user2.info()
結果為5735,說明5735個用戶的記錄是完整的。然後通過這些id篩選每張表的記錄:
還是以銀行流水記錄為例:
# 先篩選出id為『user2』的記錄bank_select = pd.merge(left=df_bank_train,right = user2,how=inner,on=user_id)# 根據收入,支出以及工資這三個欄位的交易類型進行篩選,並按id進行分組統計,生成新的變數。b1 = bank_select[(bank_select[loan_type] == 0)].groupby([user_id], as_index=False) # 收入統計b2 = bank_select[(bank_select[loan_type] == 1)].groupby([user_id], as_index=False) # 支出統計b3 = bank_select[(bank_select[income] == 1)].groupby([user_id], as_index=False) # 工資收入統計c1 = b1[loan_amount].agg({income_num: count, income_amount: sum})c2 = b2[loan_amount].agg({expen_num: count, expen_amount: sum})c3 = b3[loan_amount].agg({wages_num: count, wages_amount: sum})# 因為不是每個id都有完整的交易記錄,對沒有記錄的缺失值填充為0.d1 = pd.merge(left=user2,right =c1,how = left,on=user_id)d1 = d1.fillna(0)d2 = pd.merge(left=user2,right =c2,how = left,on=user_id)d2 = d2.fillna(0)d3 = pd.merge(left=user2,right =c3,how = left,on=user_id)d3 = d3.fillna(0)# 合併三張帶有新變數的表。bank_train = d1.merge(d2)bank_train = bank_train.merge(d3)bank_train.head()
這裡因為對用戶id進行了分組統計,所以產生了6個新的變數:
income_num : 放款前用戶收入筆數
income_amount: 放款前用戶收入總計
expen_num: 放款前用戶支出筆數
expen_amount: 放款前用戶支出總計
wages_num: 放款前用戶工資收入筆數
wages_amount: 放款前用戶工資收入總計
然後對其他表也進行篩選,最後將篩選後的6張表進行合併:
df_train = user_train.merge(bank_train)df_train = df_train.merge(bill_train)df_train = df_train.merge(brows_train)df_train = df_train.merge(overdue_train)df_train.head()
df_train就是最終合成的數據表,包含了用戶信息,銀行流水信息,信用卡賬單信息以及用戶瀏覽行為信息。一共包含25個欄位。
df_train.info()
因為省略了其它表的篩選過程,所以有些地方的改動需說明一下:
--信用卡賬單記錄表去掉了時間戳,銀行id和還款狀態這幾個變數,因為原先的表是按時間的維度決定每條記錄的唯一性,現在按用戶id分組後我作了每個id記錄均值化處理,所以裡面的連續型變數都是取平均後的結果,另外還款狀態這個變數之所以剔除,因為我發現這個變數的正負值分布太不均衡了:
它的正負值比例居然達到10000:1,為了不影響模型的準確度,故沒有把它篩選進來,
--瀏覽行為表裡剔除了瀏覽子行為這個變數,因為確實理解不了它的含義是什麼,另外對瀏覽行為數據變數作了分組計數處理。
在這裡小夥伴們如果有什麼問題可以跟我討論,一起研究一下怎麼更好地作這個項目的數據預處理。
【特徵構建與選擇】
1.基於業務理解篩選特徵
終於到了數據建模的第一步:特徵構建選擇,在之前寫的文章:
Summer Memories:Python機器學習實戰--美國房價預測
里我簡單介紹了一下機器學習里特徵選擇的方法,不過在這裡我打算先基於對信用卡逾期的業務理解來初步篩選特徵。我們先對df_train里的各個特徵做一下分類:
首先看一下用戶的基本屬性:
基於我自己的理解,跟逾期行為相關的因素有:性別,年齡,教育程度,婚姻情況,收入,用戶是否有放貸,車貸等其他經濟情況。案例數據里我們可以先篩選出:
性別,教育程度,婚姻狀況。
職業,戶口類型這兩個特徵雖然能反映用戶的收入情況,但不確定性很大,所以不考慮篩選進來,我們可以從用戶在銀行的收入/支出記錄來側面反映用戶的經濟情況。
所以第一步篩選的特徵為:
sex , education, marriage,income_num,income_amount,expen_num,
expen_amount,wages_num,wages_amount。
這裡注意收入/支出/工資的筆數和金額數目是線性關係(如下圖),所以後續要重新構建經濟情況的特徵(金額數目/筆數), 而且收入與支出有強的共線性,只要選擇其中一個特徵即可。
接下來是信用卡記錄上的特徵:
信用卡上的特徵很多,我們可以先用相關性熱力圖看一下各個特徵之間的關係:
internal_chars = [last_bill_amount,last_repay_amount,credit_amount,cur_bill_bal,cur_bill_minrepay ,cons_num,cur_bill_amount,adjust_amount,cyclic_accr,avail_bal,borrow_cash]corrmat = df_train[internal_chars].corr()f, ax = plt.subplots(figsize=(8, 5))plt.xticks(rotation=0)sns.heatmap(corrmat, square=False, linewidths=.5, annot=True)
從圖中可以看出倆倆之間相關性程度比較高的特徵有:
last_bill_amount,last_repay_amount,cur_bill_amount,credit_amount
這四者之間, 另外還有cur_bill_bal 與 cur_bill_minrepay。
之前在網上找了一個計算還款的公式,可以根據這個公式加深對於特徵關係的理解:
本期應還金額 = 上期賬單金額-上期還款金額 + 本期賬單金額 - 本期調整金額 + 循環利息
這個公式的意思是:
上期還款金額 <上期賬單的最低還款額(一般是賬單金額*10%)或者不還,就視為逾期,而且本期的還款要加上循環利息和上期未還款的那部分。
上期最低還款額<上期還款金額<上期賬單金額,不視為逾期,但本期的還款要加上循環利息
上期還款金額 >上期賬單金額,也就是說用戶還多了,那麼本期的還款會減去一個調整金額(多還的那部分),循環利息不計。
所以上期的賬單金額與還款金額是密切相關的,相關係數也高,可以引入一個新的特徵:
上期還款差額 =上期賬單金額 - 上期還款金額, 上期還款差額還會直接影響用戶的信用額度以及本期的賬單金額。
本期的賬單餘額與最低還款額具有高度共線性,決定只選用最低還款額。
另外調整金額和循環利息是跟「上期的還款差額」有關的:
還款差額>0,需要計算循環利息,調整金額不計
還款差額<0,需要計算調整金額,循環利息不計
所以可以將還款差額進行「特徵二值化」來代替這兩個特徵。
可用餘額=銀行核定的信用卡額度-尚未交還的賬單上欠款-未入賬但已發生的交易金額-其他相關利息、費用。所以可用餘額與信用卡額度,上期還款差額,循環利息等都有一定的關係,但讓我感到奇怪的是,相關係數圖上並沒有表現出來,所以暫時不篩選進這個特徵。
預借現金額度,是指持卡人使用信用卡通過ATM等自助終端提取現金的最高額度,取現額度包含於信用額度之內,一般是信用額度的50%左右,所以可以不用這個特徵,選擇信用額度即可。
講了這麼多,現在可以篩選出我們要的信用卡記錄特徵了:
last_repay_diff(上期還款差額),credit_amount,cur_bill_minrepay,cur_bill_amount
cons_num這5個特徵。
最後我們把基於業務理解的特徵選擇結果展示出來:
同時重新構建數據表:
df_train[expen_avg] = df_train.apply(lambda x:x.expen_amount/x.expen_num,axis=1)df_train[wages_avg] = df_train.apply(lambda x:x.wages_amount/x.wages_num,axis=1)df_train[last_repay_diff] = df_train.apply(lambda x:x.last_bill_amount - x.last_repay_amount,axis=1)df_select = df_train.loc[:,[user_id,sex,education,marriage,expen_avg,wages_avg,last_repay_diff, credit_amount,cur_bill_amount,cur_bill_minrepay,cons_num,brows_beh,overdue]].fillna(0)df_select.info()
2. 基於機器學習進一步篩選特徵:
根據業務理解篩選的特徵數仍有11個,故還需要用機器學習的方法對特徵作進一步降維。
在篩選前我們將last_repay_diff這個特徵作「二值化」處理:
df_select[last_diff_label] = df_select.apply(lambda x: 0 if x.last_repay_diff < 0 else 1,axis=1)
11個特徵中sex,education,marriage,last_diff_label為分類變數,其它為連續型數值變數。
我們採取兩種特徵選擇方法:filter法和Wrapper法
--Filter法篩選:
x = df_select.drop([user_id,last_repay_diff,overdue],axis=1)x = np.array(x)y = df_select[[overdue]]y = np.array(y)from sklearn.feature_selection import SelectKBest, f_classifselector = SelectKBest(f_classif, k=5)a=selector.fit(x,y)print(np.array(a.scores_),
,a.get_support())
篩選的結果為:sex ,education, expen_avg, wages_avg, cur_bill_minrepay
--Wrapper法:
因為特徵既包含分類型,又有連續型,所以選用的基模型為決策樹:
from sklearn.tree import DecisionTreeClassifierfrom sklearn.feature_selection import RFEmodel1 = DecisionTreeClassifier()rfe = RFE(model1,5)rfe = rfe.fit(x,y)print(rfe.support_) print(rfe.ranking_)
篩選的結果為:
expen_avg, credit_amount, cur_bill_amount, cur_bill_minrepay, brows_beh
發現倆種方法篩選的結果相差很大,wrapper法沒有選擇任何一個分類型變數,而filter法選擇了「sex」和「education」兩個分類型變數,相同點是都選擇了expen_avg和cur_bill_minrepay。
用交叉檢驗的方法看一下哪種方法好吧:
from sklearn.model_selection import cross_val_scorex_test1 = df_select[[sex,education,expen_avg,wages_avg,cur_bill_minrepay]]x_test1 = np.array(x_test1)y_test1 = df_select[[overdue]]y_test1 = np.array(y_test1)m1 = DecisionTreeClassifier()m1.fit(x_test1,y_test1)scores = -cross_val_score(m1, x_test1, y_test1, cv=5, scoring= neg_mean_absolute_error)print(np.mean(scores))
filter法結果為:0.24080262097612498
from sklearn.model_selection import cross_val_scorex_test2 = df_select[[expen_avg,credit_amount,cur_bill_amount,cur_bill_minrepay,brows_beh]]x_test2 = np.array(x_test2)y_test2 = df_select[[overdue]]y_test2 = np.array(y_test2)m2 = DecisionTreeClassifier()m2.fit(x_test2,y_test2)scores = -cross_val_score(m2, x_test2, y_test2, cv=5, scoring= neg_mean_absolute_error)print(np.mean(scores))
Wrapper法結果為:0.2537052432049981
交叉檢驗的結果表明還是用Filter法好一點,接下來就是建模了,既然篩選的5個特徵既有分類型,又有連續型,用決策樹是最合適了。
【數據建模】
1.拆分訓練集和測試集數據:比例為4:1
from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2, random_state = 0)
2. 設定決策樹參數,進行建模
這裡採用的是決策樹里的CART法,之所以不用ID3和C4.5,是因為這兩個方法不能處理連續型數據,必須轉換成離散型。
from sklearn import treeclf = tree.DecisionTreeClassifier(criterion=gini, splitter=best, max_depth=3, min_samples_split=10, min_samples_leaf=5 )clf = clf.fit(x_train,y_train)y_pred = clf.predict(x_test)y_pred = y_pred[:, np.newaxis]y_pred
3.模型結果評價
看一下模型結果分析報告:
from sklearn.metrics import classification_reportprint(classification_report(y_test, y_pred))
精確率0.82,召回率0.86,f1_score為0.81,模型的綜合評價還是可以的。
接下來可以對模型進行調參,在這裡我主要對樹的深度進行了不同設置,發現當樹的深度為3時,模型的結果是比較好的。
另外決策樹最大的問題是會容易出現」過擬合」,我在網上看到的解決方法主要有倆種:
1.對樹進行剪枝,包括預剪枝和後剪枝
2.用隨機森林的方法,進行隨機選取樣本和隨機選取屬性
這裡就不再進行闡述了,後續有時間會把模型調優這部分給補上,先寫到這裡啦~~
推薦閱讀:
※機器學習入門之泰坦尼克案例
※2-2 Cost Function
※專家分享:入門深度學習應該學什麼
※天池智能製造預測大賽-解決思路筆記
※機器學習最難區分的4個概念,一文釐清!