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

大家好,我是你们的老朋友。今天咱们聊聊数据清洗与预处理。说实话,这活儿看着不起眼,但在我做过的所有大模型项目里,它占的时间往往比模型训练还长。你想想看,数据要是脏的,模型再牛也白搭。我有个习惯,拿到数据第一件事不是看算法,而是先做一轮清洗。嗯,咱们今天就把它掰开揉碎了讲清楚。

缺失值处理:别让空值毁了你的模型

缺失值,说白了就是数据里那些空着的格子。我在项目中遇到过最夸张的情况——一个客户数据集,某个字段缺失率高达70%。当时我差点想放弃这个字段,但后来发现它其实是个关键特征。

处理缺失值,我一般分三步走:

  1. 先诊断:看看缺失率有多高。低于5%的,直接删掉行;5%-20%的,考虑填充;超过20%的,得慎重了。
  2. 再选策略:常用的有均值填充、中位数填充、众数填充。我个人习惯用中位数,因为它对异常值不敏感。
  3. 最后验证:填充完一定要检查分布有没有变。我吃过一次亏,用均值填充后,整个特征的方差被压缩了,模型直接崩了。

核心原则:缺失值处理没有银弹。你得根据业务场景和数据分布来选方法。千万别无脑用均值填充。

举个例子,假设你有个房价数据集,卧室数量字段有缺失。如果缺失率低,直接删行;如果缺失率高,可以用众数填充(因为大多数房子是3室)。但如果你用均值填充,可能会填出2.7间卧室这种奇怪的值。

# 我常用的缺失值处理代码
import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('house_data.csv')

# 查看缺失率
missing_rate = df.isnull().mean()
print(missing_rate)

# 中位数填充数值型字段
df['bedrooms'].fillna(df['bedrooms'].median(), inplace=True)

# 众数填充类别型字段
df['neighborhood'].fillna(df['neighborhood'].mode()[0], inplace=True)

小技巧:对于时间序列数据,我建议用前向填充或后向填充。比如传感器数据,前一秒的值往往比均值更靠谱。

异常值检测与处理:揪出那些捣蛋鬼

异常值,就是数据里那些明显不对劲的值。比如一个人年龄填了200岁,或者月收入填了-5000。这些值会严重拉偏模型的训练方向。

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

方法 适用场景 我的经验
Z-Score 正态分布数据 阈值一般设3,超过3个标准差就算异常
IQR(四分位距) 非正态分布数据 Q1-1.5*IQR 到 Q3+1.5*IQR 之外的都是异常
DBSCAN聚类 高维数据 能发现局部异常,但参数调起来有点麻烦

我曾经在一个电商项目中,用IQR方法发现了一批异常订单。有个用户一天内下了500单,明显是刷单行为。嗯,这里要注意:异常值不一定是错误,它可能是真正的业务异常。所以处理前一定要跟业务方确认。

避坑指南:我曾经直接把所有异常值删掉,结果模型在测试集上表现很好,上线后却崩了。后来才发现,那些异常值其实是真实业务场景的一部分。所以,处理异常值前,先问问自己:这个值是真的错误,还是只是罕见但真实的情况?

# IQR方法检测异常值
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1

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

# 标记异常值
df['is_outlier'] = (df['price'] < lower_bound) | (df['price'] > upper_bound)

# 处理方式:可以删除、截尾或替换
df_clean = df[~df['is_outlier']]

数据去重:别让重复数据骗了你

数据去重,听起来简单,但坑不少。我见过一个团队,因为没做去重,模型在训练集上准确率99%,测试集上只有60%。为什么?因为重复数据导致过拟合了。

去重分两种:

  • 完全去重:所有字段都相同。这个直接用 drop_duplicates() 就行。
  • 部分去重:只有关键字段相同。比如用户ID相同,但其他字段不同。这种情况要小心,可能是数据更新导致的。

我个人习惯在做去重之前,先看看重复数据的分布。如果重复率超过10%,那数据源可能有问题。我曾经在一个爬虫项目中,发现重复率高达30%,后来发现是爬虫逻辑写错了,同一个页面抓了多次。

# 完全去重
df.drop_duplicates(inplace=True)

# 基于关键字段去重(保留第一条)
df.drop_duplicates(subset=['user_id'], keep='first', inplace=True)

# 查看重复率
duplicate_rate = df.duplicated().mean()
print(f'重复率: {duplicate_rate:.2%}')

小技巧:对于文本数据,去重前最好先做标准化。比如把"北京"和"北京市"统一成"北京",否则去重效果会打折扣。

数据标准化与归一化:让特征站在同一起跑线

标准化和归一化,说白了就是把不同量纲的数据拉到同一个尺度上。你想想看,如果房价用万元,面积用平方米,那模型肯定会偏向房价这个特征,因为它的数值更大。

我常用的方法:

方法 公式 适用场景
Z-Score标准化 (x - μ) / σ 数据近似正态分布,或者有异常值
Min-Max归一化 (x - min) / (max - min) 数据有明确边界,比如像素值0-255
Robust标准化 (x - median) / IQR 数据有大量异常值

我在项目中遇到过一个问题:用Min-Max归一化后,模型训练速度确实快了,但预测结果却变差了。后来发现,因为测试集中出现了训练集里没有的新值,导致归一化后的值超出了[0,1]范围。嗯,这里要注意:归一化时一定要保存训练集的min和max,用同样的参数去处理测试集。

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-Score标准化
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['price', 'area']])

# Min-Max归一化
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df[['price', 'area']])

# 保存参数供测试集使用
import joblib
joblib.dump(scaler, 'scaler.pkl')

核心原则:标准化和归一化不是必须的。对于树模型(如随机森林、XGBoost),它们对尺度不敏感,可以不做。但对于神经网络、SVM、KNN等模型,这一步是必须的。

好了,数据清洗与预处理这块就讲到这里。说实话,这些技巧看起来简单,但真正用好的话,能让你的模型效果提升一大截。我见过太多人把时间花在调参上,却忽略了数据质量。记住一句话:垃圾进,垃圾出。数据干净了,模型自然就强了。