来源链接:https://www.bilibili.com/video/BV1cGA3zREMA?spm_id_from=333.788.videopod.sections&vd_source=e73a152dada4626bad49c30d848902f7

数据预处理全攻略

📋 知识要点总结

本笔记系统介绍了机器学习中数据预处理的全流程,涵盖以下核心内容:

  • 数据预处理的必要性:Garbage In, Garbage Out 原则
  • 核心工具库:pandas、matplotlib、seaborn、scikit-learn
  • 数据集划分:训练集/测试集的正确分割时机
  • 探索性数据分析 (EDA):使用可视化发现数据问题
  • 数据清洗:处理重复值、缺失值、异常值
  • 特征编码:序数编码、独热编码、标签编码
  • 特征工程:特征构造与删除
  • 特征缩放:标准化 (Standardization) 与归一化 (Normalization)
  • Pipeline 流水线:使用 scikit-learn 自动化预处理流程

1. 数据预处理概述

1.1 为什么需要数据预处理

在前面的课程中,我们介绍了线性回归模型和梯度下降算法。很多同学掌握后直接用自己的数据进行模型训练,但结果往往不及预期。原因在于前面 demo 中展示的数据非常干净,而现实世界中的数据往往充满各种问题:

  • 存在异常值和缺失值
  • 包含文本、类别等非数值类型的数据
  • 量纲差距巨大(例如房价是百万级,房间个数却是个位数)
  • 特征之间的差异过大导致模型训练困难

在算法界有一句至理名言:“Garbage In, Garbage Out”。如果数据质量低,那么训练出来的模型质量也肯定特别低。

1.2 四大核心工具

数据处理中最常用的四个 Python 工具:

工具 功能
pandas 处理二维数据(类似 Excel 表格),支持各种常见快捷处理
matplotlib Python 最基础的数据可视化库
seaborn 基于 matplotlib 的高级可视化库,语法更简洁,图表更美观
scikit-learn 机器学习的标准库,包含丰富的预处理工具和常用算法实现

screenshot*


2. 数据集介绍与初步分析

2.1 Titanic 数据集

本课程使用开源的 Titanic 生存预测数据集作为示例。虽然 seaborn 中也内置了该数据集,但已经做过预处理,所以我们尽可能使用其原始数据来学习预处理过程。

2.2 数据基本情况

加载数据后可使用以下方法查看基本情况:

df.shape      # 查看数据形状
df.head() # 查看前5条数据
df.info() # 查看数据类型与缺失情况

Titanic 数据集共有 891 行 × 12 列(11 个特征 + 1 个标签 Survived)。

通过 df.info() 可观察到:

  • 大多数字段有 891 条非空数据
  • Age 只有 714 条非空(缺失较多)
  • Cabin 仅有 204 条非空(缺失严重)
  • Embarked(登陆港口)只有 2 条缺失
  • 许多字段是 object(字符串)类型,不是 int 或 float

screenshot*


3. 数据集划分:预处理前的关键步骤

3.1 数据集的分类

  • 训练集 (Training Set):用于训练模型学习数据中的规律
  • 验证集 (Validation Set):用来调整超参数或评估模型在未见数据上的表现
  • 测试集 (Test Set):模拟真实世界的数据,评估模型的泛化能力

3.2 为什么要先划分再预处理

很多初学者最容易犯的错误是先对所有数据做预处理,然后再划分。这是不正确的。

原因:如果对所有数据做完预处理,里面会包含很多主观的认知。再对测试集进行验证发现也 OK,但这只代表模型对自己修改过的数据预测得准,而不能代表对真实世界(未修改过的)数据预测得准。被修改过之后会造成数据泄露 (Data Leakage)。

3.3 正确做法

  • 只能在训练集上进行 fit(学习参数)
  • 对于测试集,只做一些转换 (transform) 工作

3.4 划分代码示例

from sklearn.model_selection import train_test_split

# 特征与标签分离
X = df.drop('Survived', axis=1) # 去掉标签列
y = df['Survived'] # 标签

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # 关键参数:保持标签分布一致
)

stratify=y 参数的重要性:如果只是纯粹随机分成 80% 和 20%,Y 值可能并不能保证两边的比例一致。设置 stratify 后,划分时会先学习数据,在分 80% 和 20% 时尽可能让标签值分布也符合原来的比例,这样更科学。例如划分后无论训练集还是测试集,生存率都保持在 38% 左右。

screenshot*


4. 探索性数据分析 (EDA)

4.1 EDA 的概念

EDA (Exploratory Data Analysis, 探索性数据分析):对数据做一次全面的体检,发现存在的大概问题,然后再对数据进行预处理。

4.2 缺失值统计

# 缺失值的绝对数量
df.isnull().sum()

# 缺失比例
df.isnull().sum() / len(df)

对 Titanic 数据集的初步分析:

  • Age:缺失率约 20%
  • Cabin:缺失率约 77%(离谱)
  • Embarked:只有 2 条缺失,占 0.3%(非常少)

4.3 数值特征统计

使用 df.describe() 查看数值特征的统计情况:

  • count:个数
  • mean:均值
  • std:标准差
  • min / 25% / 50% / 75% / max:最小值、各分位数、最大值

分位数 vs 均值:分位数(如第 75 百分位)比平均值在大部分时候更加可靠。防止出现”姚明和潘长江平均身高是 1.9 米”的偏差情况。

观察发现:Fare(票价)最大为 512,最小为 0,差距过大,中间值很小,需要进行缩放。

screenshot*

4.4 数据可视化

使用 matplotlib 和 seaborn 对数据进行可视化,可以看出更多端倪:

4.4.1 存活率分布

存活率看起来比死亡率要低一些,可能大部分人不幸遇难了。

screenshot*

4.4.2 年龄分布

从 20~40 岁左右人数最多,但年龄大的也很多,年龄小的也有一些(可能有人带着孩子或携家带口)。

4.4.3 票价分布

票价分布严重右偏:左边有极值,导致右边变成长尾。最高一个达到 512,第二就只有 150。这个数据对模型训练极不友好,需要做特殊处理。

screenshot*

4.4.4 性别与存活率

有趣的是,女性的存活率要远远高于男性,虽然理论上男性身体更强壮存活率应该更高。

4.4.5 船票等级与存活率

一等舱 > 二等舱 > 三等舱的存活率,可能有钱的人更容易活下来。

screenshot*

4.4.6 相关性热力图

通过热力图查看哪些属性与存活率最相关:

  • 正相关:Fare(票价)越高越相关
  • 相关性较弱:Age(年龄)关系没那么大
  • 负相关:Pclass(舱位等级)数值越小(一等舱),存活率越高
  • 影响不大:SibSp(亲属个数)

screenshot*

4.5 EDA 关键发现与预处理方向

通过 EDA 得到了几个关键发现,指导后续预处理:

  1. 女性存活率远高于男性 → 性别是强特征,需要编码为数值
  2. 一等舱存活率最高 → Pclass 是强特征,需要保留
  3. Age 缺了 20% → 需要填充;分布偏态,应用中位数而非均值
  4. Cabin 客舱信息缺了 77% → 信息量极少,可直接删除
  5. Fare 严重右偏 → 需要对数变换或截断处理
  6. 明显无用的特征 → 删除或提取关键信息

5. 数据清洗

5.1 处理重复值

在数据清洗中,首先要把重复值剔除掉。

# 删除重复行
X_train = X_train.drop_duplicates()
y_train = y_train.loc[X_train.index] # 关键:同步删除对应标签行

重要提醒:特征和标签是靠索引值对应的,从 X_train 中删除一行之后,从 y_train 中也要把对应的行给删掉。

5.2 处理缺失值

缺失值的处理方法根据缺失率不同而不同:

5.2.1 缺失率极低(< 5%)且随机缺失

  • 可直接删除对应的行样本

5.2.2 缺失率特别高(> 70%)

  • 信息量非常少
  • 可直接删除该列
  • 例如 Cabin 缺失 77%,可以删除

5.2.3 中等缺失率:填充策略

不要轻易删除数据,因为数据来之不易:

  • 删除一行可能浪费 249 个其他特征

数值型特征:

  • 用均值或中位数填充
  • 中位数对极值不敏感,在数据偏态时更稳健
  • 大部分情况下使用中位数而非均值

类别型特征:

  • 用众数 (most_frequent) 填充,即出现次数最高的值
  • 或者新增一个 “Unknown” 类别,保留业务信息
  • 例如:有些缺失并非数据获取问题,而是”就是没有”,需要根据业务判断

screenshot*

5.2.4 使用 scikit-learn 处理缺失值

from sklearn.impute import SimpleImputer

# 数值型:用中位数填充
imputer_median = SimpleImputer(strategy='median')
imputer_median.fit(X_train) # 关键:只在训练集上 fit
X_train[['Age']] = imputer_median.transform(X_train[['Age']])
X_test[['Age']] = imputer_median.transform(X_test[['Age']]) # 用训练集的中位数

# 类别型:用众数填充
imputer_mode = SimpleImputer(strategy='most_frequent')
imputer_mode.fit(X_train) # 关键:只在训练集上 fit
X_train[['Embarked']] = imputer_mode.transform(X_train[['Embarked']])
X_test[['Embarked']] = imputer_mode.transform(X_test[['Embarked']])

核心原则:

  • 只能对训练集进行 fit(学习参数)
  • 对于训练集和测试集都做 transform(转换)
  • 否则相当于作弊(数据泄露)

5.2.5 特殊处理:Cabin 客舱信息

由于 Cabin 缺失 77%,可做以下处理:

  1. 添加新列 Has_Cabin:是否有客舱信息(0/1)
  2. 删除原来的 Cabin 列

这样做既保留了部分信息,又避免了缺失值问题。

5.3 处理异常值

5.3.1 异常值的定义

异常值是指显著偏离其他观测值的极端值(极大或极小)。

5.3.2 箱线图(Box Plot)识别

使用箱线图可以直观看出异常值情况:

  • Age:中位数在中间,但 60~80 岁之间有很多离群点
  • Fare:极度右偏,只有一个值在高位(如 512),绝大部分票价都很低
  • SibSp:大部分在 0~1 之间,少数较大值可能是真实情况

screenshot*

5.3.3 截断策略(IQR 方法)

设置一个合理的区间,超出范围的值截断到边界值:

  • 计算 Q1(25 分位数)和 Q3(75 分位数)
  • IQR = Q3 - Q1
  • 合理区间:[Q1 - 1.5×IQR, Q3 + 1.5×IQR]
  • 对于 Fare:最小也得是 0(票价不能为负),最大是 Q3 + 1.5×IQR
# 截断到合理区间
Q1 = X_train['Fare'].quantile(0.25)
Q3 = X_train['Fare'].quantile(0.75)
IQR = Q3 - Q1
lower = max(0, Q1 - 1.5 * IQR)
upper = Q3 + 1.5 * IQR

X_train['Fare'] = X_train['Fare'].clip(lower, upper)
X_test['Fare'] = X_test['Fare'].clip(lower, upper)

重要原则:在数据预处理时,尽量不要删除数据。截断(clip)比删除更合理。


6. 特征编码

6.1 特征编码的目的

将文本类特征映射成数值类,使模型能进行数学运算。线性回归的本质就是进行数学运算。

6.2 三种主要编码方式

编码方式 是否保留顺序 适用场景
序数编码 保留 有顺序关系的类别(如学历、满意度)
独热编码 不保留 无顺序关系的类别(如颜色、城市)
标签编码 不保留 二分类、树模型、多分类标签

screenshot*

6.3 序数编码 (Ordinal Encoding)

把类别映射为整数,严格保留顺序。

适用场景:虽然用文本表示但有顺序关系的数据:

  • 学历:低、中、高 → 0、1、2
  • 客户满意度:4 颗星、3 颗星 → 4、3

代码实现:

from sklearn.preprocessing import OrdinalEncoder

# 定义顺序
categories = [['low', 'medium', 'high']]
encoder = OrdinalEncoder(categories=categories)
encoder.fit(X_train)
X_train_encoded = encoder.transform(X_train)
X_test_encoded = encoder.transform(X_test)

6.4 独热编码 (One-Hot Encoding)

核心思想:把每个类别都变成一个独立的列,用 0 和 1 表示。

为什么要这么做?
假设颜色有红、绿、蓝三种:

  • 用序数编码会变成 1、2、3
  • 这会让模型错误认为”蓝 = 红 × 3”或”蓝色最大最好”
  • 但实际上颜色没有顺序关系

独热编码做法:

  • 红色 → [1, 0, 0]
  • 绿色 → [0, 1, 0]
  • 蓝色 → [0, 0, 1]

优点:完全消除顺序关系,模型能正确学习
缺点:明显增加列数。如果类别有 500 个,就需要增加 499 列,训练时非常复杂。类目过多时需用 embedding 等高级方法。

代码实现:

from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder()
encoder.fit(X_train[['Embarked']])
# 转换后增加 3 列(S, C, Q),变为 0/1 编码

screenshot*

6.5 标签编码 (Label Encoding)

特点:

  • 把文本映射成数字,不保证顺序
  • 引入了数值大小,但数值本身无意义
  • 按字母顺序或出现顺序决定(如 0、1、2、3、4…)

严格适用范围:

✅ 可以使用:

  • 二分类特征(如性别:男/女),比独热更省空间
  • 分类任务的标签列(最终预测结果),因为算法要求输出数字
  • 树模型:对数值大小不敏感

❌ 严禁使用:

  • 线性模型中无序特征(必须用独热)
  • 多分类任务的无序特征

代码实现:

from sklearn.preprocessing import LabelEncoder

encoder = LabelEncoder()
X_train['Sex_encoded'] = encoder.fit_transform(X_train['Sex'])
X_test['Sex_encoded'] = encoder.transform(X_test['Sex'])
# fit_transform = fit + transform 一步到位

6.6 编码方式选择总结

场景 推荐编码
线性模型 + 无序特征 独热编码
线性模型 + 有序特征 序数编码
二分类 标签编码
树模型 标签编码
神经网络 Embedding(特征过多时)
多分类标签 标签编码

7. 特征工程:构造与删除

7.1 核心理念

原始数据只是原材料,需要基于业务理解对数据集做关键处理,帮助模型挖掘数据背后的真正规律。

7.2 特征构造

示例 1:合并家庭规模

  • Titanic 中有多个与亲戚数量相关的特征(SibSp, Parch)
  • 合并为 FamilySize = SibSp + Parch + 1
  • 标记 IsAlone:如果 FamilySize = 1 则为 1
  • 家庭结构可能影响逃生策略

示例 2:从姓名提取头衔 (Title)

  • 很多人的名字里带着头衔(Mr, Mrs, Master 等)
  • 特殊头衔(Lady, Sir)能反映社会地位
  • 这些信息可能影响逃生策略
  • 提取后作为新特征使用

screenshot*

7.3 特征删除

  • 缺失值太多的列
  • 格式混乱的列
  • 明显会导致数据泄露的列

7.4 特征工程的本质

去粗取精:把杂乱的原始记录转化成模型更容易理解的高质量特征。

💡 提示:在后面的无监督学习中,降维 (Dimensionality Reduction) 会做更多此类操作。无监督学习常作为监督学习的前一步,对数据做处理。


8. 特征缩放

8.1 为什么需要特征缩放

数据集中部分特征的量纲差异可能非常大:

  • Age:0~80
  • Fare:0~100
  • SibSp:0~8

如果不进行缩放:

  • 模型会错误地认为数值比较大的特征更重要
  • 会赋予其过大的权重
  • 导致梯度下降更新参数时反复震荡
  • 需要非常多的迭代次数才能找到最优解
  • 甚至可能最终都无法收敛

类比:上节课讲梯度下降时,把房间面积改成百平米,实际就是最简单的数据缩放。

8.2 标准化 (Standardization)

定义:把数据转换为一个均值为 0、标准差为 1 的分布。

公式:
$$X_{std} = \frac{X - \mu}{\sigma}$$

其中 $\mu$ 是均值,$\sigma$ 是标准差。

特点:

  • 不会强制压缩到特定区间,所以可能为负数
  • 只要均值 ≈ 0,标准差 ≈ 1 即可
  • 非常受异常值影响(应在标准化前先处理异常值)
  • 最常用,尤其是数据分布近似正态分布时

适用场景:

  • 线性模型
  • 逻辑回归
  • 基本上都要用标准化

screenshot*

代码实现:

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train) # 关键:只在训练集上 fit
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

8.3 归一化 (Normalization)

定义:把数据线性变换到一个固定的区间,通常是 [0, 1]。

公式:
$$X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}$$

特点:

  • 严格限定数据范围
  • 确保结果中不会出现负数
  • 受异常值影响非常大(离群点会破坏正常分布)

适用场景:

  • 图像处理(像素天然在 0~255 之间)
  • 神经网络输入(因为 sigmoid 激活函数输出 0~1)
  • 深度学习中用得更多

screenshot*

8.4 标准化 vs 归一化对比

特性 标准化 归一化
输出范围 不固定(可能为负) 固定 [0, 1]
受异常值影响 较小 较大
适用分布 近似正态分布 任意分布
典型应用 线性模型、逻辑回归 神经网络、图像处理

9. Pipeline 流水线

9.1 为什么需要 Pipeline

前面的预处理步骤非常多,流程特别长,中间容易出错。必须严格记住:

  • 只能在训练集上 fit
  • 在训练集和测试集上 transform

scikit-learn 提供了两个工具帮助自动化:

  • Pipeline:串联多个处理步骤
  • ColumnTransformer:对不同列应用不同处理

9.2 使用示例

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

# 数值特征的处理流水线
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])

# 类别特征的处理流水线
cat_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder())
])

# 组合
preprocessor = ColumnTransformer([
('num', num_pipeline, num_cols),
('cat', cat_pipeline, cat_cols)
])

# 一键处理
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)

screenshot*


10. 核心原则总结

数据预处理的四个核心原则:

10.1 Garbage In, Garbage Out

数据质量决定模型上限。再好的算法也没法从垃圾数据中学到有价值的规律。

10.2 先划分数据集,再做预处理

  • 训练集:fit(学习规律)
  • 测试集:transform(用训练集学到的参数做转换)
  • 避免数据泄露

10.3 用 EDA 探索数据

  • 用图表看看数据究竟有什么问题
  • 不知道要做哪些预处理时,EDA 是首选

10.4 用 Pipeline 自动化流程

  • 处理步骤多,容易出错
  • 用 Pipeline 封装,避免忙中出错

11. 完整预处理流程回顾

1. 加载数据 → df
2. 划分数据集 → X_train, X_test, y_train, y_test
3. EDA 探索性分析 → 发现数据问题
4. 数据清洗
├── 处理重复值
├── 处理缺失值(删除/填充)
└── 处理异常值(截断)
5. 特征编码
├── 数值特征:保持原样
└── 类别特征:独热/序数/标签编码
6. 特征工程
├── 特征构造(合并、提取)
└── 特征删除(无用列)
7. 特征缩放
├── 标准化(StandardScaler)
└── 归一化(MinMaxScaler)
8. Pipeline 封装
9. 准备训练模型

经过完整的数据预处理后,就可以进入真正的模型训练与评估阶段了。如果发现模型无法拟合数据或出现过拟合,下节课将学习如何识别和处理这些问题。


refer

-

声明:

  1. 若文章存在错误,望诸君不吝指正^

  2. blog仅供个人记录学习所用

  3. 部分笔记由于年代久远,做的笔记找不到最初是引用谁的,若是不允许引用转载,请联系我