3. 数据准备:特征工程基础、数据清洗与缺失值处理

好,咱们进入实战环节的第一步——数据准备。

说实话,很多做风控的朋友都容易忽略这一步。他们觉得建模才是核心,数据随便洗洗就行。我刚开始做评分卡的时候也这么想,结果模型上线后,坏账率比预期高了整整一倍。后来一查,原来是某个字段的缺失值处理方式出了问题。

嗯,从那以后,我再也不敢小看数据准备了。

3.1 特征工程:从原始数据到有效特征

特征工程,说白了就是把原始数据变成模型能理解、能利用的信息。在贷前评分卡里,我们面对的数据通常来自三方面:

  • 用户填表数据:年龄、收入、职业、学历等
  • 征信数据:逾期次数、负债率、查询次数等
  • 行为数据:登录频率、浏览时长、填写时长等

我个人习惯把特征分成三类:

特征类型 举例 处理方式
数值型 年龄、收入、负债金额 标准化、分箱、对数变换
分类型 职业、学历、婚姻状况 独热编码、标签编码、WOE编码
时序型 近6个月逾期次数、近3个月查询次数 滑动窗口、趋势特征、波动率

这里有个坑,我踩过好几次——时序特征一定要用窗口期计算。比如“近6个月逾期次数”,你不能用全量数据算,否则会引入未来信息。你想想看,模型在训练时看到了未来的数据,上线后肯定要翻车。

核心原则:特征工程必须保证“时间一致性”。训练集和测试集的特征计算逻辑必须完全一致,且不能使用未来信息。

3.2 数据清洗:脏数据是模型的毒药

数据清洗,我把它叫做“排雷”。

常见的脏数据有:

  • 重复数据:同一个用户申请了多次,只保留最新一条
  • 异常值:年龄200岁、月收入1000万这种明显不合理的
  • 逻辑矛盾:学历是博士,但年龄只有18岁
  • 格式错误:手机号少一位、身份证号乱填

我曾经遇到过一个项目,数据里有个字段叫“家庭月收入”,最大值是9999999。一开始以为是正常数据,后来发现是系统默认值。这种错误如果不处理,模型会严重高估用户的还款能力。

清洗的流程,我一般这么走:

  1. 去重:按用户ID去重,保留申请时间最新的那条
  2. 范围校验:年龄18-70岁,收入1000-500000,超出则标记为异常
  3. 逻辑校验:学历和年龄是否匹配,职业和收入是否合理
  4. 格式校验:手机号11位,身份证号18位,银行卡号符合Luhn算法

小技巧:清洗规则最好写成配置文件,方便后续复用和调整。我习惯用YAML文件来管理所有清洗规则,这样每次迭代只需要改配置文件,不用改代码。

3.3 缺失值处理:别让“不知道”毁了模型

缺失值处理,是数据准备里最考验经验的一环。

为什么会有缺失?原因很多:

  • 用户不愿意填(比如收入、婚姻状况)
  • 系统没采集到(比如某些字段只在特定渠道出现)
  • 数据源不一致(比如征信报告里某些字段为空)

处理缺失值,我总结了三种策略:

策略一:删除

如果某个字段缺失率超过70%,我建议直接删除。为什么?因为缺失太多,这个字段的信息量已经很少了,强行填充反而会引入噪声。

策略二:填充

常用的填充方法有:

填充方法 适用场景 注意事项
均值/中位数填充 数值型字段,分布较对称 异常值会影响均值,建议用中位数
众数填充 分类型字段 注意类别分布是否均匀
模型预测填充 缺失率较低,且与其他字段相关性高 计算量大,容易过拟合
固定值填充 业务含义明确的字段 比如收入缺失填0,表示“未知”

策略三:单独作为一类

对于分类型字段,我建议把“缺失”单独作为一个类别。比如婚姻状况,缺失值就填“未知”。这样模型可以学到“缺失”本身也是一种信息。

注意:千万不要在训练集和测试集上分别计算填充值。正确的做法是:在训练集上计算填充值(比如中位数),然后把这个值应用到测试集和验证集上。

3.4 实战代码:缺失值处理流水线

下面是我在实际项目中常用的缺失值处理代码。嗯,这里要注意,我习惯用Pipeline来封装,方便复用。

import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

# 定义数值型字段的填充策略
numeric_features = ['age', 'income', 'debt_ratio']
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median'))
])

# 定义分类型字段的填充策略
categorical_features = ['marriage', 'education', 'occupation']
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='constant', fill_value='未知'))
])

# 组合成预处理流水线
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

# 应用流水线
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)

这段代码的核心逻辑是:数值型字段用中位数填充,分类型字段用“未知”填充。而且我用了ColumnTransformer,可以同时处理不同类型的字段,非常方便。

3.5 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张流程图:

数据准备知识体系 原始数据 特征工程 数据清洗 缺失值处理 数值型 | 分类型 | 时序型 去重 | 范围校验 | 逻辑校验 删除 | 填充 | 单独成类 干净、可用的特征集

这张图把本章的核心内容串起来了。从原始数据出发,经过特征工程、数据清洗、缺失值处理三个环节,最终得到干净可用的特征集。每一步都有对应的处理策略,缺一不可。

我的建议:在实际项目中,数据准备的时间至少要占到整个项目周期的40%。别急着建模,先把数据搞干净。你想想看,地基没打好,房子盖得再漂亮也是危房。

好了,数据准备这块就讲到这里。记住一句话:垃圾进,垃圾出。数据质量决定了模型的天花板,算法只是帮你接近这个天花板而已。


公众号:蓝海资料掘金营,微信deep3321