数据清洗基础:缺失值处理、重复数据去重、异常值检测与处理、数据标准化
数据清洗,说白了就是给数据「洗澡」。我做了这么多年数据集,见过太多脏数据了。你想想看,原始数据就像刚从工地挖出来的矿石,里面什么都有——缺胳膊少腿的、重复的、离谱的、单位不统一的。不洗干净,后面模型训练就是白费功夫。
这一章,我带你过一遍数据清洗的四个核心环节。嗯,都是我在项目里踩过坑、填过土的经验。
缺失值处理:别急着删,先看看情况
缺失值是最常见的问题。我记得刚入行那会儿,拿到一个用户行为数据集,一看有30%的字段是空的,差点直接删掉。后来被老工程师骂了一顿——「你删数据之前,想过为什么缺失吗?」
缺失值处理,一般有三种思路:
- 直接删除:如果缺失比例很小(比如<5%),而且缺失是随机的,直接删掉行或列就行。我习惯先看缺失比例,再决定。
- 填充:用均值、中位数、众数填充。数值型数据我常用中位数,因为它不受异常值影响。类别型数据用众数。
- 模型预测:用其他字段预测缺失值。这个方法精度高,但成本也高。我一般只在关键字段缺失时才用。
重要原则:缺失值处理没有银弹。你得先搞清楚「为什么缺失」——是数据采集漏了?还是用户主动不填?不同的原因,处理方式完全不同。
# 我个人常用的缺失值处理流程
import pandas as pd
import numpy as np
# 查看缺失比例
def check_missing(df):
missing = df.isnull().sum()
missing_pct = missing / len(df) * 100
return pd.DataFrame({'缺失数': missing, '缺失比例(%)': missing_pct})
# 数值型字段:用中位数填充
df['age'].fillna(df['age'].median(), inplace=True)
# 类别型字段:用众数填充
df['gender'].fillna(df['gender'].mode()[0], inplace=True)
# 时间序列:用前向填充
df['temperature'].fillna(method='ffill', inplace=True)
我的小技巧:填充之前,先建一个「是否缺失」的标记字段。有时候缺失本身就是一个重要特征。比如用户没填收入,可能说明他收入不稳定——这对风控模型很有价值。
重复数据去重:别让一条数据算两次
重复数据的问题,比你想的严重。我在做一个电商推荐系统时,发现某个用户的购买记录重复了3次。结果模型以为他特别爱买某件商品,疯狂给他推同类产品——用户体验直接崩了。
重复数据分两种:
- 完全重复:所有字段都一样。这个简单,直接
drop_duplicates()就行。 - 部分重复:只有部分字段重复。比如同一个用户ID出现多次,但其他字段不同。这时候你得判断——是同一个用户的不同行为?还是数据采集出了问题?
# 完全重复去重
df.drop_duplicates(inplace=True)
# 基于关键字段去重(保留第一条)
df.drop_duplicates(subset=['user_id', 'timestamp'], keep='first', inplace=True)
# 查看重复情况
duplicates = df[df.duplicated(subset=['user_id'], keep=False)]
print(f'重复用户数: {len(duplicates)}')
避坑指南:我曾经犯过一个错——直接按主键去重,结果把用户的正常多次行为记录删掉了。比如用户一个月内买了3次东西,这3条记录都是有效的。去重前,一定要搞清楚业务逻辑。
异常值检测与处理:别让「离群点」带偏模型
异常值,就是那些「离谱」的数据。比如用户年龄填了200岁,或者某个商品的销量突然暴涨100倍。这些数据要么是录入错误,要么是特殊情况。
我常用的检测方法:
- 3σ原则:数据服从正态分布时,超过均值±3个标准差的值视为异常。简单粗暴,但有效。
- 箱线图法:用四分位数判断。低于Q1-1.5IQR或高于Q3+1.5IQR的,算异常。这个方法不受分布假设限制,我比较常用。
- 业务规则:比如年龄不能超过150,价格不能为负数。这个最靠谱,但需要懂业务。
# 箱线图法检测异常值
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_clean = df[~df['price'].isin(outliers['price'])]
# 截断(用边界值替换)
df['price'] = df['price'].clip(lower=lower_bound, upper=upper_bound)
我的经验:异常值不一定要删。有时候异常值本身就是信号——比如金融欺诈检测中,异常交易才是我们要找的目标。先标记出来,再根据业务决定怎么处理。
数据标准化:让不同尺度的数据「对齐」
数据标准化,说白了就是把不同量纲的数据拉到同一个尺度上。比如年龄(0-100)和收入(0-100万),如果不标准化,模型会天然认为收入更重要——因为它的数值更大。
我常用的标准化方法:
- Z-score标准化:减去均值,除以标准差。处理后数据均值为0,标准差为1。适合数据近似正态分布的情况。
- Min-Max归一化:缩放到[0,1]区间。适合有明确边界的数据,比如像素值0-255。
- Robust标准化:用中位数和IQR代替均值和标准差。对异常值不敏感,我比较推荐。
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# Z-score标准化
scaler = StandardScaler()
df['age_scaled'] = scaler.fit_transform(df[['age']])
# Min-Max归一化
scaler = MinMaxScaler()
df['income_scaled'] = scaler.fit_transform(df[['income']])
# Robust标准化(抗异常值)
scaler = RobustScaler()
df['price_scaled'] = scaler.fit_transform(df[['price']])
注意:标准化要在训练集上拟合,然后应用到验证集和测试集。千万别在整个数据集上一起标准化——那会造成数据泄露,模型评估结果会虚高。
本章知识体系
下面这张图,是我梳理的数据清洗核心流程。你可以把它当作一个检查清单——每次做数据清洗时,对着过一遍。
数据清洗这件事,看起来琐碎,但真的不能跳过。我见过太多团队,模型效果不好就拼命调参、换算法,结果最后发现是数据没洗干净。嗯,先把基础打牢,后面才能走得远。
公众号:蓝海资料掘金营,微信deep3321