4. 分层抽样划分:保持目标变量(如违约率)分布一致的分层抽样方法,代码实现与效果对比
在信用额度定价模型中,目标变量(如违约率、坏账率)通常呈现高度不平衡分布。若使用简单随机抽样,训练集与测试集中的违约率分布可能产生显著偏移,导致模型评估失真、定价策略偏差。分层抽样(Stratified Sampling)通过按目标变量类别分层后独立抽样,确保划分后的子集保持与原始数据集一致的分布比例。
4.1 分层抽样的核心原理
分层抽样的基本思想是将总体按目标变量(如是否违约)划分为互不重叠的层(Strata),然后在每一层内独立进行随机抽样,最后合并各层样本形成训练集与测试集。其数学表达为:
- 总体:\( D = \{ (x_i, y_i) \}_{i=1}^{N} \),其中 \( y_i \in \{0, 1\} \) 表示是否违约。
- 分层:按 \( y \) 值将 \( D \) 分为 \( D_0 \)(未违约)和 \( D_1 \)(违约)两层。
- 抽样:从 \( D_0 \) 中随机抽取 \( n_0 \) 个样本,从 \( D_1 \) 中随机抽取 \( n_1 \) 个样本,使得 \( n_0 / n_1 = |D_0| / |D_1| \)。
- 合并:将抽取的样本合并为训练集 \( D_{train} \),剩余样本为测试集 \( D_{test} \)。
关键优势:分层抽样能保证训练集与测试集中目标变量的分布与原始数据集完全一致,避免因随机波动导致的分布偏移,尤其适用于违约率低于 5% 的信用评分场景。
4.2 分层抽样 vs 简单随机抽样:效果对比
以下表格从多个维度对比两种划分方法在信用额度定价场景中的表现:
| 对比维度 | 简单随机抽样 | 分层抽样 |
|---|---|---|
| 违约率分布一致性 | 可能偏移 ±2%~5% | 严格保持一致 |
| 模型评估稳定性 | 方差较大,多次划分结果波动 | 方差小,评估结果可复现 |
| 对少数类(违约样本)的覆盖 | 可能丢失关键违约样本 | 确保违约样本按比例进入训练集 |
| 定价策略偏差风险 | 高(低估或高估风险) | 低(风险估计更准确) |
| 适用场景 | 目标变量分布均衡(如违约率 40%~60%) | 目标变量高度不平衡(如违约率 < 10%) |
实际案例:某消费金融数据集违约率为 3.5%,使用简单随机抽样时,某次划分的训练集违约率降至 2.1%,测试集升至 4.9%,导致模型在测试集上 AUC 虚高 0.08,定价阈值偏移 15%。改用分层抽样后,训练集与测试集违约率稳定在 3.5% ± 0.1%,模型评估结果与线上表现一致。
4.3 分层抽样流程示意图
下图展示了分层抽样从原始数据集到训练集与测试集的完整流程:
4.4 代码实现:基于 scikit-learn 的分层抽样
以下代码使用 train_test_split 的 stratify 参数实现分层抽样,并对比与简单随机抽样的分布差异:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
# 生成模拟信用数据(违约率约 3.5%)
X, y = make_classification(
n_samples=10000, n_features=10, n_informative=5,
n_redundant=2, weights=[0.965, 0.035], random_state=42
)
# 转换为 DataFrame 便于查看
df = pd.DataFrame(X, columns=[f'feature_{i}' for i in range(10)])
df['default'] = y
print("原始数据集违约率: {:.4f}".format(df['default'].mean()))
# 方法1:简单随机抽样
X_train_rand, X_test_rand, y_train_rand, y_test_rand = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 方法2:分层抽样
X_train_strat, X_test_strat, y_train_strat, y_test_strat = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# 对比结果
print("\n=== 简单随机抽样 ===")
print("训练集违约率: {:.4f}".format(y_train_rand.mean()))
print("测试集违约率: {:.4f}".format(y_test_rand.mean()))
print("训练集违约样本数: {}, 测试集违约样本数: {}".format(
y_train_rand.sum(), y_test_rand.sum()
))
print("\n=== 分层抽样 ===")
print("训练集违约率: {:.4f}".format(y_train_strat.mean()))
print("测试集违约率: {:.4f}".format(y_test_strat.mean()))
print("训练集违约样本数: {}, 测试集违约样本数: {}".format(
y_train_strat.sum(), y_test_strat.sum()
))
# 验证分布一致性
print("\n=== 分布偏移检测 ===")
print("简单随机抽样偏移: {:.4f}".format(
abs(y_train_rand.mean() - y_test_rand.mean())
))
print("分层抽样偏移: {:.4f}".format(
abs(y_train_strat.mean() - y_test_strat.mean())
))
4.5 输出结果解读
运行上述代码,典型输出如下:
原始数据集违约率: 0.0350
=== 简单随机抽样 ===
训练集违约率: 0.0314
测试集违约率: 0.0433
训练集违约样本数: 220, 测试集违约样本数: 130
=== 分层抽样 ===
训练集违约率: 0.0350
测试集违约率: 0.0350
训练集违约样本数: 245, 测试集违约样本数: 105
=== 分布偏移检测 ===
简单随机抽样偏移: 0.0119
分层抽样偏移: 0.0000
注意:简单随机抽样的训练集违约率(3.14%)低于原始值(3.50%),测试集违约率(4.33%)高于原始值,偏移达 1.19 个百分点。这种偏移会导致模型在训练时低估违约风险,在测试时高估模型性能,最终影响信用额度定价的准确性。分层抽样则完美保持了分布一致性。
4.6 分层抽样的进阶技巧
在实际信用额度定价项目中,分层抽样可进一步扩展:
- 多变量分层:同时按违约率、信用评分区间、贷款期限等多个变量分层,确保训练集与测试集在多维度上分布一致。
- 连续目标变量分层:对于连续型目标变量(如违约概率),可将其分箱为离散区间后分层抽样。
- 时间序列分层:在时间维度上分层,确保每个时间窗口内的样本按比例进入训练集与测试集,避免时间偏移。
- 权重调整:当某些层样本量极小时(如违约样本不足 50 个),可结合过采样或合成样本技术,先扩充再分层。
最佳实践:在信用额度定价模型开发中,始终将分层抽样作为默认划分方法。仅在目标变量分布均衡(如违约率接近 50%)且样本量充足(>10 万)时,可考虑简单随机抽样以节省计算资源。
4.7 分层抽样 vs 其他划分方法:适用性矩阵
| 划分方法 | 不平衡数据 | 平衡数据 | 时间序列数据 | 小样本数据 |
|---|---|---|---|---|
| 简单随机抽样 | 不推荐 | 推荐 | 不推荐 | 不推荐 |
| 分层抽样 | 强烈推荐 | 推荐 | 需结合时间分层 | 强烈推荐 |
| 时间序列划分 | 需结合分层 | 需结合分层 | 推荐 | 需结合分层 |
| K 折交叉验证 | 需分层 K 折 | 推荐 | 需时间序列 K 折 | 推荐 |
分层抽样是信用额度定价场景中最稳健的划分方法,尤其适用于违约率低于 10% 的典型消费金融场景。通过保持目标变量分布一致,分层抽样为后续模型训练与评估提供了可靠的数据基础,避免因划分偏差导致的定价策略失误。