外部数据引入分析与量化评估

3 阅读2分钟

一、数据分析基础方法论

1. 从业务视角出发的分析模型

  • 5W2H分析法: 明确为什么要引(Why)、谁在用(Who)、什么场景(What)。

  • 漏斗模型: 外部数据在业务流(如获客、反欺诈、转化)中起到的支撑节点。

2. 数据探索性分析 (EDA)

  • 描述性统计: 分布、均值、缺失率、异常值监测。

  • 相关性分析: 外部字段与目标变量(如违约、流失)的统计关联。

3. 因果分析 vs. 相关性分析

  • 外部数据往往是“影子指标”,如何区分相关性与因果逻辑。

3.1. 皮尔逊相关系数

皮尔逊相关系数(Pearson Correlation Coefficient)是衡量两个变量之间线性相关程度的指标。它的取值范围在 [1,1][-1, 1] 之间:

  • 1:完全正相关。

  • 0:不相关。

  • -1:完全负相关。

其数学公式定义为两个变量的协方差除以它们标准差的乘积:

ρX,Y=cov(X,Y)σXσY


3.2. Python 实现 Demo

在 Python 中,通常有三种主流方式来计算。我为你准备了一个对比示例:

Python

import numpy as np
import pandas as pd
from scipy import stats

# 准备模拟数据
x = [10, 20, 30, 40, 50]
y = [12, 24, 33, 39, 55]

# 1. 使用 Numpy (最常用,返回相关系数矩阵)
# matrix[0, 1] 就是 x 和 y 的相关系数
corr_matrix = np.corrcoef(x, y)
pearson_np = corr_matrix[0, 1]

# 2. 使用 Scipy (最专业,还会返回 p-value 用于显著性检验)
# p-value 越小,说明相关性越显著
pearson_sp, p_value = stats.pearsonr(x, y)

# 3. 使用 Pandas (适合处理表格数据)
s1 = pd.Series(x)
s2 = pd.Series(y)
pearson_pd = s1.corr(s2)

print(f"Numpy 计算结果: {pearson_np:.4f}")
print(f"Scipy 计算结果: {pearson_sp:.4f} (p-value: {p_value:.4e})")
print(f"Pandas 计算结果: {pearson_pd:.4f}")

3.3. 核心注意事项

  • 线性限制:皮尔逊系数只能捕捉线性关系。如果两个变量是曲线关系(比如 y=x2y = x^2),皮尔逊系数可能会很低,但它们依然有很强的相关性。

二、外部数据引入的评价维度

解决“新数据好不好”的问题,分为三个关键层级。

1. 基础质量评估 (Technical Quality)

  • 覆盖率 (Coverage): 与企业存量 ID(如手机号、UID)的匹配率。

  • 准确性 (Accuracy): 抽样核验真实性(例如:外部提供的年龄是否与我司实名信息一致)。

  • 时效性 (Freshness): 数据的更新频率与实际发生时间的时间差。

2. 独特性与稀缺性 (Uniqueness)

  • 字段重合度: 新数据中有多少比例是现有数据已涵盖的?

  • 维度补完: 新数据是否提供了现有数据无法触达的维度(如:我司只有消费数据,新数据提供了社交偏好)。

3. 稳定性与合规性 (Sustainability)

  • 波动率: 外部接口响应速度及数据回传的月度波动情况。

  • 合规风险: 授权链路的合法性评估。


三、以原有数据为基准的“增益”评价

如何量化新数据带来的“额外价值”。

1. 基准对比法 (Benchmarking)

  • 存量 vs. 增量: 建立 A/B 测试逻辑。

  • 对比组 A: 仅使用原有数据构建模型/分析。

  • 实验组 B: 原有数据 + 新引入数据。

2. 量化评价指标:IV 值与特征重要性

衡量的是数据的特征重要性

  • IV (Information Value): 衡量新字段对目标变量的解释能力。应用于分类(特别是二分类)场景。

  • SHAP 值: 在机器学习模型中,量化每一个外部字段对结果的具体贡献度。可以应用于分类,回归场景。

3. 性能增益分析 (Lift Analysis)

衡量的是模型最终结果以及对模型的提升对业务具体量化影响

  • 模型指标: KS值提升、AUC提升、Precision/Recall 的变化。

  • 业务指标: 引入新数据后,获客成本(CPA)降低了多少?坏账率下降了几个百分点?


四、衡量特征的指标解释

1. IV值介绍

IV (Information Value,信息价值) 是衡量一个自变量(特征)对目标变量(通常是“好/坏”二分类)预测能力的最常用指标之一。 简单来说:IV 值越高,说明这个特征越能把“好人”和“坏人”区分开。

1.1. IV 值的计算逻辑

在计算 IV 之前,必须先理解 WOE (Weight of Evidence,证据权重)

第一步:分箱 (Binning)

将连续型变量(如年龄、收入)或离散型变量(如省份)分成若干组。

第二步:计算各组的 WOE 值

对于每一个分箱 ii,其 WOE 的计算公式为:

WOEi=ln(Goodi/Total_GoodBadi/Total_Bad)WOE_i = \ln \left( \frac{\text{Good}_i / \text{Total\_Good}}{\text{Bad}_i / \text{Total\_Bad}} \right)

  • GoodiGood_i: 该组内好客户数量

  • BadiBad_i: 该组内坏客户数量

  • Total_GoodTotal\_Good: 全样本好客户总数

  • Total_BadTotal\_Bad: 全样本坏客户总数

第三步:计算该特征的 IV 值

将所有分箱的贡献加总:

IV=i=1n(GoodiTotal_GoodBadiTotal_Bad)×WOEiIV = \sum_{i=1}^{n} \left( \frac{Good_i}{Total\_Good} - \frac{Bad_i}{Total\_Bad} \right) \times WOE_i


1.2. 举例说明

假设我们要评价“消费水平” 这个特征的预测能力。

样本总量: 1000人(其中好人 800,坏人 200)。

我们取其中一个分箱:“高消费人群”(特征还有“中消费”、“低消费”等其他值,类别型变量一般按照类别值直接分组)。

维度高消费组 (组 i)全样总量
好人 (Good)400800
坏人 (Bad)20200

计算过程:

  1. 好人占比 (Gi/GTG_i/G_T) = 400/800=0.5400 / 800 = 0.5 (50%)

  2. 坏人占比 (Bi/BTB_i/B_T) = 20/200=0.120 / 200 = 0.1 (10%)

  3. 计算该组 WOE = ln(0.5/0.1)=ln(5)1.61\ln(0.5 / 0.1) = \ln(5) \approx 1.61

  4. 计算该组 IV 贡献 = (0.50.1)×1.61=0.4×1.61=0.644(0.5 - 0.1) \times 1.61 = 0.4 \times 1.61 = 0.644

最后,将“中消费”、“低消费”等其他组的 IV 贡献全部加起来,得到该特征的总 IV 值。


1.3. 如何解读 IV 值的结果?

在行业实践中,我们通常按以下标准给特征“打分”:

IV 值范围预测能力 (Predictive Power)处理建议
< 0.02几乎无预测能力剔除,不引入模型
0.02 - 0.1弱预测能力留用观察
0.1 - 0.3中等预测能力优质特征,重点引入
0.3 - 0.5强预测能力极佳特征
> 0.5极强/怀疑需检查是否包含“未来信息”(如:用还款金额预测是否违约)

1.4. 为什么要用 IV 而不是直接看相关系数?

  • 非线性捕捉:IV 基于分箱,可以捕捉非线性关系(比如:年龄太小或太大风险都高,中间风险低)。

  • 量纲无关:无论原始分值是 0-100 还是 300-900,计算出的 IV 尺度是一致的,方便不同来源的数据横向对比。

1.5 计算 WOE为什么要用好坏比的对数,而不是直接计算好坏比比值

WOE计算公式如下:

WOEi=ln(%Goodi%Badi)WOE_i = \ln \left( \frac{\% \text{Good}_i}{\% \text{Bad}_i} \right)

大家经常会问:为什么要费力气取个自然对数(ln\ln)呢?直接用比例不香吗? 其实,取 ln\ln 是为了解决三个核心的数学和工程问题:


1.5.1. 将“比例”转化为“线性”

如果不用 ln\ln,直接使用 %Good%Bad\frac{\% \text{Good}}{\% \text{Bad}}(即 Odds 几率),它的取值范围是 [0,+)[0, +\infty)

  • 当 Good 远多于 Bad 时,数值会飞速膨胀。

  • 当 Bad 远多于 Good 时,数值被挤压在 0011 之间。

取了 ln\ln 之后:

  • 数值范围变成了 (,+)(-\infty, +\infty),且以 0 为中位点。

  • WOE 为正: 该组别违约风险低(Good 占比高)。

  • WOE 为负: 该组别违约风险高(Bad 占比高)。

  • WOE 为 0: 该组别完全没有区分度。

这种对称性让模型(尤其是逻辑回归)处理起来更加“舒服”。

1.5.2. 逻辑回归的“天作之合”

信用评分卡底层通常使用 逻辑回归(Logistic Regression)。逻辑回归的模型方程本身就是基于 Log-odds 的:

ln(p1p)=β0+β1x1++βnxn\ln \left( \frac{p}{1-p} \right) = \beta_0 + \beta_1x_1 + \dots + \beta_nx_n

当你预先对特征进行了 WOE 转换,你实际上是将特征转换到了与目标函数相同的尺度上。这样,特征与目标变量之间就建立了一种线性关系。这不仅提高了模型的拟合效果,还让回归系数 β\beta 变得非常容易解释。

1.5.3. 处理极值与鲁棒性

原始数据中常有极端异常值(比如年收入 1 个亿和年收入 1 万)。

  • 通过**分箱(Binning)**后再计算 WOE,这些异常值会被归入某个特定的区间。

  • ln\ln 函数具有“压缩”大数值、拉伸小数值的特性,这进一步增强了模型对噪声和异常值的抵抗力,让模型更加稳健。


1.5.4. 总结

ln\ln 的本质是为了“对称化”和“线性化”。它把非线性的比例关系,转换成了逻辑回归最喜欢的线性输入,从而方便我们后续计算 IV(Information Value)值来筛选变量,并最终生成直观的信用评分。


1.6 为什么说逻辑回归的模型方程本身就是基于对数几率( Log-odds) 的

1.6.1. 从线性回归到概率的困境

在线性回归中,我们的方程是 y=β0+β1xy = \beta_0 + \beta_1x

  • 问题: 线性回归的预测值范围是 (,+)(-\infty, +\infty)

  • 矛盾: 但我们要预测的概率 pp 必须在 [0,1][0, 1] 之间。

为了强行把 (,+)(-\infty, +\infty) 的线性结果映射到 [0,1][0, 1],数学家引入了 Sigmoid 函数

p=11+e(β0+β1x)p = \frac{1}{1 + e^{-(\beta_0 + \beta_1x)}}

1.6.2. 反向推导:寻找“线性”本质

如果我们把上面的 Sigmoid 方程反过来推导,看看等号右边保留线性部分时,等号左边会变成什么:

  1. 首先,求出“不发生的概率”与“发生的概率”之比(即 Odds,几率):

    p1p=eβ0+β1x\frac{p}{1-p} = e^{\beta_0 + \beta_1x}

  2. 然后,为了消掉右边的指数 ee,我们对等号两边同时取自然对数 ln\ln

    ln(p1p)=β0+β1x\ln \left( \frac{p}{1-p} \right) = \beta_0 + \beta_1x

瞧!这就是答案。 等号左边的 ln(p1p)\ln \left( \frac{p}{1-p} \right) 正是 Log-odds(也叫 Logit 变换)。这个方程告诉我们:逻辑回归的本质,是用特征的线性组合去拟合“对数几率”。


1.6.3. 为什么 Log-odds 这么重要?

之所以说模型方程“基于”它,是因为它完成了两个关键转换:

转换阶段取值范围物理意义
概率 (pp)[0,1][0, 1]发生的可能性,但不是线性的。
几率 (Odds)[0,+)[0, +\infty)发生与不发生的比例,消除了上限。
对数几率 (Log-odds)(,+)(-\infty, +\infty)完美的线性尺度,与线性回归方程无缝对接。
1.6.4. 总结

当你看到逻辑回归的输出是 0.70.7(概率)时,在模型的“大脑”深处,它实际上是在计算 ln(0.70.3)0.847\ln(\frac{0.7}{0.3}) \approx 0.847(Log-odds)。

这就是为什么我们在计算 WOE 时也要取 ln\ln——因为只有取了 ln\ln,WOE 的量纲才和逻辑回归内部计算的量纲完全一致。 这样,每一个分组的 WOE 值就可以像积木一样,直接通过加权求和来影响最终的信用分。

1.7. 代码示例


import pandas as pd
import numpy as np

def calculate_iv_and_woe(df, target, feature, bins=10):
    """
    计算单个特征的 WOE 和 IV 值
    :param df: pd.DataFrame 数据集
    :param target: 目标变量列名 (1代表坏人, 0代表好人)
    :param feature: 待计算特征列名
    :param bins: 分箱数量 (仅针对数值型)
    :return: feature_iv (float), stats_df (pd.DataFrame)
    """
    
    # 1. 自动分箱:如果是数值型则分箱,如果是分类型则按原值分组
    if df[feature].dtype in [np.float64, np.int64]:
        # 使用等频分箱,重复值较多时使用 rank 处理
        df['temp_bin'] = pd.qcut(df[feature], q=bins, duplicates='drop')
    else:
        df['temp_bin'] = df[feature]

    # 2. 统计各箱的好坏人数
    stats = df.groupby('temp_bin')[target].agg(['count', 'sum'])
    stats.columns = ['Total', 'Bad']
    stats['Good'] = stats['Total'] - stats['Bad']
    
    # 3. 计算全样本的好坏总数
    total_good = stats['Good'].sum()
    total_bad = stats['Bad'].sum()
    
    # 4. 计算占比 (为了防止除以0,加入一个极小值 epsilon)
    eps = 1e-9
    stats['Good_Dist'] = stats['Good'] / (total_good + eps)
    stats['Bad_Dist'] = stats['Bad'] / (total_bad + eps)
    
    # 5. 计算 WOE
    stats['WOE'] = np.log((stats['Good_Dist'] + eps) / (stats['Bad_Dist'] + eps))
    
    # 6. 计算 IV 贡献项并加总
    stats['IV_part'] = (stats['Good_Dist'] - stats['Bad_Dist']) * stats['WOE']
    iv = stats['IV_part'].sum()
    
    return iv, stats

# --- 模拟业务数据测试 ---
np.random.seed(42)
n = 1000
test_data = pd.DataFrame({
    'Income': np.random.normal(5000, 2000, n),
    'Education': np.random.choice(['High', 'Medium', 'Low'], n),
    'Label': np.random.choice([0, 1], n, p=[0.8, 0.2]) # 20% 坏人率
})

# 计算 Income 的 IV
income_iv, income_table = calculate_iv_and_woe(test_data, 'Label', 'Income')

print(f"Income 特征的 IV 值为: {income_iv:.4f}")
print("-" * 30)
print("分箱明细表 (前5行):")
print(income_table[['Total', 'Good', 'Bad', 'WOE', 'IV_part']].head())

# --- 批量计算所有特征的 IV 排序 ---
def batch_iv(df, target, features):
    iv_dict = {}
    for f in features:
        iv, _ = calculate_iv_and_woe(df, target, f)
        iv_dict[f] = iv
    return pd.Series(iv_dict).sort_values(ascending=False)

# 运行批量计算
features_to_check = ['Income', 'Education']
iv_ranking = batch_iv(test_data, 'Label', features_to_check)
print("\n特征 IV 值排行榜:")
print(iv_ranking)

2.SHAP值介绍

在引入“蚂蚁洞察评分”后,如果模型给某人的信用分降低了,是因为他的“中证信”本身就低,还是因为新引入的“蚂蚁分”起到了关键作用?SHAP 值就是用来精确拆解这个问题的工具。

2.1. 为什么要这样设计 SHAP 值?(核心哲学)

SHAP 的核心理念源于博弈论中的 沙普利值 (Shapley Value)。它的设计初衷是为了解决一个经典的博弈问题:

场景:几个玩家(特征)共同参与一场游戏(模型预测),最终赢得了一笔奖金(预测值)。由于玩家之间存在配合(特征交互),如何公平地把奖金分给每一个玩家?

设计逻辑的三个核心原则:
  1. 本地准确性(Local Accuracy): 所有特征的贡献值(SHAP值)之和,必须等于模型的预测值与平均预测值之间的差额。这意味着模型预测的每一分波动都能找到归属。

  2. 缺失性(Missingness): 如果一个特征在模型中没有被用到,或者对某个样本没有取值,它的贡献必须为 0。

  3. 一致性(Consistency): 如果一个模型发生了改变,使得某个特征的贡献增加了,那么该特征的 SHAP 值不应该减少。


2.2. SHAP 值是怎么计算的?

SHAP 值的计算并不是简单的“删掉这个特征看结果变化”,因为它考虑了特征之间的组合效应

2.2.1. 计算步骤(简化版逻辑):

假设我们有三个特征:AA(中证信)、BB(蚂蚁分)、CC(年龄)。我们要计算特征 BB 的贡献。

  1. 排列组合:列出所有不包含 BB 的特征子集:\emptyset(空集)、{A}\{A\}{C}\{C\}{A,C}\{A, C\}

  2. 边际贡献:分别计算在这些子集中加入 BB 后,模型预测值的变化:

    • 加入 BB 后的预测值 - 只有 AA 时的预测值 = BB 在集合 {A}\{A\} 中的边际贡献。
  3. 加权平均:由于在不同大小的集合中加入 BB 的重要性不同,需要对这些边际贡献进行加权。

  4. 总和:所有可能组合下的边际贡献的加权平均值,就是特征 BB 的 SHAP 值。

数学表达

ϕi=S{x1,,xp}{xi}S!(pS1)!p![f(S{xi})f(S)]\phi_i = \sum_{S \subseteq \{x_1, \dots, x_p\} \setminus \{x_i\}} \frac{|S|!(p - |S| - 1)!}{p!} [f(S \cup \{x_i\}) - f(S)]

2.2.2. 具体说明:
2.2.2.1. 组合的分类

我们要把所有不含 BB 的组合按**规模(Size)**分类:

组合规模 (S)不含 B 的组合集合 S对应权重 (直观理解)权重分值 (p=3)
0空集只有一种方式:B 独自上场1/3
1{A}, {C}B 加入一个已有成员的团队1/6 (每个组合)
2{A, C}B 作为最后一块拼图加入1/3
2.2.2.2. 权重的数学逻辑

公式中的权重部分是:S!(pS1)!p!\frac{|S|!(p - |S| - 1)!}{p!}

  • pp:特征总数(你的例子中 p=3p=3)。

  • S|S|:当前子集的特征数量。

  • 分母 p!p!:代表所有特征进入模型的所有排列可能性(全排列)。

  • 分子 S!(pS1)!|S|!(p - |S| - 1)!:代表了在这种特定规模下,该组合出现的次数

2.2.2.3. 实例演算(以 BB 为例)

假设我们要算 BB 的 SHAP 值,总特征数 p=3p=3。总排列数 p!=3×2×1=6p! = 3 \times 2 \times 1 = 6

子集 S规模边际贡献权重计算 (p=3)权重结果
空集0f(B) - f(Base)(0! * 2!) / 61/3
{A}1f(A,B) - f(A)(1! * 1!) / 61/6
{C}1f(C,B) - f(C)(1! * 1!) / 61/6
{A, C}2f(A,C,B) - f(A,C)(2! * 0!) / 61/3

验证权重之和1/3+1/6+1/6+1/3=11/3 + 1/6 + 1/6 + 1/3 = 1。权重分配非常公平。


2.3. 实例:评价“蚂蚁洞察评分”

假设模型的平均信用分(基准值)是 600分。 对于客户张三,模型预测结果是 750分(高出了 150分)。

通过 SHAP 拆解,我们发现:

  • 中证信评分 (特征A):SHAP = +80+80

  • 蚂蚁洞察评分 (特征B):SHAP = +50+50

  • 年龄 (特征C):SHAP = +20+20

结论

  1. 80+50+20=15080 + 50 + 20 = 150。这完美解释了张三为什么比平均水平高出 150 分。

  2. 虽然中证信贡献最大,但“蚂蚁洞察评分”提供了 50分 的正向拉动力。

  3. 业务决策:这 50 分的拉动力如果能显著提升模型对高分人群的识别精度,那么采购蚂蚁数据的决策就是正确的。

2.3.1. 具体计算过程
第一步:计算所有可能的“边际贡献”

假设我们要计算**特征 B(蚂蚁分)**的 SHAP 值。我们需要模拟在不同情况下,加入 B 后预测值(信用分)的变化。

场景(子集 S)加入 B 前的得分 f(S)加入 B 后的得分 f(S∪{B})边际贡献 (差值)
空集 \emptyset600 (基准值)640+40+40
只有 A680735+55+55
只有 C610665+55+55
有 A 和 C700750 (最终预测)+50+50

第二步:乘法与求和(加权平均)

现在,我们将刚才表格中的“边际贡献”与之前计算出的“权重”一一对应相乘。

场景边际贡献×权重=分项结果
独自上场+40+40×\times2/62/6==13.3313.33
加入 A+55+55×\times1/61/6==9.179.17
加入 C+55+55×\times1/61/6==9.179.17
最后加入+50+50×\times2/62/6==16.6716.67

最后一步:求和

13.33+9.17+9.17+16.6748.3413.33 + 9.17 + 9.17 + 16.67 \approx \mathbf{48.34}

这个 48.34(约等于你例子中的 50)就是特征 B 的 SHAP 值


为什么要搞这么复杂?

你可能会想:直接看“有 B”和“没 B”的差值不就行了吗?为什么要分这么多场景加权?

原因在于“协同效应”:

  • 有时候,特征 B 只有在特征 A 存在时才起作用(比如:由于有 A 的背书,B 的信用评分才更有说服力)。

  • 有时候,特征 B 和 A 功能重复(比如:A 和 B 都能证明你有钱,那第二个进来的贡献就会变小)。

SHAP 通过这种全排列加权的方式,把这些复杂的相互作用(Interaction)公平地拆解到了每一个特征头上。


2.4. 与传统指标(如特征重要性)的区别

维度传统 Feature Importance (如随机森林)SHAP 值
方向性只能告诉你特征重要,不能告诉你正向还是负向。能明确告诉你该特征是增加了信用分还是降低了信用分。
个体化全局指标,所有人的特征重要性是一样的。可以针对每一个具体的客户进行拆解(局部解释)。
公平性容易偏向取值多的连续变量。基于博弈论,计算公允,能处理特征间的复杂交互。

2.5. 代码示例


import pandas as pd
import numpy as np
import shap
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

  

# 1. 模拟业务数据 (延续之前的场景)

np.random.seed(42)
n_samples = 1000

  

# 模拟特征:中证信评分、蚂蚁洞察评分、年龄、年收入

data = {
'CSI_Score': np.random.normal(600, 100, n_samples),
'Ant_Score': np.random.normal(500, 150, n_samples),
'Age': np.random.randint(18, 70, n_samples),
'Annual_Income': np.random.normal(15, 5, n_samples) # 单位:万元
}

X = pd.DataFrame(data)

  
# 模拟目标变量:综合信用分 (由以上特征非线性组合而成)
# 设定蚂蚁分在特定条件下有更高贡献,体现外部数据增益
y = (0.4 * X['CSI_Score'] +
0.3 * X['Ant_Score'] +
0.1 * X['Age'] * (X['Ant_Score'] > 600) + # 交互效应
0.2 * X['Annual_Income'] * 10 +
np.random.normal(0, 10, n_samples))

# 2. 训练模型
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 3. 计算 SHAP 值 (核心步骤)
# 使用 TreeExplainer,它是专门为树模型(RF, XGBoost, LightGBM)优化的算法
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# 4. 可视化分析

# (A) 全局解释:特征重要性条形图
# 展示每个特征对模型预测的平均贡献绝对值

print("正在生成全局特征重要性图...")
plt.figure()
shap.summary_plot(shap_values, X_test, plot_type="bar", show=False)
plt.title("Global Feature Importance (SHAP)")
plt.show()

  
# (B) 摘要图:展示特征对预测结果的正负影响
# 每一行代表一个特征,每个点代表一个样本
# 颜色表示特征值大小(红高蓝低),横坐标表示SHAP值

print("正在生成特征影响摘要图...")
plt.figure()
shap.summary_plot(shap_values, X_test, show=False)
plt.title("SHAP Summary Plot")
plt.show()

  

# (C) 个体解释:瀑布图 (针对测试集中的第 1 个客户)
# 形象展示该客户从“基准分”到“预测分”的演变过程

print("正在为单个客户生成预测贡献拆解...")

# 获取基准值(模型在训练集上的平均输出)

expected_value = explainer.expected_value

# 注意:新版本 shap 库推荐使用 shap.Explanation 对象进行瀑布图绘制

test_sample_idx = 0
exp = shap.Explanation(
values=shap_values[test_sample_idx],
base_values=expected_value,
data=X_test.iloc[test_sample_idx],
feature_names=X.columns
)

  

plt.figure()
shap.plots.waterfall(exp, show=False)
plt.title(f"Individual Prediction Explanation (Customer {test_sample_idx})")
plt.show()

  

# (D) 依赖图:观察“蚂蚁评分”与“中证信评分”的交互

print("正在生成特征交互依赖图...")
plt.figure()
shap.dependence_plot("Ant_Score", shap_values, X_test, interaction_index="CSI_Score", show=False)
plt.title("Dependence Plot: Ant_Score vs CSI_Score")
plt.show()

五、衡量模型的指标解释

1. KS值介绍

KS 值的计算逻辑非常经典。简单来说,它衡量的是“坏人(违约者)被模型识别出的速度”与“好人(正常人)被误伤的速度”之间的最大差值

以下是具体的计算步骤:

1.1. 计算步骤(分步拆解)

假设我们要计算“中证信评分”的 KS 值:

  1. 排序:将所有客户按照模型给出的违约概率由高到低排序(或者按信用评分由低到高排序)。

  2. 分箱/分组:通常将排序后的客户等分成 10 组(Deciles,每组 10% 的人)或 20 组。

  3. 计算各组人数:计算每一组内实际的“好人”人数和“坏人”人数。

  4. 计算累计占比

    • 计算到当前组为止,累计的坏人占总坏人的比例(Cumulative % of Bad)。

    • 计算到当前组为止,累计的好人占总好人的比例(Cumulative % of Good)。

  5. 计算差值(KS)

    • 在每一个组,计算:累计坏人占比累计好人占比| \text{累计坏人占比} - \text{累计好人占比} |

    • 所有组中最大的那个差值,即为该模型的 KS 值。


1.2. 数学公式与图形理解

在数学上,KS 统计量的公式为:

KS=maxTPRFPRKS = \max | \text{TPR} - \text{FPR} |

  • TPR(真阳率):累计找出了多少比例的坏人。

  • FPR(假阳率):累计误伤了多少比例的好人。

形象理解:

想象一个跑步比赛。坏人队和好人队都在跑向终点(100%)。

  • 如果模型很强,坏人队会跑得飞快(很早就在高风险区间扎堆),而好人队跑得很慢。

  • 在路程的某一点,坏人队已经跑了 70%,而好人队才跑了 20%。

  • 这时他们的距离差 70%20%=50%70\% - 20\% = 50\%。这个 50% (0.5) 就是 KS 值。

既然我们找到了两条曲线差距最大的那个点(即 KS 值 对应的位置),我们就拥有了一个非常强力的“分水岭” 🌊。在风控逻辑中,这个点通常被用来设定决策阈值(Threshold/Cut-off)

我们可以从以下两个视角来利用这个点:

1.2.1. 理论上的“最佳平衡点”

在没有其他业务约束(如必须通过多少人)的情况下,KS 值对应的得分通常是区分效果最好的阈值。

  • 操作:将 KS 最大处的得分设为准入线。

  • 结果:在这个点拒绝掉的人群中,包含了比例最高的坏人,同时尽可能少地误伤好人。它最大化了“抓坏人”和“留好人”的收益差。

1.2.2. 业务驱动的“灵活调整”

实际业务中,我们不一定非要死守 KS 那个点,而是以它为基准进行偏移:

  • 激进策略:如果公司现在想冲规模,我们会把阈值往“分数更低”的方向挪。虽然 KS 会下降(区分度没那么极致了),但能通过更多的人。

  • 保守策略:如果最近坏账率飙升,我们会把阈值往“分数更高”的方向挪。虽然会误伤一些好人,但能拦截掉绝大多数高风险客户。

1.4. KS 值的标准对照表

在外部数据评价时,你可以参考这个标准来衡量新引入数据的质量:

KS 值范围模型区分能力评价
< 0.20模型基本无用,区分能力极弱。
0.20 - 0.40表现一般,具有一定的业务价值。
0.40 - 0.50表现优异,属于非常好的模型/数据特征。
0.50 - 0.75表现极好,属于顶级区分度。
> 0.75极高,需要警惕是否发生了“数据泄露”(即特征中包含了结果)。

2. AUC值介绍

AUC 的计算过程时,最重要的是从“排序”的角度切入,而不是纠结于复杂的积分公式。以下是 AUC 计算的三个层级解释,从直观理解到数学计算:

2.1. 直观定义

AUC 的全称是 Area Under Curve(曲线下的面积)。

  • 它的物理含义是:随机抽取一个“坏人”和一个“好人”,模型给“坏人”打出的违约概率(y_prob)高于“好人”的概率。

  • 如果 AUC = 0.8,意味着有 80% 的情况下,模型能准确识别出谁更坏。

深度对比:KS 与 AUC 的“对话”

既然我们已经聊完了这两个指标,让我们把它们放在一起总结一下:

  • AUC 像是一个全能评价:它告诉你,如果随机挑一个好人和一个坏人,模型有多大概率能把坏人排在好人前面。它不关心具体的阈值。

  • KS 像是一个实战指南:它直接指出了模型区分能力最强的那一点,告诉你业务上最理想的“切割位置”。

“AUC 不关心你打的具体分数是 600 还是 0.6,它只关心你是否把坏人排在了好人的前面。它是对模型排序能力最纯粹的考核。”


2.2. 计算过程

假设我们有 5 个样本,模型给出的违约概率如下:

样本 ID真实标签 (1=坏)模型预测概率 (y_prob)
A10.9
B00.7
C10.6
D00.4
E00.2
第一步:计算所有可能的“一好一坏”组合

在这个例子中,有 2 个坏人 (A, C) 和 3 个好人 (B, D, E)。

总共有 2×3=62 \times 3 = 6 种组合:

  1. (A, B), (A, D), (A, E)

  2. (C, B), (C, D), (C, E)

第二步:判断每种组合中,坏人的概率是否高于好人
  • (A, B): 0.9 > 0.7 (正确, +1)

  • (A, D): 0.9 > 0.4 (正确, +1)

  • (A, E): 0.9 > 0.2 (正确, +1)

  • (C, B): 0.6 < 0.7 (错误, +0)

  • (C, D): 0.6 > 0.4 (正确, +1)

  • (C, E): 0.6 > 0.2 (正确, +1)

注:如果概率相等,记为 0.5。

第三步:计算总得分占比

AUC=正确组合数总组合数=1+1+1+0+1+16=560.833AUC = \frac{\text{正确组合数}}{\text{总组合数}} = \frac{1+1+1+0+1+1}{6} = \frac{5}{6} \approx 0.833


2.3. 几何计算过程(ROC 曲线法)

在程序中,通常是通过 ROC 曲线 来计算面积:

  1. 设定阈值:从 1.0 到 0.0 设定一系列阈值(Thresholds)。

  2. 计算坐标点

    • 对于每个阈值,计算 TPR (真阳率,纵坐标) 和 FPR (假阳率,横坐标)。

    • TPR = 找出的坏人 / 总坏人,纵坐标

    • FPR = 误伤的好人 / 总好人,横坐标

  3. 连线绘图:将这些点连接成一条曲线。

  4. 积分求面积:计算这条曲线与横轴围成的面积。

3. 代码实现

from sklearn.metrics import roc_curve, roc_auc_score

def calculate_model_performance(y_true, y_prob):
    """
    计算并输出 KS 和 AUC 指标
    """
    # 计算 AUC
    auc = roc_auc_score(y_true, y_prob)
    
    # 计算 ROC 曲线数据
    fpr, tpr, thresholds = roc_curve(y_true, y_prob)
    
    # 计算 KS:TPR 与 FPR 差值的绝对最大值
    ks = max(abs(tpr - fpr))
    
    print(f"--- 模型评估结果 ---")
    print(f"AUC: {auc:.4f}")
    print(f"KS : {ks:.4f}")
    
    # 寻找 KS 对应的最佳阈值 (可选)
    idx = np.argmax(abs(tpr - fpr))
    best_threshold = thresholds[idx]
    print(f"KS 最佳切割阈值: {best_threshold:.4f}")
    
    return auc, ks, fpr, tpr

# 示例调用
# y_true 为 0/1 标签,y_prob 为模型预测的违约概率
# calculate_model_performance(y_true, y_prob)