数据预处理基础:数据清洗与特征工程的第一步
数据预处理,说白了就是给原始数据「洗澡」。我做了这么多年特征工程,见过太多「垃圾进、垃圾出」的案例。你想想看,模型再牛,喂进去的数据有问题,结果能好到哪去?
这一章我们重点聊三个核心环节:数据清洗、标准化/归一化、离散化。嗯,都是基本功,但恰恰是这些基本功决定了项目的成败。
核心观点:数据预处理占整个特征工程工作量的 60%-80%。别急着调模型,先把数据收拾干净。
1. 数据清洗:缺失值处理
缺失值,每个做数据的人都会遇到。我记得刚入行时,拿到一个客户数据集,30% 的年龄字段是空的。当时我直接删了那些行,结果模型上线后,年轻用户群体预测准确率惨不忍睹。
处理缺失值,常见策略就三种:
- 删除法:直接删掉有缺失的行或列。适合缺失比例小(<5%)的情况。
- 填充法:用均值、中位数、众数或模型预测值填充。
- 标记法:把缺失本身作为一个特征,比如加一列 is_null 标志。
我的经验:数值型字段我习惯用中位数填充,因为不受异常值影响。类别型字段用众数,或者单独分一类叫「未知」。
import pandas as pd
import numpy as np
# 示例数据
df = pd.DataFrame({'age': [25, 30, np.nan, 35, 40, np.nan, 28],
'income': [50000, 60000, 55000, np.nan, 70000, 65000, 48000]})
# 中位数填充数值型
df['age'].fillna(df['age'].median(), inplace=True)
# 均值填充
df['income'].fillna(df['income'].mean(), inplace=True)
print(df)
2. 数据清洗:异常值处理
异常值,说白了就是那些「离谱」的数据点。比如用户年龄填了 200 岁,或者月收入填了 10 个亿。我做过一个电商项目,有个用户的订单金额是平均值的 500 倍,后来发现是测试数据混进来了。
怎么发现异常值?常用方法:
- 3σ 原则:超出均值 ±3 倍标准差的数据,视为异常。
- 箱线图法:超出 Q1-1.5IQR 或 Q3+1.5IQR 的数据。
- 业务规则:比如年龄不能大于 120,金额不能为负数。
⚠️ 注意:别一棍子打死所有异常值。有些异常值其实是「宝藏」,比如金融欺诈检测中,异常交易恰恰是我们要找的目标。
# 使用 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)} 个异常值")
3. 数据标准化与归一化
为什么要做标准化?你想想看,如果特征 A 的取值范围是 0-1,特征 B 是 0-100000,很多模型(比如 SVM、KNN、神经网络)会天然偏向 B,因为它的数值大。这不公平。
两种主流方法:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Z-score 标准化 | z = (x - μ) / σ | 无固定范围,均值为0,标准差为1 | 数据近似正态分布,或存在异常值 |
| Min-Max 归一化 | x' = (x - min) / (max - min) | [0, 1] | 数据有明确边界,无异常值 |
我个人习惯:如果后续要用 PCA 或线性模型,优先 Z-score。如果是神经网络,Min-Max 更常见。当然,没有标准,得看数据说话。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score 标准化
scaler_z = StandardScaler()
df_z = scaler_z.fit_transform(df[['age', 'income']])
# Min-Max 归一化
scaler_mm = MinMaxScaler()
df_mm = scaler_mm.fit_transform(df[['age', 'income']])
避坑指南:我曾经在时间序列预测中,不小心对整个数据集做了标准化,导致数据泄露——测试集的信息混进了训练集。正确的做法是:先拆分训练集和测试集,再用训练集的参数(μ、σ 或 min、max)去变换测试集。
4. 数据离散化
离散化,就是把连续值切成几段。为什么要这么做?有些模型(比如决策树)对连续值不太友好,而且离散化后特征更稳定,不容易受异常值干扰。
三种常见方法:
- 等宽离散化:把数据范围等分成 k 段。比如 0-100 分成 [0,20), [20,40), ..., [80,100]。简单粗暴,但数据分布不均匀时效果差。
- 等频离散化:每段包含相同数量的样本。适合长尾分布的数据。
- 聚类离散化:用 KMeans 等聚类算法,把数据聚成 k 类,每类作为一个区间。效果最好,但计算量大。
import pandas as pd
from sklearn.cluster import KMeans
# 等宽离散化
df['age_bin'] = pd.cut(df['age'], bins=4, labels=['青年', '壮年', '中年', '老年'])
# 等频离散化
df['income_bin'] = pd.qcut(df['income'], q=4, labels=['低', '中低', '中高', '高'])
# 聚类离散化
kmeans = KMeans(n_clusters=3, random_state=42)
df['cluster'] = kmeans.fit_predict(df[['income']])
我的建议:等宽适合数据分布均匀的情况;等频适合偏态分布;聚类适合对精度要求高的场景。我一般先用等频试试,不行再换聚类。别一上来就上最复杂的。
5. 本章小结
数据预处理,说白了就是给模型喂「干净饭」。缺失值、异常值、标准化、离散化,每一步都影响最终效果。我见过太多人跳过这些步骤直接调参,结果浪费大量时间。
记住这个顺序:先清洗(缺失值 → 异常值),再变换(标准化/归一化),最后离散化。顺序错了,效果天差地别。