基于Python的網(wǎng)貸信用評(píng)分卡模型——分析與建模

摘要

本文是利用Python語(yǔ)言對(duì)Kaggle上Give Me Some Credit網(wǎng)貸數(shù)據(jù)集進(jìn)行申請(qǐng)?jiān)u分卡(a卡)的數(shù)據(jù)清洗姐霍,建模镊折,熟悉評(píng)分卡構(gòu)建流程恨胚。結(jié)合信貸評(píng)分卡原理赃泡,從數(shù)據(jù)預(yù)處理升熊、建模分析级野、創(chuàng)建信用評(píng)分卡到建立自動(dòng)評(píng)分機(jī)制蓖柔,創(chuàng)立了一個(gè)簡(jiǎn)單的信用準(zhǔn)入評(píng)分系統(tǒng)况鸣。并對(duì)建立基于AI 的機(jī)器學(xué)習(xí)評(píng)分卡系統(tǒng)的路徑進(jìn)行思考和總結(jié)潜索。

工作原理

信用評(píng)分卡是一種成熟的預(yù)測(cè)方法竹习,在信用風(fēng)險(xiǎn)評(píng)估以及金融風(fēng)險(xiǎn)控制領(lǐng)域更是得到了比較廣泛的使用由驹,其原理是將模型變量利用WOE-IV編碼離散化之后運(yùn)用logistic回歸模型進(jìn)行的一種二分類(lèi)廣義線性模型。
評(píng)分卡由一系列特征組成默刚,每個(gè)特征對(duì)應(yīng)申請(qǐng)表上的一個(gè)數(shù)據(jù)(年齡荤西、銀行流水勉躺、收入等)饵溅。并且一個(gè)特征項(xiàng)都會(huì)存在一串屬性(對(duì)于年齡這個(gè)問(wèn)題蜕企,答案可能就有30歲以下轻掩、30到40歲等)唇牧。開(kāi)發(fā)評(píng)分卡模型的過(guò)程中奋构,必須先確定屬性與申請(qǐng)人未來(lái)信用表現(xiàn)之間的相關(guān)關(guān)系,分配適當(dāng)?shù)?strong>權(quán)重分值径缅。分值越大纳猪,說(shuō)明該屬性蘊(yùn)含的信用表現(xiàn)越佳氏堤。而申請(qǐng)得分就是其分值的簡(jiǎn)單求和鼠锈。一旦評(píng)分大于等于金融放款機(jī)構(gòu)所設(shè)定的閾值,表示該申請(qǐng)人的風(fēng)險(xiǎn)水平是可接受的范圍同欠,可以被批準(zhǔn)铺遂。

數(shù)據(jù)處理

載入庫(kù)

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use("bmh")
plt.rc('font', family='SimHei', size=13)
%matplotlib inline

import os

導(dǎo)入數(shù)據(jù)

data_train = pd.read_csv('cs-training.csv')
data_test = pd.read_csv('cs-test.csv')

探索性數(shù)據(jù)分析

特征解釋?zhuān)?/h3>
data_train.info()

有缺失值的特征:MonthlyIncome炕檩,NumberOfDependents笛质。

修改列名:

columns = ({'SeriousDlqin2yrs':'Label',
            'RevolvingUtilizationOfUnsecuredLines':'Revol',
           'NumberOfOpenCreditLinesAndLoans':'NumOpen',
           'NumberOfTimes90DaysLate':'Num90late',
           'NumberRealEstateLoansOrLines':'NumEstate',
           'NumberOfTime60-89DaysPastDueNotWorse':'Num60-89late',
           'NumberOfDependents':'NumDependents',
           'NumberOfTime30-59DaysPastDueNotWorse':'Num30-59late'}
    )
data_train.rename(columns=columns,inplace = True)
data_test.rename(columns=columns,inplace = True)

badrate

bad = data_train.loc[data_train['Label']==1].shape[0]
good = data_train.loc[data_train['Label']==0].shape[0]
print('badrate為:{:.2f}%'.format(round(bad*100/(good+bad),2)))

壞客戶只占用戶6.68%。

異常值探索

特征 年齡

年齡分布

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

從圖可以發(fā)現(xiàn),年齡基本呈正態(tài)分布肩杈。

異常值處理:
處理數(shù)據(jù)異常值一般有:3倍標(biāo)準(zhǔn)差區(qū)分異常值扩然、分箱四分位距區(qū)分異常值或者直接去除首尾各1%的數(shù)據(jù)這三種方法。

數(shù)據(jù)現(xiàn)在是處于正態(tài)分布兵拢,故采用3倍標(biāo)準(zhǔn)差去除異常说铃。

data_train = data_train[data_train['age']!=0]
age_Mean = np.mean(data_train['age'])
age_Std = np.std(data_train['age'])
UpLimit = round((age_Mean + 3*age_Std),2)
DownLimit = round((age_Mean - 3*age_Std),2)
data_train = data_train[(data_train['age']>=DownLimit)&(data_train['age']<=UpLimit)]

觀察年齡大小對(duì)label值的影響

##
#先做一個(gè)等距粗分箱
data_train['ageCat'] = np.nan
data_train.loc[(data_train['age']>18)&(data_train['age']<40),'ageCat'] = 1 
data_train.loc[(data_train['age']>=40)&(data_train['age']<60),'ageCat'] = 2 
data_train.loc[(data_train['age']>=60)&(data_train['age']<80),'ageCat'] = 3
data_train.loc[(data_train['age']>=80),'ageCat'] = 4

BadrateList = {}

for m in data_train['ageCat'].value_counts().index:
    data = data_train.loc[data_train['ageCat']==m]
    Badrate = data.loc[data['Label']==0].shape[0]/data.loc[data['Label']==1].shape[0]
    BadrateList[m] = Badrate
BadrateList=dict(sorted(BadrateList.items(),key=lambda x:x[0]))
f,ax = plt.subplots(figsize=(8,5))
sns.countplot('ageCat', hue='Label', data=data_train, ax=ax)
ax2 = ax.twinx()
ax2.plot([x-1 for x in BadrateList.keys()], [round(x,2) for x in BadrateList.values()],'ro--',linewidth=2)

可以看到年齡越大植捎,badrate越大焰枢,呈現(xiàn)單調(diào)性济锄。為后面的等頻分箱求WOE提供參考荐绝。

特征 Revol

該特征的意義是:除了房貸車(chē)貸之外的信用卡金額(即貸款金額額度)/信用卡總額度低滩。即通常是小于等于1的恕沫,一般大于1是屬于透支鲸阔。

print(data_train['Revol'][data_train['Revol']>1].count())
print(data_train['Revol'].max())
print(data_train['Revol'].min())

3321
50708.0
0.0

特征值超過(guò)幾萬(wàn)顯然不太正常褐筛,估計(jì)很有可能是沒(méi)有除以分母信用卡總額度渔扎,而是分子的純信用卡賬面貸款金額
現(xiàn)在嘗試確定屬于透支的最大數(shù)字和異常值的范圍:

#四分位距法
Percentile = np.percentile(data_train['Revol'],[0,25,50,75,100])
IQR = Percentile[3] - Percentile[1]
UpLimit = Percentile[3]+IQR*1.5
DownLimit = Percentile[1]-IQR*1.5
print('上限為:{0}, 下限為:{1}'.format(UpLimit,DownLimit))
print('上界異常值占比:{0}%'.format(round(data_train[data_train['Revol']>UpLimit].shape[0]*100/data_train.shape[0],2)))

上限為:1.3529709321249999, 下限為:-0.763938468875
上界異常值占比:0.51%
#分段觀察Revol分布
f,[[ax1,ax2],[ax3,ax4]]=plt.subplots(2,2,figsize=(20,10))
sns.distplot(data_train['Revol'][data_train['Revol']<1],bins=10,ax=ax1)
sns.distplot(data_train['Revol'][(data_train['Revol']>=1)&(data_train['Revol']<100)],bins=10,ax=ax2)
sns.distplot(data_train['Revol'][(data_train['Revol']>=100)&(data_train['Revol']<10000)],bins=10,ax=ax3)
sns.distplot(data_train['Revol'][(data_train['Revol']>=10000)&(data_train['Revol']<51000)],bins=10,ax=ax4)
ax1.set_title('0=<Revol<1')
ax2.set_title('1=<Revol<100')
ax3.set_title('100=<Revol<10000')
ax4.set_title('10000=<Revol<51000')
plt.show()

print('大于等于1的數(shù)量為:{0}'.format(data_train.loc[data_train['Revol']>=1].shape[0]))
print('大于等于1的數(shù)量為:{0}'.format(data_train.loc[data_train['Revol']>=1].shape[0]))
  • Revol小于1的分布中,大部分?jǐn)?shù)據(jù)都處于(0,0.1)區(qū)間,而隨著Revol特征值變大琼蚯,數(shù)量遞減遭庶;
  • 在Revol大于1的數(shù)值分布中峦睡,都明顯的呈現(xiàn)了遞減趨勢(shì)煎谍,主要集中在100以?xún)?nèi)的范圍內(nèi)呐粘;

將1~50000等頻分箱作岖,計(jì)算相應(yīng)的badrate進(jìn)行觀察

#qcut等頻分箱
data2 = data_train[(data_train['Revol']>1)&(data_train['Revol']<51000)]
data2['Revol_100'] = pd.qcut(data2['Revol'],10)
badrate ={}
revol_100_list = list(set(data2['Revol_100']))
for x in revol_100_list:
    a = data2[data2['Revol_100'] == x]
    bad = a['Label'][a['Label']==1].count()
    good = a['Label'][a['Label']==0].count()
    badrate[x]=bad/(good+bad)
f = zip(badrate.keys(),badrate.values())

badrate = pd.DataFrame(f)
badrate.columns = ['cut','badrate']
badrate = badrate.sort_values('cut')
print(badrate)
badrate.plot('cut','badrate')

                cut   badrate
7    (0.999, 1.007]  0.282282
3    (1.007, 1.014]  0.313253
6    (1.014, 1.026]  0.340361
5    (1.026, 1.045]  0.406627
0    (1.045, 1.075]  0.409639
1    (1.075, 1.118]  0.439759
4    (1.118, 1.215]  0.493976
2    (1.215, 1.442]  0.481928
8    (1.442, 2.198]  0.427711
9  (2.198, 50708.0]  0.129518
data_train['Revol'].loc[data_train['Revol']>=2].count()

371

從業(yè)務(wù)角度出發(fā),隨著信用卡貸款比例增高与柑,客戶風(fēng)險(xiǎn)越高价捧,badrate就會(huì)越大结蟋。Revol大于1推正,客戶就已經(jīng)處于“透支”狀態(tài)植榕,但是"透支"比例總是有上限的尊残。當(dāng)Revol大于2時(shí)寝衫,badrate顯著下降至12.95%慰毅,接近總體badrate的6.68%汹胃,那么可以將Revol大于2的371個(gè)客戶的Revol值看作屬于0~1之間统台。

特征 DebtRatio

該特征情況經(jīng)過(guò)探索,情況和Revol類(lèi)似:也是疑似存在比率分母缺失贵扰。那么用相似的方法處理。

print(data_train['DebtRatio'][data_train['DebtRatio']>1].count())
print(data_train['DebtRatio'].max())
print(data_train['DebtRatio'].min())

35122
329664.0
0.0
data3['D_100'] = pd.qcut(data3['DebtRatio'],10)
badrate ={}
D100_list = list(set(data3['D_100']))
for x in D100_list:
    a = data3[data3['D_100'] == x]
    bad = a['Label'][a['Label']==1].count()
    good = a['Label'][a['Label']==0].count()
    badrate[x]=bad/(good+bad)
f = zip(badrate.keys(),badrate.values())

badrate = pd.DataFrame(f)
badrate.columns = ['cut','badrate']
badrate = badrate.sort_values('cut')
print(badrate)
badrate.plot('cut','badrate',figsize=(20,8))
data_train['DebtRatio'].loc[data_train['DebtRatio']>=2].count()

31200

經(jīng)過(guò)比對(duì),當(dāng)DebtRatio>2時(shí)球切,badrate就下降至與總體一致绒障,故后面將DebtRatio>2與DebtRatio<1分成一箱吨凑。

缺失值探索

特征 NumDependents

NumDependents中缺失值占比

print(data_train[data_train['NumDependents'].isnull()].shape[0])
print(round(data_train[data_train['NumDependents'].isnull()].shape[0]/data_train.shape[0],4))
3911
0.0261

計(jì)算缺失值和未缺失值的badrate

d_Null = data_train.loc[data_train['NumDependents'].isnull()]
d_notNull = data_train.loc[data_train['NumDependents'].notnull()]
print('缺失值為{0}%'.format(round(d_Null[d_Null['Label']==1].shape[0]*100/d_Null.shape[0],2)))
print('未缺失值為{0}%'.format(round(d_notNull[d_notNull['Label']==1].shape[0]*100/d_notNull.shape[0],2)))

缺失值為4.55%
未缺失值為6.74%

說(shuō)明badrate在NumDependents的缺失值下并沒(méi)有區(qū)別,不能將缺失值看作一種特殊的狀態(tài)對(duì)待户辱,那么可以填充缺失值

print(data_train.loc[data_train['MonthlyIncome'].isnull()].shape[0])
print(data_train.loc[(data_train['NumDependents'].isnull())&(data_train['MonthlyIncome'].isnull())].shape[0])

29708
3911

從上面得知MonthlyIncome為空的客戶鸵钝,NumDependents也為空。
下面觀察MonthlyIncome為空時(shí)庐镐,NumDependents不為空的情況。并期望用相似的情況解決NumDependents的空值

D_Null = data_train.loc[(data_train['MonthlyIncome'].isnull())&(data_train['NumDependents'].notnull()),'NumDependents']
D_Null.value_counts().plot(kind='bar', figsize=(10,5))

該情況下必逆,數(shù)據(jù)絕大部分集中于0處痕届,這也和NumDependents總體分布相似,那么填充為0值末患。

data_train['NumDependents'] = data_train['NumDependents'].fillna(0)

特征 MonthlyIncome

缺失情況

print(data_train[data_train['MonthlyIncome'].isnull()].shape[0])
print(round(data_train[data_train['MonthlyIncome'].isnull()].shape[0]/data_train.shape[0],4))

29708
0.1981

缺失比例接近20%研叫,考慮使用隨機(jī)森林算法填充:

data_train.drop(['Unnamed: 0'],axis=1)
from sklearn.ensemble import RandomForestRegressor
process_miss = data_train.iloc[:,[5,0,1,2,3,4,6,7,8,9]]
known = process_miss[process_miss.MonthlyIncome.notnull()].values
unknown = process_miss[process_miss.MonthlyIncome.isnull()].values
x = known[:,1:]
y = known[:,0]
rf = RandomForestRegressor(random_state=0,n_estimators=100,max_depth=3,max_features=3,n_jobs=-1)
rf.fit(x,y)
pre = rf.predict(unknown[:,1:]).round(0)
data_train.loc[data_train['MonthlyIncome'].isnull(),'MonthlyIncome'] = pre

特征 Num30-59、 60-89 璧针、90 late

f,[ax,ax1,ax2]=plt.subplots(1,3,figsize=(20,5))
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()
for i in ['Num30-59late','Num60-89late','Num90late']:
    print(data_train[i][data_train[i]>20].count())

269
269
269

從圖中看出嚷炉,三個(gè)特征均有大于20的異常值。一般來(lái)說(shuō)逾期不會(huì)存在這么多次數(shù)探橱,并且異常個(gè)數(shù)很少申屹,可以考慮刪去绘证。

特征 NumOpen和NumEstate

分布和上個(gè)特征類(lèi)似,都是存在少量上界異常值:大于50和大于30

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

建模部分

導(dǎo)入庫(kù)及數(shù)據(jù)

import numpy as np
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use("bmh")
plt.rc('font', family='SimHei', size=13)
%matplotlib inline
import math
import os
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_predict
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.preprocessing import StandardScaler
# 導(dǎo)入數(shù)據(jù)
data_train = pd.read_csv('cs-training.csv')
data_test = pd.read_csv('cs-test.csv')
columns = ({'SeriousDlqin2yrs':'Label',
            'RevolvingUtilizationOfUnsecuredLines':'Revol',
           'NumberOfOpenCreditLinesAndLoans':'NumOpen',
           'NumberOfTimes90DaysLate':'Num90late',
           'NumberRealEstateLoansOrLines':'NumEstate',
           'NumberOfTime60-89DaysPastDueNotWorse':'Num60-89late',
           'NumberOfDependents':'NumDependents',
           'NumberOfTime30-59DaysPastDueNotWorse':'Num30-59late'}
          )
data_train.rename(columns=columns,inplace = True)
data_test.rename(columns=columns,inplace = True)

處理測(cè)試集異常值

data_train = data_train[data_train['age']!=0]
data_train = data_train[data_train['Num30-59late']<20]
data_train = data_train[data_train['Num60-89late']<20]
data_train = data_train[data_train['Num90late']<20]
data_train= data_train[data_train['NumOpen']<50]
data_train= data_train[data_train['NumEstate']<30]
data_train = data_train.drop(['Unnamed: 0'],axis=1)
data_test = data_test.drop(['Unnamed: 0'],axis=1)

補(bǔ)全測(cè)試集和驗(yàn)證集缺失值

#隨機(jī)森林補(bǔ)全income
def missfill(df):
    process_miss = df.iloc[:,[5,1,2,3,4,6,7,8,9]]
    known = process_miss[process_miss.MonthlyIncome.notnull()].as_matrix()
    unknown = process_miss[process_miss.MonthlyIncome.isnull()].as_matrix()
    X = known[:,1:]
    y = known[:,0]
    rfr = RandomForestRegressor(random_state=0,n_estimators=100,max_depth=3,n_jobs=-1哗讥,max_features = 3)
    rfr.fit(X,y)
    pre= rfr.predict( unknown[:,1:]).round(0)
    return pre
# 預(yù)處理
def processing(data):

   
    data["NumDependents"] = np.abs(data["NumDependents"])
    data["NumDependents"] = data["NumDependents"].fillna(0)
    data["NumDependents"] = data["NumDependents"].astype('int64')
    data.loc[(data['NumDependents']>=7), 'NumDependents'] = 7

    
    pre = missfill(data)
    data.loc[data['MonthlyIncome'].isnull(),'MonthlyIncome'] = pre
    
    #構(gòu)造新的特征
    data["Defaulted"] = data["Num90late"] + data["Num60-89late"] + data["Num30-59late"]
    data["Loans"] = data["NumOpen"] + data["NumEstate"]
    data["DebtPay"] =np.absolute( data["DebtRatio"] * data["MonthlyIncome"])
    data["DebtPay"] = data["DebtPay"].astype('int64')
    data["age"] = data["age"].astype('int64')
    data["MonthlyIncome"] = data["MonthlyIncome"].astype('int64')
    data["age_map"] = data["age"]
    data["WithDependents"] = data["NumDependents"]
    return data
def hand_bin(data):
    # 手動(dòng)分箱
    
    # Revol特征分箱
    data.loc[(data['Revol']>=0)&(data['Revol']<1), 'Revol'] = 0
    data.loc[(data['Revol']>1)&(data['Revol']<=30), 'Revol'] = 1
    data.loc[(data['Revol']>30), 'Revol'] = 0
    
    # DebtRatio特征分箱
    data.loc[(data['DebtRatio']>=0)&(data['DebtRatio']<1), 'DebtRatio'] = 0
    data.loc[(data['DebtRatio']>=1)&(data['DebtRatio']<2), 'DebtRatio'] = 1
    data.loc[(data['DebtRatio']>=2), 'DebtRatio'] = 0
    
    # Num30-59late, Num60-89late, Num90late,NumOpen
    data.loc[(data['Num30-59late']>=8), 'Num30-59late'] = 8
    data.loc[(data['Num60-89late']>=7), 'Num60-89late'] = 7
    data.loc[(data['Num90late']>=10), 'Num90late'] = 10
    data.loc[(data['NumEstate']>=8), 'NumEstate'] = 8
    
    # 衍生變量分箱
    data.loc[(data["Defaulted"] >= 1), "Defaulted"] = 1
    data.loc[(data["Loans"] <= 5), "Loans"] = 0
    data.loc[(data["Loans"] > 5), "Loans"] = 1
    data.loc[(data["WithDependents"] >= 1), "WithDependents"] = 1
    
    data.loc[(data["age"] >= 18) & (data["age"] < 60), "age_map"] = 1
    data.loc[(data["age"] >= 60), "age_map"] = 0
    data["age_map"] = data["age_map"].replace(0, "older")
    data["age_map"] = data["age_map"].replace(1, "senior")
    data = pd.concat([data, pd.get_dummies(data.age_map,prefix='is')], axis=1)
    
    return data
Ytest = data_test['Label']
Xtest = data_test.drop(['Label'], axis=1)
Ytrain = data_train['Label']
Xtrain = data_train.drop(['Label'], axis=1)

data_train = processing(data_train)
data_test = processing(data_test)
data_train = hand_bin(data_train)
data_test = hand_bin(data_test)
data_train.drop('age_map',axis=1,inplace=True)
data_test.drop('age_map',axis=1,inplace=True)

特征相關(guān)性熱力圖

corr = data_train.corr()
plt.figure(figsize=(14,12))
sns.heatmap(corr, annot=True)

將變量分成離散變量和連續(xù)變量分別進(jìn)行進(jìn)行woe,iv值計(jì)算:

dvar = ['Revol','DebtRatio','Num30-59late', 'Num60-89late','Num90late','Defaulted','WithDependents',
        'NumEstate','NumDependents','Loans','is_senior','is_older']
svar = ['MonthlyIncome','age','NumOpen','DebtPay']
def bin_woe(tar, var, n=None, cat=None):
    total_bad = tar.sum()
    total_good =tar.count()-total_bad
    totalRate = total_good/total_bad
    
    if cat == 's':
        msheet = pd.DataFrame({tar.name:tar,var.name:var,'bins':pd.qcut(var, n, duplicates='drop')})
        grouped = msheet.groupby(['bins'])
    elif (cat == 'd') and (n is None):
        msheet = pd.DataFrame({tar.name:tar,var.name:var})
        grouped = msheet.groupby([var.name])
        
    groupBad = grouped.sum()[tar.name]
    groupTotal = grouped.count()[tar.name]
    groupGood = groupTotal - groupBad
    groupRate = groupGood/groupBad
    groupBadRate = groupBad/groupTotal
    groupGoodRate = groupGood/groupTotal

    woe = np.log(groupRate/totalRate)

    iv = np.sum((groupGood/total_good-groupBad/total_bad)*woe)
    
    if cat == 's':
        new_var, cut = pd.qcut(var, n, duplicates='drop',retbins=True, labels=woe.tolist())
    elif cat == 'd':
        dictmap = {}
        for x in woe.index:
            dictmap[x] = woe[x]
        new_var, cut = var.map(dictmap), woe.index
    
    return woe.tolist(), iv, cut, new_var

def woe_vs(data):
    cutdict = {}
    ivdict = {}
    woe_dict = {}
    woe_var = pd.DataFrame()
    for var in data.columns:
        if var in dvar:
            woe, iv, cut, new = bin_woe(data['Label'], data[var], cat='d')
            woe_dict[var] = woe
            woe_var[var] = new
            ivdict[var] = iv
            cutdict[var] = cut
        elif var in svar:
            woe, iv, cut, new = bin_woe(data['Label'], data[var], n=5, cat='s')
            woe_dict[var] = woe
            woe_var[var] = new
            ivdict[var] = iv
            cutdict[var] = cut

    ivdict = sorted(ivdict.items(), key=lambda x:x[1], reverse=False)
    iv_vs = pd.DataFrame([x[1] for x in ivdict],index=[x[0] for x in ivdict],columns=['IV'])
    ax = iv_vs.plot(kind='bar',
                    figsize=(12,12),
                    title='Feature Importance',
                    fontsize=10,
                    width=0.8)
    ax.set_ylabel('Features')
    ax.set_xlabel('IV of Features')
    
    return ivdict, woe_var, woe_dict, cutdict



iv_info, woe_data, woe_dict, cut_dict = woe_vs(data_train)

由此可以開(kāi)始選擇選擇變量

選擇變量

選擇變量的基本規(guī)則是根據(jù)熱力圖和上面的IV圖和在特征相關(guān)性高的變量中嚷那,選擇IV值高的規(guī)則,可以選擇如下變量:
is_senior | is_older | age:選age
WithDependents | NumDependents:選NumDependents
Defaulted | Num30-59late:選Defaulted
NumOpen |Loans:選NumOpen
最后保留的變量

ivinfo_keep = ['Revol', 'age', 'DebtRatio', 'MonthlyIncome',
               'NumOpen', 'Num90late', 'NumEstate', 'Num60-89late', 
               'NumDependents','Defaulted','DebtPay']

再次計(jì)算相關(guān)系數(shù):


可以判斷各變量之間不存在明顯的相關(guān)性

邏輯回歸建模

X = woe_data[ivinfo_keep]
y = Ytrain
X_train, X_val, y_train, y_val = train_test_split(X,y,random_state=42)
logit = LogisticRegression(random_state=0,
                           solver="sag",
                           penalty="l2",
                           class_weight="balanced",
                           C=1.0,
                           max_iter=500)
logit.fit(X_train, y_train)
logit_scores_proba = logit.predict_proba(X_train)
logit_scores = logit_scores_proba[:,1]

利用ROC曲線判斷效果:

def plot_roc_curve(fpr, tpr, auc_score, label=None):
    plt.figure(figsize=(10,8))
    plt.plot(fpr, tpr, linewidth=2, label='AUC = %0.2f'%auc_score)
    plt.plot([0,1],[0,1], "k--")
    plt.axis([0,1,0,1])
    plt.xlabel("False Positive Rate")
    plt.ylabel("True Positive rate")
    plt.legend()


fpr_logit, tpr_logit, thresh_logit = roc_curve(y_train, logit_scores)
auc_score = roc_auc_score(y_train, logit_scores)
print("AUC:  {}".format(auc_score))
plot_roc_curve(fpr_logit,tpr_logit, auc_score)

AUC: 0.8290290455012129
從上面圖形和auc值可以看出擬合效果較好杆煞,接下來(lái)開(kāi)始計(jì)算評(píng)分卡:

coe = logit.coef_
intercept = logit.intercept_

B = 20 / math.log(2)
A = 600 + B / math.log(20)
# 基礎(chǔ)分
base = round(A-B * intercept[0], 0)

featurelist = []
woelist = []
coelist = []
cutlist = []
for k,v in woe_dict.items():
    if k in ivinfo_keep:
        for n in range(0,len(v)):
            featurelist.append(k)
            woelist.append(v[n])
            coelist.append(coe[0][n])
            cutlist.append(cut_dict[k][n])
            
scoreboard = pd.DataFrame({'feature':featurelist, 'woe':woelist, 'coe':coelist, 'cut':cutlist},
                          columns=['feature','cut','woe','coe'])
scoreboard['score'] = round(-scoreboard['woe']*scoreboard['coe']*B,0).astype('int64')
scoreboard

結(jié)果如下:


總結(jié):

1.用戶的屬性有千千萬(wàn)萬(wàn)個(gè)維度魏宽,而評(píng)分卡模型所選用的特征很有限,這時(shí)候就需要一定的原則對(duì)變量篩選:預(yù)測(cè)能力决乎、變量相關(guān)性等队询。

  1. 評(píng)分卡模型采用的是對(duì)每個(gè)字段的分段進(jìn)行評(píng)分,對(duì)于特征的分段處理就是采用變量分箱构诚。
  2. 對(duì)字段的每個(gè)分段進(jìn)行評(píng)分的方式是采用WOE-IV編碼蚌斩,將預(yù)測(cè)概率值轉(zhuǎn)化為評(píng)分,利用變量相關(guān)性分析確保其合理性范嘱。
    4.本質(zhì)上是采用邏輯回歸進(jìn)行二分類(lèi)送膳,但重點(diǎn)在于特征篩選和處理方面。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丑蛤,一起剝皮案震驚了整個(gè)濱河市肠缨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盏阶,老刑警劉巖晒奕,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異名斟,居然都是意外死亡脑慧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)砰盐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闷袒,“玉大人,你說(shuō)我怎么就攤上這事岩梳∧抑瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵冀值,是天一觀的道長(zhǎng)也物。 經(jīng)常有香客問(wèn)我,道長(zhǎng)列疗,這世上最難降的妖魔是什么滑蚯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上告材,老公的妹妹穿的比我還像新娘坤次。我一直安慰自己,他們只是感情好斥赋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布缰猴。 她就那樣靜靜地躺著,像睡著了一般疤剑。 火紅的嫁衣襯著肌膚如雪滑绒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天骚露,我揣著相機(jī)與錄音蹬挤,去河邊找鬼缚窿。 笑死棘幸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倦零。 我是一名探鬼主播误续,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扫茅!你這毒婦竟也來(lái)了蹋嵌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤葫隙,失蹤者是張志新(化名)和其女友劉穎栽烂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體恋脚,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腺办,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糟描。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀喉。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖船响,靈堂內(nèi)的尸體忽然破棺而出躬拢,到底是詐尸還是另有隱情,我是刑警寧澤见间,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布聊闯,位于F島的核電站,受9級(jí)特大地震影響米诉,放射性物質(zhì)發(fā)生泄漏馅袁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一荒辕、第九天 我趴在偏房一處隱蔽的房頂上張望汗销。 院中可真熱鬧犹褒,春花似錦、人聲如沸弛针。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)削茁。三九已至宙枷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茧跋,已是汗流浹背慰丛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘾杭,地道東北人诅病。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像粥烁,于是被迫代替她去往敵國(guó)和親贤笆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容