评分卡中WOE和IV详解 Python实现

  信用评分卡模型是信用风险评估中普遍使用的模型,而在模型建立过程中,一般采用WOE(Weight Of Evidence 证据权重)对自变量进行编码,并根据IV(Information Value 信息量)作为变量筛选指标。

1 WOE

  WOE(Weight Of Evidence 证据权重)是一种对自变量编码的方法,需注意的是在WOE编码前需对数据进行分箱(分组或离散化)操作。
具体而言,对于特征每个分组的WOE值计算公式如下:
评分卡中WOE和IV详解 Python实现_第1张图片
  其中,pyi是分组i中正样本(响应客户,目标列Y=1)占所有正样本的比例,pni是分组i中负样本(未响应客户,目标列Y=0)占所有负样本的比例,yi是分组i中正样本数量,yT是所有正样本数量,ni是分组i中负样本数量,nT是所有负样本数量。
  从公式可以看出,WOE即分组内正负占比与样本整体正负占比的差异。WOE越大,这种差异越大,这个分箱里的样本响应的可能性就越大,WOE越小,分箱内样本响应的可能想性越低。
  举例说明
  以下表数据为例,在对数据分组后计算每个分箱的WOE值
评分卡中WOE和IV详解 Python实现_第2张图片
  根据WOE对每个分箱的计算公式,计算得出WOE值,如下表所示:
评分卡中WOE和IV详解 Python实现_第3张图片
  WOE单调性
  由于评分卡通常采用线性分类器Logistics Regression,入模数据最好呈现单调性。因此,在计算出每个分箱的WOE值后,需同时校验训练数据和测试的WOE是否呈现单调性,若非单调且在业务中无法解释(如随着年龄的增长,体能的变化呈现“n”形),需要修改或合并分箱,从而使WOE呈现单调。如上表中的“年龄”在分箱后WOE呈现单调。
评分卡中WOE和IV详解 Python实现_第4张图片

2 IV

  IV(Information Value 信息量)是评分卡模型中的一个常见指标,在金融评分卡常用于衡量自变量对因变量预测能力的指标。基本思想是根据该特征所命中正负样本的比率与总正负样本的比率,来对比和计算其关联程度。
  具体而言,对于特征每个分箱的IV值计算公式如下:
评分卡中WOE和IV详解 Python实现_第5张图片
  特征的 IV 值即为所有分箱的 IV 值相加,计算公式如下:
评分卡中WOE和IV详解 Python实现_第6张图片
  其中,bins为变量分组个数。
  从IV的计算公式可见,对于分箱中正样本和负样本的比例与样本整体正样本和负样本的比例相差越大,IV值越大,否则,IV值越小。金融风控场景中,通过IV进行特征筛选的评价标准如下:
评分卡中WOE和IV详解 Python实现_第7张图片
  举例说明
  根据IV对每个分箱的计算公式,在计算出WOE值的基础上,得出IV值,计算如下:
评分卡中WOE和IV详解 Python实现_第8张图片

3 Python实现

  WOE和IV计算Python实现。需注意,为防止WOE计算时log计算错误,需在计算箱内占比时(即WOE公式中的pyi和pni),对组内正样本数和负样本数加1(即yi+1,ni+1)。

import numpy as np
import pandas as pd
import copy


def calculate_woe_iv(dataset):
    """
    对分箱后的特征计算WOE和IV
    :param dataset:DataFrame,计算数据,需要在特征分箱后的数据
    :return:
        iv: float,iv值
        df:DataFrame,woe和IV计算后结果

    Example
    -----------------------------------------------------------------
    >>> import random
    >>> data = pd.DataFrame([[random.random(),random.randint(0,1)] for _ in range(500)],columns=['feature','label'])
    >>> df = cut_width(dataset=data,inputcol='feature',labelcol='label',bins=10)
    >>> df.rename(columns={0:'neg',1:'pos'},inpalce=True)
    >>> iv, woe_iv_df = calculate_woe_iv(dataset=df)
    >>> iv
    0.037619588549634465
    >>> woe_iv_df
    label               neg  pos  pos_rate  neg_rate       woe        iv
    feature
    (-0.000313, 0.103]   23   27  0.104869  0.103004  0.017940  0.000033
    (0.103, 0.206]       23   27  0.104869  0.103004  0.017940  0.000033
    (0.206, 0.312]       29   21  0.082397  0.128755 -0.446365  0.020693
    (0.312, 0.418]       22   28  0.108614  0.098712  0.095591  0.000947
    (0.418, 0.535]       19   31  0.119850  0.085837  0.333793  0.011353
    (0.535, 0.614]       22   28  0.108614  0.098712  0.095591  0.000947
    (0.614, 0.705]       24   26  0.101124  0.107296 -0.059249  0.000366
    (0.705, 0.8]         24   26  0.101124  0.107296 -0.059249  0.000366
    (0.8, 0.891]         22   28  0.108614  0.098712  0.095591  0.000947
    (0.891, 0.991]       25   25  0.097378  0.111588 -0.136210  0.001936
    """
    df = copy.copy(dataset)
    df['pos_rate'] = (df['pos'] + 1) / df['pos'].sum()  # 计算每个分组内的响应(Y=1)占比,加1为了防止在计算woe时分子分母为0
    df['neg_rate'] = (df['neg'] + 1) / df['neg'].sum()  # 计算每个分组内的未响应(Y=0)占比
    df['woe'] = np.log(df['pos_rate'] / df['neg_rate'])  # 计算每个分组的WOE
    df['iv'] = (df['pos_rate'] - df['neg_rate']) * df['woe']  # 计算每个分组的IV
    iv = df['iv'].sum()
    return iv, df
    
def cut_width(dataset, inputcol, labelcol='label', bins=10):
    """
    等宽分箱
    :param dataset: DataFrame,计算数据
    :param inputcol: String,待分箱列列名
    :param labelcol: String,目标列列名
    :param bins: int,正整数,分箱数
    :return:
    :return:
        df: DataFrame,分箱后结果

    Example
    -----------------------------------------------------------------
    >>> import random
    >>> data = pd.DataFrame([[random.random(),random.randint(0,1)] for _ in range(500)],columns=['feature','label'])
    >>> df = cut_width(data,inputcol='feature',labelcol='label',bins=10)
    >>> df
        label                             good  bad
    feature
    (-0.0009308000000000001, 0.0968]    23   27
    (0.0968, 0.188]                     27   23
    (0.188, 0.29]                       25   25
    (0.29, 0.385]                       32   18
    (0.385, 0.472]                      31   19
    (0.472, 0.567]                      24   26
    (0.567, 0.686]                      24   26
    (0.686, 0.778]                      24   26
    (0.778, 0.912]                      26   24
    (0.912, 0.999]                      29   21
    """
    df = copy.copy(dataset)
    df[inputcol] = pd.qcut(x=df[inputcol], q=bins)
    df = pd.crosstab(index=df[inputcol], columns=df[labelcol], margins=False)
    return df
    ```

你可能感兴趣的:(金融风控,机器学习,python)