基於Python互聯網金融LeningClub貸款違約預測模型
來自專欄 數據分析學習之路
1 項目簡介
- 採用了Lending Club 信用貸款違約數據是美國網路貸款平台 LendingClub 在2007-2015年間的信用貸款情況數據,主要包括貸款狀態和還款信息。附加屬性包括:信用評分、地址、郵編、所在州等,累計75個屬性(列),890000筆 貸款(行)。
- 貸款違約預測模型,使用了Numpy,Pandas,Sklearn科學計算包完成數據清洗,構建特徵工程,以及完成預約模型的訓練,數據可視化採用了Matplotlib及Seaborn等可視化包。
2 數據清洗過程
2.1 數據導入以及預處理
導入相關數據分析及可視化包
#導入相關庫import pandas as pdpd.set_option(display.float_format, lambda x: %.4f % x) #為了直觀的顯示數字,不採用科學計數法import numpy as np%matplotlib inlineimport matplotlib as mplimport matplotlib.pyplot as pltplt.style.use(ggplot) #風格設置近似R這種的ggplot庫import seaborn as snssns.set_style(whitegrid)import missingno as msnoimport warningswarnings.filterwarnings(ignore) #為了整潔,去除彈出的warnings
導入LendingClub貸款數據
#導入數據及預覽前三行data=pd.read_csv("D:kaggleLending club loan dataloan.csv")data.head(3)
本人電腦配置有限,為了加快計算速度,僅僅選擇2015年度的貸款數據
#選擇2015年度的貸款數據data_15=data[(data.issue_d==Jan-2015) |(data.issue_d==Feb-2015) |(data.issue_d==Mar-2015) |(data.issue_d==Apr-2015) |(data.issue_d==Apr-2015) |(data.issue_d==Apr-2015) |(data.issue_d==May-2015) |(data.issue_d==Jun-2015) |(data.issue_d==Jul-2015) |(data.issue_d==Aug-2015) |(data.issue_d==Sep-2015) |(data.issue_d==Oct-2015) |(data.issue_d==Nov-2015) |(data.issue_d==Dec-2015) ]
統計2015年度數據每列的缺失值情況。
#統計每列的缺失值情況check_null = data_15.isnull().sum(axis=0).sort_values(ascending=False)/float(len(data)) #查看缺失值比例print(check_null[check_null > 0.2]) # 查看缺失比例大於20%的屬性。
從上圖中可以看出,數據集中有很多列都有缺失值,所以我們要判斷此列的數據對預測結果是否有影響,如果沒有影響,可以將此列刪除,本文中我們將缺失值超過40%的列刪除。
#刪除缺失值超過40%的列thresh_count = len(data_15)*0.4 # 設定閥值data_15 = data_15.dropna(thresh=thresh_count, axis=1 ) #若某一列數據缺失的數量超過閥值就會被刪除
再次檢查缺失值的情況,只有6列的數據還有缺失值。
#按缺失值比例從大到小排列data_15.isnull().sum(axis=0).sort_values(ascending=False)/float(len(data_15))
查看數據類型的大概分布情況
data_15.dtypes.value_counts() # 分類統計數據類型
使用pandas的loc切片方法,得到每列至少有2個分類特徵的數組集
#loc切片得到每列至少有2個分類特徵的數組集data_15 = data_15.loc[:,data_15.apply(pd.Series.nunique)!=1]
查看數據的變化,列數少了1列。
data_15.dtypes.value_counts()# 分類統計數據類型
上述過程,刪除了較多缺失值的特徵,以下將對有缺失值的特徵進行處理
2.2 缺失值處理
「Object」和「float64「類型缺失值的處理方法不一樣,所以將兩者分開進行處理。
- 首先處理「Object」分類變數缺失值。
#便於理解將變數命設置為loansloans=data_15loans.shape
初步了解「Object」變數概況。
#初步了解「Object」變數概況pd.set_option(display.max_rows,None)loans.select_dtypes(include=[object]).describe().T
「Object」分類變數缺失值概況。
#查看「Object」分類變數缺失值概況。objectColumns = loans.select_dtypes(include=["object"]).columnsloans[objectColumns].isnull().sum().sort_values(ascending=False)
使用『unknown』來填充缺失值。
#使用『unknown』來填充缺失值objectColumns = loans.select_dtypes(include=["object"]).columns # 篩選數據類型為object的數據loans[objectColumns] = loans[objectColumns].fillna("Unknown") #以分類「Unknown」填充缺失值
確認「Object」分類變數無缺失值。
#查看「Object」分類變數缺失值情況loans[objectColumns].isnull().sum().sort_values(ascending=False)
- 處理「float64」數值型變數缺失值。
loans.select_dtypes(include=[np.number]).isnull().sum().sort_values(ascending=False)
結果發現只有兩個變數存在缺失值,使用mean值來填充缺失值。
#利用sklearn模塊中的Imputer模塊填充缺失值numColumns = loans.select_dtypes(include=[np.number]).columnsfrom sklearn.preprocessing import Imputerimr = Imputer(missing_values=NaN, strategy=mean, axis=0) # 針對axis=0 列來處理imr = imr.fit(loans[numColumns])loans[numColumns] = imr.transform(loans[numColumns])
再次查看數值變數缺失值。
loans.select_dtypes(include=[np.number]).isnull().sum().sort_values(ascending=False)
從上表中可以看到數值變數中已經沒有缺失值。
2.3 數據過濾
本文的目的是對平台用戶的貸款違約做出預測,所以需要篩選得到一些對用戶違約有影響的信息,其他不相關的冗餘信息,需要將其刪除掉。
首先查看所有的分類標籤
loans.columns
- sub_grade:與Grade的信息重複
- emp_title :缺失值較多,同時不能反映借款人收入或資產的真實情況
- zip_code:地址郵編,郵編顯示不全,沒有意義
- addr_state:申請地址所屬州,不能反映借款人的償債能力
- last_credit_pull_d :LendingClub平台最近一個提供貸款的時間,沒有意義
- policy_code : 變數信息全為1
- pymnt_plan 基本是n
- title: title與purpose的信息重複,同時title的分類信息更加離散
- next_pymnt_d : 下一個付款時間,沒有意義
- policy_code : 沒有意義
- collection_recovery_fee: 全為0,沒有意義
- earliest_cr_line : 記錄的是借款人發生第一筆借款的時間
- issue_d : 貸款發行時間,這裡提前向模型泄露了信息
- last_pymnt_d、collection_recovery_fee、last_pymnt_amnt: 預測貸款違約模型是貸款前的風險控制手段,這些貸後信息都會影響我們訓練模型的效果,在此將這些信息刪除
- url:所有的行都不同,沒有分類意義
將以上重複或對構建預測模型沒有意義的屬性進行刪除。
#刪除對模型沒有意義的列loans2=loans.drop([sub_grade, emp_title, title, zip_code, addr_state,url], axis=1, inplace = True)loans3=loans.drop([issue_d, pymnt_plan, earliest_cr_line, initial_list_status, last_pymnt_d,next_pymnt_d,last_credit_pull_d], axis=1, inplace = True)
再次查看『Object』類型變數,只剩下8個分類變數。
object_columns_df3 =loans.select_dtypes(include=["object"]) #篩選數據類型為object的變數print(object_columns_df3.iloc[0])
3. 特徵工程
特徵工程師機器學習最重要的一部分,希望找到的特徵是最貼近實際業務場景的,所以要反覆去找特徵,只需要最少的特徵得到簡單的模型,並且有最好的預測效果。
本文將特徵工程主要分3大部分:特徵抽象 、特徵縮放 、特徵選擇
3.1 特徵抽象
數據集中有很多的「Object」類型的分類變數存在,但是對於這種變數,機器學習演算法不能識別,需要將其轉化為演算法能識別的數據類型。
- 首先對於"loan_status"數據類型轉換。
#統計"loan_status"數據的分布loans[loan_status].value_counts()
將上表中的違約編碼為1,正常的為0進行編碼。
#使用Pandas replace函數定義新函數:def coding(col, codeDict): colCoded = pd.Series(col, copy=True) for key, value in codeDict.items(): colCoded.replace(key, value, inplace=True) return colCoded
#把貸款狀態LoanStatus編碼為違約=1, 正常=0:pd.value_counts(loans["loan_status"])loans["loan_status"] = coding(loans["loan_status"], {Current:0,Fully Paid:0 ,In Grace Period:1 ,Late (31-120 days):1 ,Late (16-30 days):1 ,Charged Off:1 ,"Issued":1 ,"Default":1 ,"Does not meet the credit policy. Status:Fully Paid":1 ,"Does not meet the credit policy. Status:Charged Off":1})print(
After Coding:)pd.value_counts(loans["loan_status"])
可視化查看"loan_status"中不同狀態的替換情況。
# 貸款狀態分布可視化fig, axs = plt.subplots(1,2,figsize=(14,7))sns.countplot(x=loan_status,data=loans,ax=axs[0])axs[0].set_title("Frequency of each Loan Status")loans[loan_status].value_counts().plot(x=None,y=None, kind=pie, ax=axs[1],autopct=%1.2f%%)axs[1].set_title("Percentage of each Loan status")plt.show()
- 變數「emp_length」、"grade"進行特徵抽象化
# 構建mapping,對有序變數"emp_length」、「grade」進行轉換mapping_dict = { "emp_length": { "10+ years": 10, "9 years": 9, "8 years": 8, "7 years": 7, "6 years": 6, "5 years": 5, "4 years": 4, "3 years": 3, "2 years": 2, "1 year": 1, "< 1 year": 0, "n/a": 0 }, "grade":{ "A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7 }}loans = loans.replace(mapping_dict) #變數映射loans[[emp_length,grade]].head() #查看效果
- 變數"home_ownership", "verification_status", "application_type","purpose", "term" 狂熱編碼
#變數狂熱編碼n_columns = ["home_ownership", "verification_status", "application_type","purpose", "term"] dummy_df = pd.get_dummies(loans[n_columns])# 用get_dummies進行one hot編碼loans = pd.concat([loans, dummy_df], axis=1) #當axis = 1的時候,concat就是行對齊,然後將不同列名稱的兩張表合併
查看編碼後的數據集。
loans.loc[:,loans.columns.str.contains("home_ownership")].head() #篩選包含home_ownership的所有變數
loans = loans.drop(n_columns, axis=1) #清除原來的分類變數
重新查看數據集中的數據類型。
loans.info() #查看數據信息
3.2 特徵縮放
採用標準化的方法進行去量綱操作,加快演算法收斂速度,採用scikit-learn模塊preprocessing的子模塊StandardScaler進行操作。
col = loans.select_dtypes(include=[int64,float64]).columnscol = col.drop(loan_status) #剔除目標變數loans_ml_df = loans # 複製數據至變數loans_ml_df###################################################################################from sklearn.preprocessing import StandardScaler # 導入模塊sc =StandardScaler() # 初始化縮放器loans_ml_df[col] =sc.fit_transform(loans_ml_df[col]) #對數據進行標準化loans_ml_df.head() #查看經標準化後的數據
以上過程完成了非數值型特徵抽象化處理,使得演算法能理解數據集中的數據,這麼多的特徵,究竟哪些特徵對預測結果影響較大,所以以下通過影響大小對特徵進行選擇。
3.2 特徵選擇
特徵的選擇優先選取與預測目標相關性較高的特徵,不相關特徵可能會降低分類的準確率,因此為了增強模型的泛化能力,我們需要從原有特徵集合中挑選出最佳的部分特徵,並且降低學習的難度,能夠簡化分類器的計算,同時幫助了解分類問題的因果關係。
一般來說,根據特徵選擇的思路將特徵選擇分為3種方法:嵌入方法(embedded approach)、過濾方法(filter approach)、包裝方法(wrapper approacch)。
- 過濾方法(filter approach): 通過自變數之間或自變數與目標變數之間的關聯關係選擇特徵。
- 嵌入方法(embedded approach): 通過學習器自身自動選擇特徵。
- 包裝方法(wrapper approacch): 通過目標函數(AUC/MSE)來決定是否加入一個變數。
本次項目採用Filter、Embedded和Wrapper三種方法組合進行特徵選擇。
首先將數據集中的貸款狀態loan_status
抽離出來。
#構建X特徵變數和Y目標變數x_feature = list(loans_ml_df.columns)x_feature.remove(loan_status)x_val = loans_ml_df[x_feature]y_val = loans_ml_df[loan_status]len(x_feature) # 查看初始特徵集合的數量
重新查看沒有貸款狀態loan_status的數據集。
x_val.describe().T # 初覽數據
- Wrapper方法
選出與目標變數相關性較高的特徵。通過暴力的遞歸特徵消除 (Recursive Feature Elimination)方法篩選30個與目標變數相關性最強的特徵,將特徵維度從59個降到30個。
from sklearn.feature_selection import RFEfrom sklearn.linear_model import LogisticRegression# 建立邏輯回歸分類器model = LogisticRegression()# 建立遞歸特徵消除篩選器rfe = RFE(model, 30) #通過遞歸選擇特徵,選擇30個特徵rfe = rfe.fit(x_val, y_val)# 列印篩選結果print(rfe.support_)print(rfe.ranking_) #ranking 為 1代表被選中,其他則未被代表未被選中
通過布爾值篩選首次降維後的變數。
col_filter = x_val.columns[rfe.support_] #通過布爾值篩選首次降維後的變數col_filter # 查看通過遞歸特徵消除法篩選的變數
- Filter方法
正常情況下,影響目標變數的因數是多元性的;但不同因數之間會互相影響(共線性 ),或相重疊,進而影響到統計結果的真實性。下一步,以下通過皮爾森相關性圖譜找出冗餘特徵並將其剔除,且通過相關性圖譜進一步引導我們選擇特徵的方向。
colormap = plt.cm.viridisplt.figure(figsize=(12,12))plt.title(Pearson Correlation of Features, y=1.05, size=15)sns.heatmap(loans_ml_df[col_filter].corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor=white, annot=True)
從上圖中得到需要刪除的冗餘特徵。
drop_col = [id,member_id,collection_recovery_fee,funded_amnt, funded_amnt_inv,installment, out_prncp, out_prncp_inv, total_pymnt_inv, total_rec_prncp, total_rec_int, home_ownership_OWN, application_type_JOINT, home_ownership_RENT , term_ 36 months, total_pymnt, verification_status_Source Verified, purpose_credit_card,int_rate]col_new = col_filter.drop(drop_col) #剔除冗餘特徵print(len(col_new))
特徵從30個降到12個,再次確認處理後的數據相關性。
col_new # 查看剩餘的特徵colormap = plt.cm.viridisplt.figure(figsize=(12,12))plt.title(Pearson Correlation of Features, y=1.05, size=15)sns.heatmap(loans_ml_df[col_new].corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor=white, annot=True)
- Embedded方法
為了了解每個特徵對貸款違約預測的影響程度,所以在進行模型訓練之前,我們需要對特徵的權重有一個正確的評判和排序,就可以通過特徵重要性排序來挖掘哪些變數是比較重要的,降低學習難度,最終達到優化模型計算的目的。
#隨機森林演算法判定特徵的重要性names = loans_ml_df[col_new].columnsfrom sklearn.ensemble import RandomForestClassifierclf=RandomForestClassifier(n_estimators=10,random_state=123)#構建分類隨機森林分類器clf.fit(x_val[col_new], y_val) #對自變數和因變數進行擬合names, clf.feature_importances_for feature in zip(names, clf.feature_importances_): print(feature)
特徵重要性從大到小排序及可視化圖形,結果發現最具判別效果的特徵是收到的最後付款總額『last_pymnt_amnt』
4.模型訓練
4.1 樣本不平衡處理
本項目中,2015年度貸款平台上違約的借款人比例很低,約為4.9%,正負樣本量非常不平衡,非平衡樣本常用的解決方式有2種:
1、過採樣(oversampling),增加正樣本使得正、負樣本數目接近,然後再進行學習。
2、欠採樣(undersampling),去除一些負樣本使得正、負樣本數目接近,然後再進行學習。
# 構建自變數和因變數X = loans_ml_df[col_new]y = loans_ml_df["loan_status"]n_sample = y.shape[0]n_pos_sample = y[y == 0].shape[0]n_neg_sample = y[y == 1].shape[0]print(樣本個數:{}; 正樣本占{:.2%}; 負樣本占{:.2%}.format(n_sample, n_pos_sample / n_sample, n_neg_sample / n_sample))print(特徵維數:, X.shape[1])
from imblearn.over_sampling import SMOTE # 導入SMOTE演算法模塊# 處理不平衡數據sm = SMOTE(random_state=42) # 處理過採樣的方法X, y = sm.fit_sample(X, y)print(通過SMOTE方法平衡正負樣本後)n_sample = y.shape[0]n_pos_sample = y[y == 0].shape[0]n_neg_sample = y[y == 1].shape[0]print(樣本個數:{}; 正樣本占{:.2%}; 負樣本占{:.2%}.format(n_sample, n_pos_sample / n_sample, n_neg_sample / n_sample))
4.2 模型訓練
採用邏輯回歸分類器 分類器進行訓練。
# 構建邏輯回歸分類器from sklearn.linear_model import LogisticRegressionclf1 = LogisticRegression() clf1.fit(X, y)
查看預測結果的準確率。
predicted1 = clf.predict(X) # 通過分類器產生預測結果from sklearn.metrics import accuracy_scoreprint("Test set accuracy score: {:.5f}".format(accuracy_score(predicted1, y,)))
利用混淆矩陣及可視化觀察預測結果
#生成混淆矩陣from sklearn.metrics import confusion_matrixm = confusion_matrix(y, predicted1) m
# 混淆矩陣可視化plt.figure(figsize=(5,3))sns.heatmap(m)
再利用sklearn.metrics子模塊classification_report查看precision、recall、f1-score的值。
#查看precision、recall、f1-score的值from sklearn.metrics import classification_reportprint(classification_report(y, predicted1))
#計算ROC值from sklearn.metrics import roc_auc_scoreroc_auc1 = roc_auc_score(y, predicted1)print("Area under the ROC curve : %f" % roc_auc1)
以上完成了全部的模型訓練及預測工作。
5. 小結
本文基於互聯網金融平台2015年度貸款數據完成信貸違約預測模型,全文包括了數據清洗,構建特徵工程,訓練模型,最後得到的模型準確率達到了0.79,召回率達到了0.68,具有較好的預測性,本文的模型可以作為信貸平台預測違約借款人的參考。
推薦閱讀:
※什麼年代了!你還在寫 ETL 代碼抽取特徵?
※如何獲取可區分性的特徵(loss functon篇上)
※文本特徵選擇(信息熵、Gini、IV、卡方值)
※特徵工程-Outliers