机器学习初学者指南 · 第4章

数据清洗与特征工程

数据质量决定模型上限——掌握从原始数据到可用特征的全流程

4.1

缺失值处理

真实数据几乎总是不完整的。缺失值可能由多种原因造成:传感器故障、用户未填写、数据合并时的不匹配、或者某些测量对特定样本不适用。如何处理缺失值,直接影响模型的最终性能。

缺失机制的类型

处理策略

缺失值处理方法

删除法

  • 行删除:删除包含缺失值的整行。适用于缺失比例很小(<5%)且为 MCAR 的情况。
  • 列删除:删除缺失比例过高的特征列。当某列缺失超过 50%-70% 时考虑。

填充法

  • 均值/中位数/众数填充:最简单的方法。数值特征用均值或中位数,类别特征用众数。
  • 前向/后向填充:适用于时间序列数据,用相邻时间点的值填充。
  • 模型预测填充:用其他特征作为输入,训练模型预测缺失值(如 KNN 填充)。
核心洞察:删除还是填充?

删除法简单但会损失信息,当缺失比例高时可能严重减少样本量。填充法保留了样本,但引入了估计误差。一般原则:缺失 < 5% 可考虑删除;缺失 5%-50% 应尝试填充;缺失 > 50% 考虑删除该列或将其转换为"是否缺失"的指示特征。

4.2

异常值检测

异常值(Outliers)是与其他观测值显著不同的数据点。它们可能是数据录入错误、测量误差,也可能是真实的极端情况(如欺诈交易)。异常值会扭曲统计量、影响模型训练,因此需要谨慎处理。

IQR 方法(箱线图法)

四分位距(Interquartile Range, IQR)是最常用的异常值检测方法之一:

IQR = Q₃ - Q₁
异常值范围:x < Q₁ - 1.5·IQR 或 x > Q₃ + 1.5·IQR

其中 Q₁ 是第 25 百分位数,Q₃ 是第 75 百分位数。IQR 方法对极端值不敏感,因为它只关注数据的中间 50%。

Z-Score 方法

Z-Score 衡量数据点距离均值多少个标准差:

Z = (x - μ) / σ

通常 |Z| > 3 的数据点被视为异常值。Z-Score 方法假设数据近似正态分布,对有明显偏斜的数据不太适用。

异常值处理策略

工程应用:金融风控中的异常检测

信用卡欺诈检测:正常的消费金额集中在一定范围内,而盗刷交易往往金额异常巨大或出现在异常地点。IQR 和 Z-Score 可以作为初筛工具,但更复杂的方法(如孤立森林、AutoEncoder)能发现高维数据中的异常模式。

传感器数据预处理:工业传感器偶尔会产生离群读数(如温度瞬间跳到 1000°C)。这些通常是硬件故障导致的,应使用滑动窗口中位数或 Hampel 滤波器进行清洗,而非简单删除。

图 4-1:Z-Score 与 IQR 方法检测异常值的原理对比
4.3

独热编码与类别特征处理

机器学习模型通常只能处理数值输入,而现实数据中大量存在类别特征(如"性别""城市""产品类型")。独热编码(One-Hot Encoding)是最常用的类别特征转换方法。

为什么需要独热编码?

假设有一个"颜色"特征,取值为 {红, 绿, 蓝}。如果直接用整数编码 {红=1, 绿=2, 蓝=3},模型会误认为"蓝"是"红"的 3 倍,"绿"介于两者之间——这种数值关系完全是虚假的。

独热编码将每个类别转换为一个二进制列:

原始特征颜色_红颜色_绿颜色_蓝
100
绿010
001

其他编码方法

核心洞察:高基数特征的编码困境

当类别特征有数千甚至数万个唯一值(如"用户ID""邮政编码")时,独热编码会产生过多的列,导致维度灾难和稀疏性问题。此时应考虑:目标编码、嵌入(Embedding)、或者将类别按频率分组(保留前 N 个高频类别,其余归为"其他")。

4.4

特征缩放:标准化与归一化

不同特征往往具有不同的量纲和数值范围(如"年龄"0-100,"收入"0-1000000)。特征缩放(Feature Scaling)将所有特征转换到相似的数值范围,对许多算法至关重要。

Z-Score 标准化(Standardization)

将特征转换为均值为 0、标准差为 1 的分布:

x' = (x - μ) / σ

标准化保留了数据的分布形状,只是改变了尺度和位置。适用于数据近似正态分布,或者算法对异常值不太敏感的场景。

Min-Max 归一化(Normalization)

将特征线性缩放到 [0, 1] 范围:

x' = (x - x_min) / (x_max - x_min)

归一化对异常值非常敏感——一个极端大值会将其他所有值压缩到接近 0。适用于数据范围已知且稳定的场景,或需要严格边界的情况(如神经网络输入、图像像素)。

需要特征缩放的算法

必须缩放:K-近邻(基于距离)、K-Means(基于距离)、SVM(基于距离)、神经网络(梯度稳定)、PCA(方差受尺度影响)。

不需要缩放:决策树、随机森林、梯度提升树(基于分裂点,不受尺度影响)。

建议缩放:线性回归、逻辑回归(梯度下降收敛更快)。

图 4-2:标准化与归一化对数据分布的影响对比
4.5

特征选择、分箱与数据不平衡

特征选择

并非所有特征都对模型有用。冗余特征会增加计算开销,噪声特征会降低模型性能。特征选择的目的是保留最相关的特征子集。

分箱(Binning)

将连续特征划分为离散区间。分箱可以减少异常值影响、捕捉非线性关系,但也可能损失信息。

数据不平衡处理

当不同类别的样本数量差异巨大时(如欺诈检测中 99.9% 正常,0.1% 欺诈),模型会倾向于预测多数类。

工程应用:特征工程实战

金融风控数据清洗:信用数据常有缺失(客户未提供某些信息)。收入缺失可以用职业+地区的均值填充;信用历史缺失可以用"无历史"作为单独类别。异常高的收入需要验证是否为数据错误。

传感器数据预处理:温度、压力、振动传感器数据通常有噪声。使用滑动窗口平均平滑数据,用 IQR 方法识别并标记异常读数。不同传感器的量纲差异巨大,必须进行标准化。

图像特征提取:原始像素值范围是 [0, 255],输入神经网络前需除以 255 归一化到 [0, 1]。对于预训练模型(如 ResNet),还需按 ImageNet 的均值和标准差进行标准化。

文本向量化:将文本转换为数值向量的过程本身就是特征工程。词袋模型(Bag of Words)、TF-IDF、Word2Vec、BERT 嵌入——从简单到复杂,每种方法都代表了不同的特征抽象层次。

Ex

例题精讲

例1:缺失值处理策略选择

某数据集有以下特征缺失情况,请选择最合适的处理策略:

  • 特征 A(年龄):缺失 3%,完全随机缺失
  • 特征 B(收入):缺失 25%,高收入人群更不愿透露
  • 特征 C(用户评论):缺失 80%,大部分用户未填写
解答

特征 A(年龄):缺失比例很低(3%)且为 MCAR,可以删除缺失行,或者用中位数填充(年龄分布可能有偏,中位数比均值更稳健)。

特征 B(收入):缺失比例中等(25%)且为 MAR。直接删除会损失大量样本。可用分组均值填充(按职业或教育水平分组计算均值),或使用随机森林预测填充。同时可以添加一个"收入是否缺失"的指示特征。

特征 C(用户评论):缺失比例过高(80%)。删除行不可行(会损失 80% 数据),保留列作为文本特征也缺乏信息。最佳策略是删除该列,或仅保留"是否有评论"作为二值特征。

A:中位数填充;B:分组均值填充+缺失指示;C:删除或转为二值指示特征。
例2:特征缩放对比

某数据集中有两个特征:

  • 年龄:[25, 30, 35, 40, 100]
  • 收入(万元):[5, 8, 12, 15, 200]

请分别用 Z-Score 标准化和 Min-Max 归一化对这两个特征进行缩放,并分析两种方法对异常值的处理差异。

计算过程

年龄(均值=46,标准差≈28.3)

Z-Score:[-0.74, -0.57, -0.39, -0.21, 1.91]

Min-Max:[0, 0.067, 0.133, 0.20, 1.0]

收入(均值=48,标准差≈76.7)

Z-Score:[-0.56, -0.52, -0.47, -0.43, 1.98]

Min-Max:[0, 0.015, 0.036, 0.051, 1.0]

差异分析

Min-Max:异常值(100岁、200万收入)将其他正常值压缩到极小的范围内(收入的前4个值都在 0-0.05 之间),导致正常样本之间的差异几乎消失。

Z-Score:异常值虽然较大(1.91 和 1.98),但正常值之间的相对差异被较好地保留。标准化对异常值更鲁棒。

Min-Max 对异常值敏感,会将正常值压缩;Z-Score 更鲁棒,优先推荐使用。
例3:IQR 异常值检测

某商品的历史日销量数据如下(单位:件):

120, 135, 128, 142, 130, 125, 138, 500, 132, 129, 140, 127

请使用 IQR 方法检测异常值。

计算过程

排序后:120, 125, 127, 128, 129, 130, 132, 135, 138, 140, 142, 500

Q₁(第25百分位)= (127 + 128) / 2 = 127.5

Q₃(第75百分位)= (138 + 140) / 2 = 139.0

IQR = 139.0 - 127.5 = 11.5

下界 = 127.5 - 1.5 × 11.5 = 127.5 - 17.25 = 110.25

上界 = 139.0 + 1.5 × 11.5 = 139.0 + 17.25 = 156.25

结论

销量 500 件 > 上界 156.25,被判定为异常值

其余所有数据都在 [110.25, 156.25] 范围内,属于正常值。

在实际业务中,500 件可能是促销活动或大客户订单导致的真实值,需要结合业务知识判断是否删除或保留。

上界=156.25,500 件被检测为异常值。应结合业务场景决定处理方式。
例4:独热编码的维度问题

某数据集有以下类别特征:

  • 性别:{男, 女} — 2 个类别
  • 城市:{北京, 上海, 广州, 深圳} — 4 个类别
  • 邮政编码:约 10000 个不同值

(a) 对这三个特征分别进行独热编码后,各产生多少列?

(b) 对于邮政编码特征,有什么更好的处理方式?

解答

(a) 性别:2 列(男, 女)。注意:某些实现会省略一列以避免多重共线性(dummy variable trap)。

城市:4 列(北京, 上海, 广州, 深圳)。

邮政编码:约 10000 列!这会导致维度灾难,数据极度稀疏,且大多数邮编只有极少数样本。

(b) 更好的处理方式

  • 映射到地理区域(如省/市),从 10000 个邮编降为几十个区域。
  • 目标编码:用该邮编的历史目标均值替换。
  • 频率编码:用该邮编出现的频率替换。
  • 聚类:将相近的邮编(地理位置相近)聚成一组。
(a) 性别2列,城市4列,邮编约10000列;(b) 应映射到更高层次(省/市)或使用目标/频率编码。
Py

Python 代码实践

本节演示 pandas 处理缺失值、sklearn 进行特征缩放和独热编码的完整流程,这些是每个 ML 项目的标准预处理步骤。

data_preprocessing.py
# 第4章:数据清洗与特征工程 import pandas as pd import numpy as np from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, MinMaxScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline # 创建示例数据 data = { '年龄': [25, 30, np.nan, 35, 40, 28], '收入': [5000, 8000, 6000, np.nan, 12000, 4500], '城市': ['北京', '上海', '北京', '广州', '上海', '深圳'], '性别': ['男', '女', '女', '男', '男', '女'] } df = pd.DataFrame(data) print("原始数据:") print(df) print(f"\n缺失情况:\n{df.isnull().sum()}") # ===== 缺失值处理 ===== # 数值特征:用中位数填充 num_imputer = SimpleImputer(strategy='median') df[['年龄', '收入']] = num_imputer.fit_transform(df[['年龄', '收入']]) print("\n填充缺失值后:") print(df) # ===== 构建预处理流水线 ===== # 数值特征:标准化 num_features = ['年龄', '收入'] num_transformer = Pipeline(steps=[ ('scaler', StandardScaler()) ]) # 类别特征:独热编码 cat_features = ['城市', '性别'] cat_transformer = Pipeline(steps=[ ('onehot', OneHotEncoder(drop='first', sparse_output=False)) ]) # 组合变换器 preprocessor = ColumnTransformer( transformers=[ ('num', num_transformer, num_features), ('cat', cat_transformer, cat_features) ]) # 执行转换 X_processed = preprocessor.fit_transform(df) feature_names = (num_features + list(preprocessor.named_transformers_['cat'] .named_steps['onehot'].get_feature_names_out(cat_features))) df_processed = pd.DataFrame(X_processed, columns=feature_names) print("\n预处理后数据(标准化 + 独热编码):") print(df_processed.round(2))
运行结果
原始数据: 年龄 收入 城市 性别 0 25.0 5000.0 北京 男 1 30.0 8000.0 上海 女 2 NaN 6000.0 北京 女 3 35.0 NaN 广州 男 4 40.0 12000.0 上海 男 5 28.0 4500.0 深圳 女 缺失情况: 年龄 1 收入 1 城市 0 性别 0 dtype: int64 填充缺失值后: 年龄 收入 城市 性别 0 25.0 5000.0 北京 男 1 30.0 8000.0 上海 女 2 31.5 6000.0 北京 女 3 35.0 6500.0 广州 男 4 40.0 12000.0 上海 男 5 28.0 4500.0 深圳 女 预处理后数据(标准化 + 独热编码): 年龄 收入 城市_上海 城市_深圳 城市_广州 性别_女 0 -1.12 -0.76 0.0 0.0 0.0 0.0 1 -0.22 0.42 1.0 0.0 0.0 1.0 2 0.00 -0.17 0.0 0.0 0.0 1.0 3 0.67 0.00 0.0 0.0 1.0 0.0 4 1.57 1.89 1.0 0.0 0.0 0.0 5 -0.89 -1.38 0.0 1.0 0.0 1.0
代码解读

SimpleImputer(strategy='median') 用中位数填充数值缺失值。中位数比均值更鲁棒,不受异常值影响。

ColumnTransformer 允许对不同列应用不同的变换——数值列标准化,类别列独热编码,是 sklearn 中处理混合类型数据的标准方式。

OneHotEncoder(drop='first') 省略每个类别特征的第一列,避免多重共线性(dummy variable trap)。例如"性别"只有"女"一列(0=男, 1=女)。

注意预处理后的数值特征均值接近 0、标准差接近 1,满足大多数 ML 算法的要求。

← 上一章:第3章 无监督与强化学习 下一章:敬请期待