上一篇文章信用評分卡模型分析(理論部分)已經介紹了信用評分卡模型的數據預處理、探索性數據分析、變量分箱和變量選擇等。接下來使用Python建立信用評分卡,對用戶行為進行打分,繼續討論信用評分卡的模型python實現和分析,信用評分的方法和自動評分系統。(建立ABC卡則需要對自變量和因變量有針對性的進行調整,流程大體一致)
流程:
- 導入數據
- 數據預處理
- 探索分析
- 特征選擇
- 模型訓練
- 模型評估
- 模型結果轉評分
- 計算用戶總分
一、導入數據
import pandas as pd
import numpy as np
path=r'/Volumes/win/1/DataStudy/dataset/Give Me Some Credit/cs-training.csv'
df=pd.read_csv(path)
df.head()

查看各字段名
df.info()

有12列數據,將ID列設置為索引列
df=df.set_index('ID',drop=True) #設置id列為索引列 df.head()

將各英文字段轉為中文字段名方便理解
states={'SeriousDlqin2yrs':'好壞客戶', 'RevolvingUtilizationOfUnsecuredLines':'可用額度比值', 'age':'年齡', 'NumberOfTime30-59DaysPastDueNotWorse':'逾期30-59天筆數', 'DebtRatio':'負債率', 'MonthlyIncome':'月收入', 'NumberOfOpenCreditLinesAndLoans':'信貸數量', 'NumberOfTimes90DaysLate':'逾期90天筆數', 'NumberRealEstateLoansOrLines':'固定資產貸款量', 'NumberOfTime60-89DaysPastDueNotWorse':'逾期60-89天筆數', 'NumberOfDependents':'家屬數量'} df.rename(columns=states,inplace=True) df.head() #修改英文字段名為中文字段名

壞客戶是1,好客戶對應0,
二、數據預處理
- 缺失值處理
- 異常值處理
1、缺失值處理
查看各字段數據缺失情況
df.info()

月收入和家屬數量存在缺失
print("月收入缺失比:{:.2%}".format(df['月收入'].isnull().sum()/df.shape[0]))

print("家屬數量缺失比:{:.2%}".format(df['家屬數量'].isnull().sum()/df.shape[0]))

月收入缺失較大,使用平均值進行填充,家屬數量缺失較少,將缺失的刪掉,另外,如果字段缺失過大,將失去分析意義,可以將整個字段刪除
df=df.fillna({'月收入':df['月收入'].mean()}) df1=df.dropna() df1.shape

2、異常值處理
可以通過箱線圖觀察異常值
import matplotlib.pyplot as plt from pylab import mpl mpl.rcParams['font.sans-serif'] = ['SimHei'] x1=df['可用額度比值'] x2=df['負債率'] x3=df1["年齡"] x4=df1["逾期30-59天筆數"] x5=df1["逾期60-89天筆數"] x6=df1["逾期90天筆數"] x7=df1["信貸數量"] x8=df1["固定資產貸款量"] fig=plt.figure(figsize=(20,15)) ax1=fig.add_subplot(221) ax2=fig.add_subplot(222) ax3=fig.add_subplot(223) ax4=fig.add_subplot(224) ax1.boxplot([x1,x2]) ax1.set_xticklabels(["可用額度比值","負債率"], fontsize=20) ax2.boxplot(x3) ax2.set_xticklabels("年齡", fontsize=20) ax3.boxplot([x4,x5,x6]) ax3.set_xticklabels(["逾期30-59天筆數","逾期60-89天筆數","逾期90天筆數"], fontsize=20) ax4.boxplot([x7,x8]) ax4.set_xticklabels(["信貸數量","固定資產貸款量"], fontsize=20)

異常值處理消除不合邏輯的數據和超級離群的數據,可用額度比值應該小於1,年齡為0的是異常值,逾期天數筆數大於80的是超級離群數據,固定資產貸款量大於50的是超級離群數據,將這些離群值過濾掉,篩選出剩余部分數據。
df1=df1[df1['可用額度比值']<1] df1=df1[df1['年齡']>0] df1=df1[df1['逾期30-59天筆數']<80] df1=df1[df1['逾期60-89天筆數']<80] df1=df1[df1['逾期90天筆數']<80] df1=df1[df1['固定資產貸款量']<50] df1.shape

三、探索分析
探索分析在整個流程當中對數據的變動不大,主要起到了一種催化的作用,作為一種“啟動”開展后續工作,可以更好的了解到數據之間的一些聯系和變化規律。同時在多變量分析中通過相關性也可以過濾掉一部分變量。
- 單變量分析
- 多變量分析
1、單變量分析
是分析一個自變量和因變量之間的聯系,此處以年齡和好壞客戶為例進行分析。
將年齡均分成5組,求出每組的總的用戶數
age_cut=pd.cut(df1['年齡'],5) age_cut_group=df1['好壞客戶'].groupby(age_cut).count() age_cut_group

求各組的壞客戶數
age_cut_grouped1=df1["好壞客戶"].groupby(age_cut).sum() age_cut_grouped1

聯結
df2=pd.merge(pd.DataFrame(age_cut_group),pd.DataFrame(age_cut_grouped1),left_index=True,right_index=True) df2.rename(columns={'好壞客戶_x':'總客戶數','好壞客戶_y':'壞客戶數'},inplace=True) df2

加一列好客戶數
df2.insert(2,"好客戶數",df2["總客戶數"]-df2["壞客戶數"]) df2

再加一列壞客戶占比
df2.insert(2,"壞客戶占比",df2["壞客戶數"]/df2["總客戶數"]) df2

ax1=df2[["好客戶數","壞客戶數"]].plot.bar(figsize=(10,5)) ax1.set_xticklabels(df2.index,rotation=15) ax1.set_ylabel("客戶數") ax1.set_title("年齡與好壞客戶數分布圖")

ax11=df2["壞客戶占比"].plot(figsize=(10,5)) ax11.set_xticklabels([0,20,29,38,47,55,64,72,81,89,98,107]) ax11.set_ylabel("壞客戶率") ax11.set_title("壞客戶率隨年齡的變化趨勢圖")

可以看出隨着年齡的增長,壞客戶率在降低,其中38~55之間變化幅度最大
2、多變量分析
多變量分析就是對各個變量之間的相關性進行探索,線性回歸模型中的特征之間由於存在精確相關關系或高度相關關系而使模型估計失真或難以估計准確,相關系數為1或者-1變量之間的相關性最大,對於相關性大的兩組變量可以擇一處理
import seaborn as sns corr = df1.corr()#計算各變量的相關性系數 xticks = list(corr.index)#x軸標簽 yticks = list(corr.index)#y軸標簽 fig = plt.figure(figsize=(15,10)) ax1 = fig.add_subplot(1, 1, 1) sns.heatmap(corr, annot=True, cmap="rainbow",ax=ax1,linewidths=.5, annot_kws={'size': 9, 'weight': 'bold', 'color': 'blue'}) ax1.set_xticklabels(xticks, rotation=35, fontsize=15) ax1.set_yticklabels(yticks, rotation=0, fontsize=15) plt.show()

可以看到各變量之間的相關性比較小,所以不需要操作,一般相關系數大於0.6可以進行變量剔除。
四、特征選擇
這里使用IV值進行特征選擇
- WOE分箱
- WOE值計算
- IV值計算
- WOE值替換
1、WOE分箱
將連續變量離散化,特征離散化后,模型會更穩定,降低了模型過擬合的風險。同時由於邏輯回歸模型的每個變量的每種情況都會有對應的特征權值,使用分箱之后可以降低數據量,使模型泛化能力增強。
在學習筆記(一)中提到woe分箱可以先進行等頻分箱,然后按照woe的單調性進行調整,保持單調性可以使連續數據轉化為離散時數據之間能有一定的聯系和趨勢而不是孤立的幾個數據(另外單調從系數的正負上也反映的單變量對結果的影響趨勢),當然woe不一定保證完全單調遞增或遞減,保持一定趨勢即可,這種趨勢一般不會發生完全逆轉(通過調整分箱值使得單調遞增轉為遞減),所以按照客觀事實進行調整即可,我們將按照此方法求取woe 值。
此處取分箱值可以先把數據導出使用excel進行調整
也可以使用python自動分箱woe轉化,見風控數據分析學習筆記(三)Python實現woe自動分箱轉化
cut1=pd.qcut(df1["可用額度比值"],4,labels=False) cut2=pd.qcut(df1["年齡"],8,labels=False) bins3=[-1,0,1,3,5,13] cut3=pd.cut(df1["逾期30-59天筆數"],bins3,labels=False) cut4=pd.qcut(df1["負債率"],3,labels=False) cut5=pd.qcut(df1["月收入"],4,labels=False) cut6=pd.qcut(df1["信貸數量"],4,labels=False) bins7=[-1, 0, 1, 3,5, 20] cut7=pd.cut(df1["逾期90天筆數"],bins7,labels=False) bins8=[-1, 0,1,2, 3, 33] cut8=pd.cut(df1["固定資產貸款量"],bins8,labels=False) bins9=[-1, 0, 1, 3, 12] cut9=pd.cut(df1["逾期60-89天筆數"],bins9,labels=False) bins10=[-1, 0, 1, 2, 3, 5, 21] cut10=pd.cut(df1["家屬數量"],bins10,labels=False)
2、WOE值計算
rate=df1["好壞客戶"].sum()/(df1["好壞客戶"].count()-df1["好壞客戶"].sum()) def get_woe_data(cut): grouped=df1["好壞客戶"].groupby(cut,as_index = True).value_counts() woe=np.log(grouped.unstack().iloc[:,1]/grouped.unstack().iloc[:,0]/rate) return woe cut1_woe=get_woe_data(cut1) cut2_woe=get_woe_data(cut2) cut3_woe=get_woe_data(cut3) cut4_woe=get_woe_data(cut4) cut5_woe=get_woe_data(cut5) cut6_woe=get_woe_data(cut6) cut7_woe=get_woe_data(cut7) cut8_woe=get_woe_data(cut8) cut9_woe=get_woe_data(cut9) cut10_woe=get_woe_data(cut10)
隨便挑幾個變量看下woe
cut1_woe.plot.bar(color='b',alpha=0.3,rot=0)

cut2_woe.plot.bar(color='b',alpha=0.3,rot=0)

cut3_woe.plot.bar(color='b',alpha=0.3,rot=0)

可以看出woe已調整到具有單調性
3、IV值計算
def get_IV_data(cut,cut_woe): grouped=df1["好壞客戶"].groupby(cut,as_index = True).value_counts() cut_IV=((grouped.unstack().iloc[:,1]/df1["好壞客戶"].sum()-grouped.unstack().iloc[:,0]/(df1["好壞客戶"].count()-df1["好壞客戶"].sum()))*cut_woe).sum() return cut_IV #計算各分組的IV值 cut1_IV=get_IV_data(cut1,cut1_woe) cut2_IV=get_IV_data(cut2,cut2_woe) cut3_IV=get_IV_data(cut3,cut3_woe) cut4_IV=get_IV_data(cut4,cut4_woe) cut5_IV=get_IV_data(cut5,cut5_woe) cut6_IV=get_IV_data(cut6,cut6_woe) cut7_IV=get_IV_data(cut7,cut7_woe) cut8_IV=get_IV_data(cut8,cut8_woe) cut9_IV=get_IV_data(cut9,cut9_woe) cut10_IV=get_IV_data(cut10,cut10_woe) IV=pd.DataFrame([cut1_IV,cut2_IV,cut3_IV,cut4_IV,cut5_IV,cut6_IV,cut7_IV,cut8_IV,cut9_IV,cut10_IV],index=['可用額度比值','年齡','逾期30-59天筆數','負債率','月收入','信貸數量','逾期90天筆數','固定資產貸款量','逾期60-89天筆數','家屬數量'],columns=['IV']) iv=IV.plot.bar(color='b',alpha=0.3,rot=30,figsize=(10,5),fontsize=(10)) iv.set_title('特征變量與IV值分布圖',fontsize=(15)) iv.set_xlabel('特征變量',fontsize=(15)) iv.set_ylabel('IV',fontsize=(15))

IV

一般選取IV大於0.02的特征變量進行后續訓練,從以上可以看出所有變量均滿足,所以選取全部的
4、WOE值替換
df_new=pd.DataFrame() #新建df_new存放woe轉換后的數據 def replace_data(cut,cut_woe): a=[] for i in cut.unique(): a.append(i) a.sort() for m in range(len(a)): cut.replace(a[m],cut_woe.values[m],inplace=True) return cut df_new["好壞客戶"]=df1["好壞客戶"] df_new["可用額度比值"]=replace_data(cut1,cut1_woe) df_new["年齡"]=replace_data(cut2,cut2_woe) df_new["逾期30-59天筆數"]=replace_data(cut3,cut3_woe) df_new["負債率"]=replace_data(cut4,cut4_woe) df_new["月收入"]=replace_data(cut5,cut5_woe) df_new["信貸數量"]=replace_data(cut6,cut6_woe) df_new["逾期90天筆數"]=replace_data(cut7,cut7_woe) df_new["固定資產貸款量"]=replace_data(cut8,cut8_woe) df_new["逾期60-89天筆數"]=replace_data(cut9,cut9_woe) df_new["家屬數量"]=replace_data(cut10,cut10_woe) df_new.head()

五、模型訓練
信用評分卡主要使用的算法模型是邏輯回歸。logistic模型客群變化的敏感度不如其他高復雜度模型,因此穩健更好,魯棒性更強。另外,模型直觀,系數含義好闡述、易理解,使用邏輯回歸優點是可以得到一個變量之間的線性關系式和對應的特征權值,方便后面將其轉成一一對應的分數形式。

使用sklearn庫
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split x=df_new.iloc[:,1:] y=df_new.iloc[:,:1] x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.6,random_state=0) model=LogisticRegression() clf=model.fit(x_train,y_train) print('測試成績:{}'.format(clf.score(x_test,y_test)))

求特征權值系數coe,后面訓練結果轉分值時會用到:
coe=clf.coef_ #特征權值系數,后面轉換為打分規則時會用到 coe

y_pred=clf.predict(x_test)
六、模型評估
模型評估主要看AUC和K-S值
from sklearn.metrics import roc_curve, auc fpr, tpr, threshold = roc_curve(y_test, y_pred) roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, color='darkorange',label='ROC curve (area = %0.2f)' % roc_auc) plt.plot([0, 1], [0, 1], color='navy', linestyle='--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.0]) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('ROC_curve') plt.legend(loc="lower right") plt.show()

roc_auc

fig, ax = plt.subplots()
ax.plot(1 - threshold, tpr, label='tpr') # ks曲線要按照預測概率降序排列,所以需要1-threshold鏡像 ax.plot(1 - threshold, fpr, label='fpr') ax.plot(1 - threshold, tpr-fpr,label='KS') plt.xlabel('score') plt.title('KS Curve') plt.ylim([0.0, 1.0]) plt.figure(figsize=(20,20)) legend = ax.legend(loc='upper left') plt.show()

max(tpr-fpr)

ROC0.57, K-S值0.15左右,建模效果一般
七、模型結果轉評分

由此可知每個變量不同分段對應的分數是B、β、ω這三個值的乘積,其中β(特征權值系數coe)和ω(WOE值)在前面已知,所以只要知道了AB的值就可以給用戶打分了,這里要求AB的值要預先設定幾個閾值,
偏移量A=offset
比例因子B=factor
b=offset+factor*log(o)
b+p=offset+factor *log(2o)

假設好壞比為20的時候分數為600分,每高20分好壞比翻一倍
現在我們求每個變量不同woe值對應的分數刻度可得:
factor = 20 / np.log(2) offset = 600 - 20 * np.log(20) / np.log(2) def get_score(coe,woe,factor): scores=[] for w in woe: score=round(coe*w*factor,0) scores.append(score) return scores x1 = get_score(coe[0][0], cut1_woe, factor) x2 = get_score(coe[0][1], cut2_woe, factor) x3 = get_score(coe[0][2], cut3_woe, factor) x4 = get_score(coe[0][3], cut4_woe, factor) x5 = get_score(coe[0][4], cut5_woe, factor) x6 = get_score(coe[0][5], cut6_woe, factor) x7 = get_score(coe[0][6], cut7_woe, factor) x8 = get_score(coe[0][7], cut8_woe, factor) x9 = get_score(coe[0][8], cut9_woe, factor) x10 = get_score(coe[0][9], cut10_woe, factor) print("可用額度比值對應的分數:{}".format(x1)) print("年齡對應的分數:{}".format(x2)) print("逾期30-59天筆數對應的分數:{}".format(x3)) print("負債率對應的分數:{}".format(x4)) print("月收入對應的分數:{}".format(x5)) print("信貸數量對應的分數:{}".format(x6)) print("逾期90天筆數對應的分數:{}".format(x7)) print("固定資產貸款量對應的分數:{}".format(x8)) print("逾期60-89天筆數對應的分數:{}".format(x9)) print("家屬數量對應的分數:{}".format(x10))

可以看出分數越高,成為壞客戶的可能性越大。像年齡越大壞客率越低,可用額度比值、逾期筆數這幾個變量的分數跨度較大對最后的總分有更大的影響,這些都印證了前面探索分析的結果。
八、計算用戶總分
1.取自動分箱的邊界分割點
cu1=pd.qcut(df1["可用額度比值"],4,labels=False,retbins=True)
bins1=cu1[1]
cu2=pd.qcut(df1["年齡"],8,labels=False,retbins=True)
bins2=cu2[1]
# bins3=[-1,0,1,3,5,13]
# cut3=pd.cut(df1["逾期30-59天筆數"],bins3,labels=False)
cu4=pd.qcut(df1["負債率"],3,labels=False,retbins=True)
bins4=cu4[1]
cu5=pd.qcut(df1["月收入"],4,labels=False,retbins=True)
bins5=cu5[1]
cu6=pd.qcut(df1["信貸數量"],4,labels=False,retbins=True)
bins6=cu6[1]
2.各變量對應的分數求和,算出每個用戶的總分
def compute_score(series,bins,score): list = [] i = 0 while i < len(series): value = series[i] j = len(bins) - 2 m = len(bins) - 2 while j >= 0: if value >= bins[j]: j = -1 else: j -= 1 m -= 1 list.append(score[m]) i += 1 return list
path2=r'/Volumes/win/1/DataStudy/dataset/Give Me Some Credit/cs-test.csv'
test1 = pd.read_csv(path2)
test1['x1'] = pd.Series(compute_score(test1['RevolvingUtilizationOfUnsecuredLines'], bins1, x1)) test1['x2'] = pd.Series(compute_score(test1['age'], bins2, x2)) test1['x3'] = pd.Series(compute_score(test1['NumberOfTime30-59DaysPastDueNotWorse'], bins3, x3)) test1['x4'] = pd.Series(compute_score(test1['DebtRatio'], bins4, x4)) test1['x5'] = pd.Series(compute_score(test1['MonthlyIncome'], bins5, x5)) test1['x6'] = pd.Series(compute_score(test1['NumberOfOpenCreditLinesAndLoans'], bins6, x6)) test1['x7'] = pd.Series(compute_score(test1['NumberOfTimes90DaysLate'], bins7, x7)) test1['x8'] = pd.Series(compute_score(test1['NumberRealEstateLoansOrLines'], bins8, x8)) test1['x9'] = pd.Series(compute_score(test1['NumberOfTime60-89DaysPastDueNotWorse'], bins9, x9)) test1['x10'] = pd.Series(compute_score(test1['NumberOfDependents'], bins10, x10)) test1['Score'] = test1['x1']+test1['x2']+test1['x3']+test1['x4']+test1['x5']+test1['x6']+test1['x7']+test1['x8']+test1['x9']+test1['x10']+600
test1.to_csv(r'/Volumes/win/1/DataStudy/dataset/Give Me Some Credit/ScoreData.csv', index=False)
評分結果:

九、總結以及展望
本文通過對kaggle上的Give Me Some Credit數據的挖掘分析,結合信用評分卡的建立原理,從數據的預處理、變量選擇、建模分析到創建信用評分,創建了一個簡單的信用評分系統。
基於AI 的機器學習評分卡系統可通過把舊數據(某個時間點后)剔除掉后再進行自動建模、模型評估、並不斷優化特征變量,使得系統更加強大。
數據來源:https://www.kaggle.com/c/GiveMeSomeCredit/data
參考:
https://www.jianshu.com/p/f931a4df202c
https://blog.csdn.net/jiabiao1602/article/details/77869524
https://blog.csdn.net/shenxiaoming77/article/details/72627882/
https://www.zhihu.com/question/24490261
https://www.jianshu.com/p/c03de958099a
https://blog.csdn.net/sinat_26917383/article/details/51725102
https://www.sohu.com/a/164097017_305272
https://www.zhihu.com/question/37405102/answers/created