kaggle风控(一)——give me some credit

信用风险计量体系包括主体评级模型和债项评级两部分。主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。
A卡,又称为申请者评级模型,主要应用于相关融资类业务中新用户的主体评级,适用于个人和机构融资主体。
B卡,又称为行为评级模型,主要应用于相关融资类业务中存量客户在续存期内的管理,如对客户可能出现的逾期、延期等行为进行预测,仅适用于个人融资主体。
C卡,又称为催收评级模型,主要应用于相关融资类业务中存量客户是否需要催收的预测管理,仅适用于个人融资主体。
F卡,又称为欺诈评级模型,主要应用于相关融资类业务中新客户可能存在的欺诈行为的预测管理,适用于个人和机构融资主体。
我们今天来做一个A卡申请者评级模型,kaggle上比较经典的一个数据集。


一、数据集

数据来源:https://www.kaggle.com/c/GiveMeSomeCredit/data
本案例是kaggle上一个很经典的评分卡案例:give me some credit。

rawdata=pd.read_csv(path+'\\give-me-some-credit-dataset\\cs-training.csv',index_col=[0])
rawdata.head()

为了方便大家理解本数据集每个特征的含义,也为了操作方便,我们将每个特征名字改为中文。

column={'ID':'用户ID',
        'SeriousDlqin2yrs':'好坏客户',
        'RevolvingUtilizationOfUnsecuredLines':'可用额度比值',
        'age':'年龄',
        'NumberOfTime30-59DaysPastDueNotWorse':'逾期30-59天笔数',
        'DebtRatio':'负债率',
        'MonthlyIncome':'月收入',
        'NumberOfOpenCreditLinesAndLoans':'信贷数量',
        'NumberOfTimes90DaysLate':'逾期90天笔数',
        'NumberRealEstateLoansOrLines':'固定资产贷款量',
        'NumberOfTime60-89DaysPastDueNotWorse':'逾期60-89天笔数',
        'NumberOfDependents':'家属数量'}
rawdata.rename(columns=column,inplace=True)
rawdata.head()
# 查看数据维度
rawdata.shape
# 查看数据类型和缺失值
rawdata.info()
# 查看标签分布
rawdata['好坏客户'].value_counts()

数据维度(150000, 11),“月收入”和“家属数量”有缺失,标签分布0:1=139974:10026。

二、特征工程

2.1 去除重复值

rawdata.drop_duplicates(inplace=True)    # 去重
rawdata.index=range(rawdata.shape[0])    # 重置索引
rawdata.shape

去重后数据维度变为(149391, 11)。

2.2 缺失值处理

# 查看缺失值情况
rawdata.isnull().mean()

可以看到,“月收入”缺失达到近20%,“家属数量”缺失较少仅有2.6%的确实。

# 查看缺失值分布形态
null_list=['月收入','家属数量']
for col in null_list:
    plt.style.use('bmh')
    plt.hist(rawdata[col].dropna().values)
    plt.show()

可以看到,数据呈现正偏态。由于“家属数量”缺失较少,我们直接用中位数填充。

rawdata['家属数量'].fillna(rawdata['家属数量'].median(),inplace=True)

“月收入”这个特征对于征信来说非常重要,在这里采用随机森林填补法来填充,即将缺失的特征值作为预测值,将未缺失的“月收入”数据作为训练样本的标签。

# 定义随机森林填补函数
def fill_missing_rf(X,y,to_fill):
    """
    使用随机森林填补一个特征的缺失值的函数
    参数:
    X:要填补的特征矩阵
    y:完整的,没有缺失值的标签
    to_fill:字符串,要填补的那一列的名称
    """
    #构建我们的新特征矩阵和新标签
    df = X.copy()
    fill = df.loc[:,to_fill]
    df = pd.concat([df.loc[:,df.columns != to_fill],pd.DataFrame(y)],axis=1)
    
    #找出我们的训练集和测试集
    Ytrain = fill[fill.notnull()]
    Ytest = fill[fill.isnull()]
    Xtrain = df.iloc[Ytrain.index,:]
    Xtest = df.iloc[Ytest.index,:]
    
    #用随机森林回归来填补缺失值
    from sklearn.ensemble import RandomForestRegressor as rfr
    rfr = rfr(n_estimators=100)
    rfr = rfr.fit(Xtrain, Ytrain)
    Ypredict = rfr.predict(Xtest)
    
    return Ypredict

用这个函数预测缺失的“月收入”部分,并替换缺失值。

X = rawdata.iloc[:,1:]
y = rawdata['好坏客户']

y_pred = fill_missing_rf(X,y,'月收入')
rawdata.loc[rawdata.loc[:,'月收入'].isnull(),'月收入'] = y_pred

2.3 异常值处理

我们先看下各列的描述性统计。

#描述性统计
rawdata.describe([0.01,0.1,0.25,.5,.75,.9,.99]).T

如果这样看的不明显,完全可以再画出箱线图。

x1=rawdata['可用额度比值']
x2=rawdata['负债率']
x3=rawdata['年龄']
x4=rawdata['逾期30-59天笔数']
x5=rawdata['逾期60-89天笔数']
x6=rawdata['逾期90天笔数']
x7=rawdata['信贷数量']
x8=rawdata['固定资产贷款量']

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)

我们可以先来看描述性统计,“年龄”的最小值居然是0,但是根据我们的常识,小于18岁是不能在银行办理信用卡或是贷款业务的。我们来看下小于18岁的有几行。

rawdata[rawdata['年龄']<18]

会发现仅有“年龄”为0的这一行数据,很显然是异常值,将其删除。

rawdata=rawdata[rawdata['年龄']!=0]

另外,通过箱线图,我们看到三个逾期天数指标(逾期30-59天、逾期60-80天,逾期90天)是存在比较严重的离群值的,这一点我们通过描述性统计也可以加以验证,这三个指标的99%分位数与max相差过大,存在异常。
因为本案例定义逾期90天以上就算作坏客户,我们先查看一下这三个逾期指标超过90天的数据。

rawdata[rawdata['逾期30-59天笔数']>90].shape
rawdata[rawdata['逾期60-89天笔数']>90].shape
rawdata[rawdata['逾期90天笔数']>90].shape

可以看到维度均为(225, 11),可以猜测这三个指标出现异常的情况发生在相同的行。

rawdata[(rawdata['逾期90天笔数']>90) & (rawdata['逾期60-89天笔数']>90) & (rawdata['逾期30-59天笔数']>90)].shape

数据维度也是(225, 11),果然验证了我们的猜想。将异常行删去。

# 删除异常数据
rawdata=rawdata[rawdata.loc[:,'逾期30-59天笔数']<90]

2.4 处理样本不平衡

我们在之前查看过标签分布情况,0:1=139974:10026,是存在严重的样本不平衡的。这是在金融风控中非常常见的,因为会存在严重违约的用户毕竟是少数。
在这里我们采取SMOTE上采样的方法处理数据不平衡。需要导入另一个机器学习库imblearn。

import imblearn
from imblearn.over_sampling import SMOTE

smote=SMOTE(random_state=404)
X,y=smote.fit_sample(df.iloc[:,1:],df.iloc[:,0])

此时我们再查看标签分布会发现数据已经平衡。

pd.Series(y).value_counts()

我们将经过平衡后的数据合并为一个新的数据集。

y=y.reshape(-1,1)
data=np.concatenate((y,X),axis=1)
data=pd.DataFrame(data,columns=df.columns)
data.head()

三、分箱

常用的分箱方法主要有三种:等频分箱、等宽分箱、CART树分箱。我们选择最优的CART树分箱方法,将分箱的函数都写在一个cart_bins.py的类文件里,下面贴出了最重要的一个自动分箱函数。

def auto_bins(df,y,x,p=0.01,max_bin=20,plot=True):
    """
    参数
    df:传入需要进行分箱的数据集
    y:标签名
    x:进行分箱的特征名
    p:叶子包含观测数的最小占比(0<p<0.5),默认为0.01
    max_bin:最大箱子数(大于2),默认为20
    plot:是否画WOE图
    """
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.model_selection import GridSearchCV
    import numpy as np
    import pandas as pd
    
    # 指明X和Y变量数据,排除NA值
    X = np.array(df[x][df[x].notna()]).reshape(-1,1)
    Y = df[y][df[x].notna()]
   
    # 调参,得到最合适的叶节点观测占比
    gini_impure = np.linspace(0,0.001,50)
    param_grid = {"min_impurity_decrease":gini_impure}
    Es = GridSearchCV(DecisionTreeClassifier(),param_grid,cv=5,iid=False)
    Es.fit(X,Y)
    
    # 使用最优参数构建树模型
    ES = DecisionTreeClassifier(min_impurity_decrease = Es.best_params_["min_impurity_decrease"],
                                max_leaf_nodes = max_bin,
                                min_samples_leaf = p)    
    ES.fit(X,Y)
    
    # 输出树结构
    TS = tree_structure(ES,x)
    
    # 抽取测试节点的阈值
    TH = ES.tree_.threshold[ES.tree_.children_left != ES.tree_.children_right]
    # 计算最优分割点
    CP = np.append(TH,np.array([X.max(),X.min()]))
    CP.sort()
    
    # 使用自定义分箱函数
    smbin_cust = smbin_cu(df,y,x,cutpoints = CP,plot = plot)    
    
    out_a = pd.Series({"Tree_structure":TS})
    
    out = smbin_cust.append(out_a)

    return(out)

我们可以来看下几个特征分箱后的WOE图。

# 可用额度比值
cut1 = auto_bins(data,'好坏客户','可用额度比值')
可用额度比值
# 年龄
cut2 = auto_bins(data,'好坏客户','年龄')
年龄
# 月收入
cut3 = auto_bins(data,'好坏客户','月收入')
月收入
# 信贷数量
cut4 = auto_bins(data,'好坏客户','信贷数量')
信贷数量
# 负债率
cut5 = auto_bins(data,'好坏客户','负债率')
负债率
# 固定资产贷款量
cut6 = auto_bins(data,'好坏客户','负债率')
固定资产贷款量
# 逾期30-59天笔数
cut7 = auto_bins(data,'好坏客户','逾期30-59天笔数')
逾期30-59天笔数
# 逾期60-89天笔数
cut8 = auto_bins(data,'好坏客户','逾期60-89天笔数')
逾期60-89天笔数
# 逾期90天笔数
cut9 = auto_bins(data,'好坏客户','逾期90天笔数')
逾期90天笔数
# 家属数量
cut10 = auto_bins(data,'好坏客户','家属数量')
家属数量

四、LR建模

金融风控中最常用的模型是逻辑回归模型,虽然现在很多企业开始使用性能更好的GBDT、XGBoost,甚至更加改进的Light GBM进行建模,但LR有着它自己独特的优势:
1、逻辑回归经过信贷历史的反复验证是有效的
2、模型比较稳定相对成熟
3、建模过程透明而不是黑箱
4、不太容易过拟合
本案例选择LR逻辑回归进行建模。
首先切分训练集和测试集。

from sklearn.model_selection import train_test_split

X_train,X_test,Y_train,Y_test = train_test_split(X,Y,test_size=0.3,random_state=1221)

用statsmodels库建模。

import statsmodels.api as sm
from statsmodels.stats.outliers_influence import variance_inflation_factor
import scipy.stats as stats

glmodel = sm.GLM(Y_train,X_train,family=sm.families.Binomial()).fit()
glmodel.summary()

一般评分卡模型中常用ROC曲线和KS曲线来评价模型的好坏。


ROC曲线

KS曲线

可以看到,AUC值为0.86,KS最大值为0.5631,模型效果还可以。

五、生成评分卡

最后生成我们的评分卡。

scorecard = smscale(glmodel,x_list,pdo=43,score=1150,odds=10)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容