特征离散化(四) 之 bestKS分箱

特征离散化(四) 之 bestKS分箱

讲完了最小熵分箱,随便也提一下bestKS分箱吧。其实看懂了最小熵分箱,很容易就能理解bestKS分箱了。两个都是自顶向下的监督分箱方法,区别就在于确定划分点的指标不同。最小熵采用的是熵值,bestKS采用的KS值。

1. KS值

KS(Kolmogorov-Smirnov)用于评估模型区分风险的能力。描述的是数据中好坏样本累计部分之间的差距 。KS值越大,表示该变量的可区分度越高,越能将正,负样本区分开来。通常来说,KS>0.2即表示该特征有较好的准确率。这里的KS值是变量的KS值,而不是模型的KS值。

KS值的计算公式:
K S i = ∣ s u m i / s u m T − ( s i z e i − s u m i ) / ( s i z e T − s u m T ) ∣ KS_i = |sum_i / sum_T - (size_i - sum_i)/ (size_T - sum_T)| KSi=sumi/sumT(sizeisumi)/(sizeTsumT)

  1. 计算每个评分区间的好坏样本数。
  2. 计算各每个评分区间的累计好样本数占总好样本数比率(good%)和累计坏样本数占总坏样本数比率(bad%)。
  3. 计算每个评分区间累计坏样本比与累计好样本占比差的绝对值(累计good%-累计bad%),然后对这些绝对值取最大值即可得到KS值。

2. bestKS分箱

首先,按照惯有的套路,先给出通用流程(这里没图哈)及相应的代码:

  1. 统计并排序(跟卡方分箱套路一样,不赘述)。
  2. 依次将待分箱变量的每个取值作为划分点,将数据集一分为二。分别计算两个子集的KS值并求和(跟最小熵分箱套路基本一样,只是指标不同)。
  3. 选择KS之和最小的值,作为划分点。
  4. 分别对划分后得到的两个子集重复步骤2&3。

此处直接给出bestKS分箱的核心代码,其余部分的介绍可以参见特征离散化(三) 之 最小熵分箱

def calc_ks(count, idx):
    """
    计算各分组的KS值
    :param count: DataFrame 待分箱变量各取值的正负样本数
    :param group: list 单个分组信息
    :return: 该分箱的ks值

    计算公式:KS_i = |sum_i / sum_T - (size_i - sum_i)/ (size_T - sum_T)|
    """
    # 计算每个评分区间的好坏账户数。
    # 计算各每个评分区间的累计好账户数占总好账户数比率(good %)和累计坏账户数占总坏账户数比率(bad %)。
    # 计算每个评分区间累计坏账户比与累计好账户占比差的绝对值(累计good % -累计bad %),然后对这些绝对值取最大值记得到KS值
    ks = 0.0
    # 计算全体样本中好坏样本的比重(左开右闭区间)
    good = count[1].iloc[0:idx + 1].sum() / count[1].sum() if count[1].sum()!=0 else 1
    bad = count[0].iloc[0:idx + 1].sum() / count[0].sum() if count[0].sum()!=0 else 1
    ks += abs(good - bad)
    
    good = count[1].iloc[idx + 1:].sum() / count[1].sum() if count[1].sum()!=0 else 1
    bad = count[0].iloc[idx + 1:].sum() / count[0].sum() if count[0].sum()!=0 else 1
    ks += abs(good - bad)
    return ks

上面的KS值的计算实现,时间复杂度为 O ( n ∗ l o g n ) O(n∗logn) O(nlogn)。下面这个的时间复杂度为$O(2n) $,原理上是一致,只是实现上的差异。感兴趣的可以看一下。

def calc_ks2(count):
    # 方法二:
    a = count.cumsum(axis=0) / count.sum(axis=0)
    a = a.fillna(1)
    a = abs(a[0] - a[1])
    
    count = count.sort_index(ascending=False)
    b = count.cumsum(axis=0) / count.sum(axis=0)
    b = b.fillna(1)
    b = abs(b[0] - b[1])
    ks = [a.values[idx] + b.values[len(a.index) - 2 - idx] for idx in range(len(a.index) - 1)]
    return ks

bestKS是取KS和最大的位置,最小熵是取总熵值最小的位置。别搞混了哈。

def get_best_cutpoint(count, group):
    """
        根据指标计算最佳分割点
        :param count:
        :param group: list 待划分的取值
        :return:
        """
    # entropy_list = [calc_ks(count, idx) for idx in range(0, len(group) - 1)]  # 左开右闭区间
    entropy_list = calc_ks2(count)
    
    intv = entropy_list.index(max(entropy_list))
    return intv

3. 完整代码参见:

https://github.com/Lucky-Bone/Discretization

你可能感兴趣的:(算法)