风控建模-----逻辑回归

目录

一、逻辑回归原理

二、逻辑回归优缺点

三、LR评分卡的构建

四、参考文献


一、逻辑回归原理

    一个样本可以理解为发生的一次事件,对于0/1分类问题来讲,产生的结果有两种可能,符合伯努利试验的概率假设。因此,我们可以说样本的生成过程即为伯努利试验过程,产生的结果(0/1)服从伯努利分布,那么对于第i个样本,概率公式表示如下:

\begin{aligned} P\left ( y^{i}=1|x^{i};\theta \right ) &= h_{\theta }\left ( x^{i} \right ) \\ P\left ( y^{i}=0|x^{i};\theta \right ) &= 1-h_{\theta }\left ( x^{i} \right ) \end{aligned}

   将上面两个公式合并在一起,可以得到第i个样本正确预测的概率:

P\left ( y^{i}|x^{i};\theta \right ) = \left ( h_{\theta }\left ( x^{i} \right )^{y^{i}} \right )\cdot \left ( 1-h_{\theta }\left ( x^{i} \right )\right )^{1-y^i}

   对于所有的样本,假设每条样本生成过程独立,在整个样本空间中(N个样本)的概率分布(即似然函数)为

P\left ( Y|X;\theta \right )=\prod_{i=1}^{N}\left ( h_{\theta } \left ( x^{i} \right )^{y_{i}}\right )\left ( 1-h_{\theta } \left ( x^{i} \right )^{1-y^{i}}\right )

接下来我们就可以通过极大似然估计方法求概率参数。

\begin{aligned} g\left ( z\right )&=\frac{1}{1+e^{-z}} \\ {g}'\left ( z \right )&=g\left ( x \right )\left ( 1-g\left ( x \right ) \right ) \end{aligned}

     g(z)为sigmoid函数,它的输入范围为−∞→+∞,而值域刚好为(0,1),正好满足概率分布为(0,1)的要求。而且它是一个单调上升的函数,具有良好的连续性,不存在不连续点,可导。

\begin{aligned} L\left ( \theta \right )&=P\left ( \vec{Y}|X;\theta \right ) \\ &=\prod_{i=1}^{N}\left ( h_{\theta } \left ( x^{i} \right )^{y_{i}}\right )\left ( 1-h_{\theta } \left ( x^{i} \right )^{1-y^{i}}\right ) \end{aligned}

为了方便参数求解,对这个公式取对数,可得对数似然函数

\begin{aligned} l\left ( \theta \right )&=\sum_{i=1}^{N}log\ l\left ( \theta \right ) \\ &=\sum_{i=1}^{N}y^{i}log\left ( h_{\theta }\left ( x^{i} \right ) \right )+\left ( 1-y^{i} \right )log\left ( 1-h_{\theta }\left ( x^{i} \right ) \right ) \end{aligned}

使用随机梯度下降的方法,对参数进行更新:

\frac{\partial }{\partial \theta_{j} }l\left ( \theta \right )=\left ( y-h_{\theta }\left ( x \right ) \right )x_{j}

迭代下述公式可救的参数:

\theta_{j}:= \theta_{j}+a\left ( y^{i}-h_{\theta }\left ( x^{i} \right ) \right )s_{j}^{i}

a表示学习率。

    LR其数学目的是求解能够让模型对数据拟合程度最高的参数的值,以此构建预测函数 ,然后将特征矩阵输入预测函数来计算出逻辑回归的结果y

二、逻辑回归优缺点

优点:

        1. 结构简单,回归计算快,可解释性强,对线性关系的拟合效果好

        2.支持增量训练

        3. 逻辑回归返回的分类结果不是固定的01,而是以小数形式呈现的类概率数字

缺点:

        1. 预测精度一般,对变量要求高,非数值需要编码,不能容忍缺失值,需要归一化。

三、LR评分卡的构建

原始代码git地址:

1. 计算登彔日期到放款日期之间的间隔天数,可以看到绝大部分的天数在180天以内.

data1['logInfo'] = data1['LogInfo3'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d'))
data1['Listinginfo'] = data1['Listinginfo1'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d'))
data1['ListingGap'] = data1[['logInfo','Listinginfo']].apply(lambda x: (x[1]-x[0]).days,axis = 1)
plt.hist(data1['ListingGap'],bins=200)
plt.xlim(xmin = 0, xmax = 400)
plt.title('Days between login date and listing date')

图片一

2. 由于绝大部分观测样本的时间跨度在半年内,所以我们选取半年内的时间切片,考虑以月为单位的时间切片,则可以衍生出30天、60天、90天、120天、150天、180天等多种选择。

  • 时间切片内的登录的次数
  • 时间切片内不同的登录方式的个数
  • 时间切片内不同登录方式的平均个数
'''
使用180天作为最大的时间窗口计算新特征
所有可以使用的时间窗口可以有7 days, 30 days, 60 days, 90 days, 120 days, 150 days and 180 days.
在每个时间窗口内,计算总的登录次数,不同的登录方式,以及每种登录方式的平均次数
'''
time_window = [7, 30, 60, 90, 120, 150, 180]
var_list = ['LogInfo1','LogInfo2']
data1GroupbyIdx = pd.DataFrame({'Idx':data1['Idx'].drop_duplicates()})
data1GroupbyIdx.head()

for tw in time_window:
    data1['TruncatedLogInfo'] = data1['Listinginfo'].map(lambda x: x + datetime.timedelta(-tw))
    temp = data1.loc[data1['logInfo'] >= data1['TruncatedLogInfo']]
    for var in var_list:
        #count the frequences of LogInfo1 and LogInfo2
        count_stats = temp.groupby(['Idx'])[var].count().to_dict()
        data1GroupbyIdx[str(var)+'_'+str(tw)+'_count'] = data1GroupbyIdx['Idx'].map(lambda x: count_stats.get(x,0))

        # count the distinct value of LogInfo1 and LogInfo2
        Idx_UserupdateInfo1 = temp[['Idx', var]].drop_duplicates()
        print(temp[['Idx', var]].shape, Idx_UserupdateInfo1.shape)
        uniq_stats = Idx_UserupdateInfo1.groupby(['Idx'])[var].count().to_dict()
        data1GroupbyIdx[str(var) + '_' + str(tw) + '_unique'] = data1GroupbyIdx['Idx'].map(lambda x: uniq_stats.get(x,0))

        # calculate the average count of each value in LogInfo1 and LogInfo2
        data1GroupbyIdx[str(var) + '_' + str(tw) + '_avg_count'] = data1GroupbyIdx[[str(var)+'_'+str(tw)+'_count',str(var) + '_' + str(tw) + '_unique']].\
            apply(lambda x: DeivdedByZero(x[0],x[1]), axis=1)
        
data1GroupbyIdx.head()    

 3. 在信用风控模型的开发中,数据集中度是常见的问题。 即在变量中,某单一数值的占比就占了全部样本值的 绝大多数。例如,在一批训练样本中,学历为本科的样本占了全部样本的90%。具有极高的集中度的字段。

col_most_values_df = pd.DataFrame.from_dict(col_most_values, orient = 'index')
col_most_values_df.columns = ['max percent']
display(col_most_values_df.head())
col_most_values_df = col_most_values_df.sort_values(by = 'max percent', ascending = False)
pcnt = list(col_most_values_df[:500]['max percent'])
vars = list(col_most_values_df[:500].index)
plt.bar(range(len(pcnt)), height = pcnt)
plt.title('Largest Percentage of Single Value in Each Variable')
#计算多数值产比超过90%的字段中,少数值的坏样本率是否会显著高于多数值。由于所有的少数值的坏样本率并没有显著高于多数值,意味着这些变量可以直接剔除
large_percent_cols = list(col_most_values_df[col_most_values_df['max percent']>=0.9].index)

bad_rate_diff = {}
for col in large_percent_cols:
    large_value = col_large_value[col]
    temp = allData[[col,'target']]
    temp[col] = temp.apply(lambda x: int(x[col]==large_value),axis=1)
    bad_rate = temp.groupby(col).mean()
    if bad_rate.iloc[0]['target'] == 0:
        bad_rate_diff[col] = 0
        continue
    bad_rate_diff[col] = np.log(bad_rate.iloc[0]['target']/bad_rate.iloc[1]['target'])

bad_rate_diff_sorted = sorted(bad_rate_diff.items(),key=lambda x: x[1], reverse=True)
bad_rate_diff_sorted_values = [x[1] for x in bad_rate_diff_sorted]
plt.bar(x = range(len(bad_rate_diff_sorted_values)), height = bad_rate_diff_sorted_values)


for col in large_percent_cols:
    if col in numerical_var:
        numerical_var.remove(col)
    else:
        categorical_var.remove(col)
    del allData[col]

图四

4.数据的质量检验-数据缺失(data missing)

数据缺失度是数据质量检验的一个重要项。需要从两个维度检验数据缺失度:
  • 字段维度,即某个字段在全部样本上的缺失值个数的占比
  • 样本维度,即某条样本在所有字段上的缺失值的占比
一般而言,字段维度的缺失程度会大于样本维度的缺失程度
缺失值处理
  • 舍弃该字段戒该条记彔:缺失占比太高
  • 补缺:缺失占比丌高,可用均值法、众数法、回归法等
  • 作为特殊值:将缺失看成一种特殊值
'''
对类别型变量,如果缺失超过80%, 就删除,否则当成特殊的状态
'''

def MissingCategorial(df,x):
    missing_vals = df[x].map(lambda x: int(x!=x))
    return sum(missing_vals)*1.0/df.shape[0]

missing_pcnt_threshould_1 = 0.8
for col in categorical_var:
    missingRate = MissingCategorial(allData,col)
    print('{0} has missing rate as {1}'.format(col,missingRate))
    if missingRate > missing_pcnt_threshould_1:
        categorical_var.remove(col)
        del allData[col]
    if 0 < missingRate < missing_pcnt_threshould_1:
        # In this way we convert NaN to NAN, which is a string instead of np.nan
        allData[col] = allData[col].map(lambda x: str(x).upper())

allData_bk = allData.copy()


'''
检查数值型变量, 缺失数值随机采样。
'''
def MissingContinuous(df,x):
    missing_vals = df[x].map(lambda x: int(np.isnan(x)))
    return sum(missing_vals) * 1.0 / df.shape[0]

missing_pcnt_threshould_2 = 0.8
deleted_var = []
for col in numerical_var:
    missingRate = MissingContinuous(allData, col)
    print('{0} has missing rate as {1}'.format(col, missingRate))
    if missingRate > missing_pcnt_threshould_2:
        deleted_var.append(col)
        print('we delete variable {} because of its high missing rate'.format(col))
    else:
        if missingRate > 0:
            not_missing = allData.loc[allData[col] == allData[col]][col]
            #makeuped = allData[col].map(lambda x: MakeupRandom(x, list(not_missing)))
            missing_position = allData.loc[allData[col] != allData[col]][col].index
            not_missing_sample = random.sample(list(not_missing), len(missing_position))
            print(len(not_missing_sample),not_missing_sample[:10])
            allData.loc[missing_position,col] = not_missing_sample
            #del allData[col]
            #allData[col] = makeuped
            missingRate2 = MissingContinuous(allData, col)
            print('missing rate after making up is:{}'.format(str(missingRate2)))

if deleted_var != []:
    for col in deleted_var:
        numerical_var.remove(col)
        del allData[col]

5. 特征分箱+woe+iv

分箱操作的优点

  • 稳定:分箱后,变量原始值在一定范围内的波动不会影响到评分结果
  • 缺失值处理:缺失值可以作为一个单独的箱,或者与其他值进行合并作为一个箱
  • 异常值处理:异常值可以和其他值合并作为一个箱
  • 无需归一化:从数值型变为类别型,没有尺度的差异

分箱操作的缺点

  • 有一定的信息丢失:数值型变量在分箱后,变为取值有限的几个箱
  • 需要编码:分箱后的变量是类别型,不能直接带入逻辑回归模型中,需要进行一次数值编码

上一节,具体讲过chimerge分箱,本章就不展开说了。

样本均匀性

基于卡方分箱法对变量进行分箱

对不同类型的变量,分箱的处理是不同的:
(1)数值型变量可直接分箱
(2)取值个数较多的类别型变量,需要用bad rate做编码转换成数值型变量,再分箱
(3)取值个数较少的类别型变量不需要分箱,但是要检查是否每个类别都有好坏样本。如果有类别只有好或坏,需要合并

'''
对于类别型变量,按照以下方式处理
1,如果变量的取值个数超过5,计算bad rate进行编码,保留。
2,除此之外,其他任何类别型变量如果有某个取值中,对应的样本全部是坏样本或者是好样本,进行合并, 对bad_rate从小到大排列,从前遍历,对为0数据合并,如果连着三个都是负样本会合并到一起,最大箱占比>0.9也会剔除。。
'''
deleted_features = []   #将处理过的变量删除,防止对后面建模的干扰
encoded_features = {}   #将bad rate编码方式保存下来,在以后的测试和生产环境中需要使用
merged_features = {}    #将类别型变量合并方案保留下来
var_IV = {}  #save the IV values for binned features       #将IV值保留和WOE值
var_WOE = {}
for col in categorical_var:
    print('we are processing {}'.format(col))
    if len(set(trainData[col]))>5:
        print('{} is encoded with bad rate'.format(col))
        col0 = str(col)+'_encoding'

        #(1), 计算坏样本率并进行编码
        encoding_result = BadRateEncoding(trainData, col, 'target')
        trainData[col0], br_encoding = encoding_result['encoding'],encoding_result['bad_rate']

        #(2), 将(1)中的编码后的变量也加入数值型变量列表中,为后面的卡方分箱做准备
        numerical_var.append(col0)

        #(3), 保存编码结果
        encoded_features[col] = [col0, br_encoding]

        #(4), 删除原始值

        deleted_features.append(col)
    else:
        bad_bin = trainData.groupby([col])['target'].sum()
        #对于类别数少于5个,但是出现0坏样本的特征需要做处理
        if min(bad_bin) == 0:
            print('{} has 0 bad sample!'.format(col))
            col1 = str(col) + '_mergeByBadRate'
            #(1), 找出最优合并方式,使得每一箱同时包含好坏样本
            mergeBin = MergeBad0(trainData, col, 'target')
            #(2), 依照(1)的结果对值进行合并
            trainData[col1] = trainData[col].map(mergeBin)
            maxPcnt = MaximumBinPcnt(trainData, col1)
            #如果合并后导致有箱占比超过90%,就删除。
            if maxPcnt > 0.9:
                print('{} is deleted because of large percentage of single bin'.format(col))
                deleted_features.append(col)
                categorical_var.remove(col)
                del trainData[col]
                continue
            #(3) 如果合并后的新的变量满足要求,就保留下来
            merged_features[col] = [col1, mergeBin]
            WOE_IV = CalcWOE(trainData, col1, 'target')
            var_WOE[col1] = WOE_IV['WOE']
            var_IV[col1] = WOE_IV['IV']
            #del trainData[col]
            deleted_features.append(col)
        else:
            WOE_IV = CalcWOE(trainData, col, 'target')
            var_WOE[col] = WOE_IV['WOE']
            var_IV[col] = WOE_IV['IV']

对于连续型变量,处理方式如下:
1,利用卡方分箱法将变量分成5个箱
2,检查坏样本率的单带性,如果发现单调性不满足,就进行合并,直到满足单调性
'''

'''
ChiMerge函数的 技术细节:
1. 可以设置缺失值或者异常值不进行分箱。
2. 等分数据集时,根据比例对应的数据。
3. 多箱合并时,chi2考虑最小合并,从0到N-1遍历,(0,1),(1,2)组队,对最小值替换,对合并值剔除。
4. 多项合并,检查是否有分箱没有分到坏样本,根据分箱的个数,确定索引,然后找分箱号前后,计算chi2进行合并。
5. 可选,对分箱的均匀性进行合并。
6. 最后返回所有分箱的分位点。

Monotone_Merge函数的 技术细节:
1. 获取当前特征的非单调的个数和索引。
2. 遍历所有的非单调的个数,
        其中每个判断与前合并,与后合并,非单调点个数的减少,均匀性进行判断。得到单个点的情况。
   综合考虑每个非单调点的个数或者均匀情况。
3. 满足单调性,或者总箱数等于2,结束。


var_cutoff = {}
for col in numerical_var:
    print("{} is in processing".format(col))
    col1 = str(col) + '_Bin'

    #(1),用卡方分箱法进行分箱,并且保存每一个分割的端点。例如端点=[10,20,30]表示将变量分为x<10,10<x<20,20<x<30和x>30.
    #特别地,缺失值-1不参与分箱
    if -1 in set(trainData[col]):
        special_attribute = [-1]
    else:
        special_attribute = []
    cutOffPoints = ChiMerge(trainData, col, 'target',special_attribute=special_attribute)
    print("cutOffPoints:",cutOffPoints)
    var_cutoff[col] = cutOffPoints
    trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints,special_attribute=special_attribute))
    
    #(2), check whether the bad rate is monotone
    BRM = BadRateMonotone(trainData, col1, 'target',special_attribute=special_attribute)
    if not BRM:
        if special_attribute == []:
            bin_merged = Monotone_Merge(trainData, 'target', col1)
            removed_index = []
            for bin in bin_merged:
                if len(bin)>1:
                    indices = [int(b.replace('Bin ','')) for b in bin]
                    removed_index = removed_index+indices[0:-1]
            removed_point = [cutOffPoints[k] for k in removed_index]
            for p in removed_point:
                cutOffPoints.remove(p)
            var_cutoff[col] = cutOffPoints
            trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints, special_attribute=special_attribute))
        else:
            cutOffPoints2 = [i for i in cutOffPoints if i not in special_attribute]
            temp = trainData.loc[~trainData[col].isin(special_attribute)]
            bin_merged = Monotone_Merge(temp, 'target', col1)
            removed_index = []
            for bin in bin_merged:
                if len(bin) > 1:
                    indices = [int(b.replace('Bin ', '')) for b in bin]
                    removed_index = removed_index + indices[0:-1]
            removed_point = [cutOffPoints2[k] for k in removed_index]
            for p in removed_point:
                cutOffPoints2.remove(p)
            cutOffPoints2 = cutOffPoints2 + special_attribute
            var_cutoff[col] = cutOffPoints2
            trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints2, special_attribute=special_attribute))

    #(3), 分箱后再次检查是否有单一的值占比超过90%。如果有,删除该变量
    maxPcnt = MaximumBinPcnt(trainData, col1)
    if maxPcnt > 0.9:
        # del trainData[col1]
        deleted_features.append(col)
        numerical_var.remove(col)
        print('we delete {} because the maximum bin occupies more than 90%'.format(col))
        continue
    
    WOE_IV = CalcWOE(trainData, col1, 'target')
    var_IV[col] = WOE_IV['IV']
    var_WOE[col] = WOE_IV['WOE']
    #del trainData[col]

6. 单变量分析

完成变量分箱、WOE编码与IV计算后,我们需要做单变量分析。

两个角度进行分析:

        1. 变量的重要性。变量的重要性可以从IV值的判断出发。不同的IV值反映出变量不同程度的重要性。

        2.变量分布的稳定性。合适的变量,各箱的占比不会很悬殊。如果某变量有一箱的占比远低于其他箱,则该变量的稳定性也较弱。

        单变量分析是从重要性及分布的稳定性两个角度来考虑。通常先选择IV高于阈值(如0.2)的变量,再挑选出分箱较均匀的变量。

for col in var_WOE.keys():
    print(col)
    col2 = str(col)+"_WOE"
    if col in var_cutoff.keys():
        cutOffPoints = var_cutoff[col]
        special_attribute = []
        if - 1 in cutOffPoints:
            special_attribute = [-1]
        binValue = trainData[col].map(lambda x: AssignBin(x, cutOffPoints,special_attribute=special_attribute))
        trainData[col2] = binValue.map(lambda x: var_WOE[col][x])
    else:
        trainData[col2] = trainData[col].map(lambda x: var_WOE[col][x])

trainData.to_csv(folderOfData+'allData_3.csv', header=True,encoding='gbk', columns = trainData.columns, index=False)

### (i) select the features with IV above the thresould
trainData = pd.read_csv(folderOfData+'allData_3.csv', header=0,encoding='gbk')
all_IV = list(var_IV.values())
all_IV = sorted(all_IV, reverse=True)
plt.bar(x=range(len(all_IV)), height = all_IV)
iv_threshould = 0.02
varByIV = [k for k, v in var_IV.items() if v > iv_threshould]

7. 多变量分析

完成单变量分析后,我们还需要对变量的整体性做把控,利用多变量分析的技术进一步缩减变量规模,形成全局更优的变量体系。多变量分析从以下两个角度分析变量的特性并完成挑选工作:

  • 变量间的两两线性相关性
  • 变量间的多重共线性

变量间不允许存在太强的两两线性相关性。主要原因是:

  • 若变量f_1和变量f_2的两两线性相关性较强,说明这两个变量间存在一定的信息冗余。同时保留在模型里,即无必要,同时也增加了模型开发、部署与维护的负担
  • 较强的线性相关性甚至会影响回归模型的参数估计。在回归模型的参数估计中,当两个变量间存在较强的线性相关性时,参数的估计会有较大的偏差

变量的两两线性相关性检验可以通过相关性矩阵来判断:

当两个变量间存在较强的线性相关性时,通常保留IV值较高的一个。

于是整个流程是:

  • 将变量按照IV进行降序排列:f_1,f_2,…,f_p
  • 令i=1,计算相关系数: σ(f_i,f_i+1), σ(f_i,f_i+2),…. σ(f_i,f_p)
  • 如果σ(f_i,f_j)较大,则剔除f_j
  • i=i+1 重复2~4,直到剩余变量中不存在较高的线性相关性。 相关系数通常用较高的阈值来对比,可以用0.7~0.9之间的数。

   我们还需要检验是否存在多重共线性(multicolinearity)。多重共线性是指,一组变量中,某一个变量与其他变量的线性组合存在较强的线性相关性。同样地,存在较强的多重共线性意味着存在信息冗余,且对模型的参数估计产生影响,多重共线性通常用方差膨胀因子(VIF)来衡量。

var_IV_selected = {k:var_IV[k] for k in varByIV}
var_IV_sorted = sorted(var_IV_selected.items(), key=lambda d:d[1], reverse = True)
var_IV_sorted = [i[0] for i in var_IV_sorted]

removed_var  = []
roh_thresould = 0.6
for i in range(len(var_IV_sorted)-1):
    if var_IV_sorted[i] not in removed_var:
        x1 = var_IV_sorted[i]+"_WOE"
        for j in range(i+1,len(var_IV_sorted)):
            if var_IV_sorted[j] not in removed_var:
                x2 = var_IV_sorted[j] + "_WOE"
                roh = np.corrcoef([trainData[x1], trainData[x2]])[0, 1]
                if abs(roh) >= roh_thresould:
                    print('the correlation coeffient between {0} and {1} is {2}'.format(x1, x2, str(roh)))
                    if var_IV[var_IV_sorted[i]] > var_IV[var_IV_sorted[j]]:
                        removed_var.append(var_IV_sorted[j])
                    else:
                        removed_var.append(var_IV_sorted[i])

var_IV_sortet_2 = [i for i in var_IV_sorted if i not in removed_var]

### (iii) check the multi-colinearity according to VIF > 10
for i in range(len(var_IV_sortet_2)):
    x0 = trainData[var_IV_sortet_2[i]+'_WOE']
    x0 = np.array(x0)
    X_Col = [k+'_WOE' for k in var_IV_sortet_2 if k != var_IV_sortet_2[i]]
    X = trainData[X_Col]
    X = np.matrix(X)
    regr = LinearRegression()
    clr= regr.fit(X, x0)
    x_pred = clr.predict(X)
    R2 = 1 - ((x_pred - x0) ** 2).sum() / ((x0 - x0.mean()) ** 2).sum()
    vif = 1/(1-R2)
    if vif > 10:
        print("Warning: the vif for {0} is {1}".format(var_IV_sortet_2[i], vif))

     一般而言,我们用10来衡量是否存在多重共线性。对于VIF>10,可以认为变量间存在多重共线性。此时,需要逐步从f_1,f_2,…,f_j剔除一个变量,剩余的变量与f_i 计算VIF。如果发现当剔除f_k后剩余变量对f_i的VIF低于10,则从f_i与f_k中剔除IV较低的一个。如果每次剔除一个变量还不能降低VIF,则每次剔除2个变量,直至变量间不存在多重共线性。

7. lr对变量的要求

  1. 变量间不存在较强的线性相关性和多重共线性

  2. 变量具有显著性
  3. 变量具有合理的业务含义,即变量对于风控业务是正确的。

第1点已经在单变量分析与多变量分析中得到一定的约束,但是未必充分。 关于第2点,需要从系数的p值进行检验 关于第3点,需要从系数的符号进行检验。

其中变量显著性
        变量显著性 为了获取与目标变量有较高相关性的变量,我们要求最终入模的变量的系数的p值很小,例如低于0.1。
如果发现模型中某些变量不显著,需要检验一下两种可能性:
        1.该变量本身不显著
        2.该变量显著,但是由于有一定的线性相关性或者多重共线性,导致该变量在多元回归下不显著 先检验1的可能性,如果排除,再检验2.
检验1的方法:
        将该变量单独与目标变量做逻辑回归模型,如果在单变量回归的情况下系数的p值仍然较高,即表明该变量本身的显著性很低。
注: 对于IV较高的变量,1的可能性较低

#########################
# Step 5: 应用逻辑回归模型#
#########################

LR = sm.Logit(y, X).fit()
summary = LR.summary2()
pvals = LR.pvalues.to_dict()
params = LR.params.to_dict()

#发现有变量不显著,因此需要单独检验显著性
varLargeP = {k: v for k,v in pvals.items() if v >= 0.1}
varLargeP = sorted(varLargeP.items(), key=lambda d:d[1], reverse = True)
varLargeP = [i[0] for i in varLargeP]
p_value_list = {}
for var in varLargeP:
    X_temp = trainData[var].copy().to_frame()
    X_temp['intercept'] = [1] * X_temp.shape[0]
    LR = sm.Logit(y, X_temp).fit()
    p_value_list[var] = LR.pvalues[var]
for k,v in p_value_list.items():
    print("{0} has p-value of {1} in univariate regression".format(k,v))


#发现有变量的系数为正,因此需要单独检验正确性
varPositive = [k for k,v in params.items() if v >= 0]
coef_list = {}
for var in varPositive:
    X_temp = trainData[var].copy().to_frame()
    X_temp['intercept'] = [1] * X_temp.shape[0]
    LR = sm.Logit(y, X_temp).fit()
    coef_list[var] = LR.params[var]
for k,v in coef_list.items():
    print("{0} has coefficient of {1} in univariate regression".format(k,v))


selected_var = [multi_analysis[0]]
for var in multi_analysis[1:]:
    try_vars = selected_var+[var]
    X_temp = trainData[try_vars].copy()
    X_temp['intercept'] = [1] * X_temp.shape[0]
    LR = sm.Logit(y, X_temp).fit()
    #summary = LR.summary2()
    pvals, params = LR.pvalues, LR.params
    del params['intercept']
    if max(pvals)<0.1 and max(params)<0:
        selected_var.append(var)

LR.summary2()

y_pred = LR.predict(X_temp)
roc_auc_score(trainData['target'], y_pred)

 8.尺度化

    得到符合要求的逻辑回归模型后,通常还需要将概率转化成分数。分数的单调性与概率相反,即分数越高表明违约的概率越低,信用资质越好。在评分卡模型中,上述过程称为“尺度化”,转换公式为:

score=Base Point + \frac{PDO}{ln\left ( 2 \right )}*(-y)

其中,y=logit(p)=log\left ( \frac{p}{1-p} \right )

Base Point:基准分

PDO:Point-to-Double Odds,好坏比每升高一倍,分数升高PDO个单位。具体反应在P这个因子中。

def Prob2Score(prob, basePoint, PDO):
    #将概率转化成分数且为正整数
    y = np.log(prob/(1-prob))
    y2 = basePoint+PDO/np.log(2)*(-y)
    score = y2.astype("int")
    return score

#########################
# 尺度化与性能检验#
#########################
scores = Prob2Score(y_pred, 200, 100)


scorecard = pd.DataFrame({'y_pred':y_pred, 'y_real':list(trainData['target']),'score':scores})
KS(scorecard,'score','y_real')
plt.hist(score,bins=100)
ROC_AUC(df, score, target)

# 也可用sklearn带的函数
roc_auc_score(trainData['target'], y_pred)

四、参考文献

  1. 菜菜机器学习--lr构建评分卡
  2. 天善智能--如何构建评分卡
  3. 小象学院--金融信贷风控中的机器学习
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值