- 拖了好久忘記了我的評分卡模型的擬寫。這一次稍微好好寫一下竿报。本文章主要是寫一下評分卡建模的主要流程
一铅乡、建模思路
二、數據集介紹
givemesomecredit --Kaggle數據集
數據來自Kaggle的Give Me Some Credit烈菌,有15萬條的樣本數據阵幸,大致情況如下:
數據屬于個人消費類貸款,只考慮信用評分最終實施時能夠使用到的數據應從如下一些方面獲取數據:
基本屬性:包括了借款人當時的年齡芽世。
償債能力:包括了借款人的月收入挚赊、負債比率。
信用往來:兩年內35-59天逾期次數捂襟、兩年內60-89天逾期次數咬腕、兩年內90天或高于90天逾期的次數欢峰。
財產狀況:包括了開放式信貸和貸款數量葬荷、不動產貸款或額度數量。
貸款屬性:暫無纽帖。
其他因素:包括了借款人的家屬數量(不包括本人在內)。
時間窗口:自變量的觀察窗口為過去兩年,因變量表現(xiàn)窗口為未來兩年碗啄。
三淑履、具體步驟與代碼
3.1 數據描述
df.rename(columns = {'SeriousDlqin2yrs':'y'},inplace = True)
df.drop(columns = 'Unnamed: 0',inplace = True) #因為id沒有什么意義,下面還要直接去重
df.info()
- 發(fā)現(xiàn)有缺失值:MonthlyIncome室囊、NumberofDependents
(df['y'].value_counts()[1])/len(df)
0.06684
- 說明樣本非常不平衡雕崩。如果兩類樣本的比例在1:5以上,則無需做樣本不平衡處理融撞。這里需要做不平衡處理盼铁。
其他的數據描述,例如繪制條形圖尝偎、相關性檢查就不再這里寫出來了饶火。
3.2 數據清洗
3.2.1 去重鹏控、缺失值處理
# 去重
dfana = df.copy()
dfana.drop_duplicates(inplace = True)
dfana.reset_index(inplace = True)
# 缺失值補充
dfana.isnull().mean()
- 查看缺失值比例
- 考慮到后面要進行分箱,如果缺失比例>5%,且好樣本率非極端肤寝,不做缺失值填補当辐,直接變成一個分箱;
- 缺失值比例<=5%,或者好壞樣本率極端(全好全壞樣本)鲤看,選擇隨機填補缘揪。
dfana[dfana['MonthlyIncome'].isnull()].y.value_counts()
- 發(fā)現(xiàn)MonthlyIncome非極端,則無需做缺失值填補
# 對NumberOfDependents進行缺失值填補
dfana['NumberOfDependents']=Fillna(dfana['NumberOfDependents'],repval ='random')
- 對NumberOfDependents進行缺失值進行隨機填補义桂。這里的Fillna函數自行定義寺晌。思路:可以用np.random.choice(array,n,replace = True),注意不要隨機抽到缺失值的了
3.2.2 異常值處理
- 對于異常值處理澡刹,首先要根據業(yè)務呻征,繪制box箱線圖發(fā)現(xià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這幾個變量的異常值。見仁見智胞锰。
3.3 不平衡樣本處理
注意:不平衡樣本處理應該在分箱之前灾锯,因為分箱之后某些信息會缺失,所以按照badrate來平衡樣本嗅榕。
- 方法有很多顺饮,這里用的是簡單的下采樣的方法。
- 無需抽樣成1:1凌那,可以抽樣成1:5兼雄,所以這里從正樣本匯總抽樣5倍的壞樣本數即可。
# 采用分層抽樣,1:5的比例進行下采樣
G_train = dfana[dfana.y == 0]
B_train = dfana[dfana.y == 1]
### 對好樣本進行抽樣帽蝶,抽樣個數選擇壞樣本個數的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 分箱處理
- 用最小卡方法(具體的方法查看評分卡模型(二)
)
具體的代碼這里不寫出來了赦肋。主要的點:
- 生成交叉表,計算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,這部分提前與上下組別合并佃乘。
- 最好定義一個dataframe記錄需要轉變前的colname,與需要轉變后的colname
# 初始化的轉換表格就是自己本身
dfres = pd.DataFrame({colname : dftbl[colname], 'trans' : dftbl[colname]})
# 找到需要轉換的變量特征驹尼,利用dfres將原來的table轉變一下
dfres.loc[i, 'trans'] = dfres.loc[i - 1, 'trans']
dftbl['anacol'] = dfres.trans
# 轉變了之后利用groupby再次合并一次
dftbl = dftbl.groupby('anacol', as_index = False,
observed = True).agg('sum')
- 計算相鄰兩個單元格的卡方值
for i in range(N_levels - 1):
dftbl.loc[i, 'chi2'] = ss.chi2_contingency(dftbl.loc[i : i + 1, [0, 1]])[0]
- 找到最小的卡方值趣避,進行向上或者向下合并,合并了之后還需要重新計算其卡方值
dftbl.loc[minindex, 'chi2'] = ss.chi2_contingency(dftbl.loc[minindex : minindex + 1,
[0, 1]])[0]
3.5 WOE\IV值
新增一個計算WOE和IV值的函數扶欣,計算分箱之后的woe值鹅巍,和整體變量的iv值千扶,然后進行反復篩選。
主要代碼:
dftbl = pd.crosstab(colname.fillna('NA'), target, normalize = 'columns') #normalize是計算target中的各項頻率骆捧!數量/列的總和
# 也就是goodpct,badpct
dftbl.columns = ['goodpct', 'badpct']
dftbl['WOE'] = np.log(dftbl.goodpct / dftbl.badpct) * 100
IV = sum((dftbl.goodpct - dftbl.badpct) * dftbl.WOE) / 100
-
經過多次嘗試澎羞,需要把分箱盡量單調,同時合并只能合并最近的箱體(已經排序過了)
- 某些特殊的分箱不滿足單調性敛苇,只需要能在業(yè)務上解釋就可以了
3.6 切分訓練集和測試集
- 在所有數值變換之后妆绞,就可以切分數據集和測試集了,一定要保證訓練集和測試集的變換是一樣的
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 特征選擇(選用)
- 這里選用的是逐步回歸法枫攀,但是數據集本身的特征就不是很多括饶,所以這里可以不需要用這個方法,同時IV值都已經滿足了要求了来涨。
- 逐步回歸法就是例如有6個特征图焰,一個model
f1 + model
f2 + model
...
f6 + model
選取其中評判標注最好的一個特征加入模型,例如是f2
selected = f2
然后
f1 +model+selected
f3 + model + selected
...
依次類推
- 發(fā)現(xiàn)剔除了'NumberOfOpenCreditLinesAndLoans_WOE
- 下面計算VIF值蹦掐,這個值是判斷多重共線的技羔,一般大于10就是有多重共線性了,需要剔除變量
- 我們發(fā)現(xiàn)所有的系數都為負卧抗,這個是因為我們計算WOE的值的時候是goodrate / badrate藤滥,所以與y關系是負相關,同時我們發(fā)現(xiàn)所有的系數都是一個方向社裆,這樣的模型才效果更好拙绊。
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]
- 結果發(fā)現(xiàn)都在小于2,說明沒有共線性泳秀,無需剔除變量
3.8 評價模型
- 計算AUC
from sklearn.metrics import auc,roc_curve, roc_auc_score
### 計算AUC值
print('訓練集的auc為:{}'.format(roc_auc_score(train_df.y,train_predict)))
print('測試集的auc為:{}'.format(roc_auc_score(test_df.y, predictions)))
訓練集的auc為:0.8320949945565318
測試集的auc為:0.8307774122803272
- 說明模型沒有過擬合标沪,在訓練集和測試集的auc都不錯
3.9 轉換成分值
利用公式進行求解:評分卡模型(三)
按照公式求出Factor和offset,當然自己要規(guī)定PDO(odds比增加一倍時候要增加的分數)晶默,β是建模的系數谨娜。
factor = pdo/np.log(2)
offset = basescore - factor * np.log(baseodds)
dfres['cardscore'] = round(offset / n - factor * (para.Intercept / n + dfres.beta * dfres.WOE))
- 注意,這里先把每一個箱體的分數計算出來磺陡,再計算每一個id的總分數,總分數就是每一個箱體相加
- 最后計算的分數特征是漠畜,577為分數均值币他,最高分為690,最低分為92.
3.10 KS指標
-
橫坐標是分數憔狞,縱坐標是壞人/好人的累計占比
- 最后繪制兩條曲線蝴悉,KS為兩條曲線的差值。發(fā)現(xiàn)最大的差值是在572分的時候KS為51分左右瘾敢,說明在572的時候好壞的占比大約占一半拍冠。
- KS值一般大于40尿这,就是可以用的模型。