评分卡

import scipy
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from imblearn.over_sampling import SMOTE
from sklearn.linear_model import LogisticRegression as LR
from sklearn.model_selection import train_test_split
plt.rcParams[u'font.sans-serif'] = ['Arial Unicode MS']

'''
1)我们首先把连续型变量分成一组数量较多的分类型变量,比如,将几万个样本分成100组,或50组
2)确保每一组中都要包含两种类别的样本,否则IV值会无法计算
3)我们对相邻的组进行卡方检验,卡方检验的P值很大的组进行合并,直到数据中的组数小于设定的N箱为止
4)我们让一个特征分别分成[2,3,4.....20]箱,观察每个分箱个数下的IV值如何变化,找出最适合的分箱个数
5)分箱完毕后,我们计算每个箱的WOE值, ,观察分箱效果
这些步骤都完成后,我们可以对各个特征都进行分箱,然后观察每个特征的IV值,以此来挑选特征。
'''
class ChiMerge(object):
    def __init__(self, X, y, n=5, q=20, graph=True):
        self.X = X
        self.y = y
        self.n = n
        self.q = q
        self.graph = graph
        self.woe = None
        
    def get_woe(self, num_bins):
        columns = ["min","max","count_0","count_1"]
        df = pd.DataFrame(num_bins,columns=columns)
        df["total"] = df.count_0 + df.count_1
        df["percentage"] = df.total / df.total.sum()
        df["bad_rate"] = df.count_1 / df.total
        df["good%"] = df.count_0/df.count_0.sum()
        df["bad%"] = df.count_1/df.count_1.sum()
        df["woe"] = np.log(df["good%"] / df["bad%"])
        return df
    
    def get_iv(self, df):
        rate = df["good%"] - df["bad%"]
        iv = np.sum(rate * df.woe)
        return iv
    
    def fit(self, train_data):
        '''获取最好的分箱个数
           train_data:包含特征和标签的数据集
           X:数据集中特征的名称 []
           y:数据集中标签的名称 []
           q:初始分箱个数
           n:目标分箱个数
        '''
        woe_ = {}
        for x in self.X:
            temp_data=train_data[[x,self.y]].copy()
            temp_data["qcut"],updown = pd.qcut(temp_data[x], retbins=True, q=self.q, duplicates="drop")
            temp_data["qcut"].value_counts()
            
            count_y0 = temp_data[temp_data[self.y] == 0].groupby(by="qcut").count()[self.y]
            count_y1 = temp_data[temp_data[self.y] == 1].groupby(by="qcut").count()[self.y]
            
            num_bins = [*zip(updown,updown[1:],count_y0,count_y1)]#num_bins值分别为每个区间的上界,下界,0出现的次数,1出现的次数
    
            #确保每个箱中都有0和1
            for i in range(self.q):
                if 0 in num_bins[0][2:]:#第一个if循环确保num_bins中的第一组一定同时存在正负样本,使用向后合并
                    num_bins[0:2] = [(num_bins[0][0],num_bins[1][1],num_bins[0][2]+num_bins[1][2], num_bins[0][3]+num_bins[1][3])]
                    continue
            
                for i in range(len(num_bins)):#第二个if循环确保num_bins中剩下组一定同时存在正负样本,使用向前合并
                    if 0 in num_bins[i][2:]:
                        num_bins[i-1:i+1] = [(num_bins[i-1][0],num_bins[i][1],num_bins[i-1][2]+num_bins[i][2],num_bins[i-1][3]+num_bins[i][3])]
                        break
                    
                else:
                    break
            
            IV = []
            axisx = []
            while len(num_bins) > self.n:
                pvs = []
                for i in range(len(num_bins)-1):
                    x1 = num_bins[i][2:]
                    x2 = num_bins[i+1][2:]
                    pv = scipy.stats.chi2_contingency([x1,x2])[1]#计算卡方值
                    pvs.append(pv)
                    
                i = pvs.index(max(pvs))#选择卡方值最大的分箱
                num_bins[i:i+2] = [(num_bins[i][0],num_bins[i+1][1],num_bins[i][2]+num_bins[i+1][2],num_bins[i][3]+num_bins[i+1][3])]
                
                bins_df = pd.DataFrame(self.get_woe(num_bins))
                axisx.append(len(num_bins))
                IV.append(self.get_iv(bins_df))
                
            woe_[x] = bins_df
            self.woe = woe_
            
            if self.graph:
                plt.figure()
                plt.plot(axisx,IV,c='k')
                plt.xticks(axisx)
                plt.title(x)
                plt.ylabel('IV值')
                plt.xlabel('分箱')
                plt.grid()
                plt.savefig('分箱操作图像/{}'.format(x))
    
    
    def get_bins_of_col(self, auto_col_bins, hand_bins, train_data):
        '''得到分箱的临界值
           auto_col_bins:自动分箱
           hand_col_bins:手动分箱
           train_data:包含特征和标签的数据集
           X:数据集中特征的名称 []
           y:数据集中标签的名称 ''
           q:初始分箱个数
           n:目标分箱个数
        '''
        hand_bins = {k:[-np.inf,*v[:-1],np.inf] for k,v in hand_bins.items()}#保证区间覆盖使用 np.inf替换最大值,用-np.inf替换最小值
        
        bins_of_col = {}
            
        # 生成自动分箱的分箱区间和分箱后的 IV 值
        for auto in auto_col_bins:
            bins_df = self.woe[auto]
            bins_list = sorted(set(bins_df["min"]).union(bins_df["max"]))
            
            bins_list[0],bins_list[-1] = -np.inf,np.inf#保证区间覆盖使用 np.inf 替换最大值 -np.inf 替换最小值
            bins_of_col[auto] = bins_list
        
        bins_of_col.update(hand_bins)#合并手动分箱数据
        
        return bins_of_col
    
    def get_woe_data(self, df, col, bins):
        df = df[[col,self.y]].copy()
        df["cut"] = pd.cut(df[col],bins)
        bins_df = df.groupby("cut")[self.y].value_counts().unstack()
        bins_df["woe"] =np.log((bins_df[0]/bins_df[0].sum())/(bins_df[1]/bins_df[1].sum()))
        #self.woe = bins_df["woe"]
        return bins_df["woe"]
    
    
    def transform(self, bins_of_col, train_data, test_data):
        """得到训练和测试的woe值
        bins_of_col:分箱的依据,各个特征的分箱临界值
        train_data:包含X,y的训练矩阵
        test_data:包含X,y的测试矩阵
        y:数据集中标签的名称 ''
        """    
        #将所有特征的WOE存储到字典当中
        woeall = {}
        for col in bins_of_col:
            woeall[col] = self.get_woe_data(train_data,col,bins_of_col[col])
        
        self.woe = woeall
        #不希望覆盖掉原本的数据,创建一个新的DataFrame,索引和原始数据model_data一模一样
        train_woe = pd.DataFrame(index=train_data.index)
        test_woe = pd.DataFrame(index=test_data.index)
        
        #将原数据分箱后,按箱的结果把WOE结构用map函数映射到数据中
        for col in bins_of_col:
            train_woe[col] = pd.cut(train_data[col],bins_of_col[col]).map(woeall[col])
            test_woe[col] = pd.cut(test_data[col],bins_of_col[col]).map(woeall[col])
            
        #将标签补充到数据中
        train_woe[self.y] = train_data[self.y]
        test_woe[self.y] = test_data[self.y]
                    
        return train_woe,test_woe

简单找了一个金融贷款数据测试一下

LR准确率(未分箱): 0.6737207362840293
KS_train: 0.38 AUC_train: 0.75
KS_test: 0.37 AUC_test: 0.75
==================================================
LR准确率(分箱): 0.7740811342109966
KS_train: 0.56 AUC_train: 0.86
KS_test: 0.55 AUC_test: 0.86
==================================================
XGBoost准确率(分箱): 0.7911628045511407
KS_train: 0.62 AUC_train: 0.89
KS_test: 0.58 AUC_test: 0.87
==================================================

使用分箱后准确率大幅度上升,使用逻辑回归过拟合现象不明显;
使用XGBoost总的准确率 较 LR(分箱) 小幅度提高,但KS指标显示存在一定过拟合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值