簡介
項目介紹: 使用 Kaggle 的 GiveMeSomeCredit 數(shù)據(jù)集, 建立邏輯回歸模型與評分卡唱矛, 對新申請信用貸款的客戶進行違約預(yù)測與信用評分,降低貸款用戶違約風(fēng)險槽驶。
負(fù)責(zé)內(nèi)容:1. 基于業(yè)務(wù)理解真竖,使用 python 結(jié)合 Matplotlib铡买、 Seaborn 庫對各變量進行統(tǒng)計分析與可視化延刘,完成數(shù)據(jù)異常匹层、缺失魂那、重復(fù)值清洗蛾号。
- 使用 python 實現(xiàn) 10 個特征變量的卡方分箱并得到對應(yīng)分箱 WOE 及 VI 值, 根據(jù)特征重要性最終篩選出 5 個進入模型的特征變量涯雅。
- 使用過采樣 SMOTE 方法處理數(shù)據(jù)不平衡問題鲜结,并建立邏輯回歸模型, 使用 AUC 與 KS 值判斷模型精確度活逆, 并預(yù)測測試集每條記錄的客戶標(biāo)簽精刷。
- 設(shè)置信用評分計算邏輯, 對測試集每位用戶進行信用打分蔗候。 拒絕低分用戶貸款申請怒允, 有效降低“壞客戶” 風(fēng)險。
一锈遥、業(yè)務(wù)理解
1. 研究背景
銀行或其他金融貸款機構(gòu)在信貸業(yè)務(wù)中扮演重要角色纫事,它會對貸款人或組織的貸款申請進行審批。這種審批決策建立在對貸款人未來財務(wù)狀況的評估所灸。信用評分卡常被運用于這種場景丽惶,幫助它們做出最好的決策。信用評分是指根據(jù)貸款客戶的各種歷史信用資料庆寺,利用一定的信用評分模型蚊夫,得到不同等級的信用分?jǐn)?shù),根據(jù)客戶的信用分?jǐn)?shù)懦尝,授信者可以通過分析客戶按時還款的可能性知纷,據(jù)此決定是否給予授信以及授信的額度和利率壤圃。如果申請人的信用評分大于等于金融放款機構(gòu)所設(shè)定的界限分?jǐn)?shù),此申請?zhí)幱诳山邮艿娘L(fēng)險水平并將被批準(zhǔn)琅轧;低于界限分?jǐn)?shù)的申請人將被拒絕或給予標(biāo)示以便進一步審查伍绳。
2. 研究目的與框架
本研究目的是建立邏輯回歸模型和信用評分卡模型,對測試集中貸款人未來是否會逾期進行預(yù)測并打分乍桂,幫助金融機構(gòu)更好的做審批決策冲杀。框架如下:
- 業(yè)務(wù)理解睹酌,項目背景权谁、研究目的及框架闡述。
- 數(shù)據(jù)探索性分析憋沿,對樣本進行描述性統(tǒng)計旺芽,并使用直方圖、箱形圖等描繪單變量分布形態(tài)辐啄,分析變量間相關(guān)性采章。
- 數(shù)據(jù)預(yù)處理,結(jié)合EDA情況進行數(shù)據(jù)清洗壶辜,包括重復(fù)值悯舟、缺失值和異常值處理。
- 分箱與變量選擇砸民,變量分箱抵怎,對應(yīng)WOE和VI值計算,并篩選對違約狀態(tài)影響最顯著的指標(biāo)進入模型阱洪。
- 模型建立與評價便贵,該步驟主要包括變量的WOE(證據(jù)權(quán)重)變換菠镇、樣本不均衡處理冗荸、邏輯回歸模型構(gòu)建和模型評估四部分。
- 測試集預(yù)測與信用評分卡建立利耍,包括預(yù)測測試集用戶違約情況并建立信用評分卡蚌本,最終對測試集用戶進行打分。
二隘梨、數(shù)據(jù)探索性分析(EDA)
1. 數(shù)據(jù)來源
數(shù)據(jù)來源于Kaggle的GiveMeSomeCredit比賽程癌,包括數(shù)據(jù)字典、訓(xùn)練集和測試集轴猎。其中訓(xùn)練集有15萬條的樣本數(shù)據(jù)嵌莉,測試集有10萬余條數(shù)據(jù)。
為方便后續(xù)處理捻脖,這里先將列名統(tǒng)一重命名為中文锐峭,并去除序號列中鼠。需要預(yù)測的是好壞客戶這個變量,其他變量均為自變量沿癞。
column={'Unnamed: 0':'用戶ID',
'SeriousDlqin2yrs':'好壞客戶',
'RevolvingUtilizationOfUnsecuredLines':'可用額度比',
'age':'年齡',
'NumberOfTime30-59DaysPastDueNotWorse':'逾期30-59天筆數(shù)',
'DebtRatio':'負(fù)債率',
'MonthlyIncome':'月收入',
'NumberOfOpenCreditLinesAndLoans':'信貸數(shù)量',
'NumberOfTimes90DaysLate':'逾期90天筆數(shù)',
'NumberRealEstateLoansOrLines':'固定資產(chǎn)貸款數(shù)量',
'NumberOfTime60-89DaysPastDueNotWorse':'逾期60-89天筆數(shù)',
'NumberOfDependents':'家屬數(shù)量'}
data.rename(columns=column,inplace=True)
#去除訓(xùn)練集中用戶ID列
data.drop(['用戶ID'],axis=1,inplace=True)
下表為整理后的數(shù)據(jù)字典描述:
2. 數(shù)據(jù)描述性統(tǒng)計
上圖可知援雇,訓(xùn)練集數(shù)據(jù)有150000行,均為數(shù)值變量椎扬,且月收入和家屬數(shù)量變量存在部分缺失惫搏。
其中月收入缺失約19.6%,家屬數(shù)量缺失約2.6%蚕涤。一般缺失值很少時可以選擇刪除缺失樣本筐赔。另外缺失值也可以選擇填充,包括根據(jù)樣本間相似性填充揖铜、變量間相關(guān)關(guān)系填充和虛擬變量填充川陆。最基本的填充法有均值、中位數(shù)蛮位、眾數(shù)填充较沪,也可以通過模型擬合等方法。具體填充方法后文數(shù)據(jù)預(yù)處理中詳述失仁。
缺失值處理方法參考 https://segmentfault.com/a/1190000015801384?utm_source=tag-newest
描述性統(tǒng)計發(fā)現(xiàn)尸曼,許多變量存在異常值,決定先對每個自變量進行單變量分析萄焦,觀察其特征分布情況控轿,方便特征工程選用合適方法對存在的異常值、缺失值進行處理拂封。
3. 單變量分析
(1) 好壞客戶
#EDA 客戶類型
plt.figure(figsize=(8,5))
cus_class=data['好壞客戶'].value_counts()
cus_class.plot('bar')
plt.title("好壞客戶分布直方圖")
plt.xlabel("好壞客戶")
plt.ylabel("頻數(shù)")
for x,y in zip(cus_class.index,cus_class):
plt.text(x, y, '%.0f' % y, ha='center', va= 'bottom',fontsize=10)
上圖中0為好客戶茬射,1為壞客戶。由直方圖可知冒签,好客戶有139974個在抛,而壞客戶僅10026個,約占6.6%萧恕,分布極其不平衡刚梭。不平衡數(shù)據(jù)會造成以總體分類準(zhǔn)確率為學(xué)習(xí)目標(biāo)的傳統(tǒng)分類算法過多地關(guān)注多數(shù)類,使少數(shù)類樣本的分類性能下降票唆。故建立邏輯回歸模型前需要進行過采樣或欠采樣處理朴读。
(2) 年齡
plt.figure(figsize=(8,5))
data_test_0=data.loc[data.好壞客戶 == 0]['年齡']
data_test_1=data.loc[data.好壞客戶 == 1]['年齡']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
上圖可以看出,年齡基本符合正態(tài)分布走趋,且整體年齡小相較年齡大違約風(fēng)險更大衅金。為進一步分析各年齡段的違約情況,接下來將年齡進行分箱并計算違約率。
age=data.loc[data['年齡']>0,['年齡','好壞客戶']]
age.loc[(data['年齡']<20),'年齡'] = '0-20'
age.loc[(data['年齡']>=20)&(data['年齡']<40),'年齡'] = '20-40'
age.loc[(data['年齡']>=40)&(data['年齡']<60),'年齡'] = '40-60'
age.loc[(data['年齡']>=60)&(data['年齡']<80),'年齡'] = '60-80'
age.loc[(data['年齡']>=80),'年齡'] = '80+'
age_bad=age.groupby('年齡')['好壞客戶'].sum()
age_total=age.groupby('年齡')['好壞客戶'].count()
age_ratio=age_bad/age_total
age_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖可知氮唯,0-20歲缺少數(shù)據(jù)酥宴,整體分析結(jié)果基本與前面一致,客戶層年齡越大您觉,違約率越低拙寡。違約率最大的年齡組為20-40歲,最小的年齡組為80+歲琳水。
(3) 月收入
a=data['月收入'].mean()+3*data['月收入'].std()
b=data['月收入'].mean()-3*data['月收入'].std()
plt.figure(figsize=(8,5))
data_test_0=data.loc[(data['月收入']<=a) & (data['月收入']>=b)][data.好壞客戶 == 0]['月收入']
data_test_1=data.loc[(data['月收入']<=a) & (data['月收入']>=b)][data.好壞客戶 == 1]['月收入']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
由于月收入被極值影響嚴(yán)重肆糕,故先排除3倍標(biāo)準(zhǔn)差的極值再畫圖。如圖在孝,月收入基本符合正態(tài)分布诚啃,滿足統(tǒng)計分析前提。月收入主要集中在0-20000以內(nèi)私沮,但是也有少數(shù)極高收入的借款人始赎。另外可以發(fā)現(xiàn),月收入低相較月收入高的客戶仔燕,違約風(fēng)險更大造垛。為進一步分析各收入段的違約情況,接下來將月收入進行分箱并計算違約率晰搀。
income=data.loc[data['月收入']>0,['月收入','好壞客戶']]
income.loc[(data['月收入']<500),'月收入'] = '1'
income.loc[(data['月收入']>=500)&(data['月收入']<1000),'月收入'] = '2'
income.loc[(data['月收入']>=1000)&(data['月收入']<5000),'月收入'] = '3'
income.loc[(data['月收入']>=5000)&(data['月收入']<10000),'月收入'] = '4'
income.loc[(data['月收入']>=10000)&(data['月收入']<20000),'月收入'] = '5'
income.loc[(data['月收入']>=20000),'月收入'] = '6'
income_bad=income.groupby('月收入')['好壞客戶'].sum()
income_total=income.groupby('月收入')['好壞客戶'].count()
income_ratio=income_bad/income_total
income_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖顯示月收入在1000-5000的人群違約率最高五辽,接著是收入在500-1000和5000-10000群體,違約比例均超出0.06外恕。
(4) 可用額度比
對可用額度比整體進行畫圖杆逗,發(fā)現(xiàn)該變量極差很大,最大值達到50000以上鳞疲。從業(yè)務(wù)邏輯出發(fā)罪郊,可用額度比值應(yīng)該處于0-1之間,但根據(jù)散點圖發(fā)現(xiàn)也有許多大于1的數(shù)字尚洽,異常值較多悔橄,猜測部分極大值可能是沒有除以可用額度的值,是純可貸款金額翎朱。由于這些異常值可能會影響分析橄维,接下來分為<=1 和 >1 兩個部分畫圖分析尺铣。
plt.figure(figsize=(8,5))
data_test_0=data.loc[data['可用額度比']<=1][data.好壞客戶 == 0]['可用額度比']
data_test_1=data.loc[data['可用額度比']<=1][data.好壞客戶 == 1]['可用額度比']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
plt.figure(figsize=(8,5))
data_test_0=data.loc[data['可用額度比']>1][data.好壞客戶 == 0]['可用額度比']
data_test_1=data.loc[data['可用額度比']>1][data.好壞客戶 == 1]['可用額度比']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
可以發(fā)現(xiàn)在可用額度比小于等于1的情況下拴曲,當(dāng)可用額度比小于0.4時,違約概率較辛莘蕖澈灼;當(dāng)其大于0.4時,可用額度比越大,違約概率越高叁熔;而當(dāng)可用額度比大于1時委乌,由于數(shù)值分布過于分散,看不出明顯規(guī)律荣回。下面將可用額度比再進行細化分箱遭贸。
bins=[0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
data['可用額度分箱']=pd.cut(data['可用額度比'],bins,right=True)
#違約率
revol_bad=data.groupby(['可用額度分箱'])['好壞客戶'].sum()
revol_total=data.groupby(['可用額度分箱'])['好壞客戶'].count()
revol_ratio=revol_bad/revol_total
revol_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖可看出可用額度比<=1時,違約率與可用額度比正相關(guān)心软『敬担可用額度比越高違約率越大。
bins=[1,10,100,1000,10000,60000]
data['可用額度分箱']=pd.cut(data['可用額度比'],bins,right=True)
#違約率
revol_bad=data.groupby(['可用額度分箱'])['好壞客戶'].sum()
revol_total=data.groupby(['可用額度分箱'])['好壞客戶'].count()
revol_ratio=revol_bad/revol_total
revol_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖可看出可用額度比>1時删铃,1-10和10-100兩個區(qū)間的違約均大于0.3耳贬,大于可用額度比小于1的違約率×匝洌可用額度比[0,10]區(qū)間基本符合上述可用額度比越大咒劲,違約風(fēng)險越大的分析。
a=data.loc[(data['可用額度比']>1) & (data['可用額度比']<=10)]['可用額度比'].count()
b=data.loc[(data['可用額度比']>1) & (data['可用額度比']<=70000)]['可用額度比'].count()
a/b
計算發(fā)現(xiàn)可用額度比大于1小于等于10的變量約占所有大于1數(shù)據(jù)的93%诫隅,過大的值可能是不具有代表性的異常值腐魂。
(5) 負(fù)債率
從業(yè)務(wù)邏輯出發(fā),負(fù)債率應(yīng)該處于0-1之間逐纬,但根據(jù)散點圖發(fā)現(xiàn)也有許多大于1的數(shù)字挤渔,異常值較多。由于這些異常值可能會影響分析风题,接下來分為<=1 和 >1 兩個部分畫圖分析判导。
plt.figure(figsize=(8,5))
data_test_0=data.loc[data['負(fù)債率']<=1][data.好壞客戶 == 0]['負(fù)債率']
data_test_1=data.loc[data['負(fù)債率']<=1][data.好壞客戶 == 1]['負(fù)債率']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
plt.show()
sns.boxplot(y=data['負(fù)債率'])
當(dāng)負(fù)債率小于等于1的時候,好客戶更多集中于0-0.4之間沛硅,而壞客戶分布較為平均且擁有長尾屬性眼刃,負(fù)債率大于0.45后,違約風(fēng)險較大摇肌。
plt.figure(figsize=(8,5))
data_test_0=data.loc[data['負(fù)債率']>1][data.好壞客戶 == 0]['負(fù)債率']
data_test_1=data.loc[data['負(fù)債率']>1][data.好壞客戶 == 1]['負(fù)債率']
sns.distplot(data_test_0.dropna(),color='g')
sns.distplot(data_test_1.dropna(),color='r')
plt.legend(['0','1'])
當(dāng)負(fù)債率大于1的時候擂红,出現(xiàn)壞客戶的概率遠大于好客戶。另外围小,負(fù)債率存在極大值昵骤。
bins=[0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
data['負(fù)債率分箱']=pd.cut(data['負(fù)債率'],bins,right=True)
#違約率
debt_bad=data.groupby(['負(fù)債率分箱'])['好壞客戶'].sum()
debt_total=data.groupby(['負(fù)債率分箱'])['好壞客戶'].count()
debt_ratio=debt_bad/debt_total
debt_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖可看出負(fù)債率<=1時,負(fù)債率與違約率正相關(guān)趨勢肯适。負(fù)債率越高違約率越大变秦。
bins=[1,5000,10000,20000,40000]
data['負(fù)債率分箱']=pd.cut(data['負(fù)債率'],bins,right=True)
#違約率
debt_bad=data.groupby(['負(fù)債率分箱'])['好壞客戶'].sum()
debt_total=data.groupby(['負(fù)債率分箱'])['好壞客戶'].count()
debt_ratio=debt_bad/debt_total
debt_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖可看出負(fù)債率>1時,基本符合負(fù)債率和違約率正相關(guān)的分析框舔。
(6) 逾期筆數(shù)(逾期30-59天筆數(shù)蹦玫、逾期60-89天筆數(shù)赎婚、逾期90天筆數(shù))
#逾期筆數(shù)
list=['逾期30-59天筆數(shù)','逾期60-89天筆數(shù)','逾期90天筆數(shù)']
#a=[ax1,ax2,ax3]
b=0
fig,a=plt.subplots(2,3,figsize=(15,8))
for i in list:
pvt=pd.pivot_table(data[['好壞客戶',i]], index=i, columns='好壞客戶', aggfunc=len)
pvt.plot(kind='bar',ax=a[0,b])
sns.boxplot(y=data[i],ax=a[1,b])
b=b+1
由箱線圖可以看出,三個逾期筆數(shù)均存在異常值樱溉,使用value_counts函數(shù)得知為96和98兩個異常值挣输,后續(xù)應(yīng)剔除。
#將大于40的數(shù)據(jù)和40合并后看一下違約率的情況
Num_bad=data.groupby(['逾期30-59天筆數(shù)'])['好壞客戶'].sum()
Num_total=data.groupby(['逾期30-59天筆數(shù)'])['好壞客戶'].count()
Num_ratio=Num_bad/Num_total
Num_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
Num_bad=data.groupby(['逾期60-89天筆數(shù)'])['好壞客戶'].sum()
Num_total=data.groupby(['逾期60-89天筆數(shù)'])['好壞客戶'].count()
Num_ratio=Num_bad/Num_total
Num_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
Num_bad=data.groupby(['逾期90天筆數(shù)'])['好壞客戶'].sum()
Num_total=data.groupby(['逾期90天筆數(shù)'])['好壞客戶'].count()
Num_ratio=Num_bad/Num_total
Num_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
部分區(qū)間逾期筆數(shù)與違約率有正相關(guān)關(guān)系福贞。
(7) 信貸數(shù)量撩嚼、固定資產(chǎn)貸款數(shù)量
#資貸數(shù)量
list=['信貸數(shù)量','固定資產(chǎn)貸款數(shù)量']
b=0
fig,a=plt.subplots(2,2,figsize=(15,8))
for i in list:
pvt=pd.pivot_table(data[['好壞客戶',i]], index=i, columns='好壞客戶', aggfunc=len)
pvt.plot(kind='bar',ax=a[0,b])
sns.boxplot(y=data[i],ax=a[1,b])
b=b+1
將信貸數(shù)量大于40的數(shù)據(jù)和40合并后看一下違約率的情況。
data.loc[data['信貸數(shù)量']>40,'信貸數(shù)量']=40
Num_bad=data.groupby(['信貸數(shù)量'])['好壞客戶'].sum()
Num_total=data.groupby(['信貸數(shù)量'])['好壞客戶'].count()
Num_ratio=Num_bad/Num_total
Num_ratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
信貸數(shù)量與違約率無明顯相關(guān)性挖帘。下面將固定資產(chǎn)貸款數(shù)量大于8的數(shù)據(jù)和8合并后看一下違約率的情況绢馍。
data.loc[data['固定資產(chǎn)貸款數(shù)量']>8,'固定資產(chǎn)貸款數(shù)量']=8
Num_bad=data.groupby(['固定資產(chǎn)貸款數(shù)量'])['好壞客戶'].sum()
Num_total=data.groupby(['固定資產(chǎn)貸款數(shù)量'])['好壞客戶'].count()
Num_ratio=Num_bad/Num_total
Num_ratio.plot(kind='bar',figsize=(8,6),color='#4682B4')
由圖可知,固定資產(chǎn)貸款數(shù)量從1開始肠套,違約率隨著固定資產(chǎn)貸款數(shù)量增大而增大舰涌。
(8) 家屬數(shù)量
plt.figure(figsize=(8,5))
cus_class=data['家屬數(shù)量'].value_counts()
cus_class.plot('bar',color='#4682B4')
plt.title("家屬數(shù)量分布直方圖")
plt.xlabel("家屬數(shù)量")
plt.ylabel("頻數(shù)")
for x,y in zip(cus_class.index,cus_class):
plt.text(x, y, '%.0f' % y, ha='center', va= 'bottom',fontsize=10)
plt.show()
plt.figure(figsize=(8,5))
sns.boxplot(y=data['家屬數(shù)量'])
將大于8的數(shù)據(jù)和8合并后看一下家屬數(shù)量的情況。
Numestate_dlq=data.groupby(['家屬數(shù)量'])['好壞客戶'].sum()
Numestate_total=data.groupby(['家屬數(shù)量'])['好壞客戶'].count()
Numestate_dlqratio=Numestate_dlq/Numestate_total
Numestate_dlqratio.plot(kind='bar',figsize=(8,5),color='#4682B4')
上圖所示你稚,家屬數(shù)量為6時違約率最高瓷耙,超過14%。但家屬數(shù)量與違約率并無顯著相關(guān)性刁赖。
三搁痛、數(shù)據(jù)預(yù)處理
1. 重復(fù)值處理
#去除訓(xùn)練集中的完全重復(fù)行
data.drop_duplicates(keep='first',inplace=True)
print("預(yù)處理后訓(xùn)練集大小 : {} ".format(data.shape))
由于訓(xùn)練集數(shù)據(jù)量較大,難免存在重復(fù)數(shù)據(jù)宇弛,為保證數(shù)據(jù)質(zhì)量鸡典,需要將訓(xùn)練集中完全重復(fù)的行數(shù)據(jù)去除。
通過上述預(yù)處理后枪芒,訓(xùn)練集長度變?yōu)?49391條彻况,列數(shù)變?yōu)?1列。
2. 缺失值處理
在前文描述性統(tǒng)計部分中舅踪,我們發(fā)現(xiàn)月收入缺失約19.6%纽甘,家屬數(shù)量缺失約2.6%。
data['家屬數(shù)量'].fillna(data['家屬數(shù)量'].median(),inplace=True) # 填充中位數(shù)
由于“家屬數(shù)量”缺失比例較少抽碌,這里選擇直接用中位數(shù)進行填充悍赢。而月收入變量缺失較多,且根據(jù)業(yè)務(wù)邏輯推測此變量對因變量影響較大货徙,這里選用隨機森林填補法來填充左权,即將缺失的特征值作為預(yù)測值,將未缺失的“月收入”數(shù)據(jù)作為訓(xùn)練樣本的標(biāo)簽痴颊。
#隨機森林預(yù)測缺失值
data_Forest=data.iloc[:,[5,0,1,2,3,4,6,7,8,9,10]]
MonthlyIncome_isnull=data_Forest.loc[data['月收入'].isnull(),:]
MonthlyIncome_notnull=data_Forest.loc[data['月收入'].notnull(),:]
from sklearn.ensemble import RandomForestRegressor
X=MonthlyIncome_notnull.iloc[:,1:].values
y=MonthlyIncome_notnull.iloc[:,0].values
regr=RandomForestRegressor(random_state=0,n_estimators=200,n_jobs=-1)
regr.fit(X,y)
MonthlyIncome_fillvalue=regr.predict(MonthlyIncome_isnull.iloc[:,1:].values).round(0)
#填充缺失值
data.loc[data['月收入'].isnull(),'月收入']=MonthlyIncome_fillvalue
3. 異常值處理
由value_counts函數(shù)可知赏迟,年齡變量存在等于0的異常值,這里進行刪除祷舀。另外瀑梗,通過箱線圖烹笔,發(fā)現(xiàn)逾期30-59天筆數(shù)裳扯、逾期60-80天筆數(shù)抛丽、逾期90天筆數(shù),這三個變量均存在嚴(yán)重的離群值饰豺,應(yīng)進行異常值處理亿鲜。
data=data[data['年齡']>0]
data=data[data['逾期30-59天筆數(shù)']<80]
data=data[data['逾期60-89天筆數(shù)']<80]
data=data[data['逾期90天筆數(shù)']<80]
4. 變量相關(guān)性分析
由于變量間共線性問題在部分機器學(xué)習(xí)模型中會顯著影響模型性能,需針對選擇的模型進行特殊處理冤吨,故這里進行相關(guān)性分析檢驗蒿柳。
fig = plt.figure(figsize = (8,5))
sns.heatmap(data.iloc[:,:11].corr(),annot=True, cmap='YlGnBu', annot_kws={'size': 9})
上圖為各自變量的相關(guān)系數(shù)矩陣,顏色越深表明兩變量相關(guān)性越高漩蟆。由圖可知垒探,除對角線區(qū)域,大多數(shù)區(qū)域顏色較淺怠李,未有變量相關(guān)性超過0.5圾叼,基本不存在變量間共線性問題。
四捺癞、分箱與變量選擇
1. 特征變量標(biāo)準(zhǔn)化
本文計劃使用邏輯回歸模型夷蚊,其中數(shù)值型變量數(shù)據(jù)需要進行歸一化處理使數(shù)值在同一個數(shù)量級上,保證邏輯回歸的收斂速度髓介。但由于后文會使用WOE轉(zhuǎn)換惕鼓,故這里不另外進行標(biāo)準(zhǔn)化處理。
2. 特征離散化(分箱)
由于本文選擇用邏輯回歸建立評分卡模型唐础,連續(xù)變量需要進行離散化箱歧,該處理會使模型更穩(wěn)定,降低過擬合的風(fēng)險一膨。首先將連續(xù)變量進行離散化分箱處理叫胁,通過比較指標(biāo)分箱后的WOE與VI值進一步確定指標(biāo)對因變量的貢獻度,選取貢獻高的變量引入后續(xù)模型汞幢。
常用的分箱方法包括無監(jiān)督分箱(等距分箱驼鹅、等頻分箱和聚類分箱)和有監(jiān)督分箱(卡方分箱和best-ks分箱)。下文將采用卡方分箱法森篷∈涔常卡方分箱是依賴于卡方檢驗的分箱方法,在統(tǒng)計指標(biāo)上選擇卡方統(tǒng)計量(chi-Square)進行判別仲智,分箱的基本思想是判斷相鄰的兩個區(qū)間是否有分布差異买乃,基于卡方統(tǒng)計量的結(jié)果進行自下而上的合并,直到滿足分箱的限制條件為止钓辆。
temp = data[['年齡','好壞客戶']]
# 定義一個卡方分箱(可設(shè)置參數(shù)置信度水平與箱的個數(shù))停止條件為大于置信水平且小于bin的數(shù)目
def ChiMerge(df, variable, flag, confidenceVal=3.841, bin=10, sample = None):
'''
運行前需要 import pandas as pd 和 import numpy as np
df:傳入一個數(shù)據(jù)框僅包含一個需要卡方分箱的變量與正負(fù)樣本標(biāo)識(正樣本為1剪验,負(fù)樣本為0)
variable:需要卡方分箱的變量名稱(字符串)
flag:正負(fù)樣本標(biāo)識的名稱(字符串)
confidenceVal:置信度水平(默認(rèn)是不進行抽樣95%)
bin:最多箱的數(shù)目
sample: 為抽樣的數(shù)目(默認(rèn)是不進行抽樣)肴焊,因為如果觀測值過多運行會較慢
'''
#進行是否抽樣操作
if sample != None:
df = df.sample(n=sample)
else:
df
#進行數(shù)據(jù)格式化錄入
total_num = df.groupby([variable])[flag].count() # 統(tǒng)計需分箱變量每個值數(shù)目
total_num = pd.DataFrame({'total_num': total_num}) # 創(chuàng)建一個數(shù)據(jù)框保存之前的結(jié)果
positive_class = df.groupby([variable])[flag].sum() # 統(tǒng)計需分箱變量每個值正樣本數(shù)
positive_class = pd.DataFrame({'positive_class': positive_class}) # 創(chuàng)建一個數(shù)據(jù)框保存之前的結(jié)果
regroup = pd.merge(total_num, positive_class, left_index=True, right_index=True,
how='inner') # 組合total_num與positive_class
regroup.reset_index(inplace=True)
regroup['negative_class'] = regroup['total_num'] - regroup['positive_class'] # 統(tǒng)計需分箱變量每個值負(fù)樣本數(shù)
regroup = regroup.drop('total_num', axis=1)
np_regroup = np.array(regroup) # 把數(shù)據(jù)框轉(zhuǎn)化為numpy(提高運行效率),每行為年齡,壞客戶數(shù)功戚,好客戶數(shù)
print('已完成數(shù)據(jù)讀入,正在計算數(shù)據(jù)初處理')
#處理連續(xù)沒有正樣本或負(fù)樣本的區(qū)間娶眷,并進行區(qū)間的合并(以免卡方值計算報錯)
i = 0
while (i <= np_regroup.shape[0] - 2): #np_regroup.shape[0]行數(shù)
if ((np_regroup[i, 1] == 0 and np_regroup[i + 1, 1] == 0) or ( np_regroup[i, 2] == 0 and np_regroup[i + 1, 2] == 0)):
np_regroup[i, 1] = np_regroup[i, 1] + np_regroup[i + 1, 1] # 正樣本
np_regroup[i, 2] = np_regroup[i, 2] + np_regroup[i + 1, 2] # 負(fù)樣本
np_regroup[i, 0] = np_regroup[i + 1, 0]
np_regroup = np.delete(np_regroup, i + 1, 0)
i = i - 1
i = i + 1
#對相鄰兩個區(qū)間進行卡方值計算
chi_table = np.array([]) # 創(chuàng)建一個數(shù)組保存相鄰兩個區(qū)間的卡方值
for i in np.arange(np_regroup.shape[0] - 1):
chi = (np_regroup[i, 1] * np_regroup[i + 1, 2] - np_regroup[i, 2] * np_regroup[i + 1, 1]) ** 2 \
* (np_regroup[i, 1] + np_regroup[i, 2] + np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) / \
((np_regroup[i, 1] + np_regroup[i, 2]) * (np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) * (
np_regroup[i, 1] + np_regroup[i + 1, 1]) * (np_regroup[i, 2] + np_regroup[i + 1, 2]))
chi_table = np.append(chi_table, chi)
print('已完成數(shù)據(jù)初處理,正在進行卡方分箱核心操作')
#把卡方值最小的兩個區(qū)間進行合并(卡方分箱核心)
while (1):
if (len(chi_table) <= (bin - 1) and min(chi_table) >= confidenceVal):
break
chi_min_index = np.argwhere(chi_table == min(chi_table))[0] # 找出卡方值最小的位置索引
np_regroup[chi_min_index, 1] = np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]
np_regroup[chi_min_index, 2] = np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]
np_regroup[chi_min_index, 0] = np_regroup[chi_min_index + 1, 0]
np_regroup = np.delete(np_regroup, chi_min_index + 1, 0)
if (chi_min_index == np_regroup.shape[0] - 1): # 最小值試最后兩個區(qū)間的時候
# 計算合并后當(dāng)前區(qū)間與前一個區(qū)間的卡方值并替換
chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2 \
* (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) / \
((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))
# 刪除替換前的卡方值
chi_table = np.delete(chi_table, chi_min_index, axis=0)
else:
# 計算合并后當(dāng)前區(qū)間與前一個區(qū)間的卡方值并替換
chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2 \
* (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) / \
((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))
# 計算合并后當(dāng)前區(qū)間與后一個區(qū)間的卡方值并替換
chi_table[chi_min_index] = (np_regroup[chi_min_index, 1] * np_regroup[chi_min_index + 1, 2] - np_regroup[chi_min_index, 2] * np_regroup[chi_min_index + 1, 1]) ** 2 \
* (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) / \
((np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]) * (np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]))
# 刪除替換前的卡方值
chi_table = np.delete(chi_table, chi_min_index + 1, axis=0)
print('已完成卡方分箱核心操作啸臀,正在保存結(jié)果')
#把結(jié)果保存成一個數(shù)據(jù)框
result_data = pd.DataFrame() # 創(chuàng)建一個保存結(jié)果的數(shù)據(jù)框
result_data['variable'] = [variable] * np_regroup.shape[0] # 結(jié)果表第一列:變量名
list_temp = []
for i in np.arange(np_regroup.shape[0]):
if i == 0:
x = '0' + ',' + str(np_regroup[i, 0])
elif i == np_regroup.shape[0] - 1:
x = str(np_regroup[i - 1, 0]) + '+'
else:
x = str(np_regroup[i - 1, 0]) + ',' + str(np_regroup[i, 0])
list_temp.append(x)
result_data['interval'] = list_temp # 結(jié)果表第二列:區(qū)間
result_data['good_count'] = np_regroup[:, 2] # 結(jié)果表第三列:負(fù)樣本數(shù)目
result_data['bad_count'] = np_regroup[:, 1] # 結(jié)果表第四列:正樣本數(shù)目
bad_total=result_data['bad_count'].sum()
good_total=result_data['good_count'].sum()
result_data['woe']=np.log((result_data['bad_count']/bad_total)/(result_data['good_count']/good_total)) # 結(jié)果表第五列:WOE值
result_data['VI']=((result_data['bad_count']/bad_total)-(result_data['good_count']/good_total))*result_data['woe']
return result_data
#調(diào)用參數(shù)并保存結(jié)果到集合
bins = ChiMerge(temp, '年齡','好壞客戶', confidenceVal=3.841, bin=10,sample=None)
print(bins)
vi=bins['VI'].sum()
print('%s特征的VI值為%.4f' %(temp.columns[0],vi))
vi_total={}
vi_total[temp.columns[0]] = vi
以年齡特征為例届宠,分箱結(jié)果如下:
接著對其余連續(xù)性變量同樣進行卡方分箱,由于特征較多而處理方式一致乘粒,這里省略其他特征的具體分箱結(jié)果豌注。
3. WOE和IV值計算與分析
由于邏輯回歸模型需要數(shù)值作為變量輸入,接下來需要計算分箱對應(yīng)WOE值灯萍。WOE越大轧铁,代表這個變量對“壞客戶“標(biāo)簽的貢獻度越大。
IV(Information Value)是與WOE密切相關(guān)的一個指標(biāo)旦棉,常用來評估變量的預(yù)測能力齿风,可以結(jié)合其結(jié)果篩選變量。在應(yīng)用實踐中他爸,其評價標(biāo)準(zhǔn)如下:
這里依舊以年齡變量為例聂宾,年齡特征的VI值為0.2589。接著對其余連續(xù)性變量同樣進行卡方分箱诊笤,并得到對應(yīng)WOE和IV值系谐。由于特征較多而處理方式一致,這里省略其他特征的具體結(jié)果讨跟。
4. 特征變量選擇
最終我們將VI值進行整理纪他,得到各特征VI值條形圖。
def draw_from_dict(dicdata,RANGE):
by_value = sorted(dicdata.items(),key = lambda item:item[1],reverse=True)
print(by_value)
x = []
y = []
for d in by_value:
x.append(d[0])
y.append(d[1])
plt.yticks(np.arange(len(x)),x)
plt.barh(np.arange(len(x)), y[0:RANGE])
plt.show()
return
a=draw_from_dict(vi_total,len(vi_total))
根據(jù)VI預(yù)測效果表晾匠,選取VI>0.1的變量進入邏輯回歸模型茶袒,包括可用額度比、逾期60-89天筆數(shù)凉馆、逾期30-59天筆數(shù)薪寓、逾期90天筆數(shù)和年齡。
五澜共、 模型建立與評價
1. WOE特征變換
邏輯回歸模型基于廣義線性回歸模型向叉,求參需要用到梯度下降法,為了加快迭代速度嗦董,不同特征的變化范圍規(guī)模相差不宜過大母谎,如果用數(shù)值直接帶入邏輯回歸模型,必須進行變量縮放京革。本文對變量進行WOE處理會將數(shù)值變量進行分箱奇唤,可以達到相似效果幸斥。將分箱后求得的WOE值替換對應(yīng)數(shù)值,方便后續(xù)建立邏輯回歸與評分模型咬扇。
2. 樣本均衡處理
原訓(xùn)練集正樣本占比93.38%甲葬,負(fù)樣本6.62%,樣本嚴(yán)重不平衡冗栗。這里使用SMOTE方法進行過采樣處理演顾,處理后樣本共278582條供搀,正負(fù)各占50%隅居。
3. 模型構(gòu)建
本文使用邏輯回歸模型
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
train_x,test_x,train_y,test_y = train_test_split(x,y,test_size = 0.3,random_state = 0)
train = pd.concat([train_y,train_x], axis =1)
test = pd.concat([test_y,test_x], axis =1)
train = train.reset_index(drop=True)
test = test.reset_index(drop=True)
lr = LogisticRegression(penalty= 'l1',solver='liblinear')
lr.fit(train_x,train_y)
4. 模型評估(ROC、AUC與KS評估)
AUC是一個衡量評分卡區(qū)分能力的量化指標(biāo)葛虐,AUC面積越大胎源,表示模型區(qū)分能力越強,在衡量一個模型是否有效的時候屿脐,AUC至少需要大于0.5涕蚤。KS值是一個衡量好壞客戶分?jǐn)?shù)距離的上限值,具體做法為將對于各個分?jǐn)?shù)區(qū)間對應(yīng)的好壞客戶累計占比進行相減的诵,取最大值万栅。好壞客戶之間的距離越大,k-s指標(biāo)越高西疤,模型的區(qū)分能力越強烦粒。
#繪制roc曲線
from sklearn.metrics import roc_curve, auc
y_pred= lr.predict(train_x)
train_predprob = lr.predict_proba(train_x)[:,1]
test_predprob = lr.predict_proba(test_x)[:,1]
FPR,TPR,threshold =roc_curve(test_y,test_predprob)
ROC_AUC= auc(FPR,TPR)
plt.plot(FPR, TPR, 'b', label='AUC = %0.2f' % ROC_AUC)
plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], 'r--')
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.ylabel('TPR')
plt.xlabel('FPR')
plt.show()
ks=max(TPR-FPR).round(2)
print("auc:{} ks:{}".format(ROC_AUC.round(2),ks))
AUC值約為0.84,說明該模型的擬合效果較好代赁。KS值約為0.52扰她,一般KS>0.3即可認(rèn)為模型有比較好的預(yù)測準(zhǔn)確性“虐總體來說模型效果不錯徒役。
六、測試集預(yù)測與信用評分卡建立
1. 導(dǎo)入測試集及數(shù)據(jù)預(yù)處理
f=open('F:\GiveMeSomeCredit\cs-test.csv')
data2=pd.read_csv(f)
column={'Unnamed: 0':'用戶ID',
'SeriousDlqin2yrs':'好壞客戶',
'RevolvingUtilizationOfUnsecuredLines':'可用額度比',
'age':'年齡',
'NumberOfTime30-59DaysPastDueNotWorse':'逾期30-59天筆數(shù)',
'DebtRatio':'負(fù)債率',
'MonthlyIncome':'月收入',
'NumberOfOpenCreditLinesAndLoans':'信貸數(shù)量',
'NumberOfTimes90DaysLate':'逾期90天筆數(shù)',
'NumberRealEstateLoansOrLines':'固定資產(chǎn)貸款數(shù)量',
'NumberOfTime60-89DaysPastDueNotWorse':'逾期60-89天筆數(shù)',
'NumberOfDependents':'家屬數(shù)量'}
data2.rename(columns=column,inplace=True)
#去除訓(xùn)練集中用戶ID列
data2.drop(['用戶ID'],axis=1,inplace=True)
將測試集導(dǎo)入窖壕,并進行與前文訓(xùn)練集一樣的數(shù)據(jù)預(yù)處理忧勿,包括缺失值填補,異常值刪除瞻讽,重復(fù)值刪除等鸳吸。
2. 分箱及WOE轉(zhuǎn)換
3. 好壞客戶預(yù)測
基于前文建立的模型給出測試集預(yù)測結(jié)果。
final_test_predict=data_test.reset_index().drop('index',axis=1).copy()
final_test_predict=final_test_predict.iloc[:,[1,2,3,4,5]]
y_pred= lr.predict(final_test_predict)
final_test_predict['好壞客戶']=pd.DataFrame(y_pred)
final_test_predict
4. 評分卡建立
在建立標(biāo)準(zhǔn)評分卡之前卸夕,需要選取幾個評分卡參數(shù):基礎(chǔ)分值盆昙、 PDO(Point-to-Double Odds,好壞比每升高一倍放刨,分?jǐn)?shù)升高PDO個單位)和好壞比。 這里取600分為基礎(chǔ)分值廉白,PDO為20 (每高20分好壞比翻一倍),好壞比取20乖寒。
# 個人總分=基礎(chǔ)分+各部分得分
import math
B = 20 / math.log(2)
A = 600 - B / math.log(20)
# 基礎(chǔ)分
base = round(A+B *lr.intercept_[0], 0)
base
下面計算各變量部分的分?jǐn)?shù)猴蹂。各部分得分函數(shù):
#計算分?jǐn)?shù)函數(shù)
def compute_score(coe,woe,factor):
scores=[]
for w in woe:
score=round(-coe*w*factor,0)
scores.append(score)
return scores
x1_revol = compute_score(lr.coef_[0][0], woe_revol, B)
x2_age = compute_score(lr.coef_[0][1], woe_age, B)
x3_30 = compute_score(lr.coef_[0][2], woe_30, B)
x4_90 = compute_score(lr.coef_[0][3], woe_60, B)
x5_60 = compute_score(lr.coef_[0][4], woe_90, B)
得到每個變量不同區(qū)間的對應(yīng)分?jǐn)?shù)。
得到訓(xùn)練集每個用戶的最終信用評分楣嘁。
#根據(jù)變量計算分?jǐn)?shù)
def change_score(series,cut,score):
list = []
i = 0
while i < len(series):
value = series[i]
j = len(cut) - 2
m = len(cut) - 2
while j >= 0:
if value >= cut[j]:
j = -1
else:
j -= 1
m -= 1
list.append(score[m])
i += 1
return list
test1=data2.reset_index().drop('index',axis=1).copy()
test1=test1.iloc[:,[1,2,3,9,7]]
#計算test里面的分?jǐn)?shù)
test1['x1_可用額度比'] = pd.Series(change_score(test1['可用額度比'], cutx1, x1_revol))
test1['x2_年齡'] = pd.Series(change_score(test1['年齡'], cutx2, x2_age))
test1['x3_逾期30-59天筆數(shù)'] = pd.Series(change_score(test1['逾期30-59天筆數(shù)'], cutx3, x3_30))
test1['x4_逾期60-89天筆數(shù)'] = pd.Series(change_score(test1['逾期60-89天筆數(shù)'], cutx4, x4_60))
test1['x5_逾期90天筆數(shù)'] = pd.Series(change_score(test1['逾期90天筆數(shù)'], cutx5, x5_90))
test1['Score'] =test1['x1_可用額度比'] + test1['x2_年齡'] + test1['x3_逾期30-59天筆數(shù)'] + test1['x4_逾期60-89天筆數(shù)'] + test1['x5_逾期90天筆數(shù)'] + base
test1.to_csv('F:\ScoreData.csv', index=False)
這里得分越低的用戶磅轻,違約風(fēng)險越大。評分卡建立后逐虚,應(yīng)通過設(shè)定cut-off準(zhǔn)入分?jǐn)?shù)將客群劃分為不同等級聋溜。這里的準(zhǔn)入分?jǐn)?shù)可以通過通過率和壞賬率評估模型在業(yè)務(wù)中的表現(xiàn)來設(shè)定,也需要結(jié)合政策叭爱、誤差等因素人工調(diào)整撮躁。下圖為測試集評分卡分?jǐn)?shù)分布情況。