在数据科学和机器学习的实际应用中,构建一个高精度的模型往往只是成功的一半。更为关键的一步,也是最容易被忽视的一步,就是如何向业务部门解释模型的价值。我们经常遇到这样的问题:模型预测了客户会购买产品,但这对公司到底意味着多少额外的收益?我们应该优先联系哪 10% 的客户以获得最大的回报?
为了回答这些问题,我们需要引入两个强有力的评估工具:增益图表 和 提升图表。在这篇文章中,我们将深入探讨这两个图表的底层逻辑、计算方法,并通过大量的 Python 代码示例,带你一步步掌握它们在商业分析中的实战应用。
为什么我们需要增益和提升图表?
你可能会问,我们已经有了 ROC 曲线和 AUC 值,为什么还需要这些图表?这是一个非常棒的问题。ROC 曲线确实能评估模型的分类能力,但它更多地关注于“区分能力”,而较少直接反映“商业收益”或“成本效益”。
增益图表和提升图表则不同,它们直接关注模型带来的“增益”和“效率提升”。在目标营销、客户流失预测、欺诈检测等场景中,正样本(如响应营销的客户、欺诈交易)通常是非常稀少的(即类别不平衡问题)。在这些情况下,我们希望通过模型将最有可能响应的用户筛选出来,从而以最小的接触成本获得最大的回报。
简单来说,这两个图表帮助我们理解:如果我们使用模型来筛选目标,相比于完全不使用模型(即随机筛选),我们的收益能增加多少倍?
核心概念解析
在开始编写代码之前,让我们先统一一下核心术语的理解。这两个图表的绘制基础都建立在将数据集按预测概率进行分桶(通常是十分位数)之上。
#### 1. 增益
增益衡量的是:在使用模型的情况下,我们在前 $N$% 的样本中(例如前 10% 最高分的用户)捕获到了多少比例的正样本。
比如,增益图显示我们在前 20% 的数据里捕获了 60% 的响应者。这意味着如果我们只对这 20% 的用户进行营销,我们就能覆盖 60% 的潜在客户。
#### 2. 提升
提升衡量的是:相比于随机猜测,我们的模型表现得有多好。它是“使用模型时的正样本捕获率”与“随机选择时的正样本捕获率”的比值。
- 如果提升值为 2,意味着使用模型比随机撒网的效率高 2 倍。
- 如果提升值为 1,意味着模型和瞎猜没区别。
- 如果提升值小于 1,恭喜你,你的模型可能比反向操作还糟糕。
绘制图表:分步计算指南
让我们通过理论步骤来拆解这两个图表的生成过程。这对于后续理解代码至关重要。
- 预测与排序:首先,我们利用逻辑回归或其他分类模型预测每个样本为正类($Y=1$)的概率。然后,我们将所有观测值按照预测概率的降序排列。最可能响应的客户排在最前面。
- 分桶(十分位):将排序后的数据集平均分成 10 组(即十分位数)。第一个十分位包含预测概率最高的 10% 用户,依此类推。
- 计算累积正样本数:对于每一个十分位,计算该组内实际正样本的数量,并计算截止到当前组的累积正样本数量。
- 计算增益:
$$Gain_i = \frac{\text{截止到第 } i \text{ 个十分位的累计正样本数}}{\text{数据集中的正样本总数}}$$
- 计算提升:
$$Lift_i = \frac{\text{截止到第 } i \text{ 个十分位的累计正样本数}}{\text{在该十分位下随机预期的累计正样本数}}$$
注:随机预期的累计正样本数通常等于(样本总量占比 \times 总正样本数)。例如在第 2 个十分位(20% 数据量),随机期望是 $0.2 \times \text{Total Positives}$。
实战演练:从零构建与可视化
光说不练假把式。为了让你彻底掌握,我们将不依赖现成的第三方库(如 scikit-plot),而是使用 Pandas 和 Matplotlib 从零开始手动计算并绘制这两个图表。这种“硬核”做法能让你对数据流动和计算细节了如指掌。
#### 场景设置
假设我们有一个目标营销场景,数据集极度不平衡(响应者很少)。我们将生成模拟数据来演示。
#### 第一步:生成模拟数据
首先,我们需要一个样本数据集。我们将使用 INLINECODE12b87914 的 INLINECODE0fe74031 来创建一个典型的二分类数据集。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# 设置随机种子以保证结果可复述
np.random.seed(42)
# 生成模拟数据:10000个样本,其中只有约1%是正样本(模拟营销场景)
# weights=[0.99, 0.01] 表示 99% 负样本,1% 正样本
X, y = make_classification(n_samples=10000, n_features=20, n_informative=15,
n_redundant=5, weights=[0.99, 0.01], random_state=42)
# 转换为 DataFrame 以方便处理
df = pd.DataFrame(X, columns=[f‘feature_{i}‘ for i in range(20)])
df[‘target‘] = y
print(f"正样本(响应者)总数: {df[‘target‘].sum()}")
print(f"正样本比例: {df[‘target‘].mean():.2%}")
#### 第二步:模型训练与概率预测
我们需要模型输出概率值,而不是简单的 0/1 分类结果。逻辑回归非常适合处理这类概率输出。
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(df.drop(‘target‘, axis=1),
df[‘target‘],
test_size=0.3,
random_state=42)
# 初始化并训练逻辑回归模型
# class_weight=‘balanced‘ 有助于处理不平衡数据,但这里我们主要关注后续的图表
model = LogisticRegression(solver=‘lbfgs‘, max_iter=1000)
model.fit(X_train, y_train)
# 获取预测概率
# predict_proba 返回两列:第0列是P(0),第1列是P(1)。我们需要第1列(正样本概率)
y_prob = model.predict_proba(X_test)[:, 1]
# 创建一个包含真实标签和预测概率的 DataFrame
result_df = pd.DataFrame({‘actual‘: y_test, ‘prob‘: y_prob})
#### 第三步:数据预处理(排序与分箱)
这是绘制图表最核心的步骤。我们需要按概率降序排列,并将其切分为 10 个等份。
# 1. 按照预测概率降序排列
result_df = result_df.sort_values(by=‘prob‘, ascending=False).reset_index(drop=True)
# 2. 重置索引并计算每个样本所在的分位数
# 比如 1000 个样本,每 100 个为一个分位数
total_samples = len(result_df)
result_df[‘decile‘] = pd.cut(
np.arange(total_samples),
bins=10,
labels=[f‘{i+1}‘ for i in range(10)]
)
# 或者更直观地,使用 qcut (需要注意重复边界值问题,这里用手动切分更稳健)
# 我们采用一种更稳健的方法:直接计算每行属于前百分之几
result_df[‘percentile‘] = np.ceil((result_df.index + 1) / total_samples * 10).astype(int)
#### 第四步:计算累积增益与提升值
让我们计算每个十分位的关键指标。
# 按十分位进行聚合统计
lift_gain_df = result_df.groupby(‘percentile‘).agg({
‘actual‘: [‘sum‘, ‘count‘] # 统计该组内的正样本数和总样本数
}).reset_index()
# 重命名列以方便理解
lift_gain_df.columns = [‘decile‘, ‘positive_count‘, ‘total_count‘]
# 计算累积正样本数
lift_gain_df[‘cumulative_positive‘] = lift_gain_df[‘positive_count‘].cumsum()
# 计算累积样本占比(用于后续作为X轴)
lift_gain_df[‘cumulative_population_pct‘] = lift_gain_df[‘total_count‘].cumsum() / total_samples
# 计算关键指标
# 1. 总的正样本数(用于计算基准)
total_positives = lift_gain_df[‘positive_count‘].sum()
# 2. 增益 = 累积正样本 / 总正样本
lift_gain_df[‘gain‘] = lift_gain_df[‘cumulative_positive‘] / total_positives
# 3. 提升 = 累积正样本占比 / 随机期望占比
# 随机期望占比就是 cumulative_population_pct
lift_gain_df[‘lift‘] = lift_gain_df[‘gain‘] / lift_gain_df[‘cumulative_population_pct‘]
# 查看计算结果
print(lift_gain_df[[‘decile‘, ‘cumulative_positive‘, ‘gain‘, ‘lift‘]])
#### 第五步:可视化增益图表
在这个图中,X 轴代表我们接触了多少比例的客户(例如 10%, 20%),Y 轴代表我们捕获了多少比例的响应者。为了对比,我们还需要添加一条对角线(代表随机选择基准)。
plt.figure(figsize=(10, 6))
# 绘制基准线(随机模型)
# 如果是随机选择,选了 10% 的人就应该有 10% 的响应者,所以是 y=x
plt.plot([0, 1], [0, 1], ‘k--‘, label=‘随机模型‘, linewidth=2)
# 绘制增益曲线
# 注意:X轴要加上 0, Y轴也要加上 0,保证曲线从原点出发
x_axis = [0] + list(lift_gain_df[‘cumulative_population_pct‘])
y_axis = [0] + list(lift_gain_df[‘gain‘])
plt.plot(x_axis, y_axis, marker=‘o‘, linewidth=2, label=‘当前模型‘, color=‘royalblue‘)
# 添加阴影区域,强调模型优于随机的部分
plt.fill_between(x_axis, y_axis, [0] + list(lift_gain_df[‘cumulative_population_pct‘]),
color=‘skyblue‘, alpha=0.2, label=‘增益面积‘)
plt.title(‘增益图表‘, fontproperties=‘SimHei‘, fontsize=16)
plt.xlabel(‘数据样本百分比 (按预测概率排序)‘, fontproperties=‘SimHei‘)
plt.ylabel(‘累积捕获正样本百分比‘, fontproperties=‘SimHei‘)
plt.legend(prop={‘family‘: ‘SimHei‘})
plt.grid(True, linestyle=‘--‘, alpha=0.7)
plt.tight_layout()
plt.show()
图表解读:
观察上面的图表,如果你的曲线是凸起的并位于基准线上方,说明模型是有效的。曲线越陡峭,说明在前几个十分位(概率最高的那部分人)中,我们捕获了越多的正样本。这就是我们常说的“帕累托法则”在数据科学中的体现——用 20% 的努力(或成本)获得 80% 的结果。
#### 第六步:可视化提升图表
提升图展示了效率的倍数。
plt.figure(figsize=(10, 6))
# 绘制提升曲线
x_axis = list(lift_gain_df[‘cumulative_population_pct‘])
y_axis = list(lift_gain_df[‘lift‘])
plt.plot(x_axis, y_axis, marker=‘o‘, linewidth=2, color=‘darkgreen‘, label=‘提升曲线‘)
# 添加基准线 (Lift = 1)
plt.axhline(y=1, color=‘red‘, linestyle=‘--‘, label=‘基准线 (Lift=1)‘, linewidth=2)
plt.title(‘提升图表‘, fontproperties=‘SimHei‘, fontsize=16)
plt.xlabel(‘数据样本百分比 (按预测概率排序)‘, fontproperties=‘SimHei‘)
plt.ylabel(‘提升值
plt.legend(prop={‘family‘: ‘SimHei‘})
plt.grid(True, linestyle=‘--‘, alpha=0.7)
# 在图表上标注数值
for a, b in zip(x_axis, y_axis):
plt.text(a, b + 0.05, f‘{b:.2f}‘, ha=‘center‘, va=‘bottom‘, fontsize=9)
plt.tight_layout()
plt.show()
图表解读:
- 在第一个十分位(10%),如果 Lift 值为 5.0,意味着这前 10% 的人的响应率是平均水平的 5 倍。
- 随着横轴向右移动,我们包含了更多低概率的客户,Lift 值通常会逐渐下降,最终趋向于 1.0(因为包含所有人的时候,平均响应率就是总体响应率,倍数为 1)。
- 判断标准:曲线越高(即越偏离 Lift=1),模型在识别高价值客户方面的表现越出色。
实战中的最佳实践与常见陷阱
掌握了基本绘制后,让我们谈谈在实战中如何更好地应用这些工具。
#### 1. 如何向非技术团队解释图表?
当你向市场经理展示这些图表时,不要只讲数学定义。试着这样沟通:
- “看增益图”:老板,如果我们只给前 20% 的客户发邮件(X轴),我们能覆盖到 60% 的潜在购买者(Y轴)。这意味着我们可以省下 80% 的营销预算,却只流失 40% 的客户。
- “看提升图”:在前 10% 的客户群体中,我们的响应率是随机发送的 3 倍。这大大降低了单次获客成本。
#### 2. 数据泄露:沉默的杀手
在计算增益和提升图时,最常犯的错误就是数据泄露。如果你在特征工程中不小心包含了“未来信息”(比如客户是否已经购买了该产品作为特征),你的模型在测试集上的表现会好得不真实。你会发现增益图是一条几乎完美的直线直冲 100%。这在现实中是不可能的。如果你的图表看起来过于完美,请首先检查是否存在特征泄露。
#### 3. 过拟合与验证集的重要性
模型在训练集上的增益图可能非常漂亮,但这并不代表它在生产环境中能复现。我们必须在验证集或测试集上绘制这两个图表。如果测试集上的增益图远低于训练集,说明模型出现了过拟合。此时,你需要考虑增加正则化、减少特征或使用集成方法。
#### 4. 不仅仅是逻辑回归
虽然我们在示例中使用了逻辑回归,但这两个图表适用于任何能输出概率的分类模型——无论是 XGBoost、LightGBM 还是神经网络。你可以尝试更换模型,对比不同模型在增益图上的表现,以此来选择最适合业务目标的模型。通常 AUC 最高的模型,其增益图也是最优的,但并不总是绝对如此,增益图能更直观地展示特定区间的表现。
#### 5. 使用 Python 库简化流程(进阶)
虽然从零编写代码能加深理解,但在生产环境中,我们需要效率。我们可以利用现有的库来快速生成这些图表。例如,scikit-plot 是一个非常强大的可视化库。
# 安装: pip install scikit-plot
import scikitplot as skplt
# scikit-plot 需要传入测试集和预测的概率矩阵
# plot_lift 函数会自动处理分箱和绘图
skplt.metrics.plot_lift_curve(y_test, model.predict_proba(X_test))
plt.show()
# 绘制累积增益图
skplt.metrics.plot_cumulative_gain(y_test, model.predict_proba(X_test))
plt.show()
这段代码仅需几行就能替代我们之前手写的几十行聚合计算,非常适合快速迭代。
总结与后续步骤
在这篇文章中,我们一起深入探讨了 增益图表 和 提升图表 的构建方法及其在商业分析中的实际价值。我们了解到,这两个图表不仅是评估模型性能的技术指标,更是连接数据科学与商业决策的桥梁。它们帮助我们在成本受限的情况下,找到效率最高的切入点。
关键要点回顾:
- 增益图告诉我们模型能覆盖多少目标客户(覆盖率)。
- 提升图告诉我们模型比随机选择强多少倍(效率倍数)。
- 两者都依赖于将样本按预测概率降序排列并进行分箱分析。
- 在处理不平衡数据集(如营销响应、欺诈检测)时,它们比准确率更有说服力。
接下来你可以尝试:
- 尝试在自己的数据集上运行上述代码,观察模型在前 10% 和 20% 分位的 Lift 值是否显著大于 1。
- 对比两个不同的模型(例如逻辑回归 vs 随机森林),看看谁的增益曲线更陡峭。
- 思考一下:在你的业务中,为了提升前 10% 的 Lift 值,你愿意牺牲多少其余部分的召回率?
希望这篇文章能帮助你更好地理解和运用这两个强大的工具!如果你在实践中有任何疑问,欢迎随时交流探讨。