4. 数据预处理

在信用评分卡项目中,数据预处理不是“清洗脏数据”的附属步骤,而是决定模型上限的关键环节。scorecardpy 虽然提供了 var_filter、woebin、scorecard 等自动化能力,但这些函数对输入数据有明确假设:缺失值需被合理标记、异常值不能污染分箱边界、类别变量必须可枚举、样本口径必须一致。若预处理阶段留下隐患,后续 IV 值、WOE 单调性和 KS 曲线都会失真。本节围绕缺失值、异常值、类型转换、样本筛选与数据集划分五个环节,给出可落地的处理策略与 scorecardpy 中的对应写法。

一、核心原理:预处理为何决定评分卡成败

评分卡的本质是把连续风险映射为离散分数,而分箱是这一映射的核心。分箱算法(如卡方分箱、决策树分箱)依赖样本在变量上的分布。缺失值若被简单填充为 0,会与真实“零值”混淆,导致该箱的坏样本率被稀释;极端异常值会把分箱边界拉向尾部,使中间密集区间被压缩;把数值型 ID 当作连续变量参与分箱,则会产生大量无意义切点。因此,预处理的目标不是“让数据好看”,而是让每个变量的分布真实反映其与违约标签的关系。

预处理的四个判断标准:缺失是否有业务含义、异常是否真实发生、类型是否匹配分箱方法、样本是否属于同一风险口径。任何一条不满足,都应先处理再进入 woebin。

二、缺失值处理:先诊断,再决策

缺失值处理的第一步是统计缺失率与缺失模式。scorecardpy 的 woebin 默认会把缺失单独作为一个箱(binmissing 相关行为),这实际上是一种“缺失即信息”的处理方式。是否保留缺失箱,取决于缺失是否与违约相关。

import scorecardpy as sc
import pandas as pd

# 缺失率统计
miss = df.isnull().mean().sort_values(ascending=False)
print(miss[miss > 0])

# 缺失与标签的关系:缺失样本的坏样本率
for col in miss[miss > 0.05].index:
    rate = df.loc[df[col].isnull(), 'label'].mean()
    print(col, '缺失坏样本率=', round(rate, 4), '整体坏样本率=', round(df['label'].mean(), 4))

处理策略可按缺失率分层:缺失率低于 5% 且缺失与标签无关时,可用中位数或众数填充;缺失率在 5%~30% 且缺失样本坏样本率显著偏离整体时,保留缺失并让分箱单独成箱;缺失率高于 50% 且无业务解释时,建议直接剔除该变量。需要强调的是,不要用标签信息去填充缺失,例如用坏样本均值填充,这会造成标签泄漏。

三、异常值检测:区分错误值与真实极端值

异常值分两类:一类是录入错误,如年龄为 999、收入为负数,应直接修正或置为缺失;另一类是真实极端值,如高净值客户的超高授信额度,不应删除,而应通过分箱将其归入尾部箱。常用检测方法包括 IQR 法则、Z-Score 和分位数截断。

# IQR 检测
Q1, Q3 = df['income'].quantile([0.25, 0.75])
IQR = Q3 - Q1
lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
outliers = df[(df['income'] < lower) | (df['income'] > upper)]
print('异常样本数:', len(outliers))

# 业务规则修正
df.loc[df['age'] > 100, 'age'] = np.nan
df.loc[df['income'] < 0, 'income'] = np.nan
不要用 3σ 法则机械删除异常值。信用数据普遍右偏,3σ 会误删大量真实高价值客户。优先用业务规则修正错误值,再用分箱自然吸收极端值。

四、变量类型转换与样本筛选

scorecardpy 的分箱函数对类型敏感:数值型走连续分箱,字符型走类别分箱。因此需要显式区分变量类型。常见转换包括:把“是否”“性别”等二值字符映射为 0/1;把有序等级(如学历)映射为有序数值;把高基数类别变量(如职业代码)先做频次合并,再转为字符型参与分箱。

# 类型转换示例
df['gender'] = df['gender'].map({'M': 1, 'F': 0})
df['education'] = df['education'].map({'小学': 1, '中学': 2, '本科': 3, '硕士及以上': 4})

# 高基数类别合并
top = df['occupation'].value_counts().nlargest(10).index
df['occupation'] = df['occupation'].where(df['occupation'].isin(top), 'OTHER')

# 样本筛选:剔除无效样本
df = df[df['age'].between(18, 70)]
df = df[df['label'].notnull()]

样本筛选需遵循三条规则:一是剔除标签缺失样本;二是按业务准入规则剔除不满足授信条件的样本;三是观察期与表现期口径一致,避免用未成熟样本训练。样本筛选后应重新检查坏样本率是否稳定。

五、数据集划分策略与检查清单

信用评分卡常用“训练集 + 测试集 + 时间外样本”三段划分。随机划分适合样本量充足且时间跨度小的场景;时间外样本(OOT)更贴近真实部署,能检验模型跨时间稳定性。scorecardpy 的 split_df 支持按比例随机划分,也支持指定 seed 保证可复现。

train, test = sc.split_df(df, y='label', ratio=0.7, seed=186)
# 时间外划分
train = df[df['apply_date'] < '2023-01-01']
oot = df[df['apply_date'] >= '2023-01-01']
环节关键动作scorecardpy 对应常见误区
缺失值统计缺失率与坏样本率,分层处理woebin 缺失单独成箱用标签均值填充造成泄漏
异常值业务规则修正错误值,分箱吸收极端值woebin 尾部箱3σ 机械删除真实高价值客户
类型转换二值映射、有序编码、高基数合并woebin 按类型分箱把 ID 当连续变量分箱
样本筛选剔除标签缺失、口径统一、观察期一致var_filter 前置混入未成熟样本
数据集划分训练/测试/OOT,固定 seedsplit_df随机划分忽略时间漂移
复盘建议:预处理完成后,先跑一次 sc.var_filter 和 sc.woebin,检查 IV 值是否异常偏高(可能泄漏)、WOE 是否单调、缺失箱坏样本率是否合理。若发现某变量 IV 超过 0.5,应回到预处理阶段排查是否引入了标签相关特征。

综上,数据预处理的核心不是追求“干净”,而是保证进入分箱的每个变量都真实、可比、可解释。缺失值要判断信息价值,异常值要区分错误与真实,类型转换要匹配分箱方法,样本筛选要统一口径,数据集划分要兼顾随机性与时间稳定性。把这五步做扎实,后续的 WOE 转换、逻辑回归和评分卡刻度才有可靠基础。