第一章:数据准备与清洗——建模的基石
各位同学,咱们直接进入正题。做信用风险模型,圈里有句话叫「垃圾进,垃圾出」。你模型再花哨,数据一塌糊涂,结果就是废纸一张。今天这章,我就带你过一遍数据准备与清洗的核心环节。
核心逻辑:数据质量决定了模型的天花板。算法只是逼近这个天花板的手段。
1.1 数据来源:你的弹药从哪来?
做违约概率评估,数据来源一般分三类。我习惯把它们比作「三根柱子」。
- 内部数据:自家银行或机构的交易记录、还款历史、客户画像。这是最核心的资产。我在项目中遇到过,有些机构内部数据字段多到上百个,但真正能用的不到三分之一——因为很多字段缺失率超过90%。
- 征信数据:央行征信、百行征信等。这是硬通货,逾期记录、查询次数、负债情况。说白了,这是客户的「信用身份证」。
- 第三方数据:运营商数据、电商数据、社保公积金等。用来做交叉验证和补充画像。但要注意合规性,我曾经因为用了不合规的第三方数据,模型上线前被风控合规部打回来重做。
你想想看,这三类数据各有优劣。内部数据最准但范围窄,征信数据权威但有延迟,第三方数据丰富但质量参差不齐。怎么取舍?我的建议是:以内部数据为骨架,征信数据为肌肉,第三方数据为皮肤。
1.2 缺失值处理:别让「不知道」毁了模型
缺失值,是数据清洗里最头疼的问题。为什么?因为很多算法(比如逻辑回归、SVM)不能容忍缺失值。你扔进去一个NaN,它直接报错给你看。
处理缺失值,我一般分三步走:
- 诊断缺失模式:先看看是随机缺失、完全随机缺失,还是非随机缺失。这决定了后续策略。
- 删除法:如果某个字段缺失率超过70%,我建议直接扔掉。别心疼,留着也是噪音。
- 填充法:常用的有均值/中位数填充、众数填充、前向/后向填充,或者用模型预测填充(比如KNN、随机森林)。
我的小技巧:对于信用评分卡这类模型,我习惯把「缺失」单独作为一个类别。比如「收入字段缺失」本身就是一个很强的风险信号——往往意味着客户收入不稳定或不愿透露。
代码示例,用pandas处理缺失值:
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('credit_data.csv')
# 查看缺失率
missing_rate = df.isnull().mean().sort_values(ascending=False)
print(missing_rate[missing_rate > 0.7]) # 缺失率超过70%的字段
# 删除高缺失率字段
df.drop(columns=['col_name'], inplace=True)
# 数值型字段用中位数填充
df['income'].fillna(df['income'].median(), inplace=True)
# 分类型字段用众数填充
df['education'].fillna(df['education'].mode()[0], inplace=True)
# 或者,把缺失单独标记
df['income_missing'] = df['income'].isnull().astype(int)
df['income'].fillna(-1, inplace=True) # 用-1代表缺失
注意:千万不要用均值填充异常值较多的字段。我曾经在一个收入分布严重右偏的数据集上用均值填充,结果模型训练出来,低收入人群的预测概率全偏了。后来改用中位数,效果好了很多。
1.3 异常值检测:揪出那些「捣乱分子」
异常值,说白了就是那些明显偏离正常范围的数据点。比如年龄字段出现200岁,或者收入字段出现-1000元。这些数据要么是录入错误,要么是极端情况。
我常用的检测方法有三种:
- 3σ原则:适用于正态分布的数据。超出均值±3倍标准差的值视为异常。
- 箱线图法(IQR):适用于偏态分布。低于Q1-1.5*IQR或高于Q3+1.5*IQR的值视为异常。
- 业务规则法:比如年龄必须在18-100岁之间,贷款金额不能为负数。这是最直接的方法。
代码示例:
# 箱线图法检测异常值
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")
# 处理方式:截断(winsorize)
df['income'] = df['income'].clip(lower=lower_bound, upper=upper_bound)
嗯,这里要注意:异常值不一定要删除。有时候异常值本身就是风险信号。比如一个客户突然申请了远超其收入水平的贷款,这可能是欺诈信号。我建议先分析异常值的业务含义,再决定处理方式。
1.4 数据标准化与归一化:让所有特征「站在同一起跑线」
为什么需要标准化?你想想看,如果收入字段范围是0-100万,年龄字段范围是18-80岁,那么收入对模型的影响天然就比年龄大。这不是因为收入更重要,只是因为它的数值更大。
常用的方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 数据近似正态分布,或使用SVM、PCA等算法 |
| Min-Max归一化 | (x - min) / (max - min) | 数据有明确边界,或使用神经网络 |
| Robust标准化 | (x - median) / IQR | 数据存在较多异常值 |
代码示例:
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# Z-score标准化
scaler_std = StandardScaler()
df['income_std'] = scaler_std.fit_transform(df[['income']])
# Min-Max归一化
scaler_mm = MinMaxScaler()
df['income_mm'] = scaler_mm.fit_transform(df[['income']])
# Robust标准化(抗异常值)
scaler_rob = RobustScaler()
df['income_rob'] = scaler_rob.fit_transform(df[['income']])
避坑指南:我曾经在训练集上做标准化后,忘了保存scaler对象。结果模型上线时,新数据没法做同样的标准化,导致预测结果全乱套。记住:一定要保存scaler,并在测试集和上线时复用同一个scaler。
知识体系总览
下面这张图,是我自己梳理的数据准备与清洗的完整流程。你可以把它当作一个检查清单:
这张图把整个流程串起来了。从数据来源开始,经过缺失值处理、异常值检测,最后到标准化与归一化。每一步都有对应的处理方法,你可以根据实际情况灵活选择。
好了,第一章的内容就到这里。数据准备与清洗是建模中最枯燥但最重要的一环。别嫌麻烦,把这一步做扎实了,后面的建模工作会顺畅很多。