4、数据清洗实战:缺失值处理、异常值检测、重复数据处理、数据标准化

数据清洗,说白了就是给数据「洗澡」。

我做了这么多年风控,见过太多「脏数据」了。有的缺失值高达80%,有的异常值能把模型带偏十万八千里。你想想看,如果连数据都是脏的,模型再牛也白搭。

这一章,咱们就把数据清洗的四个核心环节掰开揉碎讲清楚。

4.1 缺失值处理

缺失值在风控数据里太常见了。用户填表时漏填、系统采集时丢包、数据迁移时出错……原因五花八门。

我个人习惯,拿到数据第一件事就是看缺失率。

import pandas as pd
import numpy as np

# 加载数据
df = pd.read_csv('credit_data.csv')

# 查看缺失率
missing_rate = df.isnull().sum() / len(df)
print(missing_rate.sort_values(ascending=False))

处理缺失值,我一般分三种情况:

  • 缺失率 < 5%:直接删除。样本量大,影响不大。
  • 缺失率 5% - 30%:填充。常用均值、中位数、众数,或者用模型预测。
  • 缺失率 > 30%:建议删除该特征。别心疼,留着反而添乱。

重点提醒:填充缺失值时,千万别用全局均值。我在项目中遇到过,用全局均值填充后,模型效果反而变差了。后来改成按分组填充——比如按用户等级、按年龄段——效果立马提升。

# 按分组填充缺失值
df['income'] = df.groupby('age_group')['income'].transform(
    lambda x: x.fillna(x.median())
)

小技巧:对于风控场景,缺失本身可能也是一种信号。比如「工作单位」字段缺失,可能意味着用户无业或自由职业,风险偏高。这时候可以保留缺失标记,作为一个新特征。

4.2 异常值检测

异常值,就是那些「离谱」的数据点。比如年龄填了200岁,月收入填了1个亿。

为什么会这样?可能是录入错误,也可能是恶意欺诈。在风控里,后者尤其值得关注。

我常用的检测方法有三种:

  1. 3σ原则:适用于正态分布的数据。超出均值±3倍标准差的值,视为异常。
  2. 箱线图法:适用于偏态分布。超出上下四分位数1.5倍IQR的值,视为异常。
  3. 孤立森林:适用于高维数据。通过随机切割来识别异常点。
# 箱线图法检测异常值
Q1 = df['loan_amount'].quantile(0.25)
Q3 = df['loan_amount'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['loan_amount'] < lower_bound) | 
              (df['loan_amount'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")

注意:异常值不一定要删除。我曾经处理过一个欺诈检测项目,发现异常值里藏着大量欺诈样本。直接删除的话,模型就学不到欺诈模式了。建议先分析异常值的分布,再决定处理方式。

处理异常值,我一般这么做:

  • 明显录入错误 → 直接删除或修正
  • 合理但极端 → 截尾处理(用上下限替换)
  • 疑似欺诈 → 单独标记,作为新特征

4.3 重复数据处理

重复数据,说白了就是同一条记录出现了多次。

原因很多:用户重复提交、系统重复采集、数据合并时没去重……

嗯,这里要注意:重复数据会放大某些样本的权重,导致模型偏向这些样本。在风控里,这可能会让模型高估或低估某些用户的风险。

# 检测重复数据
duplicates = df.duplicated(subset=['user_id', 'apply_date'], keep='first')
print(f"重复记录数: {duplicates.sum()}")

# 删除重复数据
df_clean = df.drop_duplicates(subset=['user_id', 'apply_date'], keep='first')

避坑指南:我曾经在处理一个信贷数据集时,发现重复率高达15%。一开始以为是数据质量问题,后来一查才发现,是同一个用户在不同时间申请了多次贷款。这种情况下,不能简单去重,而是要保留最近一次申请记录,或者聚合处理。

重复数据的处理原则:

  • 完全重复 → 保留一条即可
  • 部分重复 → 根据业务逻辑判断保留哪条
  • 时间序列重复 → 保留最新记录

4.4 数据标准化

数据标准化,就是把不同量纲的数据统一到同一个尺度上。

你想想看,年龄是0-100,收入是0-100万,这两个特征直接放到模型里,收入会主导模型训练,年龄几乎没发挥作用。这显然不合理。

常用的标准化方法:

方法 公式 适用场景
Z-score标准化 (x - μ) / σ 数据近似正态分布
Min-Max归一化 (x - min) / (max - min) 数据有明确边界
Robust标准化 (x - median) / IQR 数据有异常值
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Z-score标准化
scaler = StandardScaler()
df['income_z'] = scaler.fit_transform(df[['income']])

# Min-Max归一化
scaler = MinMaxScaler()
df['income_mm'] = scaler.fit_transform(df[['income']])

# Robust标准化(抗异常值)
scaler = RobustScaler()
df['income_rb'] = scaler.fit_transform(df[['income']])

个人经验:在风控场景里,我比较推荐Robust标准化。因为风控数据经常有异常值,Min-Max会被异常值拉偏,Z-score也会受影响。Robust用中位数和IQR,抗干扰能力强很多。

标准化后的数据,均值为0,方差为1(Z-score),或者范围在0-1之间(Min-Max)。这样不同特征之间才能公平地参与模型训练。

好了,数据清洗的四个核心环节就讲完了。记住一句话:数据清洗不是一次性的工作,而是贯穿整个建模过程的。每次拿到新数据,都要重新审视一遍。

下一章,咱们聊聊特征工程——怎么从原始数据里挖出更有价值的特征。