数据预处理:数据清洗、缺失值处理、异常值检测与处理
数据预处理,说白了就是给数据「洗澡」。
我见过太多项目,模型选得再好,算法调得再精,结果数据一塌糊涂,最后全白干。你想想看,垃圾进,垃圾出,这是数据挖掘的铁律。
这一章,咱们就聊聊怎么把数据洗干净。
数据清洗:先看看数据有多脏
拿到数据的第一件事,不是建模,是看数据长什么样。
我个人习惯,先用 df.info() 和 df.describe() 扫一眼。看看字段类型对不对,数值范围合不合理。
常见的数据脏问题:
- 格式不一致:比如日期字段,有的写"2024-01-01",有的写"2024/01/01",还有写"2024年1月1日"的。
- 重复数据:同一个用户被录入了两次,特征一模一样。
- 无效值:年龄字段出现 -1,或者性别字段出现"男"、"male"、"M"混用。
- 逻辑错误:注册时间晚于最后登录时间,这种明显不合理。
我在项目中遇到过最离谱的一次,某个字段叫「收入」,结果有人填了"保密",有人填了"0",还有人填了"999999999"。嗯,这种数据不洗,模型根本没法用。
# 快速检查数据质量
import pandas as pd
df = pd.read_csv('raw_data.csv')
print(df.info()) # 看字段类型和非空数量
print(df.describe()) # 看数值分布
print(df.duplicated().sum()) # 看重复行数
缺失值处理:别急着删
缺失值,是数据预处理里最头疼的问题之一。
很多人一看到缺失值,第一反应就是删掉。我建议你冷静一下。缺失值背后可能藏着信息。
缺失值的三种类型
| 类型 | 含义 | 例子 |
|---|---|---|
| 完全随机缺失 | 缺失与数据本身无关 | 问卷被风吹走了一页 |
| 随机缺失 | 缺失与其他已知变量有关 | 高收入人群更不愿意填收入 |
| 非随机缺失 | 缺失与缺失值本身有关 | 病情越重的人越容易漏填症状 |
为什么会这样?因为不同类型的缺失,处理方式完全不同。
常用的处理方法
- 直接删除:缺失比例很小(比如 <5%),且是随机缺失,可以删。
- 填充均值/中位数:数值型字段,分布比较对称用均值,有偏态用中位数。
- 填充众数:分类字段,用出现最多的类别填充。
- 前向/后向填充:时间序列数据,用上一个或下一个值填充。
- 模型预测填充:用其他字段训练模型,预测缺失值。这个方法比较重,但效果最好。
我的经验:千万别用0去填充缺失值,除非你确定0有意义。我曾经见过有人把年龄缺失全填0,结果模型把0岁用户当成了一个重要群体……
# 缺失值处理示例
# 数值型字段用中位数填充
df['age'].fillna(df['age'].median(), inplace=True)
# 分类字段用众数填充
df['gender'].fillna(df['gender'].mode()[0], inplace=True)
# 时间序列用前向填充
df['temperature'].fillna(method='ffill', inplace=True)
异常值检测:别让一个点带偏整个模型
异常值,就是那些「格格不入」的数据点。
你想想看,如果全班同学身高都在160-180之间,突然来了个3米高的,那这个点就会把均值拉偏。模型也一样,一个异常值可能让回归线歪到天上去。
常用的检测方法
| 方法 | 适用场景 | 原理 |
|---|---|---|
| Z-Score | 数据近似正态分布 | 超过3个标准差视为异常 |
| IQR(四分位距) | 数据有偏态 | Q1-1.5*IQR 或 Q3+1.5*IQR 之外 |
| 孤立森林 | 高维数据 | 随机切分,异常点更容易被孤立 |
| DBSCAN | 有聚类结构的数据 | 不在任何簇中的点视为异常 |
注意:异常值不一定是错误。比如信用卡欺诈检测,异常值恰恰是我们想找的目标。所以,先搞清楚业务含义,再决定怎么处理。
# IQR方法检测异常值
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)} 个异常值")
异常值的处理方式
- 删除:确认是录入错误,直接删。
- 截尾:把异常值替换成边界值(比如用上下限替换)。
- 视为缺失:先标记为缺失,再用缺失值处理方法处理。
- 保留:如果异常值有业务意义,比如高净值客户,那就留着。
避坑指南:我曾经在处理电商数据时,发现某个用户的订单金额是其他用户的100倍。一开始想删掉,后来一查,是个企业大客户。嗯,要是删了,整个模型就失真了。
数据预处理的整体流程
说了这么多,咱们理一下顺序。
- 第一步:数据概览——看字段、看类型、看缺失、看分布。
- 第二步:数据清洗——去重、格式统一、逻辑校验。
- 第三步:缺失值处理——分析缺失类型,选择填充或删除。
- 第四步:异常值检测与处理——用IQR或Z-Score检测,结合业务决定去留。
- 第五步:数据保存——把清洗后的数据存下来,方便后续使用。
我个人习惯,每一步都会写一个检查点。比如清洗后看看还有没有重复值,填充后看看分布有没有变化。这样出了问题,能快速定位。
# 完整的数据预处理流水线
def preprocess_data(df):
# 1. 去重
df = df.drop_duplicates()
# 2. 缺失值处理
for col in df.columns:
if df[col].dtype in ['int64', 'float64']:
df[col].fillna(df[col].median(), inplace=True)
else:
df[col].fillna(df[col].mode()[0], inplace=True)
# 3. 异常值处理(IQR方法)
for col in df.select_dtypes(include=['int64', 'float64']).columns:
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
df[col] = df[col].clip(lower, upper)
return df
clean_df = preprocess_data(df)
数据预处理这件事,看起来琐碎,但恰恰是最能体现功力的地方。
我见过很多新手,花80%的时间调模型,结果数据预处理只花了20%。真正有经验的人,会把60%的时间花在数据准备上。因为数据质量,决定了模型的天花板。
嗯,这一章就到这里。下一章咱们聊聊特征工程,怎么从原始数据里挖出更有用的信息。