数据清洗与预处理:缺失值处理、异常值检测与处理、重复值处理、数据标准化与归一化
各位同学,咱们今天聊点实在的。数据清洗这步,说白了就是给原始数据「洗澡」。我做了这么多年风控模型,见过太多人一上来就调参、跑模型,结果数据里全是坑。嗯,最后模型效果差,还找不到原因。其实80%的问题都出在数据预处理上。
你想想看,银行给你的客户数据,可能来自十几个系统。有的字段是空的,有的值明显不合理,还有的重复记录。这些脏数据不处理,模型学到的全是噪音。今天我就把这几块内容掰开揉碎了讲清楚。
核心原则:数据清洗不是机械操作,每一步都要问自己「为什么这么做」。我见过有人把所有缺失值都填0,结果模型学出来全是错的。
一、缺失值处理
缺失值,说白了就是数据里「没填」或者「填不了」的部分。比如客户年龄字段为空,或者收入字段没记录。我个人习惯先分三类:
- 完全随机缺失:比如系统故障导致部分记录丢失,这种可以直接删除或填充均值
- 随机缺失:比如高收入人群更不愿意填收入,这种需要建模预测
- 非随机缺失:比如只有坏客户才缺失某个字段,这种要特别小心
我在项目中遇到过一件事。某次做信用卡欺诈模型,发现「工作单位」字段缺失率高达40%。一开始我想直接删掉这列,后来仔细分析发现——缺失工作单位的客户,欺诈率是正常客户的3倍。你看,缺失本身就是一个特征。
处理方式我一般这么选:
| 缺失率 | 推荐方法 | 适用场景 |
|---|---|---|
| <5% | 直接删除记录 | 数据量大,缺失随机 |
| 5%-20% | 均值/中位数填充 | 数值型特征,分布较集中 |
| 20%-50% | 模型预测填充 | 有相关特征可辅助预测 |
| >50% | 单独作为一列 | 缺失本身有信息量 |
# 我常用的缺失值处理代码
import pandas as pd
import numpy as np
# 查看缺失率
def missing_analysis(df):
missing = df.isnull().sum()
missing_pct = missing / len(df) * 100
return pd.DataFrame({'缺失数': missing, '缺失率%': missing_pct})
# 数值型用中位数填充
df['income'].fillna(df['income'].median(), inplace=True)
# 类别型用众数填充
df['occupation'].fillna(df['occupation'].mode()[0], inplace=True)
# 高缺失率字段,新增缺失标记
df['income_missing'] = df['income'].isnull().astype(int)
我的小技巧:填充缺失值时,可以试试「分组填充」。比如按城市分组,用该城市的平均收入填充缺失值。这样比全局填充更合理。
二、异常值检测与处理
异常值,就是那些「离谱」的数据点。比如客户年龄填了200岁,或者月收入填了1个亿。这些数据要么是录入错误,要么是恶意造假。
我常用的检测方法有三种:
- 3σ原则:数据服从正态分布时,超过均值±3倍标准差的值视为异常
- 箱线图法:用四分位数判断,超过Q1-1.5IQR或Q3+1.5IQR的为异常
- 业务规则法:比如年龄不能小于0,收入不能为负数
我曾经踩过一个坑。某次做反欺诈模型,用3σ原则检测异常值,把一些高收入客户全删了。结果模型上线后,对高净值客户完全失效。后来才发现,这些「异常值」其实是真实的高端客户。所以啊,异常值不一定是错误,也可能是业务上的「特殊群体」。
# 箱线图法检测异常值
def detect_outliers_iqr(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
return outliers
# 处理方式:截断而不是删除
df['income'] = df['income'].clip(lower=lower_bound, upper=upper_bound)
注意:异常值处理一定要结合业务。我建议先标记异常值,单独分析一下,再决定是删除、截断还是单独建模。
三、重复值处理
重复值,就是一模一样的记录出现了多次。原因可能是系统重复录入、数据合并时没去重等。重复值会让模型「过度学习」某些样本,导致偏差。
我一般分两步走:
- 完全重复:所有字段都一样,直接删除保留一条
- 部分重复:关键字段相同但其他字段不同,需要合并或保留最新记录
举个例子。某次做贷款申请数据,发现同一个身份证号出现了3次,但收入字段不一样。后来查出来是客户申请了3次,每次填的收入都不同。这种情况,我一般取最近一次申请的数据,或者取平均值。
# 完全重复删除
df.drop_duplicates(inplace=True)
# 按关键字段去重,保留最新记录
df.sort_values('apply_date', ascending=False)
df.drop_duplicates(subset=['id_card'], keep='first', inplace=True)
避坑指南:我曾经在合并多个数据源时,忘了检查重复值。结果模型训练集里有一半是重复数据,验证集效果虚高。上线后直接崩了。所以去重一定要放在清洗的第一步。
四、数据标准化与归一化
标准化和归一化,说白了就是把不同量纲的数据拉到同一个尺度上。比如年龄是0-100,收入是0-100万,如果不处理,模型会天然认为收入更重要。
我常用的方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 数据近似正态分布,有异常值 |
| Min-Max归一化 | (x - min) / (max - min) | 数据有明确边界,如年龄0-100 |
| Robust缩放 | (x - median) / IQR | 数据有较多异常值 |
我个人习惯:如果后续用逻辑回归或神经网络,一定要做标准化。如果是树模型(随机森林、XGBoost),其实不做也行,但做了也没坏处。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler = StandardScaler()
df['income_scaled'] = scaler.fit_transform(df[['income']])
# Min-Max归一化
scaler = MinMaxScaler(feature_range=(0, 1))
df['age_scaled'] = scaler.fit_transform(df[['age']])
重要提醒:标准化和归一化的参数(均值、标准差、最小值、最大值)一定要用训练集计算,然后应用到测试集和验证集。千万不能在整个数据集上统一计算,否则会造成数据泄露。
嗯,到这里数据清洗的核心内容就讲完了。你可能会问,这些步骤的顺序重要吗?我个人建议:先处理缺失值,再检测异常值,然后去重,最后做标准化。因为异常值检测时,缺失值会影响结果;去重后数据量稳定了,再做标准化更合理。
最后说一句,数据清洗没有标准答案。每个项目的数据都不一样,关键是要理解你的数据,理解业务逻辑。我见过有人花80%的时间在清洗上,20%的时间建模,结果模型效果反而最好。别急着跑模型,先把数据洗干净。
公众号:蓝海资料掘金营,微信deep3321