数据清洗与预处理:Pandas入门、缺失值处理、数据类型转换、异常值检测
数据采集回来,你以为就能直接建模了?
太天真了。真实世界的数据,用「脏乱差」来形容一点都不过分。
我做了这么多年性能优化,见过最离谱的一次——某业务线的数据表里,用户年龄字段居然出现了「-999」和「一百二十岁」混在一起的情况。你说这种数据不洗,模型能学出什么来?
所以这一章,咱们就聊聊数据清洗与预处理。说白了,就是把原始数据从「毛坯房」装修成「精装房」的过程。
核心观点:数据清洗不是可有可无的步骤,而是决定后续分析成败的关键。80%的数据分析时间都花在清洗上,这不是夸张。
Pandas 入门:你的瑞士军刀
做数据清洗,Pandas 是绕不开的工具。我个人习惯把它比作「数据界的 Excel 加强版」——但比 Excel 灵活一百倍。
先看最基本的:怎么把数据读进来。
import pandas as pd
# 读取 CSV 文件
df = pd.read_csv('user_data.csv')
# 看看数据长什么样
print(df.head()) # 前5行
print(df.info()) # 列名、非空计数、数据类型
print(df.describe()) # 数值列的统计摘要
这三行代码,我几乎每天都会敲一遍。info() 尤其重要——它能一眼看出哪些列有缺失值,哪些列类型不对。
小技巧:如果数据量很大(几百万行),读 CSV 时可以加 nrows=1000 先抽样看看结构,确认没问题再全量加载。我曾经因为没加这个参数,等一个 5GB 的文件加载等了十分钟……
缺失值处理:别让「空」毁了你的模型
缺失值,说白了就是数据里的「窟窿」。为什么会有窟窿?原因太多了:用户没填、系统没记录、传输丢了……
处理缺失值,主要有三种思路:
| 方法 | 适用场景 | 代码示例 | 注意事项 |
|---|---|---|---|
| 直接删除 | 缺失比例很小(<5%) | df.dropna() |
会丢失样本,慎用 |
| 填充固定值 | 类别字段、已知默认值 | df.fillna(0) |
不要随意填 0,会引入偏差 |
| 统计量填充 | 数值字段、分布较均匀 | df.fillna(df.mean()) |
中位数比均值更稳健 |
| 前向/后向填充 | 时间序列数据 | df.fillna(method='ffill') |
适合连续变化的数据 |
| 插值法 | 有序数据、趋势明显 | df.interpolate() |
线性插值最常用 |
举个例子,假设用户年龄字段有缺失:
# 查看缺失情况
print(df['age'].isnull().sum())
# 用中位数填充(推荐,不受极端值影响)
df['age'].fillna(df['age'].median(), inplace=True)
# 或者按性别分组填充(更精细)
df['age'] = df.groupby('gender')['age'].transform(
lambda x: x.fillna(x.median())
)
避坑指南:我曾经在一个电商项目中,直接对所有缺失值填了 0。结果模型训练出来,预测的转化率比实际低了 30%。后来才发现——那些缺失值其实是「用户未填写」,不代表「用户没有」。填 0 等于给模型喂了错误信号。
数据类型转换:让数据「说人话」
你有没有遇到过这种情况——明明是一个日期,Pandas 却把它当成字符串?明明是一个数值,却显示为 object 类型?
嗯,这就是数据类型不匹配。不改过来,后续分析全是坑。
常见的转换场景:
- 字符串转数值:
pd.to_numeric(),遇到无法转换的可以设errors='coerce'变成 NaN - 字符串转日期:
pd.to_datetime(),可以指定format参数加速解析 - 数值转类别:
df['col'].astype('category'),节省内存、提升性能
# 字符串转数值(带错误处理)
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 字符串转日期
df['order_date'] = pd.to_datetime(df['order_date'], format='%Y-%m-%d')
# 数值转类别(比如性别编码 0/1)
df['gender_code'] = df['gender_code'].astype('category')
你想想看,如果日期是字符串类型,你想按月份聚合——根本做不到。必须先转成 datetime,才能用 df.resample('M') 这种操作。
性能优化小贴士:如果你的数据里有大量重复的字符串(比如城市名、类别标签),转成 category 类型可以节省 50%-90% 的内存。我优化过一个 2GB 的数据集,转完 category 后只剩 300MB——加载速度直接起飞。
异常值检测:揪出那些「不听话」的数据
异常值,就是那些明显偏离正常范围的数据点。比如用户年龄 200 岁、订单金额 -100 元、一天内登录 10000 次……
检测异常值,我一般用三种方法:
1. 基于统计的方法(3σ 原则)
假设数据服从正态分布,超过均值 ± 3 倍标准差的值就是异常。
mean = df['amount'].mean()
std = df['amount'].std()
outliers = df[(df['amount'] < mean - 3*std) | (df['amount'] > mean + 3*std)]
2. 基于四分位数的方法(IQR)
不受极端值影响,更稳健。超过 Q1 - 1.5*IQR 或 Q3 + 1.5*IQR 的就是异常。
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['amount'] < Q1 - 1.5*IQR) | (df['amount'] > Q3 + 1.5*IQR)]
3. 基于业务规则的方法
这个最靠谱。比如订单金额不能为负、年龄不能超过 120。业务规则比任何统计方法都直接。
# 业务规则过滤
df = df[(df['age'] > 0) & (df['age'] < 120)]
df = df[df['amount'] >= 0]
注意:异常值不一定是「错误」。比如双十一的订单量暴增 10 倍——从统计上看是异常,但从业务上看是正常。所以检测出异常后,一定要结合业务判断,不要一刀切删除。
实战:一个完整的清洗流程
把上面这些串起来,就是一个完整的清洗流水线:
def clean_data(df):
# 1. 删除全空的行和列
df.dropna(how='all', inplace=True)
df.dropna(axis=1, how='all', inplace=True)
# 2. 填充缺失值(数值列用中位数,类别列用众数)
for col in df.select_dtypes(include='number').columns:
df[col].fillna(df[col].median(), inplace=True)
for col in df.select_dtypes(include='object').columns:
df[col].fillna(df[col].mode()[0], inplace=True)
# 3. 类型转换
df['date'] = pd.to_datetime(df['date'], errors='coerce')
# 4. 异常值处理(IQR 方法)
for col in df.select_dtypes(include='number').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
# 调用
df_clean = clean_data(df)
这个函数我用了好几年,每次拿到新数据,先跑一遍再说。当然,具体参数要根据业务调整——比如金融数据对异常值更敏感,可能需要更严格的阈值。
总结一下:数据清洗没有银弹。Pandas 给你的是工具,怎么用、用多狠,取决于你对业务的理解。我见过太多人把清洗当成「体力活」随便糊弄,结果模型上线后各种翻车。记住——数据质量,决定了分析的天花板。