一、数据分析基础方法论
1. 从业务视角出发的分析模型
-
5W2H分析法: 明确为什么要引(Why)、谁在用(Who)、什么场景(What)。
-
漏斗模型: 外部数据在业务流(如获客、反欺诈、转化)中起到的支撑节点。
2. 数据探索性分析 (EDA)
-
描述性统计: 分布、均值、缺失率、异常值监测。
-
相关性分析: 外部字段与目标变量(如违约、流失)的统计关联。
3. 因果分析 vs. 相关性分析
- 外部数据往往是“影子指标”,如何区分相关性与因果逻辑。
3.1. 皮尔逊相关系数
皮尔逊相关系数(Pearson Correlation Coefficient)是衡量两个变量之间线性相关程度的指标。它的取值范围在 之间:
-
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. 核心注意事项
- 线性限制:皮尔逊系数只能捕捉线性关系。如果两个变量是曲线关系(比如 ),皮尔逊系数可能会很低,但它们依然有很强的相关性。
二、外部数据引入的评价维度
解决“新数据好不好”的问题,分为三个关键层级。
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 值
对于每一个分箱 ,其 WOE 的计算公式为:
-
: 该组内好客户数量
-
: 该组内坏客户数量
-
: 全样本好客户总数
-
: 全样本坏客户总数
第三步:计算该特征的 IV 值
将所有分箱的贡献加总:
1.2. 举例说明
假设我们要评价“消费水平” 这个特征的预测能力。
样本总量: 1000人(其中好人 800,坏人 200)。
我们取其中一个分箱:“高消费人群”(特征还有“中消费”、“低消费”等其他值,类别型变量一般按照类别值直接分组)。
| 维度 | 高消费组 (组 i) | 全样总量 |
|---|---|---|
| 好人 (Good) | 400 | 800 |
| 坏人 (Bad) | 20 | 200 |
计算过程:
-
好人占比 () = (50%)
-
坏人占比 () = (10%)
-
计算该组 WOE =
-
计算该组 IV 贡献 =
最后,将“中消费”、“低消费”等其他组的 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计算公式如下:
大家经常会问:为什么要费力气取个自然对数()呢?直接用比例不香吗? 其实,取 是为了解决三个核心的数学和工程问题:
1.5.1. 将“比例”转化为“线性”
如果不用 ,直接使用 (即 Odds 几率),它的取值范围是 。
-
当 Good 远多于 Bad 时,数值会飞速膨胀。
-
当 Bad 远多于 Good 时,数值被挤压在 到 之间。
取了 之后:
-
数值范围变成了 ,且以 0 为中位点。
-
WOE 为正: 该组别违约风险低(Good 占比高)。
-
WOE 为负: 该组别违约风险高(Bad 占比高)。
-
WOE 为 0: 该组别完全没有区分度。
这种对称性让模型(尤其是逻辑回归)处理起来更加“舒服”。
1.5.2. 逻辑回归的“天作之合”
信用评分卡底层通常使用 逻辑回归(Logistic Regression)。逻辑回归的模型方程本身就是基于 Log-odds 的:
当你预先对特征进行了 WOE 转换,你实际上是将特征转换到了与目标函数相同的尺度上。这样,特征与目标变量之间就建立了一种线性关系。这不仅提高了模型的拟合效果,还让回归系数 变得非常容易解释。
1.5.3. 处理极值与鲁棒性
原始数据中常有极端异常值(比如年收入 1 个亿和年收入 1 万)。
-
通过**分箱(Binning)**后再计算 WOE,这些异常值会被归入某个特定的区间。
-
函数具有“压缩”大数值、拉伸小数值的特性,这进一步增强了模型对噪声和异常值的抵抗力,让模型更加稳健。
1.5.4. 总结
取 的本质是为了“对称化”和“线性化”。它把非线性的比例关系,转换成了逻辑回归最喜欢的线性输入,从而方便我们后续计算 IV(Information Value)值来筛选变量,并最终生成直观的信用评分。
1.6 为什么说逻辑回归的模型方程本身就是基于对数几率( Log-odds) 的
1.6.1. 从线性回归到概率的困境
在线性回归中,我们的方程是 。
-
问题: 线性回归的预测值范围是 。
-
矛盾: 但我们要预测的概率 必须在 之间。
为了强行把 的线性结果映射到 ,数学家引入了 Sigmoid 函数:
1.6.2. 反向推导:寻找“线性”本质
如果我们把上面的 Sigmoid 方程反过来推导,看看等号右边保留线性部分时,等号左边会变成什么:
-
首先,求出“不发生的概率”与“发生的概率”之比(即 Odds,几率):
-
然后,为了消掉右边的指数 ,我们对等号两边同时取自然对数 :
瞧!这就是答案。 等号左边的 正是 Log-odds(也叫 Logit 变换)。这个方程告诉我们:逻辑回归的本质,是用特征的线性组合去拟合“对数几率”。
1.6.3. 为什么 Log-odds 这么重要?
之所以说模型方程“基于”它,是因为它完成了两个关键转换:
| 转换阶段 | 取值范围 | 物理意义 |
|---|---|---|
| 概率 () | 发生的可能性,但不是线性的。 | |
| 几率 (Odds) | 发生与不发生的比例,消除了上限。 | |
| 对数几率 (Log-odds) | 完美的线性尺度,与线性回归方程无缝对接。 |
1.6.4. 总结
当你看到逻辑回归的输出是 (概率)时,在模型的“大脑”深处,它实际上是在计算 (Log-odds)。
这就是为什么我们在计算 WOE 时也要取 ——因为只有取了 ,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)。它的设计初衷是为了解决一个经典的博弈问题:
场景:几个玩家(特征)共同参与一场游戏(模型预测),最终赢得了一笔奖金(预测值)。由于玩家之间存在配合(特征交互),如何公平地把奖金分给每一个玩家?
设计逻辑的三个核心原则:
-
本地准确性(Local Accuracy): 所有特征的贡献值(SHAP值)之和,必须等于模型的预测值与平均预测值之间的差额。这意味着模型预测的每一分波动都能找到归属。
-
缺失性(Missingness): 如果一个特征在模型中没有被用到,或者对某个样本没有取值,它的贡献必须为 0。
-
一致性(Consistency): 如果一个模型发生了改变,使得某个特征的贡献增加了,那么该特征的 SHAP 值不应该减少。
2.2. SHAP 值是怎么计算的?
SHAP 值的计算并不是简单的“删掉这个特征看结果变化”,因为它考虑了特征之间的组合效应。
2.2.1. 计算步骤(简化版逻辑):
假设我们有三个特征:(中证信)、(蚂蚁分)、(年龄)。我们要计算特征 的贡献。
-
排列组合:列出所有不包含 的特征子集:(空集)、、、。
-
边际贡献:分别计算在这些子集中加入 后,模型预测值的变化:
- 加入 后的预测值 - 只有 时的预测值 = 在集合 中的边际贡献。
-
加权平均:由于在不同大小的集合中加入 的重要性不同,需要对这些边际贡献进行加权。
-
总和:所有可能组合下的边际贡献的加权平均值,就是特征 的 SHAP 值。
数学表达:
2.2.2. 具体说明:
2.2.2.1. 组合的分类
我们要把所有不含 的组合按**规模(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. 权重的数学逻辑
公式中的权重部分是:
-
:特征总数(你的例子中 )。
-
:当前子集的特征数量。
-
分母 :代表所有特征进入模型的所有排列可能性(全排列)。
-
分子 :代表了在这种特定规模下,该组合出现的次数。
2.2.2.3. 实例演算(以 为例)
假设我们要算 的 SHAP 值,总特征数 。总排列数 。
| 子集 S | 规模 | 边际贡献 | 权重计算 (p=3) | 权重结果 |
|---|---|---|---|---|
| 空集 | 0 | f(B) - f(Base) | (0! * 2!) / 6 | 1/3 |
| {A} | 1 | f(A,B) - f(A) | (1! * 1!) / 6 | 1/6 |
| {C} | 1 | f(C,B) - f(C) | (1! * 1!) / 6 | 1/6 |
| {A, C} | 2 | f(A,C,B) - f(A,C) | (2! * 0!) / 6 | 1/3 |
验证权重之和:。权重分配非常公平。
2.3. 实例:评价“蚂蚁洞察评分”
假设模型的平均信用分(基准值)是 600分。 对于客户张三,模型预测结果是 750分(高出了 150分)。
通过 SHAP 拆解,我们发现:
-
中证信评分 (特征A):SHAP =
-
蚂蚁洞察评分 (特征B):SHAP =
-
年龄 (特征C):SHAP =
结论:
-
。这完美解释了张三为什么比平均水平高出 150 分。
-
虽然中证信贡献最大,但“蚂蚁洞察评分”提供了 50分 的正向拉动力。
-
业务决策:这 50 分的拉动力如果能显著提升模型对高分人群的识别精度,那么采购蚂蚁数据的决策就是正确的。
2.3.1. 具体计算过程
第一步:计算所有可能的“边际贡献”
假设我们要计算**特征 B(蚂蚁分)**的 SHAP 值。我们需要模拟在不同情况下,加入 B 后预测值(信用分)的变化。
| 场景(子集 S) | 加入 B 前的得分 f(S) | 加入 B 后的得分 f(S∪{B}) | 边际贡献 (差值) |
|---|---|---|---|
| 空集 | 600 (基准值) | 640 | |
| 只有 A | 680 | 735 | |
| 只有 C | 610 | 665 | |
| 有 A 和 C | 700 | 750 (最终预测) |
第二步:乘法与求和(加权平均)
现在,我们将刚才表格中的“边际贡献”与之前计算出的“权重”一一对应相乘。
| 场景 | 边际贡献 | × | 权重 | = | 分项结果 |
|---|---|---|---|---|---|
| 独自上场 | |||||
| 加入 A | |||||
| 加入 C | |||||
| 最后加入 |
最后一步:求和
这个 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 值:
-
排序:将所有客户按照模型给出的违约概率由高到低排序(或者按信用评分由低到高排序)。
-
分箱/分组:通常将排序后的客户等分成 10 组(Deciles,每组 10% 的人)或 20 组。
-
计算各组人数:计算每一组内实际的“好人”人数和“坏人”人数。
-
计算累计占比:
-
计算到当前组为止,累计的坏人占总坏人的比例(Cumulative % of Bad)。
-
计算到当前组为止,累计的好人占总好人的比例(Cumulative % of Good)。
-
-
计算差值(KS):
-
在每一个组,计算:。
-
所有组中最大的那个差值,即为该模型的 KS 值。
-
1.2. 数学公式与图形理解
在数学上,KS 统计量的公式为:
-
TPR(真阳率):累计找出了多少比例的坏人。
-
FPR(假阳率):累计误伤了多少比例的好人。
形象理解:
想象一个跑步比赛。坏人队和好人队都在跑向终点(100%)。
-
如果模型很强,坏人队会跑得飞快(很早就在高风险区间扎堆),而好人队跑得很慢。
-
在路程的某一点,坏人队已经跑了 70%,而好人队才跑了 20%。
-
这时他们的距离差 。这个 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) |
|---|---|---|
| A | 1 | 0.9 |
| B | 0 | 0.7 |
| C | 1 | 0.6 |
| D | 0 | 0.4 |
| E | 0 | 0.2 |
第一步:计算所有可能的“一好一坏”组合
在这个例子中,有 2 个坏人 (A, C) 和 3 个好人 (B, D, E)。
总共有 种组合:
-
(A, B), (A, D), (A, E)
-
(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。
第三步:计算总得分占比
2.3. 几何计算过程(ROC 曲线法)
在程序中,通常是通过 ROC 曲线 来计算面积:
-
设定阈值:从 1.0 到 0.0 设定一系列阈值(Thresholds)。
-
计算坐标点:
-
对于每个阈值,计算 TPR (真阳率,纵坐标) 和 FPR (假阳率,横坐标)。
-
TPR = 找出的坏人 / 总坏人,纵坐标
-
FPR = 误伤的好人 / 总好人,横坐标
-
-
连线绘图:将这些点连接成一条曲线。
-
积分求面积:计算这条曲线与横轴围成的面积。
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)