数据清洗与预处理:Pandas入门、缺失值处理、数据类型转换、异常值检测

数据采集回来,你以为就能直接建模了?
太天真了。真实世界的数据,用「脏乱差」来形容一点都不过分。

我做了这么多年性能优化,见过最离谱的一次——某业务线的数据表里,用户年龄字段居然出现了「-999」和「一百二十岁」混在一起的情况。你说这种数据不洗,模型能学出什么来?

所以这一章,咱们就聊聊数据清洗与预处理。说白了,就是把原始数据从「毛坯房」装修成「精装房」的过程。

核心观点:数据清洗不是可有可无的步骤,而是决定后续分析成败的关键。80%的数据分析时间都花在清洗上,这不是夸张。

数据清洗与预处理知识体系 数据清洗与预处理核心流程 原始数据 Pandas 数据加载与初步探索 缺失值处理 删除 / 填充 / 插值 数据类型转换 数值 / 时间 / 类别 异常值检测 统计 / 业务 / 模型 清洗后的干净数据 进入分析与建模 清洗质量决定分析天花板

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 给你的是工具,怎么用、用多狠,取决于你对业务的理解。我见过太多人把清洗当成「体力活」随便糊弄,结果模型上线后各种翻车。记住——数据质量,决定了分析的天花板。