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

這是 Python數據科學的第 53 篇原創文章

【作者】:xiaoyu

【介紹】:一個半路轉行的數據挖掘工程師

【知乎專欄】:https://zhuanlan.zhihu.com/pypcfx

全文2548字 | 閱讀需要10分鐘

1項目介紹

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

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

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

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

2數據探索

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

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

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

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總結

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

下一篇將介紹如何進行介紹:

1. 如何從做woe轉化 2. 利用iv值進行篩選變量 3. 變量是如何衍生的 4. 如何使用auc評估模型 5. 建模參數調節和樣本不均衡處理 6. 最後又是如何生成相應的評分卡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章