数据预处理:缺失值处理、标准化、数据类型转换

数据预处理,说白了就是给数据“洗澡”。你拿到的原始数据,往住缺胳膊少腿、单位不统一、类型还乱七八糟。不做预处理就直接跑异常值检测?结果一定是灾难。

我个人习惯把预处理分成三步走:缺的补上、拉的缩回来、乱的捋顺。每一步都有坑,我在项目里都踩过,咱们一个一个说。

注意:预处理的顺序很重要。我建议先处理缺失值,再做标准化,最后调数据类型。别反过来,否则容易让填充值污染了标准化后的分布。

1. 缺失值处理

老手看数据,第一眼都是查缺失。我入行时犯过一个低级错误:用均值填充了所有缺失值,结果把异常值检测的阈值整体拉偏了——后来复盘发现,那批缺失根本不是随机的,而是传感器超量程后自动置空的。教训深刻。

1.1 检测缺失

import pandas as pd
import numpy as np

# 快速概览
print(df.info())

# 每列缺失数量
print(df.isnull().sum())

# 缺失占比
print(df.isnull().mean())

为什么先查缺失占比?如果一个特征缺失超过70%,我通常会直接丢掉。但如果它是业务强相关的字段(比如用户ID),那就得想办法补。

1.2 处理方法

方法做法我什么时候用
直接删除dropna() 删行或删列缺失量少(<5%),且随机分布
固定值填充fillna(0) 或 fillna('未知')分类特征,缺失本身可代表一种类别
统计量填充均值、中位数、众数数值型,且缺失机制为随机(MCAR)
前向/后向填充ffill() / bfill()时间序列,丢失的是中间几个点
插值填充interpolate()序列数据,趋势明显,线性插值效果好
模型预测填充KNN / 回归模型缺失量不大,但特征间相关性较强
我的经验:先画缺失值热力图(missingno 库),看清缺失是随机散布还是集中成块。成块缺失往往有系统原因,不能简单用均值填。

说到代码实现,有个小细节容易翻车。看例子:

# 错误示范——原地修改,没有视图反馈
df.fillna(df.mean(), inplace=True)

# 我的习惯——先复制再填充
df_clean = df.copy()
df_clean['age'] = df_clean['age'].fillna(df_clean['age'].median())

# 如果是时间序列
df_clean['temperature'] = df_clean['temperature'].interpolate(method='linear')

嗯,这里要注意:inplace=True 虽然省事,但会丢失原始数据。异常值检测经常要回溯对比,我一般保留原始列,新建一列放处理后的值。

2. 标准化(归一化)

标准化不是数学游戏,是真有用。你想想看,“年龄 0~100”和“收入 3000~50000”两个特征,量级差了500倍。异常值检测算法大多依赖距离或密度,大数会把小数吃掉。

2.1 主流方法对比

方法公式核心输出范围抗离群值我的首选场景
Z-score(x - μ) / σ无固定边界弱数据近似正态分布
Min-Max(x - min) / (max - min)[0, 1]极弱已知上下界,无量纲均一化
Robust(x - median) / IQR无固定边界强数据含离群值,且我不打算删
MaxAbsx / |max|[-1, 1]弱稀疏数据或中心在0附近

我在做金融交易异常检测时踩过一个坑:有一批佣金数据包含极端大额交易(正常值的几百倍),用 Min-Max 归一化后,90%的正常数据被压到了 0~0.05 之间,几乎失去区分度。后来改用 Robust 标准化,问题解决。

from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Z-score 标准化
scaler_z = StandardScaler()
df['age_z'] = scaler_z.fit_transform(df[['age']])

# Robust 标准化(推荐含离群值的数据)
scaler_r = RobustScaler()
df['income_robust'] = scaler_r.fit_transform(df[['income']])

# Min-Max 归一化
scaler_mm = MinMaxScaler()
df['score_norm'] = scaler_mm.fit_transform(df[['score']])
小技巧:如果你做的是生产流水线,建议把 scaler 的参数(均值、标准差、分位数)保存下来,用 joblib 或 pickle。新数据来的时候,直接用 fit 好的 scaler 做 transform,保证历史和新数据在同一尺度。

还有个常见问题:分训练集和测试集时,标准化参数该用谁的?记住一条铁律——只用训练集的统计量去 fit,然后 transform 训练集和测试集。我曾经见过有人把测试集和训练集拼在一起 fit,结果测试集信息泄露,上线后准确率直接崩了。

from sklearn.model_selection import train_test_split

X_train, X_test = train_test_split(df, test_size=0.2, random_state=42)

scaler = RobustScaler()
X_train_scaled = scaler.fit_transform(X_train)   # 只 fit 训练集
X_test_scaled = scaler.transform(X_test)         # 用训练集的参数转测试集

3. 数据类型转换

这个环节最容易被忽略,但它往往是 bug 的温床。我记得有次跑 Isolaction Forest,死活不出结果,查了半天——有个“用户等级”列存的是字符串“1”、“2”、“3”,模型把它当成类别特征直接扔掉。白白浪费半天时间。

3.1 数值类型统一

# 查看每列类型
print(df.dtypes)

# 强制转数值(错误值变 NaN)
df['price'] = pd.to_numeric(df['price'], errors='coerce')

# 浮点转整型(先确认无小数)
df['year'] = df['year'].astype('int32')

# 省内存:int64 转 int32 或 float32
df['age'] = df['age'].astype('float32')

这里有个陷阱:errors='coerce' 会把无法转换的值变成 NaN,但如果你事先没处理这些 NaN,它们会跟着进模型。我一般会先做一次缺失值处理,再转类型。

3.2 类别编码

编码方式适用场景举例我的推荐
Label Encoding有序类别学历:小学→0,中学→1,大学→2必修
One-Hot Encoding无序类别,基数小国别:中国→[1,0,0]最常用
Frequency Encoding类别很多,且有频次信息用该类别出现次数替代原值树模型专用
Target Encoding类别与目标变量强相关用该类别的目标均值替代小心过拟合
from sklearn.preprocessing import LabelEncoder, OneHotEncoder

# Label Encoding
le = LabelEncoder()
df['education_code'] = le.fit_transform(df['education'])

# One-Hot Encoding
df_onehot = pd.get_dummies(df['country'], prefix='country')

# 合并回主表
df = pd.concat([df, df_onehot], axis=1)
警告:One-Hot Encoding 后特征维度暴增,如果原始类别有 100 种,就会多出 100 列。对局部异常因子(LOF)这类基于距离的算法非常不友好。我一般会先看类别基数,超过 50 类就改用 Frequency 或 Target Encoding。

3.3 时间特征转换

时间戳是个宝贝,但机器不认“2024-03-15 14:30:00”这种格式。必须拆成数值特征:

# 字符串转时间对象
df['timestamp'] = pd.to_datetime(df['timestamp'])

# 拆出周期特征
df['hour'] = df['timestamp'].dt.hour
df['day_of_week'] = df['timestamp'].dt.dayofweek
df['month'] = df['timestamp'].dt.month
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)

# 计算时间差
df['time_since_epoch'] = (df['timestamp'] - pd.Timestamp('1970-01-01')).dt.total_seconds()

为什么拆小时和星期?我做过一个电商异常检测项目,周末深夜的大额订单异常概率明显高于工作日上午。如果不把这些周期拆出来,异常检测模型只会把深夜订单全判为异常——但它可能只是“周五晚上促销活动”的正常峰值。

数据预处理三步走 原始数据 1. 缺失值处理 删除缺失 统计量填充 模型插值 2. 标准化 3. 数据类型转换 顺序不能乱:缺失值 → 标准化 → 类型转换。反之可能让填充值影响数据分布。