「实战案例」基于Python语言开发的信用评分卡(上)

数据大师
数据大师
数据大师
294
文章
0
评论
2021-08-1811:04:57 评论 1,070 11836字
摘要

今天给大家介绍一个经典数据挖掘应用案例——金融常见信用评分卡的开发解析。

 

  • 一、信用评分卡的应用场景

    与信用评分卡挂钩的信用评级在全球金融领域有广泛应用。它涉及到公司管理,企业债发行,企业融资,企业上市,企业并购,个人炒股和购买公司债券等多个场景。

    1.企业债发行企业主体信用评级越高,意味着该企业抗风险能力、盈利能力越强,发债融资成本越低。作为企业董事会,管理者,决策者应该了解企业主要财务指标,了解自身企业评级。企业发债时都需要评级。国内评级这块主要分为AAA、AA+、AA、AA-、A+。企业评级过低会限制某些工具的发行额度、交易场所可质押性等等如果是评级太低,企业发行债券没人买。目前广大投资者的投资门槛是AA。2.企业上市企业上市融资时也需要信用评级。目前国内只有优质企业才能上市融资,上市牌照是很难拿的。如果财务指标和资质不达标,则不能上市。3.企业并购企业并购是企业之间的兼并和收购行为。如果收购方不能很好了解对方企业评级,买来的企业可能是包装过的垃圾企业。失败企业并购会让企业背上巨大债务负担。4.个人炒股个人炒股投资者如果不懂企业评级,也难以买到优质股,容易被人忽悠买到垃圾股。建议不会数据科学的人趁早离开股市和金融投资,否则可能欠下巨额债务。如果个人通过学习机器学习模型,可以识别有价值企业,股票,债券,财富升值概率显著高于不懂模型的人。

    二、金融信贷基础概念

    在进行项目实施开发前,对应用场景进行深入了解,是我们数据人的基本功。

    1.信用风险

    信贷业务,又称贷款业务,是商业银行等信贷机构最重要的资产业务和主要赢利手段。信贷机构通过放款收回本金和利息,扣除成本后获得利润。对有贷款需求的用户,信贷机构首先要对其未来的还款表现进行预测,然后将本金借贷给还款概率大的用户。但这种借贷关系,可能发生信贷机构(通常是银行)无法收回所欠本金和利息而导致现金流中断和回款成本增加的可能性风险,这就是信用风险,它是金融风险的主要类型。

    2信用评分

    在信贷管理领域,关于客户信用风险的预测,目前使用最普遍的工具为信用评分卡,它源于20世纪的银行与信用卡中心。在最开始的审批过程中,用户的信用等级由银行聘用的专家进行主观评判。而随着数据分析工具的发展、量化手段的进步,各大银行机构逐渐使用统计模型将专家的评判标准转化为评分卡模型。如今,风险量化手段早已不局限于银行等传统借贷机构,持牌互联网公司的金融部门、持牌消费金融公司等均有成体系的风险量化手段。其应用的范围包括进件、贷后管理及催收等。信用评分不但可以筛选高风险客户,减少损失发生,也可以找出相对优质的客户群,发掘潜在商机。

    顾名思义,评分卡是一张有分数刻度和相应阈值的表。对于任何一个用户,总能根据其信息找到对应的分数。将不同类别的分数进行汇总,就可以得到用户的总分数。信用评分卡,即专门用来评估用户信用的一张刻度表,这里我们举一个简单的例子:假设我们有一个评分卡,包含四个变量(特征),即居住条件、年龄、贷款目的和现址居住时长(见表2-1)

    「实战案例」基于Python语言开发的信用评分卡(上)

    表2-1 简单评分卡

    用表2-1这张简单的评分卡,我们能轻而易举地计算得分。一个47岁、租房、在当前住址住了10年、想借钱度假的申请者得到53分(20+17+16+0=53),另一个25岁、有自己的房产、在当前住址住了2年、想借钱买二手车的人也同样得到53分(5+30+9+9=53)。同样地,一个38岁、与父母同住、在当前住址住了18个月、想借钱装修的人也得到53分(15+20+4+14=53)。事实上,我们一共有七个组合可以得到53分,他们虽然各自情况都不一样,但对贷款机构来说代表了同样的风险水平。该评分系统采用了补偿机制,即借款人的缺点可以用优点去弥补。

    总的来说,信用评分卡就是通过用数据对客户还款能力和还款意愿进行定量评估的系统。从20世纪发展至今,其种类已非常多,目前应用最广泛最多的主要分为以下四种:

    • 申请评分卡(ApplicationCard):申请评分卡通常用于贷前客户的进件审批。在没有历史平台表现的客群中,外部征信数据及用户的资产质量数据通常是影响客户申请评分的主要因素。
    • 行为评分卡(BehaviorCard):行为评分卡用于贷中客户的升降额度管理,主要目的是预测客户的动态风险。由于客户在平台上已有历史数据,通常客户在该平台的历史表现对行为评分卡的影响最大。
    • 催收评分卡(CollectionCard):催收评分卡一般用于贷后管理,主要使用催收记录作为数据进行建模。通过催收评分对用户制定不同的贷后管理策略,从而实现催收人员的合理配置。
    • 反欺诈评分卡(Anti-fraudCard):反欺诈评分卡通常用于贷前新客户可能存在的欺诈行为的预测管理,适用于个人和机构融资主体。

    其中前三种就是我们俗称的"ABC"卡。A卡一般可做贷款0-1年的信用分析;B卡则是在申请人一定行为后,有了较大消费行为数据后的分析,一般为3-5年;C卡则对数据要求更大,需加入催收后客户反应等属性数据。

    四种评分卡中,最重要的就是申请评分卡,目的是把风险控制在贷前的状态;也就是减少交易对手未能履行约定契约中的义务而造成经济损失的风险。违约风险包括了个人违约、公司违约、主权违约,本案例只讲个人违约。

    三、案例背景

    发放贷款给合适的客户是银行收入的一大来源,在条件允许的范围内,银行希望贷出去的钱越多越好,贷款多意味着对应的收入也多,但是如果把钱贷给了信用不好的人或者企业,就会面临贷款收不回来的情况。

    对于贷款申请的审批,传统人工审批除了受审批人员的专业度影响外,也会受到其主观影响,另一方面专业人员的培养通常也需要一个较长的周期。 而信用评分卡技术的变量、评分标准和权重都是给定的。同一笔业务,只要录入要素相同,就会给出一个参考结果。既提高了审批效率也减少了人为因素的干扰,如人工审批过程中的随意性和不一致性。确保了贷款审批标准的客观性、标准化和一致性;保证风险特征相近的贷款申请能够得到相似的审批结果,如审批通过与否、授信额度、利率水平等。

    信用评分卡技术在20世纪50年代即广泛应用于消费信贷,尤其是在信用卡领域。随着信息技术的发展和数据的丰富,信用评分卡技术也被用于对小微企业贷款的评估,最初是拥有大量客户数据信息的大型银行。如富国银行1993年首先在小微企业贷款领域应用信用评分卡技术。随后,美国很多社区银行等中小银行也开始广泛应用小微企业信用评分系统。

    现中国的某银行信用卡中心的贷款申请业务近期又增长了10%,原来的申请评分卡已出现数据偏移,监测到审批准确度有下降趋势。作为信用卡中心的风控建模分析师,小王接到风控总监下发的任务:基于近两年的历史数据(见"数据集介绍"的Train_data.csv),重新建立一张"申请评分卡"用于预测申请者未来是否会发生90天以上的逾期行为,以此来判断给哪些客户予以发放,哪些客户予以拒绝。

    1.数据集介绍

    • 训练数据:Train_data.csv。该数据即有特征X又有标签y,是小王用来建模的数据。
    • 预测数据:Predict_data.csv。该数据只有特征X没有标签y,为小王需要预测的数据。也即新进的申请信用卡的客户相关信息。

     

    四、信用评分卡如何做数据准备工作

    1、导入相关依赖模块

    # 加载所需包%matplotlib inlineimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport osimport woe.feature_process as fpimport woe.eval as evalfrom collections import Counterfrom sklearn.model_selection import train_test_splitimport seaborn as sns

    plt.rc("font", family="SimHei", size=13) # 显示中文plt.rcParams["axes.unicode_minus"] = False # 用来正常显示负

    import syssys.path.append("E:大数据实验室_教研部案例集自定义函数") # 添加自定义函数文件所在的环境路径from summary_df import data_summary #用于数据探查from score_scale import score_scalefrom score_predict import score_predict

    2.数据探索

    os.chdir(r"E:大数据实验室_教研部案例集案例1_信用评分卡data")data_org = pd.read_csv("Train_data.csv")

    data_summary(data_org)

    「实战案例」基于Python语言开发的信用评分卡(上)

    3.数据准备

    (1)数据清洗-ALL数据

    data_org.drop_duplicates(inplace=True)

    #data_org.shape#(149391, 11)

    • 可见数据中存在重复行。

    a = Counter(data_org["Label"])print("原数据集的正负样本量为:",a)print("原数据集好坏样本的比例为:{:.5f}".format(a[0]/a[1]))

    原数据集的正负样本量为: Counter({0: 139382, 1: 10009})原数据集好坏样本的比例为:13.92567

    • result: 我们知道一般建模所需的好坏客户样本比率约为3:1~5:1。评分卡建模通常要求正负样本的数量都不少于1500个。但样本量也并非越大越好,当总样本量超过50000个时,模型的效果将不再随着样本量的增加而有显著变化了,而且数据处理与模型训练过程也较为耗时。
    • 这里我们需要对样本做欠采样(Subsampling):
      • 负样本取10000条
      • 正样本取40000条

    数据筛选与数据平衡

    from sklearn.utils import shuffledata_org = shuffle(data_org) # 全体数据做随机打乱data_bad = data_org[data_org["Label"]==1] # 坏客户data_good = data_org[data_org["Label"]==0] # 好客户data_bad_select = data_bad.iloc[:10000,:] #选前10000条坏客户data_good_select = data_good.iloc[:40000,:] #选前40000条好客户data_select = pd.concat([data_bad_select,data_good_select]) #最终数据

    # data_select.shape#(50000, 11)

    拆分训练集和测试集

    from sklearn.model_selection import train_test_splitfrom collections import Counter# 数据提取与数据分割X = data_select.drop(["Label"],axis=1) # 特征列y = data_select["Label"] # 标签列X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=0)print("训练集正负样本数据量:{}".format(Counter(y_train)))print("测试集正负样本数据量:{}".format(Counter(y_test)))

    训练集正负样本数据量:Counter({0: 28017, 1: 6983})测试集正负样本数据量:Counter({0: 11983, 1: 3017})

    (2)数据清洗-训练数据

    缺失值处理

    查看缺失值分布情况

    import missingno as msno # 处理缺失值的包,需要安装

    msno.matrix(X_train)plt.show()

    「实战案例」基于Python语言开发的信用评分卡(上)

    plt.imshow(~X_train.isna(),aspect="auto")plt.gray()

    「实战案例」基于Python语言开发的信用评分卡(上)

    缺失值处理的常用方法:

    缺失值处理方法的选择,主要依据是业务逻辑和缺失值占比,在对预测结果的影响尽可能小的情况下,对缺失值进行处理以满足算法需求,所以要理解每个缺失值处理方法带来的影响,下面的缺失值处理方法没有特殊说明均是对特征(列)的处理:

    1. 占比较多:如80%以上,直接删除该变量
    • 如果某些行缺失值占比较多,或者缺失值所在字段是苛刻的必须有值的,删除行
  • 占比一般:如30%-80%:将缺失值作为单独的一个分类
  • 占比少:10%-30%:多重插补:认为若干特征之间有相关性,则可以相互预测缺失值
  • 占比较少:10%以下,单一值替换,如中位数,众数,或者从业务理解上用0值、特殊值填充
  • 在决策树中可以将缺失值处理融合到算法里:按比重分配
  • 如果特征是连续的,则其他已有值分箱
  • 如果特征是分类的,考虑其他分类是否需要重分箱
  • 需满足的假设:MAR:Missing At Random:数据缺失的概率仅和已观测的数据相关,即缺失的概率与未知的数据无关,即与变量的具体数值无关
  • 迭代(循环)次数可能的话超过40,选择所有的变量甚至额外的辅助变量
  • 详细的计算过程参考:Multiple Imputation by Chained Equations: What is it and how does it work?

这里的占比并不是固定的,例如缺失值占比只有5%,仍可以用第二种方法,主要依据业务逻辑和算法需求

本数据的缺失值处理逻辑:

  • 对于信用评分卡来说,由于所有变量都需要分箱,故这里缺失值作为单独的箱子即可
  • 对于最后一列Dependents,缺失值占比只有2.56%,作为单独的箱子信息不够,故做单一值填补,这列表示家庭人口数,有右偏的倾向,且属于计数的数据,故使用中位数填补
  • 这里没必要进行多重插补,下面的多重插补只是为了让读者熟悉此操作

单一值替换填充缺失值

# Dependents的缺失值用其中位数替换NOD_median = X_train["Dependents"].median()X_train["Dependents"].fillna(NOD_median,inplace=True) # fillna填补缺失值的函数,这里用中位数填补X_train["Dependents"].fillna(NOD_median,inplace=True) # 对测试集填充缺失值#MonthlyIncome 列的缺失值超过5%,可作为单独的一个分类处理。这里统一将赋值为-8X_train["MonthlyIncome"].fillna(-8,inplace=True)X_train["MonthlyIncome"].fillna(-8,inplace=True)

C:Anaconda3libsite-packagespandascoregeneric.py:6287: SettingWithCopyWarning:A value is trying to be set on a copy of a slice from a DataFrameSee the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copyself._update_inplace(new_data)

check数据是否还有缺失值

a = data_summary(X_train)data_missing_summary = a[a["percent"]<1]ifdata_missing_summary.shape[0]==0:print("数据已经没有缺失值!")else:print("数据还存在缺失值,请输出数据框data_missing_summary查看缺失情况!")

数据已经没有缺失值!

异常值处理

异常值处理的常用方法:

  1. 删除对应的样本数据,即所在的行
  2. 替换成缺失值,当缺失值处理
  3. 盖帽法处理

结合业务逻辑和算法需求判断是否需要处理异常值以及如何处理,一般情况下盖帽法即可,即将极端异常的值改成不那么异常的极值。不过一些算法例如决策树中连续变量的异常值也可以不做处理。

自定义盖帽法函数

盖帽法将某连续变量均值上下三倍标准差范围外的记录替换为均值上下三倍标准差值,即盖帽处理。

def cap(x,lower=True,upper=True):"""函数功能:盖帽法处理异常值。参数解释:x: 表示输入的Series对象lower:表示是否替换1%分位数upper:表示是否替换99%分位数"""# 生成分位数:1%和99%的分位数quantile = [0.01,0.99]Q01,Q99=x.quantile(quantile).values.tolist()#替换异常值为指定的分位数if lower:out = x.mask(x<Q01,Q01)if upper:out = x.mask(x>Q99,Q99)return(x)

# 先保留原数据X_train盖帽法中的极端值,以备后面训练集和预测集处理其异常值时使用df_min_max = X_train.apply(lambda x:x.quantile([0.01,0.99]))

#盖帽法处理异常值X_train = X_train.apply(cap)

data_summary(X_train)

「实战案例」基于Python语言开发的信用评分卡(上)

(3)数据清洗-测试集数据

# 用训练的信息填充X_test["Dependents"].fillna(NOD_median,inplace=True) # 对测试集填充缺失值X_test["MonthlyIncome"].fillna(-8,inplace=True)

def cap2(x,df_min_max,lower=True,upper=True):"""函数功能:用训练集中的盖帽法极端值处理测试集的异常值。参数解释:x: 表示输入的Series对象;df_min_max:是训练集中的盖帽法极端值数据框。"""col_name = x.nameQ01,Q99 = df_min_max[col_name].values#替换异常值为指定的分位数if lower:out = x.mask(x<Q01,Q01)if upper:out = x.mask(x>Q99,Q99)return(x)

# 用训练集中的盖帽法极端值处理测试集的异常值for itr in df_min_max.columns:X_test[itr] = cap2(X_test[itr],df_min_max)

# 合并数据X,Ydata_train = pd.concat([X_train,y_train],axis=1)data_test = pd.concat([X_test,y_test],axis=1)

训练数据特征处理

WOE分箱

我们要制作评分卡,最终想要得到的结果是要给各个特征进⾏分档,以便业务⼈员能够根据新客户填写的信息为客户打分。我们知道变量(即特征)的形态可分为离散型和连续型,离散型天然就是分档的,因此,我们需要重点如何使连续变量分档,即连续变量离散化。

连续变量离散化,我们也常称为分箱或者分组操作。它是评分卡制作过程中⼀个非常重要的步骤,是评分卡最难,也是最核⼼的思路。目的就是使拥有不同属性的客户被分成不同的类别,进而评上不同的分数。在评分卡建模流程中,我们常用WOE(Weight of Evidence,迹象权数)方法对变量进行分箱。

WOE分箱的好处:

  • 避免变量值中出现极端值(Outliers)的情形,
  • 减少模型过度配适(Overfitting)的现象。

(4)Python代码实现woe

"""woe分箱, iv and transform:woe包的使用条件:因变量y的列名必须为"target",列值必须为0或1."""data_train.rename(columns={"Label": "target"}, inplace=True) # 修改列名为"target"civ_dict ={}all_cnt = len(data_train) # 所有样本数据量n_positive = sum(data_train["target"]) # 正样本数据量n_negtive = all_cnt - n_positive # 负样本数据量for column in set(data_train.columns)-set(["target"]):if data_train[column].dtypes == "object":civ = fp.proc_woe_discrete(data_train, column, n_positive, n_negtive, 0.05*all_cnt, alpha=0.02)else:civ = fp.proc_woe_continuous(data_train, column, n_positive, n_negtive, 0.05*all_cnt, alpha=0.02)civ_dict[column]=civ

--------------process continuous variable:Age--------------------------process continuous variable:Dependents------------------process continuous variable:OverDue_60_89days-----------------process continuous variable:OverDue_90days-------------------process continuous variable:CreditLoans----------------------process continuous variable:DebtRatio-------------------process continuous variable:OverDue_30_59days-----------------process continuous variable:MortgageLoans---------------process continuous variable:AvailableBalanceRatio---------------process continuous variable:MonthlyIncome----------

变量的IV值

用与之相关的另一个重要概念,IV值(Information Value,信息值)则用来衡量该变量(特征)对好坏客户的预测能力。

查看每个变量对应的IV值

civ_df = eval.eval_feature_detail(list(civ_dict.values()),out_path="output_feature_detail_20210218.csv") # 输出分箱结果# 整理输出每个变量的IV值iv_result_a = civ_df[["var_name","iv"]].drop_duplicates()iv_result = iv_result_a["iv"]iv_result.index = iv_result_a["var_name"].values

AgeDependentsOverDue_60_89daysOverDue_90daysCreditLoansDebtRatioOverDue_30_59daysMortgageLoansAvailableBalanceRatioMonthlyIncome

# 查看每个变量的IV值iv_result.sort_values().plot(kind="barh")

「实战案例」基于Python语言开发的信用评分卡(上)

iv_result.sort_values(ascending=False)

AvailableBalanceRatio 1.173317OverDue_90days 0.845325OverDue_30_59days 0.723239OverDue_60_89days 0.574336MonthlyIncome 0.084926DebtRatio 0.082108CreditLoans 0.073128Age 0.063129MortgageLoans 0.054396Dependents 0.035709Name: iv, dtype: float64

  • 可见所有的IV值均大于0.02,故这里使用所有变量

# 删除iv值过小的变量iv_thre = 0.02 #阈值设置为0.02keep_vars_iv = iv_result[iv_result > iv_thre]# 筛选出iv值(大于阈值)有辨别能力的变量

keep_vars_iv

Age 0.063129Dependents 0.035709OverDue_60_89days 0.574336OverDue_90days 0.845325CreditLoans 0.073128DebtRatio 0.082108OverDue_30_59days 0.723239MortgageLoans 0.054396AvailableBalanceRatio 1.173317MonthlyIncome 0.084926Name: iv, dtype: float64

(5)相关系数热力图

通过变量直接的相关性系数,建立相关性矩阵,观察变量之间的关系,可以进行初步的多重共线性筛选。

全部变量的相关信息热力图

p_corr = data_train.corr() # 相关系数矩阵,对称矩阵p_corr_tril = np.tril(p_corr,0) # 因此取下三角矩阵label_col = p_corr.columnsres= pd.DataFrame(p_corr_tril,columns=label_col,index=label_col)sns.heatmap(res,cmap="Blues");

「实战案例」基于Python语言开发的信用评分卡(上)

部分变量相关度(>threshold)热力图

#只相关系数较高的值,比如大于threshold以上的值threshold = 0.75 # 阈值,通常取0.75,0.8附近的值p_corr_tril[p_corr_tril<=threshold]=0.01 # 相关度小于threshold的值都置为0.01

label_col = p_corr.columnsres2= pd.DataFrame(p_corr_tril,columns=label_col,index=label_col)sns.heatmap(res2,cmap="Blues");

「实战案例」基于Python语言开发的信用评分卡(上)

# 查看iv值col=["OverDue_30_59days","OverDue_60_89days", "OverDue_90days"]keep_vars_iv.loc[col]

OverDue_30_59days 0.723239OverDue_60_89days 0.574336OverDue_90days 0.845325Name: iv, dtype: float64

  • 可以卡看出"OverDue3059days","OverDue6089days", "OverDue90days"这三个变量之间具有非常高的相关度。为剔除多重共线性的影响,我们这里只保留IV值最大的变量"OverDue90days",删除"OverDue3059days","OverDue6089days"。

# 经过IV值和相关度筛选后保留的变量keep_vars = set(keep_vars_iv.index) - {"OverDue_30_59days","OverDue_60_89days"}

(6)训练集和测试集WOE特征转换

keep_var_woe={} # 筛选后保留的变量{变量名:变量名_woe}for i in keep_vars:keep_var_woe[i] = i+"_woe"

#WOE特征转换for var_name,var_woe in keep_var_woe.items():data_train[var_woe] = fp.woe_trans(data_train[var_name], civ_dict[var_name])data_test[var_woe] = fp.woe_trans(data_test[var_name], civ_dict[var_name])

X_train_woe = data_train[keep_var_woe.values()] # train集的WOE特征X_test_woe = data_test[keep_var_woe.values()] # test集的WOE特征

print("训练集经过WOE转换后的shape:{}".format(X_train_woe.shape))print("测试集经过WOE转换后的shape:{}".format(X_test_woe.shape))

训练集经过WOE转换后的shape:(35000, 8)测试集经过WOE转换后的shape:(15000, 8)

End.

作者:CDA数据分析师

本文为转载分享,如果涉及作品、版权和其他问题,请联系我们第一时间删除(微信号:lovedata0520)

更多文章前往首页浏览http://www.itongji.cn/

 

  • 我的微信公众号
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: