P2P網貸信用評分項目分享(一)

項目介紹app


此項目爲kaggle競賽平臺的give me some credits。其目的是預測銀行用戶違約機率,以輔助銀行判斷是否要對用戶進行放貸。關於風險控制建模的大體流程可參考如下連接:ide


此項目提供樣本數量多,但變量特徵比較少,相比實際業務的開展確定是遠遠不夠的。可是做爲入門風控建模,瞭解建模開發流程倒是個不錯的選擇。項目擬使用所提供的數據集創建一個申請評分卡(A卡),並能夠對用戶自動評分。測試


其實在實際建模過程當中是要結合業務端的,對於好壞用戶如何定義?逾期多少DPD算是壞用戶?表現期和觀察期又是如何定義的?每一個公司的業務不同,面向客戶羣體也不同,這些指標在各個公司都不必定是相同的。好比,好壞用戶就須要根據滾動率來觀察,幾期事後逾期率會達到穩定,又如經過帳齡分析來定義表現期窗口的時間長度等。spa


本項目僅供學習使用,對於業務指標不進行過多考慮,而側重於建模的技術方面。3d


2數據探索code


和以前的套路同樣,建模前的數據探索十分重要,發現數據分佈特徵,數據聯繫和內在規律等。首先導入數據後觀察數據缺失值,異常值,分佈規律等。orm

# 導入數據集,合併訓練數據和測試數據
data_train = pd.read_csv('cs-training.csv')
data_test = pd.read_csv('cs-test.csv')
# df = data_train.append(data_test)


圖片

經過觀察,含有缺失值的特徵有:MonthlyIncome,NumberOfDependents兩個。爲了方便後面的使用,將特徵名稱修改爲短名稱。blog

columns = ({'SeriousDlqin2yrs':'IsDlq',
            'RevolvingUtilizationOfUnsecuredLines':'Revol',
           'NumberOfOpenCreditLinesAndLoans':'NumOpen',
           'NumberOfTimes90DaysLate':'Num90late',
           'NumberRealEstateLoansOrLines':'NumEstate',
           'NumberOfTime60-89DaysPastDueNotWorse':'Num60-89late',
           'NumberOfDependents':'NumDependents',
           'NumberOfTime30-59DaysPastDueNotWorse':'Num30-59late'}
          )


這樣,很是長的特徵名稱就便於咱們後續操做了。
圖片


好壞比


plt.figure(figsize=(8,5))
sns.countplot("IsDlq", data=data_train)
plt.show()

badNum = data_train.loc[data_train['IsDlq']==1].shape[0]
goodNum = data_train.loc[data_train['IsDlq']==0].shape[0]
print('訓練集中客戶好壞比爲:{0}%'.format(round(badNum*100/(goodNum+badNum),2)))


圖片

很明顯,數據不均衡,壞用戶只佔了6.68%,後續建模部分進行處理。


age特徵分佈


f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,8))
sns.distplot(data_train['age'],ax=ax1)
sns.boxplot(y='age',data=data_train,ax=ax2)
plt.show()


圖片

雖而後續會使用分箱以及woe方法(增長魯棒性,加強了對異常值干擾),仍是常規性的檢查一下異常值。

# 3倍標準差定義異常值
ageMean = np.mean(data_train['age'])
ageStd = np.std(data_train['age'])
ageUpLimit = round((ageMean + 3*ageStd),2)
ageDownLimit = round((ageMean - 3*ageStd),2)
print('年齡異常值上限爲:{0}, 下限爲:{1}'.format(ageUpLimit,ageDownLimit))

年齡異常值上限爲:96.61, 下限爲:7.98。

# 四分位距觀察異常值
agePercentile = np.percentile(data_train['age'],[0,25,50,75,100])
ageIQR = agePercentile[3] - agePercentile[1]
ageUpLimit = agePercentile[3]+ageIQR*1.5
ageDownLimit = agePercentile[1]-ageIQR*1.5
print('年齡異常值上限爲:{0}, 下限爲:{1}'.format(ageUpLimit,ageDownLimit))
print('上屆異常值佔比:{0} %'.format(data_train[data_train['age']>96].shape[0]*100/data_train.shape[0]))
print('下屆異常值佔比:{0} %'.format(data_train[data_train['age']<8].shape[0]*100/data_train.shape[0]))

年齡異常值上限爲:96.0, 下限爲:8.0,上屆異常值佔比:0.03 %,下屆異常值佔比:0.00067 %。


結論

  • 明顯觀察到有個0歲的客戶,這實際上不可能,至少要大於18歲成年之後才能夠貸款,故將之移除。

  • 而年齡大於96歲是有可能的,判斷是噪聲,並非異常值,由於大於等於96歲的客戶有98人,其中最大的年齡爲109。


再看一下age特徵對目標變量的影響,將age劃分爲幾個年齡段,而後繪製出各個年齡段的違約率。

圖片

結論:能夠看到年齡越大,好壞比越大,說明隨着年齡增大,違約的比例逐漸減小。這爲咱們後面woe分箱提供了參考,呈現了單調性。


Revol特徵


f,ax=plt.subplots(figsize=(10,5))
plt.scatter(data_train['Revol'], data_train['age'],color='g')
plt.show()


結論:這個特徵值是百分比。含義是:除了房貸車貸以外的信用卡帳面金額(即貸款金額)/信用卡總額度。實際上,這個特徵值大部分狀況是小於1的,由於超出額度屬於透支。可是咱們發現有不少特徵值已經達到了幾萬,這在實際中是不可能的。推測頗有多是沒有除以分母信用卡額度,而是分子的純信用卡帳面貸款金額。

咱們須要肯定的是透支的最大值是什麼?即透支多少算是正常值?數值多大能夠確認它是沒除以分母的異常值?

觀察一下Revol特徵各個分段下的分佈狀況。

圖片


觀察到現象:

  • 小於1的分佈中,大部分客戶都處於0.1的位置,而隨着Revol特徵值變大,數量成遞減趨勢。

  • 對於其它大於1的數值分佈,也都明顯的呈現了遞減趨勢。

  • 小於1的特徵值佔總數量的97%,大於1的數量爲5531。


下面來深刻研究一下大於1的特徵值對壞帳率有什麼影響,以及找到透支的閾值

圖片

經過上面觀察:Revol特徵值在10到100之間中,壞帳客戶的值多在10到20之間,而且其相應的DebtRatio也很高。而其餘Revol特徵值高(>20)的但DebtRadio低的並非壞帳客戶。所以,推測可能的異常值閾值(即透支的上限)在20-30左右。

下面咱們經過具體數據來肯定具體的閾值在哪。

圖片

根據觀察的現象,咱們能夠看到:

0-1之間的壞帳率爲5.99%。按理說,隨着比例升高,壞帳率也應該升高,尤爲是在透支的狀況下。在1-30區間內,已經屬於透支狀態,壞帳率39%,達到了最高。可是透支是不可能無限升高的,會有個閾值。 從30到100區間,壞帳率開始降低,壞帳率開始降低恢復正常,說明30左右的值(即3000%左右)可能就是正常透支的閾值。

所以,將數值超過30的都定義爲異常值,並將大於30的值與0-1之間合併。


NumDependents特徵


f,ax=plt.subplots(figsize=(10,5))
sns.countplot(x='NumDependents',hue='IsDlq',data=data_train,ax=ax)
plt.show()
print('Dependents爲0的機率爲:{0}%'.format(round(data_train[data_train['NumDependents']==0].shape[0]*100/data_train.shape[0],2)))


圖片

圖片

發現:NumDependents的缺失值爲6550個,而NumDependents和MonthlyIncome同時缺失的數量也是6550個。

結論:

  • 說明NumDependents缺失的樣本MonthlyIncome也缺失。

咱們想要經過找類似的方法來填補缺失的Dependents,由於有以上結論,因此咱們觀察一下MonthlyIncome缺失,但NumDependents不缺失的樣本是如何的。

圖片


MonthlyIncome特徵


f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,5))
sns.kdeplot(data_train['MonthlyIncome'],ax=ax1)
sns.boxplot(y='MonthlyIncome',data=data_train,ax=ax2)
plt.show()


圖片

Dependents缺失的樣本壞帳率爲:4.56%,Dependents不缺失的樣本壞帳率爲:6.74%。


因爲缺失值佔比達到近20%,直接刪除會損失數據信息,中位數/平均數進行大量填補效果並很差,這裏選擇隨機森林建模預測缺失值。


Num30-59 | 60-89 | 90 late特徵


f,[ax,ax1,ax2]=plt.subplots(1,3,figsize=(20,5))
# sns.boxplot(y=['Num30-59late','Num90late'],data=df,ax=ax)
ax.boxplot(x=data_train['Num30-59late'])
ax1.boxplot(x=data_train['Num60-89late'])
ax2.boxplot(x=data_train['Num90late'])
ax.set_xlabel('Num30-59late')
ax1.set_xlabel('Num60-89late')
ax2.set_xlabel('Num90late')
plt.show()


圖片

data_train.loc[(data_train['Num30-59late']>=8), 'Num30-59late'] = 8
Num30_59lateDlq = data_train.groupby(['Num30-59late'])['IsDlq'].sum()
Num30_59lateAll = data_train.groupby(['Num30-59late'])['IsDlq'].count()
Num30_59lateGroup = Num30_59lateDlq/Num30_59lateAll
Num30_59lateGroup.plot(kind='bar',figsize=(10,5))

Num30_59lateDf = pd.DataFrame(Num30_59lateDlq)
Num30_59lateDf['All'] = Num30_59lateAll
Num30_59lateDf['BadRate'] = Num30_59lateGroup
Num30_59lateDf

圖片

DebtRatio


同Revol使用的方法同樣,因爲存在大量的異常值,固也對其進行了分段來分析壞帳率的特色。這部分分箱的觀察分佈對於後續的woe計算轉化頗有幫助,固然這些特徵指標的分箱也要結合實際業務理解來劃分。


圖片

結論:將debtratio>2的都視爲異常值,並將這些異常值與0-1之間的debtratio分爲一組。


NumEstate特徵


f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,5))
sns.kdeplot(data_train['NumEstate'],ax=ax1)
sns.boxplot(y='NumEstate',data=data_train,ax=ax2)
plt.show()


圖片

看到大於50的值爲明顯異常值。


data_train.loc[(data_train['NumEstate']>=8), 'NumEstate'] = 8
NumEstateDlq = data_train.groupby(['NumEstate'])['IsDlq'].sum()
NumEstateAll = data_train.groupby(['NumEstate'])['IsDlq'].count()
NumEstateGroup = NumEstateDlq/NumEstateAll
NumEstateGroup.plot(kind='bar',figsize=(10,5))

NumEstateDf = pd.DataFrame(NumEstateDlq)
NumEstateDf['All'] = NumEstateAll
NumEstateDf['BadRate'] = NumEstateGroup
NumEstateDf

圖片

NumOpen特徵


data_train.loc[(data_train['NumOpen']>=36), 'NumOpen'] = 36
NumOpenDlq = data_train.groupby(['NumOpen'])['IsDlq'].sum()
NumOpenAll = data_train.groupby(['NumOpen'])['IsDlq'].count()
NumOpenGroup = NumOpenDlq/NumOpenAll
NumOpenGroup.plot(kind='bar',figsize=(10,5))

NumOpenDf = pd.DataFrame(NumOpenDlq)
NumOpenDf['All'] = NumOpenAll
NumOpenDf['BadRate'] = NumOpenGroup
NumOpenDf


圖片

3總結


因爲特徵數量比較少,因此對每一個特徵都進行了簡單的探索。固然這些這些都只是單變量分析,旨在初步瞭解特徵分佈特色和一些通用的規律。因爲內容較多固設置爲一篇介紹。

相關文章
相關標籤/搜索