机器学习-专题详述

【代码】评分卡实例:完整建模代码

作者 : 老饼 发表日期 : 2022-06-26 10:28:04 更新日期 : 2024-10-11 02:46:32
本站原创文章,转载请说明来自《老饼讲解-机器学习》www.bbbdata.com



本文是评分卡实例中的完整建模代码,实例中的每个细节,都是以此代码为准

通过本文的代码,可以完整的构一个评分卡模型,本代码作为建立评分卡模型时复制与借鉴DEMO




  01. 评分卡-python代码-说明  




本节对本文的评分卡代码进行简单介绍




   评分卡代码-说明   


本代码是评分卡实例中的建模代码(不包含变量分析与分箱)
本代码相对较长,主要是为了便于学习,较详尽地展示各个细节
 在实际使用时,建议根据实际需要进行分段借鉴
  ✍️关于代码中使用的数据  
数据可在Q群下载地址:Q群:714824181,加群申请时填写:评分卡数据 
 
Q群中共有两份数据:
1. 原始数据《GiveMeSomeCredit.zip》
2. 分箱数据《t_score_card_grp.xlsx》    
原始数据进行分箱后就得到分箱数据,建模时要用的是分箱数据
利用本文的代码和分箱数据t_score_card_grp.xlsx,就能跑出完整评分卡结果






  02. 评分卡实例-代码  




本节展示完整的评分卡建模代码,方便学习、建模时借鉴




       评分卡实例-代码      


评分卡完整建模代码如下
# -*- coding: utf-8 -*-
"""
评分卡完整建模代码
"""
import pandas as pd
import numpy as np
import math
import matplotlib.pyplot as plt 
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics

#==============读取数据===================================
raw_data = pd.read_excel(r'C:\Users\dbat2\Desktop\t_score_card_grp.xlsx')
is_bad_col_name  =  'is_bad' 

#====建模数据预处理:转woe==================
'''获取woe表:特征、特征分组与对应的woe'''
def get_woe_tb(data,is_bad_col_name):                                                              
    woe_tb = pd.DataFrame(columns=['feture','grp','woe'])                                                    # 初始化woe映射表
    sample_num = data.shape[0]                                                                               # 样本个数
    bad_tt = data[is_bad_col_name].sum()                                                                     # 坏客户个数
    godd_tt = sample_num- bad_tt                                                                             # 好客户个数
																									        
    for col, row in data.items():                                                                            # 逐个变量(即每列)循环
        if(col !=is_bad_col_name):                                                                           # isbad标签列不算变量,不处理
            woe_df =data.groupby(col)[is_bad_col_name].agg([('bad_cn','sum'),('cn','count')])                # 按当前变量分组统计坏客户个数与总客户个数
            woe_df['grp'] = woe_df.index                                                                     # 添加组别号   
            woe_df.loc[woe_df['bad_cn']==0,'bad_cn']=1                                                       # 如果坏客户个数为0,则设为1.这样计算woe才不报错
            woe_df.loc[woe_df['bad_cn']==sample_num,'bad_cn'] -=1                                            # 如果好客户个数为0,则设为1.这样计算woe才不报错
            woe_df['woe']=np.log((woe_df['bad_cn']/bad_tt)/((woe_df['cn'] -woe_df['bad_cn'])/godd_tt))       # 按公式计算woe
            woe_df['feture'] = col                                                                           # 记录变量名称
            woe_tb = pd.concat([woe_tb,woe_df[['feture','grp','woe']]],axis=0)                               # 将当前变量的woe数据添加到woe总表
    return woe_tb                                                                                            # 返回woe总表
																									        
'''将数据转换为woe '''                                                                                      
def data_to_woe(data,is_bad_col_name):                                                                      
    woe_data = data.copy()                                                                                   # 先将数据复制一份
    woe_tb = get_woe_tb(data,is_bad_col_name)                                                                # 计算woe映射表
    for col, row in woe_data.items():                                                                        # 逐个变量(即每列)循环
        if(col !=is_bad_col_name):                                                                           # isbad标签列不算变量,不处理
            woe_df = woe_tb[woe_tb['feture']==col]                                                           # 将当前变量的woe映射提取出来
            woe_dict = {woe_df['grp'][col]:woe_df['woe'][col] for col in woe_df.index}                       # 将woe映射转换为字典对象{组号:woe值}
            woe_data[col] = woe_data[col].map(woe_dict)                                                      # 将变量组别转换为woe值
    return woe_data,woe_tb                                                                                   # 返回转换后的woe数据,以及woe映射表
																									        
# ---将数据转为woe---                                                                                       
data,woe_tb_all = data_to_woe(raw_data,is_bad_col_name)                                                      # data是转为woe的数据,woe_tb是每个特征的woe
y    = np.array(data[is_bad_col_name])                                                                       # y数据(数据中的is_bad列)
X    = np.array(data.drop(is_bad_col_name, axis=1))                                                          # x数据(数据中去掉is_bad列)
feature_names = data.columns[data.columns!=is_bad_col_name]                                                  # 变量名称

#==============训练模型===========================
'''逐步回归挑选变量'''
def select_feture(clf,X,y,feature_names):
    #-----逐步回归挑选变量--------------------                                                              
    select_fetures_idx = []                                                                                  # 已挑选的变量  
    var_pool   = np.arange(X.shape[1])                                                                       # 待挑选变量池
    auc_rec    = []                                                                                          # 用于记录AUC
    print("\n===========逐回步归过程===============")                                                        
    while(len(var_pool)>0):                                                                                  # 如果还有变量,则继续挑选变量
        max_auc  = 0                                                                                         # 初始化最佳的AUC
        best_var = None                                                                                      # 初始化本轮最佳变量
        #---选出剩余变量中能带来最好效果的变量--------                                                       
        for i in var_pool:                                                                                   # 逐个变量评估"加入该变量的效果"
            # -------将新变量和已选变量一起训练模型------                                                    
            cur_x = X[:,select_fetures_idx+[i]]                                                              # 新变量和已选变量作为建模数据                
            clf.fit(cur_x,y)                                                                                 # 训练模型
            pred_prob_y = clf.predict_proba(cur_x)[:,1]                                                      # 预测概率
            cur_auc = metrics.roc_auc_score(y,pred_prob_y)                                                   # 计算AUC
            # ------更新最佳变量---------------------------                                                  
            if(cur_auc>max_auc):                                                                             # 如果当前变量的AUC更好,
                max_auc =  cur_auc                                                                           # 将当前AUC作为最佳AUC
                best_var = i                                                                                 # 将当前变量作为最佳变量
        #-------检验新变量能否带来显著效果---------------------------                                       
        last_auc = auc_rec[-1] if len(auc_rec)>0 else 0.0001                                                 # 未加入本次变量时的AUC
        valid = True  if ((max_auc-last_auc)/last_auc>0.005) else False                                      # 检查加入本次变量AUC提高是否显著
																									        
        # 如果有显著效果,则将该变量添加到已选变量                                                          
        if(valid):                                                                                          
            print("本轮最佳AUC:",max_auc,",本轮最佳变量:",feature_names[best_var])                          # 打印本轮的最佳AUC和最佳变量
            auc_rec.append(max_auc)                                                                          # 记录本轮AUC
            select_fetures_idx.append(best_var)                                                              # 将本次选择的变量加入"已选变量池"
            var_pool = var_pool[var_pool!=best_var]                                                          # 删除待选变量池
        # 如果没有显著效果,则停止添加变量                                                                  
        else:                                                                                                # 如果效果不显著,就不再添加变量
            print("本轮最佳AUC:",max_auc,",本轮最佳变量:",feature_names[best_var],',效果不明显,不再添加变量')
            break
    return select_fetures_idx,feature_names[select_fetures_idx]                                              # 返回选择的变量索引以及变量名称
																									         
#----数据归一化------                                                                                        
xmin   = X.min(axis=0)                                                                                       # X的最小值
xmax   = X.max(axis=0)                                                                                       # X的最大值
X_norm =(X-xmin)/(xmax-xmin)                                                                                 # 将X归一化
#将数据分为训练数据与测试数据                                                                                
X_train,X_test,y_train,y_test = train_test_split(X_norm,y,test_size=0.2,random_state=0)                      # 将X,y分割为训练数据与测试数据(测试数据20%)

# 初始化模型(备注:penalty一开始训练时先用'none',训练出的系数不满意,再切换回l2)
clf = LogisticRegression(penalty='l2',dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, 
                   class_weight=None, random_state=0, solver='lbfgs', max_iter=100, multi_class='ovr',
                   verbose=0, warm_start=False, n_jobs=None, l1_ratio=None)

# ----用逐步回归挑选变量----
select_fetures_idx,select_fetures = select_feture(clf,X_train,y_train,feature_names)                         # 用逐步回归挑选变量
print("最终选用变量",len(select_fetures),"个:",list(select_fetures))                                         # 打印挑选的变量名称和变量个数

#----将挑选出的数据进行模型训练-------
clf.fit(X_train[:,select_fetures_idx],y_train)

#=============模型预测与结果评估======================================
pred_y            = clf.predict(X_train[:,select_fetures_idx])                                               # 用模型预测训练样本的好坏标签
pred_prob_y_train = clf.predict_proba(X_train[:,select_fetures_idx])[:,1]                                    # 用模型预测训练样本的坏客户概率
train_auc         = metrics.roc_auc_score(y_train,pred_prob_y_train)                                         # 计算训练样本的AUC
																							                 
pred_prob_y_test = clf.predict_proba(X_test[:,select_fetures_idx])[:,1]                                      # 用模型预测测试样本的坏客户概率
test_auc  = metrics.roc_auc_score(y_test,pred_prob_y_test)                                                   # 计算测试样本的AUC
																							                 
pred_prob_y = clf.predict_proba(X_norm[:,select_fetures_idx])[:,1]                                           # 用模型预测所有样本的坏客户概率
																							                 
print("\n============建模结果=================")                                                             
print("最终选用变量",len(select_fetures),"个:",list(select_fetures))                                         # 打印选择的变量个数
print("AUC:",train_auc)                                                                                      # 打印训练样本的AUC
print("AUC_Test:",test_auc)                                                                                  # 打印测试样本的AUC
																							                 
#------------提取系数w与阈值b-----------------------                                                         
w_norm = clf.coef_[0]                                                                                        # 模型系数(对应归一化数据)
b_norm = clf.intercept_[0]                                                                                   # 模型阈值(对应归一化数据)
w = w_norm/(xmax[select_fetures_idx]-xmin[select_fetures_idx])                                               # 模型系数(对应原始数据)
b = b_norm -  (w_norm/(xmax[select_fetures_idx] - xmin[select_fetures_idx])).dot(xmin[select_fetures_idx])   # 模型阈值(对应原始数据)
#------------打印模型结果信息--------------------------
print("\n=========模型参数==========")   
print("模型系数(对应原始数据):",w)
print("模型阈值(对应原始数据):",b)

#===========模型提取与模型预测================================================
# 模型提取
model_feture = feature_names[select_fetures_idx].to_list()                                                   # 模型使用的变量名称
woe_tb       = woe_tb_all[ woe_tb_all['feture'].isin(model_feture)].reset_index(drop=True)                   # 模型使用的WOE表格
md_logit     ={ 'woe_tb':woe_tb,'model_feture':model_feture,'w':w,'b':b }                                    # 将woe映射表、变量名称、模型权重阈值封装为模型

'''该方法用于模型预测'''
def cal_pred_prob(md,X):
    woe_tb = md['woe_tb']                                                                                    # WOE映射表
    w = md['w']                                                                                              # 模型权重
    b = md['b']                                                                                              # 模型阈值
    X_woe = X.copy()                                                                                         # 将X复制一份,用于转换为woe
    for feture in md_logit['model_feture']:                                                                  # 逐变量
        woe_df = woe_tb[woe_tb['feture']==feture]                                                            # 提取当前变量的woe映射
        woe_dict = {woe_df['grp'][col]:woe_df['woe'][col] for col in woe_df.index}                           # 将woe映射转换为字典对象
        X_woe[feture] = X_woe[feture].map(woe_dict)                                                          # 将样本的组别转换为woe值
    prob_y = 1/(1+np.exp(-(X_woe.dot(w)+ b) ))                                                               # 用公式预测
    return prob_y                                                                                            # 返回预测结果
																					                        
# 用提取的模型进行概率预测                                                                                  
model_X = raw_data[md_logit['model_feture']]                                                                 # 从原始数据中提取出模型预测时的X
prob_y  = cal_pred_prob(md_logit,model_X)                                                                    # 计算模型的预测结果

# 公式计算的概率与sklearn自带概率预测的最大误差
test_self_err = abs(pred_prob_y-prob_y).max()      

# ==============将逻辑回归模型转为分数表形式输出模型:===============================
# 依赖于:逻辑回归模型md_logit
#-----从逻辑回归模型中提取相应数据----------                                           
model_feture = md_logit['model_feture']                                                  # 模型所用的变量
b            = md_logit['b']                                                             # 模型的阈值
woe_tb       = md_logit['woe_tb'].copy()                                                 # 模型的woe映射表
											                                             
#-------分数转换参数设置-----                                                            
init_socre  = 600                                                                        # 初始分数
init_odds   = 50                                                                         # 初始分数所对应的odd
delta_score = 20                                                                         # 增加的分数
delta_rate  = 2                                                                          # 对应"增加分数",odd所下降的倍数
															                             
#----通过factor、offset计算基础分和每个特征每组得分---------                             
factor = - delta_score/np.log(delta_rate)                                                # 计算factor
offset = init_socre - factor*np.log(init_odds)                                           # 计算offset
															                             
base_score        = offset + factor*b                                                    # 计算基本分
score_tb          = woe_tb.copy()                                                        # 计算特征得分表
for i in range(len(model_feture)):                                                       # 逐变量记录特征系数
   score_tb.loc[score_tb['feture'] ==  model_feture[i],'coef'] = md_logit['w'][i]        # 特征系数
score_tb['score'] = factor*( score_tb['coef']*score_tb['woe'] )                          # 计算特征得分

#----模型输出:基础分,每个特征每组得分表,模型特征名称---------
md = {'score_tb'     : score_tb,
      'base_score'   : base_score,
      'model_feture' : model_feture}

# ==============通过模型计算分数(以评分卡表的形式)=====================================
#---分数计算函数------
def cal_score(md,X):
    # -----提取模型数据--------
    score_tb   = md['score_tb']                                                           # 特征得分表
    base_score = md['base_score']                                                         # 基础分
    fetures    = md['model_feture']                                                       # 模型特征
														                                  
    # -----将分数表转成字典形式----------                                                 
    score_dict ={feture:{}  for feture in fetures}                                        # 初始化特征得分字典
    for i in range(score_tb.shape[0]):                                                    # 将特征得分表中的数据逐条记填充到字典
        feture = score_tb.loc[i,'feture']                                                 # 本次的特征
        grp    = score_tb.loc[i,'grp']                                                    # 本次的组别
        score_dict[feture][grp]= score_tb.loc[i,'score']                                  # 记录当前特征、组别所对应的分数
														                                  
    # -----将特征组别转换成分数 --------                                                  
    score = X[fetures].copy()                                                             # 将X复制一份,作为分数的初始值
    for col in fetures:                                                                   # 逐个变量转换为分数
        score[col]= score[col].map(score_dict[col])                                       # 将当前变量的组别转换为分数
														                                  
    #-------计算总得分---------------------------                                         
    score['base_score'] = base_score                                                      # 添加基础分
    score['score'] = score.sum(axis=1)                                                    # 计算总分
    return score                                                                          # 返回总评分

#---计算样本的分数------
model_X = raw_data[md['model_feture']]
pred_rs = cal_score(md,model_X)
pred_rs['is_bad'] = raw_data['is_bad']

#=========================计算分数阈值表并查看分数分布===============================
# 依赖:pred_rs:存放样本客户的评分score与标签is_bad的
#----阈值表计算函数-----
def cal_score_threshold_tb(score_df,bin_step=10,is_bad_col_name='is_bad',score_col_name='score'):
    # -----计算分组起始结束字段--------------
    bin_start = math.trunc(score_df[score_col_name].min()/bin_step)*bin_step                     
    bin_end   =  math.trunc(score_df[score_col_name].max()/bin_step+1)*bin_step
    score_thd = pd.DataFrame(columns=['分组名称','本组客户','本组好客户','本组坏客户'])         
    #-----统计分组内的好坏客户个数-------
    for cur_bin in range(bin_start,bin_end,bin_step):
        cur_bin_name ='['+str(cur_bin)+'-'+str(cur_bin+bin_step)+')'
        cur_score_df = score_df[(score_df[score_col_name]>=cur_bin)&(score_df[score_col_name]<cur_bin+bin_step)][is_bad_col_name]
        bad_cn       = cur_score_df.sum()
        cn           = cur_score_df.shape[0]
        score_thd.loc[score_thd.shape[0]]=[cur_bin_name,cn,cn-bad_cn,bad_cn]
    #------计算阈值表其它字段-------------------    
    score_thd['总客户']      = score_thd['本组客户'].sum()
    score_thd['总好客户']    = score_thd['本组好客户'].sum()
    score_thd['总坏客户']    = score_thd['本组坏客户'].sum()
    score_thd['阈值']        = score_thd['分组名称'].apply(lambda x: '<'+x.split('-')[1].replace(')',''))  
    score_thd['损失客户']    = score_thd['本组客户'].cumsum()
    score_thd['损失客户%']   = score_thd['损失客户']/score_thd['总客户']
    score_thd['损失好客户']  = score_thd['本组好客户'].cumsum()
    score_thd['损失好客户%'] = score_thd['损失好客户']/score_thd['总好客户']
    score_thd['剔除坏客户']  = score_thd['本组坏客户'].cumsum()
    score_thd['剔除坏客户%'] = score_thd['剔除坏客户']/score_thd['总坏客户']
    tmp = score_thd['本组客户'].copy()
    tmp[tmp==0] = 1
    score_thd['本组坏客户占比']       = score_thd['本组坏客户']/tmp
    score_thd['损失客户中坏客户占比'] = score_thd['剔除坏客户']/score_thd['损失客户']
    return score_thd

# --------计算分数阈值表---------------
score_df  = pred_rs[['score','is_bad']]
score_thd = cal_score_threshold_tb(score_df,bin_step=10)

# --------画出分数分布------------------
x_axis = score_thd['分组名称'].apply(lambda x: x.split('-')[0].replace('[',''))  
plt.bar(x_axis, score_thd['本组好客户'], align="center", color="#66c2a5",label="good")
plt.bar(x_axis, score_thd['本组坏客户'], align="center", bottom=score_thd['本组好客户'], color='r', label="bad")
plt.rcParams["figure.figsize"] = (9, 4) # 设置figure_size尺寸
plt.legend()
plt.show()
代码运行结果如下:
 
 
 
可以看到,用逐步回归选择了4个变量作为最终的入模变量,最终建模AUC为0.85
 从分数分布图中可看到,分数分布倾向于二八分布(帕累托分布),在国内小贷中,则是正态分布更常见些




      评分卡实例-代码说明       


代码主要包括五个模块:
 1、分组转woe                                
 
将分组数据转换为woe数据   
 2、建模                                          
 
(1) 数据归一化                     
(2) 逐步回归挑选变量          
(3) 训练逻辑回归模型         
(4) AUC打印                      
(5) 提取模型参数               
 3、模型提取与生成评分卡               
 
(1) 提取逻辑回归模型          
(2) 生成评分卡                   
            (3) 展示如何利用评分卡对样本评分
 4、生成阈值表                              
    
                             计算样本评分,对分数进行分段,最后统计阈值表
 5、评分分布图                              
    
画出样本评分在各分段的分布





好了,以上就是评分卡的完整建模代码了~







 End 





联系老饼