数据清洗与预处理:缺失值处理、异常值检测与处理、重复值处理、数据标准化与归一化

各位同学,咱们今天聊点实在的。数据清洗这步,说白了就是给原始数据「洗澡」。我做了这么多年风控模型,见过太多人一上来就调参、跑模型,结果数据里全是坑。嗯,最后模型效果差,还找不到原因。其实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)

注意:异常值处理一定要结合业务。我建议先标记异常值,单独分析一下,再决定是删除、截断还是单独建模。

三、重复值处理

重复值,就是一模一样的记录出现了多次。原因可能是系统重复录入、数据合并时没去重等。重复值会让模型「过度学习」某些样本,导致偏差。

我一般分两步走:

  1. 完全重复:所有字段都一样,直接删除保留一条
  2. 部分重复:关键字段相同但其他字段不同,需要合并或保留最新记录

举个例子。某次做贷款申请数据,发现同一个身份证号出现了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%的时间建模,结果模型效果反而最好。别急着跑模型,先把数据洗干净。

数据清洗与预处理流程 原始数据 步骤1:缺失值处理 步骤2:异常值检测与处理 步骤3:重复值处理 步骤4:标准化与归一化 关键要点 缺失值:填充/删除/标记 异常值:3σ/箱线图/业务 重复值:完全/部分去重 标准化:Z-score/Min-Max 顺序:先清洗后标准化 避免数据泄露 结合业务理解数据

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