- 拖了好久忘記了我的評(píng)分卡模型的擬寫。這一次稍微好好寫一下。本文章主要是寫一下評(píng)分卡建模的主要流程
一、建模思路
二、數(shù)據(jù)集介紹
givemesomecredit --Kaggle數(shù)據(jù)集
數(shù)據(jù)來自Kaggle的Give Me Some Credit,有15萬條的樣本數(shù)據(jù),大致情況如下:
數(shù)據(jù)屬于個(gè)人消費(fèi)類貸款,只考慮信用評(píng)分最終實(shí)施時(shí)能夠使用到的數(shù)據(jù)應(yīng)從如下一些方面獲取數(shù)據(jù):
基本屬性:包括了借款人當(dāng)時(shí)的年齡。
償債能力:包括了借款人的月收入、負(fù)債比率。
信用往來:兩年內(nèi)35-59天逾期次數(shù)、兩年內(nèi)60-89天逾期次數(shù)、兩年內(nèi)90天或高于90天逾期的次數(shù)。
財(cái)產(chǎn)狀況:包括了開放式信貸和貸款數(shù)量、不動(dòng)產(chǎn)貸款或額度數(shù)量。
貸款屬性:暫無。
其他因素:包括了借款人的家屬數(shù)量(不包括本人在內(nèi))。
時(shí)間窗口:自變量的觀察窗口為過去兩年,因變量表現(xiàn)窗口為未來兩年。
三、具體步驟與代碼
3.1 數(shù)據(jù)描述
df.rename(columns = {'SeriousDlqin2yrs':'y'},inplace = True)
df.drop(columns = 'Unnamed: 0',inplace = True) #因?yàn)閕d沒有什么意義,下面還要直接去重
df.info()
- 發(fā)現(xiàn)有缺失值:MonthlyIncome、NumberofDependents
(df['y'].value_counts()[1])/len(df)
0.06684
- 說明樣本非常不平衡。如果兩類樣本的比例在1:5以上,則無需做樣本不平衡處理。這里需要做不平衡處理。
其他的數(shù)據(jù)描述,例如繪制條形圖、相關(guān)性檢查就不再這里寫出來了。
3.2 數(shù)據(jù)清洗
3.2.1 去重、缺失值處理
# 去重
dfana = df.copy()
dfana.drop_duplicates(inplace = True)
dfana.reset_index(inplace = True)
# 缺失值補(bǔ)充
dfana.isnull().mean()
- 查看缺失值比例
- 考慮到后面要進(jìn)行分箱,如果缺失比例>5%,且好樣本率非極端,不做缺失值填補(bǔ),直接變成一個(gè)分箱;
- 缺失值比例<=5%,或者好壞樣本率極端(全好全壞樣本),選擇隨機(jī)填補(bǔ)。
dfana[dfana['MonthlyIncome'].isnull()].y.value_counts()
- 發(fā)現(xiàn)MonthlyIncome非極端,則無需做缺失值填補(bǔ)
# 對(duì)NumberOfDependents進(jìn)行缺失值填補(bǔ)
dfana['NumberOfDependents']=Fillna(dfana['NumberOfDependents'],repval ='random')
- 對(duì)NumberOfDependents進(jìn)行缺失值進(jìn)行隨機(jī)填補(bǔ)。這里的Fillna函數(shù)自行定義。思路:可以用np.random.choice(array,n,replace = True),注意不要隨機(jī)抽到缺失值的了
3.2.2 異常值處理
- 對(duì)于異常值處理,首先要根據(jù)業(yè)務(wù),繪制box箱線圖發(fā)現(xiàn)異常變量,進(jìn)行剔除或者代替。這里具體的發(fā)現(xiàn)方法不做描述。
dfana = dfana[dfana.age > 0]
dfana = dfana[(dfana['NumberOfTime30-59DaysPastDueNotWorse'] < 90)]
dfana.loc[(dfana.RevolvingUtilizationOfUnsecuredLines > 30),'RevolvingUtilizationOfUnsecuredLines'] = 0.5
dfana.loc[(dfana.DebtRatio > 2),'DebtRatio'] = 0.5
- 通過探索發(fā)現(xiàn)處理了age、NumberOfTime30-59DaysPastDueNotWorse、RevolvingUtilizationOfUnsecuredLines、DebtRatio這幾個(gè)變量的異常值。見仁見智。
3.3 不平衡樣本處理
注意:不平衡樣本處理應(yīng)該在分箱之前,因?yàn)榉窒渲竽承┬畔?huì)缺失,所以按照badrate來平衡樣本。
- 方法有很多,這里用的是簡單的下采樣的方法。
- 無需抽樣成1:1,可以抽樣成1:5,所以這里從正樣本匯總抽樣5倍的壞樣本數(shù)即可。
# 采用分層抽樣,1:5的比例進(jìn)行下采樣
G_train = dfana[dfana.y == 0]
B_train = dfana[dfana.y == 1]
### 對(duì)好樣本進(jìn)行抽樣,抽樣個(gè)數(shù)選擇壞樣本個(gè)數(shù)的5倍
G_train_sample = G_train.sample(n=B_train.shape[0] * 5, frac=None, replace=False, weights=None, random_state=101, axis=0)
dfana_t = []
dfana_t = pd.concat([G_train_sample,B_train])
dfana_t.reset_index(inplace = True,drop = True)
dfana_t.drop(columns = 'index',inplace = True)
3.4 分箱處理
- 用最小卡方法(具體的方法查看評(píng)分卡模型(二)
)
具體的代碼這里不寫出來了。主要的點(diǎn):
- 生成交叉表,計(jì)算badrate:
dftbl = pd.crosstab(dfana[colname], dfana[target])
dftbl.reset_index(inplace = True)
dftbl['badrate'] = dftbl[1] / (dftbl[0] + dftbl[1])
- 需要提前處理badrate極端的組別,badrate = 0 or 1,這部分提前與上下組別合并。
- 最好定義一個(gè)dataframe記錄需要轉(zhuǎn)變前的colname,與需要轉(zhuǎn)變后的colname
# 初始化的轉(zhuǎn)換表格就是自己本身
dfres = pd.DataFrame({colname : dftbl[colname], 'trans' : dftbl[colname]})
# 找到需要轉(zhuǎn)換的變量特征,利用dfres將原來的table轉(zhuǎn)變一下
dfres.loc[i, 'trans'] = dfres.loc[i - 1, 'trans']
dftbl['anacol'] = dfres.trans
# 轉(zhuǎn)變了之后利用groupby再次合并一次
dftbl = dftbl.groupby('anacol', as_index = False,
observed = True).agg('sum')
- 計(jì)算相鄰兩個(gè)單元格的卡方值
for i in range(N_levels - 1):
dftbl.loc[i, 'chi2'] = ss.chi2_contingency(dftbl.loc[i : i + 1, [0, 1]])[0]
- 找到最小的卡方值,進(jìn)行向上或者向下合并,合并了之后還需要重新計(jì)算其卡方值
dftbl.loc[minindex, 'chi2'] = ss.chi2_contingency(dftbl.loc[minindex : minindex + 1,
[0, 1]])[0]
3.5 WOE\IV值
新增一個(gè)計(jì)算WOE和IV值的函數(shù),計(jì)算分箱之后的woe值,和整體變量的iv值,然后進(jìn)行反復(fù)篩選。
主要代碼:
dftbl = pd.crosstab(colname.fillna('NA'), target, normalize = 'columns') #normalize是計(jì)算target中的各項(xiàng)頻率!數(shù)量/列的總和
# 也就是goodpct,badpct
dftbl.columns = ['goodpct', 'badpct']
dftbl['WOE'] = np.log(dftbl.goodpct / dftbl.badpct) * 100
IV = sum((dftbl.goodpct - dftbl.badpct) * dftbl.WOE) / 100
-
經(jīng)過多次嘗試,需要把分箱盡量單調(diào),同時(shí)合并只能合并最近的箱體(已經(jīng)排序過了)
image.png
image.png
- 某些特殊的分箱不滿足單調(diào)性,只需要能在業(yè)務(wù)上解釋就可以了
3.6 切分訓(xùn)練集和測(cè)試集
- 在所有數(shù)值變換之后,就可以切分?jǐn)?shù)據(jù)集和測(cè)試集了,一定要保證訓(xùn)練集和測(cè)試集的變換是一樣的
from sklearn.model_selection import train_test_split
train_df, test_df = train_test_split(dfana_fit,test_size = 0.3)
train_df.shape, test_df.shape
3.7 特征選擇(選用)
- 這里選用的是逐步回歸法,但是數(shù)據(jù)集本身的特征就不是很多,所以這里可以不需要用這個(gè)方法,同時(shí)IV值都已經(jīng)滿足了要求了。
- 逐步回歸法就是例如有6個(gè)特征,一個(gè)model
f1 + model
f2 + model
...
f6 + model
選取其中評(píng)判標(biāo)注最好的一個(gè)特征加入模型,例如是f2
selected = f2
然后
f1 +model+selected
f3 + model + selected
...
依次類推
- 發(fā)現(xiàn)剔除了'NumberOfOpenCreditLinesAndLoans_WOE
- 下面計(jì)算VIF值,這個(gè)值是判斷多重共線的,一般大于10就是有多重共線性了,需要剔除變量
- 我們發(fā)現(xiàn)所有的系數(shù)都為負(fù),這個(gè)是因?yàn)槲覀冇?jì)算WOE的值的時(shí)候是goodrate / badrate,所以與y關(guān)系是負(fù)相關(guān),同時(shí)我們發(fā)現(xiàn)所有的系數(shù)都是一個(gè)方向,這樣的模型才效果更好。
from statsmodels.stats.outliers_influence import variance_inflation_factor
colnames = list(model.params.index)
colnames.remove('Intercept')
train_X_M = np.matrix(train_df[colnames])
VIF_list = [variance_inflation_factor(train_X_M, i) for i in range(train_X_M.shape[1])]
VIF_list
[1.345451226908903,
1.2336645504356645,
1.203776578215364,
1.2737738281637017,
1.1334798511412756,
1.0174816425613178,
1.0462200835734954,
1.0972902825775086,
1.0547503741366757]
- 結(jié)果發(fā)現(xiàn)都在小于2,說明沒有共線性,無需剔除變量
3.8 評(píng)價(jià)模型
- 計(jì)算AUC
from sklearn.metrics import auc,roc_curve, roc_auc_score
### 計(jì)算AUC值
print('訓(xùn)練集的auc為:{}'.format(roc_auc_score(train_df.y,train_predict)))
print('測(cè)試集的auc為:{}'.format(roc_auc_score(test_df.y, predictions)))
訓(xùn)練集的auc為:0.8320949945565318
測(cè)試集的auc為:0.8307774122803272
- 說明模型沒有過擬合,在訓(xùn)練集和測(cè)試集的auc都不錯(cuò)
3.9 轉(zhuǎn)換成分值
利用公式進(jìn)行求解:評(píng)分卡模型(三)
按照公式求出Factor和offset,當(dāng)然自己要規(guī)定PDO(odds比增加一倍時(shí)候要增加的分?jǐn)?shù)),β是建模的系數(shù)。
factor = pdo/np.log(2)
offset = basescore - factor * np.log(baseodds)
dfres['cardscore'] = round(offset / n - factor * (para.Intercept / n + dfres.beta * dfres.WOE))
- 注意,這里先把每一個(gè)箱體的分?jǐn)?shù)計(jì)算出來,再計(jì)算每一個(gè)id的總分?jǐn)?shù),總分?jǐn)?shù)就是每一個(gè)箱體相加
- 最后計(jì)算的分?jǐn)?shù)特征是,577為分?jǐn)?shù)均值,最高分為690,最低分為92.
3.10 KS指標(biāo)
-
橫坐標(biāo)是分?jǐn)?shù),縱坐標(biāo)是壞人/好人的累計(jì)占比
KS指標(biāo).png - 最后繪制兩條曲線,KS為兩條曲線的差值。發(fā)現(xiàn)最大的差值是在572分的時(shí)候KS為51分左右,說明在572的時(shí)候好壞的占比大約占一半。
- KS值一般大于40,就是可以用的模型。