欢迎光临
我们一直在努力

进阶篇-机器学习篇-4--数据预处理与特征工程:机器学习真正的核心

《数据预处理与特征工程:机器学习真正的核心》

作者:Weisian
发布时间:2026年3月

在这里插入图片描述

为什么说预处理才是机器学习的灵魂?

你是不是也曾满心欢喜地调参、换模型,却发现模型效果始终上不去?是不是总疑惑"明明用了顶级算法,结果却一塌糊涂"?

答案很扎心:模型再好,数据垃圾=结果垃圾(GIGO,Garbage In, Garbage Out)。机器学习的世界里,数据预处理和特征工程才是真正的核心——据行业统计,一个数据科学家80%的时间都花在数据清洗、转换和特征处理上,而不是调参或建模。

在这里插入图片描述

今天,我想和你分享数据预处理这个看似枯燥、实则决定项目成败的关键环节。这不是理论课,而是一份实战指南,我会用泰坦尼克号数据集带你走一遍完整的流程,每一步都配有可运行的代码和直观的运行结果。


第一部分:先搞懂:数据预处理到底要解决什么问题?

1.1 一个生活化的类比:教孩子认水果

想象一下:你要教一个孩子认识水果。你给他看的苹果照片有的模糊不清,有的被咬了一口,有的标签写的是"橘子"但图片是苹果——你觉得这个孩子能学会正确识别苹果吗?

机器学习模型也是同样的道理。如果喂给模型的数据充满缺失值、异常值、格式混乱的文本,再优秀的算法也学不到有用的规律。

在这里插入图片描述

1.2 数据质量决定模型上限

业界有个广为流传的说法:

  • 特征工程和数据处理决定模型的上限
  • 模型和算法只是在逼近这个上限

这意味着,再花哨的算法也拯救不了糟糕的数据。与其花时间调参,不如先把数据整理好。

1.3 数据预处理要解决的核心问题

原始数据往往存在这些"毛病":

  • 缺失值:比如乘客的年龄、船舱信息为空
  • 异常值:比如年龄出现200岁、票价为负数
  • 重复值:同一条数据被多次录入
  • 格式问题:类别变量(如性别、港口)是文本格式,模型不认识
  • 量纲问题:年龄(0-100)和票价(0-500)数值范围差异大,影响模型判断

在这里插入图片描述

我们的目标,就是把这些"脏乱差"的原始数据,变成规整、统一、符合模型要求的"干净数据"。


第二部分:通用数据预处理Pipeline(可直接套用)

我把数据预处理总结为五个核心步骤,你可以把它当作一个检查清单:

数据预处理Pipeline:
1. 数据探索与理解 → 2. 缺失值处理 → 3. 异常值处理
4. 特征编码与缩放 → 5. 特征选择与分割

在这里插入图片描述

下面我们一步步来看具体怎么做,所有代码都经过实测,可直接复制运行。


第三部分:实战演练——泰坦尼克号Titanic数据集预处理

环境准备(首次使用)

先安装必要的依赖库:

pip install pandas numpy scikit-learn

在这里插入图片描述

3.1 第一步:加载数据,初步探索

核心目标:先摸清数据的"家底",知道数据有多少行多少列、哪些列有缺失值、数据类型是什么。

讲师解读

这一步就像医生看病先问诊,只有了解数据的基本情况,后续的处理才有方向。我们需要重点关注:

  • 数据集的形状(行数×列数)
  • 各列的数据类型(数值型/文本型)
  • 缺失值的分布(哪些列缺失、缺失多少)
数据示例(titanic_train.csv)

可以看到如下的数据集中存在Age数据部分缺失,cabin数据大量缺失,性别使用字符串描述等问题。

在这里插入图片描述

示例代码

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler, LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 用于打印分隔线,让输出更清晰
def print_step(step_name):
print("\\n" + "="*70)
print(f" 📌 {step_name}")
print("="*70)

print("="*70)
print("🚀 数据预处理完整流程演示(增强版)")
print("="*70)

# =============================================
# 加载原始数据
# =============================================
print_step("第1步:加载原始数据")

# — 请修改为你的实际文件路径 —
file_path = 'F:/study/AI/python/python/机器学习/titanic_train.csv'
# ——————————

try:
df = pd.read_csv(file_path)
print(f"✅ 成功加载数据: {file_path}")
except FileNotFoundError:
print(f"❌ 找不到文件: {file_path}")
exit(1)

print(f"数据集形状: {df.shape}")
print("\\n🔍 前20行数据:")
print(df.head(20))
print("\\n🔍 缺失值统计:")
print(df.isnull().sum())
print("\\n🔍 数据信息:")
df.info()

在这里插入图片描述

运行结果分析

======================================================================
📌 第1步:加载原始数据
======================================================================
✅ 成功加载数据: F:/study/AI/python/python/机器学习/titanic_train.csv
数据集形状: (50, 12)

🔍 前20行数据:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q
6 7 0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.8625 E46 S
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.0750 NaN S
8 9 1 3 Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) female 27.0 0 2 347742 11.1333 NaN S
9 10 1 2 Nasser, Mrs. Nicholas (Adele Achem) female 14.0 1 0 237736 30.0708 NaN C
10 11 1 3 Sandstrom, Miss. Marguerite Rut female 4.0 1 1 PP 9549 16.7000 G6 S
11 12 1 1 Bonnell, Miss. Elizabeth female 58.0 0 0 113783 26.5500 C103 S
12 13 0 3 Saundercock, Mr. William Henry male 20.0 0 0 A/5. 2151 8.0500 NaN S
13 14 0 3 Andersson, Mr. Anders Johan male 39.0 1 5 347082 31.2750 NaN S
14 15 0 3 Vestrom, Miss. Hulda Amanda Adolfina female 14.0 0 0 350406 7.8542 NaN S
15 16 1 2 Hewlett, Mrs. (Mary D Kingcome) female 55.0 0 0 248706 16.0000 NaN S
16 17 0 3 Rice, Master. Eugene male 2.0 4 1 382652 29.1250 NaN Q
17 18 1 2 Williams, Mr. Charles Eugene male NaN 0 0 244373 13.0000 NaN S
18 19 0 3 Vander Planke, Mrs. Julius (Emelia Maria Vande… female 31.0 1 0 345763 18.0000 NaN S
19 20 1 3 Masselmani, Mrs. Fatima female NaN 0 0 2649 7.2250 NaN C

🔍 缺失值统计:
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 14
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 41
Embarked 0
dtype: int64

🔍 数据信息:
<class 'pandas.DataFrame'>
RangeIndex: 50 entries, 0 to 49
Data columns (total 12 columns):
# Column Non-Null Count Dtype
— —— ————– —–
0 PassengerId 50 non-null int64
1 Survived 50 non-null int64
2 Pclass 50 non-null int64
3 Name 50 non-null str
4 Sex 50 non-null str
5 Age 36 non-null float64
6 SibSp 50 non-null int64
7 Parch 50 non-null int64
8 Ticket 50 non-null str
9 Fare 50 non-null float64
10 Cabin 9 non-null str
11 Embarked 50 non-null str
dtypes: float64(2), int64(5), str(5)
memory usage: 4.8 KB

关键发现:

  • 数据集有50行12列
  • Age列有14个缺失值(占28%)
  • Cabin列有41个缺失值(占82%)
  • Sex、Embarked是文本类型,模型无法直接处理
  • Age、Fare是数值类型,但量纲差异大

在这里插入图片描述

3.2 第二步:数据清洗(缺失值+异常值+重复值)

核心目标:修复数据中的"硬伤",包括缺失值、异常值、重复值,让数据变得完整、准确。

讲师解读

数据清洗就像整理房间:

  • 缺失值:就像房间里缺了家具,要么补回来,要么把空位置去掉
  • 异常值:就像房间里放了个超大的柜子,占地方还没用,需要调整
  • 重复值:就像房间里有两套一样的沙发,需要扔掉一套
示例代码

# =============================================
# 数据清洗
# =============================================
print_step("第2步:数据清洗")

df_clean = df.copy()

# 3.1 删除缺失过多的列(Cabin列缺失82%,无保留价值)
if 'Cabin' in df_clean.columns:
df_clean = df_clean.drop('Cabin', axis=1)
print("✅ 删除 'Cabin' 列(缺失过多)")

# 3.2 删除Embarked缺失的行(如果有的话)
initial_rows = df_clean.shape[0]
df_clean = df_clean.dropna(subset=['Embarked'])
print(f"✅ 删除 'Embarked' 缺失的行(从 {initial_rows} 行到 {df_clean.shape[0]} 行)")

# 3.3 填充Age缺失值(用中位数,避免受极端值影响)
age_median = df_clean['Age'].median()
# 安全的赋值方式,避免 ChainedAssignmentError
df_clean.loc[:, 'Age'] = df_clean['Age'].fillna(age_median)
print(f"✅ 'Age' 用中位数 {age_median:.1f} 填充")

# 3.4 处理异常值(Fare用盖帽法,限制数值范围)
Q1 = df_clean['Fare'].quantile(0.25)
Q3 = df_clean['Fare'].quantile(0.75)
IQR = Q3 Q1
lower = Q1 1.5 * IQR
upper = Q3 + 1.5 * IQR
df_clean.loc[:, 'Fare'] = df_clean['Fare'].clip(lower, upper)
print(f"✅ 'Fare' 异常值处理(盖帽法范围: {lower:.2f}{upper:.2f})")

# 3.5 删除重复值
initial_rows = df_clean.shape[0]
df_clean = df_clean.drop_duplicates()
print(f"✅ 删除重复行(从 {initial_rows} 行到 {df_clean.shape[0]} 行)")

print("\\n🔍 清洗后数据前20行:")
print(df_clean.head(20))
print("\\n🔍 清洗后缺失值统计:")
print(df_clean.isnull().sum())

在这里插入图片描述

运行结果分析

======================================================================
📌 第2步:数据清洗
======================================================================
✅ 删除 'Cabin' 列(缺失过多)
✅ 删除 'Embarked' 缺失的行(从 50 行到 50 行)
✅ 'Age' 用中位数 27.0 填充
✅ 'Fare' 异常值处理(盖帽法范围: -23.07 – 59.88)
✅ 删除重复行(从 50 行到 50 行)

🔍 清洗后数据前20行:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.250000 S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 59.883275 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.925000 S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.100000 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.050000 S
5 6 0 3 Moran, Mr. James male 27.0 0 0 330877 8.458300 Q
6 7 0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.862500 S
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.075000 S
8 9 1 3 Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) female 27.0 0 2 347742 11.133300 S
9 10 1 2 Nasser, Mrs. Nicholas (Adele Achem) female 14.0 1 0 237736 30.070800 C
10 11 1 3 Sandstrom, Miss. Marguerite Rut female 4.0 1 1 PP 9549 16.700000 S
11 12 1 1 Bonnell, Miss. Elizabeth female 58.0 0 0 113783 26.550000 S
12 13 0 3 Saundercock, Mr. William Henry male 20.0 0 0 A/5. 2151 8.050000 S
13 14 0 3 Andersson, Mr. Anders Johan male 39.0 1 5 347082 31.275000 S
14 15 0 3 Vestrom, Miss. Hulda Amanda Adolfina female 14.0 0 0 350406 7.854200 S
15 16 1 2 Hewlett, Mrs. (Mary D Kingcome) female 55.0 0 0 248706 16.000000 S
16 17 0 3 Rice, Master. Eugene male 2.0 4 1 382652 29.125000 Q
17 18 1 2 Williams, Mr. Charles Eugene male 27.0 0 0 244373 13.000000 S
18 19 0 3 Vander Planke, Mrs. Julius (Emelia Maria Vande… female 31.0 1 0 345763 18.000000 S
19 20 1 3 Masselmani, Mrs. Fatima female 27.0 0 0 2649 7.225000 C

🔍 清洗后缺失值统计:
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Embarked 0
dtype: int64

关键成果:

  • 所有缺失值都已处理,数据完整
  • Fare列的极端值被限制在合理范围
  • 没有重复行,数据唯一

在这里插入图片描述

3.3 第三步:特征编码(让模型读懂文本)

核心目标:把文本类型的类别变量转换成模型能理解的数值形式。

讲师解读

这就像给外国人翻译中文标签:

  • 标签编码:适用于有序类别(如1等舱>2等舱>3等舱),直接翻译成1、2、3
  • 独热编码:适用于无序类别(如性别、港口),不能简单用1、2表示,要做成"是否"的形式(如Sex_female=1表示女性,Sex_male=0表示不是男性)
示例代码

# =============================================
# 特征编码
# =============================================
print_step("第3步:特征编码")

df_encoded = df_clean.copy()

# 3.1 标签编码(有序变量:Pclass)
le = LabelEncoder()
df_encoded.loc[:, 'Pclass_encoded'] = le.fit_transform(df_encoded['Pclass'])
print("✅ 'Pclass' 标签编码完成")
print(f" 编码映射: {dict(zip(le.classes_, le.transform(le.classes_)))}")

# 3.2 独热编码(无序变量:Sex, Embarked)
# Sex编码
sex_dummies = pd.get_dummies(df_encoded['Sex'], prefix='Sex')
df_encoded = pd.concat([df_encoded, sex_dummies], axis=1)
print("✅ 'Sex' 独热编码完成,新增列: ", list(sex_dummies.columns))

# Embarked编码
embarked_dummies = pd.get_dummies(df_encoded['Embarked'], prefix='Embarked')
df_encoded = pd.concat([df_encoded, embarked_dummies], axis=1)
print("✅ 'Embarked' 独热编码完成,新增列: ", list(embarked_dummies.columns))

print("\\n🔍 编码后数据前20行(只显示新特征列):")
print(df_encoded[['Pclass_encoded', 'Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S']].head(20))
print("\\n🔍 编码后缺失值统计:")
print(df_encoded.isnull().sum())

在这里插入图片描述

运行结果分析

======================================================================
📌 第3步:特征编码
======================================================================
✅ 'Pclass' 标签编码完成
编码映射: {np.int64(1): np.int64(0), np.int64(2): np.int64(1), np.int64(3): np.int64(2)}
✅ 'Sex' 独热编码完成,新增列: ['Sex_female', 'Sex_male']
✅ 'Embarked' 独热编码完成,新增列: ['Embarked_C', 'Embarked_Q', 'Embarked_S']

🔍 编码后数据前20行(只显示新特征列):
Pclass_encoded Sex_female Sex_male Embarked_C Embarked_Q Embarked_S
0 2 False True False False True
1 0 True False True False False
2 2 True False False False True
3 0 True False False False True
4 2 False True False False True
5 2 False True False True False
6 0 False True False False True
7 2 False True False False True
8 2 True False False False True
9 1 True False True False False
10 2 True False False False True
11 0 True False False False True
12 2 False True False False True
13 2 False True False False True
14 2 True False False False True
15 1 True False False False True
16 2 False True False True False
17 1 False True False False True
18 2 True False False False True
19 2 True False True False False

🔍 编码后缺失值统计:
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Embarked 0
Pclass_encoded 0
Sex_female 0
Sex_male 0
Embarked_C 0
Embarked_Q 0
Embarked_S 0
dtype: int64

关键知识点:

  • 标签编码保留了类别间的顺序关系(1等舱=0 < 2等舱=1 < 3等舱=2)
  • 独热编码把每个类别变成独立的列,避免模型误解类别间的大小关系
  • 编码后数据仍无缺失值,可继续下一步

在这里插入图片描述

3.4 第四步:特征缩放(统一数据量纲)

核心目标:把不同范围的数值特征缩放到同一尺度,避免数值大的特征主导模型判断。

讲师解读

这就像用尺子量身高和体重:

  • 身高用厘米(100-200),体重用公斤(50-100),直接比较不公平
  • 标准化:把数据变成均值0、标准差1,适合正态分布的数据
  • 归一化:把数据缩放到0-1之间,适合有明确边界的数据
示例代码

# =============================================
# 特征缩放
# =============================================
print_step("第4步:特征缩放")

# 5.1 标准化(均值0,标准差1)
scaler_std = StandardScaler()
# fit_transform 返回一个 numpy 数组,需要转换成 DataFrame 再赋值
age_fare_std = scaler_std.fit_transform(df_encoded[['Age', 'Fare']])
df_encoded[['Age_std', 'Fare_std']] = age_fare_std
print("✅ 标准化完成(均值0,标准差1)")

# 5.2 归一化(范围0-1)
scaler_mm = MinMaxScaler()
age_fare_norm = scaler_mm.fit_transform(df_encoded[['Age', 'Fare']])
df_encoded[['Age_norm', 'Fare_norm']] = age_fare_norm
print("✅ 归一化完成(范围0-1)")

print("\\n🔍 缩放效果对比(前20行):")
print(df_encoded[['Age', 'Age_std', 'Age_norm', 'Fare', 'Fare_std', 'Fare_norm']].head(20))
print("\\n🔍 缩放后缺失值统计:")
print(df_encoded.isnull().sum())

在这里插入图片描述

运行结果分析

======================================================================
📌 第4步:特征缩放
======================================================================
✅ 标准化完成(均值0,标准差1)
✅ 归一化完成(范围0-1)

🔍 缩放效果对比(前20行):
Age Age_std Age_norm Fare Fare_std Fare_norm
0 22.0 -0.398791 0.312500 7.250000 -0.850799 0.000475
1 38.0 0.796089 0.562500 59.883275 2.307955 1.000000
2 26.0 -0.100071 0.375000 7.925000 -0.810289 0.013293
3 35.0 0.572049 0.515625 53.100000 1.900861 0.871183
4 35.0 0.572049 0.515625 8.050000 -0.802787 0.015667
5 27.0 -0.025391 0.390625 8.458300 -0.778283 0.023421
6 54.0 1.990970 0.812500 51.862500 1.826593 0.847683
7 2.0 -1.892392 0.000000 21.075000 -0.021100 0.263017
8 27.0 -0.025391 0.390625 11.133300 -0.617745 0.074220
9 14.0 -0.996232 0.187500 30.070800 0.518778 0.433850
10 4.0 -1.743032 0.031250 16.700000 -0.283663 0.179934
11 58.0 2.289690 0.875000 26.550000 0.307479 0.366989
12 20.0 -0.548151 0.281250 8.050000 -0.802787 0.015667
13 39.0 0.870769 0.578125 31.275000 0.591047 0.456718
14 14.0 -0.996232 0.187500 7.854200 -0.814538 0.011949
15 55.0 2.065650 0.828125 16.000000 -0.325673 0.166640
16 2.0 -1.892392 0.000000 29.125000 0.462016 0.415889
17 27.0 -0.025391 0.390625 13.000000 -0.505716 0.109669
18 31.0 0.273329 0.453125 18.000000 -0.205644 0.204621
19 27.0 -0.025391 0.390625 7.225000 -0.852299 0.000000

🔍 缩放后缺失值统计:
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Embarked 0
Pclass_encoded 0
Sex_female 0
Sex_male 0
Embarked_C 0
Embarked_Q 0
Embarked_S 0
Age_std 0
Fare_std 0
Age_norm 0
Fare_norm 0
dtype: int64

关键观察:

  • 标准化后的Age_std均值接近0,Fare_std标准差接近1
  • 归一化后的Age_norm和Fare_norm都在0-1之间
  • 缩放不引入缺失值,数据质量保持完好

在这里插入图片描述

3.5 第五步:准备建模数据+划分数据集

核心目标:选择合适的特征,划分训练集和测试集,为建模做准备。

讲师解读

这就像准备食材:

  • 选择特征:挑出能帮助模型预测的"有用食材"
  • 划分数据集:把食材分成两部分,一部分用来学做菜(训练集),一部分用来尝味道(测试集)
示例代码

# =============================================
# 准备建模数据
# =============================================
print_step("第5步:准备建模数据")

# 选择特征
feature_cols = ['Pclass_encoded', 'Age_std', 'Fare_std',
'Sex_female', 'Sex_male',
'Embarked_C', 'Embarked_Q', 'Embarked_S']

# 确保所有特征列都存在
available_features = [col for col in feature_cols if col in df_encoded.columns]
if len(available_features) != len(feature_cols):
missing = set(feature_cols) set(available_features)
print(f"⚠️ 警告: 以下特征列缺失: {missing}")

X = df_encoded[available_features]
y = df_encoded['Survived']

print(f"特征矩阵 X 形状: {X.shape}")
print(f"目标变量 y 形状: {y.shape}")
print(f"使用的特征: {available_features}")

# **关键检查:训练模型前,确保 X 中没有 NaN**
print("\\n🔍 最终特征矩阵 X 的缺失值统计:")
print(X.isnull().sum())

if X.isnull().sum().sum() > 0:
print("❌ 错误:特征矩阵 X 中仍存在 NaN!无法训练模型。")
else:
print("✅ 特征矩阵 X 已无缺失值,可以继续。")

# =============================================
# 划分训练集和测试集
# =============================================
print_step("第6步:划分数据集")

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)

print(f"训练集 X_train: {X_train.shape}, y_train: {y_train.shape}")
print(f"测试集 X_test: {X_test.shape}, y_test: {y_test.shape}")

在这里插入图片描述

运行结果分析

======================================================================
📌 第5步:准备建模数据
======================================================================
特征矩阵 X 形状: (50, 8)
目标变量 y 形状: (50,)
使用的特征: ['Pclass_encoded', 'Age_std', 'Fare_std', 'Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S']

🔍 最终特征矩阵 X 的缺失值统计:
Pclass_encoded 0
Age_std 0
Fare_std 0
Sex_female 0
Sex_male 0
Embarked_C 0
Embarked_Q 0
Embarked_S 0
dtype: int64
✅ 特征矩阵 X 已无缺失值,可以继续。

======================================================================
📌 第6步:划分数据集
======================================================================
训练集 X_train: (40, 8), y_train: (40,)
测试集 X_test: (10, 8), y_test: (10,)

关键要点:

  • 特征矩阵无缺失值,满足建模要求
  • 训练集占80%(40行),测试集占20%(10行)
  • random_state=42确保结果可复现

3.6 第六步:训练模型+验证效果

核心目标:用处理好的数据训练模型,验证预处理的效果。

讲师解读

这是检验我们预处理成果的关键一步:如果模型准确率高,说明我们的数据处理得好;如果准确率低,可能需要回头检查数据处理的环节。

示例代码

# =============================================
# 训练模型
# =============================================
print_step("第7步:训练模型")

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)

# 预测
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)

# 评估
train_acc = accuracy_score(y_train, y_pred_train)
test_acc = accuracy_score(y_test, y_pred_test)

print(f"训练集准确率: {train_acc:.4f}")
print(f"测试集准确率: {test_acc:.4f}")

运行结果分析

======================================================================
📌 第7步:训练模型
======================================================================
训练集准确率: 0.8250
测试集准确率: 0.7000

在这里插入图片描述

结果解读:

  • 训练集准确率82.5%,说明模型在训练数据上学习得不错
  • 测试集准确率70%,说明模型有一定的泛化能力
  • 这个结果验证了我们的预处理流程是有效的

3.7 进阶:使用Pipeline避免数据泄露

核心目标:用Pipeline把预处理和建模整合起来,避免数据泄露,让代码更优雅。

讲师解读

数据泄露是预处理中最容易踩的坑!就像考试前偷看了答案,测试集的信息提前泄露给了训练过程,导致模型评估结果不准。Pipeline能自动确保:

  • 只在训练集上做fit(学习预处理参数)
  • 测试集只做transform(应用训练集学到的参数)
示例代码(可选)

# =============================================
# 使用Pipeline的优雅实现(示例)
# =============================================
print_step("第8步:使用Pipeline(防止数据泄露)")

from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

# 重新加载原始数据
df_pipe = pd.read_csv('titanic_train.csv')

# 定义Pipeline
numeric_features = ['Age', 'Fare']
categorical_features = ['Pclass', 'Sex', 'Embarked']

# 数值特征处理
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])

# 类别特征处理
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# 组合预处理器
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])

# 完整Pipeline
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', LogisticRegression(max_iter=1000))
])

# 准备数据
X_pipe = df_pipe.drop('Survived', axis=1)
y_pipe = df_pipe['Survived']

# 划分数据集
X_train_p, X_test_p, y_train_p, y_test_p = train_test_split(
X_pipe, y_pipe, test_size=0.2, random_state=42
)

# 训练
pipeline.fit(X_train_p, y_train_p)

# 预测
y_pred_p = pipeline.predict(X_test_p)
pipeline_acc = accuracy_score(y_test_p, y_pred_p)

print(f"Pipeline模型准确率: {pipeline_acc:.4f}")
print("\\nPipeline自动完成了:")
print("- 缺失值填充(Age用中位数,类别用missing)")
print("- 特征缩放(标准化)")
print("- 类别编码(独热编码)")
print("- 避免数据泄露")

在这里插入图片描述


第四部分:完整的预处理代码模板(可直接套用)

为了方便你后续使用,我把整个流程整理成一个完整的模板。

1. 模板代码

# =============================================
# 完整的泰坦尼克号数据预处理模板
# =============================================
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler, LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 美化输出的辅助函数
def print_step(step_name):
print("\\n" + "="*70)
print(f" 📌 {step_name}")
print("="*70)

print("="*70)
print("🚀 数据预处理完整流程演示(可直接套用)")
print("="*70)

# 1. 加载数据
print_step("第1步:加载原始数据")
file_path = 'F:/study/AI/python/python/机器学习/titanic_train.csv'
try:
df = pd.read_csv(file_path)
print(f"✅ 成功加载数据: {file_path}")
print(f"数据集形状: {df.shape}")
print("缺失值统计:\\n", df.isnull().sum())
except FileNotFoundError:
print(f"❌ 找不到文件: {file_path}")
exit(1)

# 2. 数据清洗
print_step("第2步:数据清洗")
df_clean = df.copy()
# 删除缺失过多的列
if 'Cabin' in df_clean.columns:
df_clean = df_clean.drop('Cabin', axis=1)
# 删除Embarked缺失行
df_clean = df_clean.dropna(subset=['Embarked'])
# 填充Age缺失值
age_median = df_clean['Age'].median()
df_clean.loc[:, 'Age'] = df_clean['Age'].fillna(age_median)
# 处理Fare异常值
Q1, Q3 = df_clean['Fare'].quantile([0.25, 0.75])
IQR = Q3 Q1
df_clean.loc[:, 'Fare'] = df_clean['Fare'].clip(Q11.5*IQR, Q3+1.5*IQR)
# 删除重复值
df_clean = df_clean.drop_duplicates()
print(f"✅ 清洗后数据集形状: {df_clean.shape}")
print("清洗后缺失值统计:\\n", df_clean.isnull().sum())

# 3. 特征编码
print_step("第3步:特征编码")
df_encoded = df_clean.copy()
# 标签编码Pclass
le = LabelEncoder()
df_encoded.loc[:, 'Pclass_encoded'] = le.fit_transform(df_encoded['Pclass'])
# 独热编码Sex和Embarked
df_encoded = pd.concat([
df_encoded,
pd.get_dummies(df_encoded['Sex'], prefix='Sex'),
pd.get_dummies(df_encoded['Embarked'], prefix='Embarked')
], axis=1)
print("✅ 编码完成,新增列:", [col for col in df_encoded.columns if col not in df_clean.columns])

# 4. 特征缩放
print_step("第4步:特征缩放")
scaler_std = StandardScaler()
df_encoded[['Age_std', 'Fare_std']] = scaler_std.fit_transform(df_encoded[['Age', 'Fare']])
print("✅ 标准化完成")

# 5. 准备建模数据
print_step("第5步:准备建模数据")
feature_cols = ['Pclass_encoded', 'Age_std', 'Fare_std',
'Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S']
X = df_encoded[feature_cols]
y = df_encoded['Survived']
print(f"✅ 特征矩阵形状: {X.shape}, 目标变量形状: {y.shape}")
print("特征矩阵缺失值统计:\\n", X.isnull().sum())

# 6. 划分数据集+训练模型
print_step("第6步:划分数据集并训练模型")
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
train_acc = accuracy_score(y_train, model.predict(X_train))
test_acc = accuracy_score(y_test, model.predict(X_test))
print(f"✅ 训练集准确率: {train_acc:.4f}, 测试集准确率: {test_acc:.4f}")

print_step("✅ 数据预处理完成!")
print("\\n核心步骤回顾:")
print("1. 数据清洗:处理缺失值、异常值、重复值")
print("2. 特征编码:标签编码(有序)、独热编码(无序)")
print("3. 特征缩放:标准化、归一化")
print("4. 数据划分:训练集/测试集")
print("5. 模型训练:验证预处理效果")
print("6. 关键:每一步后检查数据,确保无NaN")

2. 数据集(titanic_train.csv示例)

本地新建一个titanic_train.csv文件,将下面的内容复制进去保存即可。

PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S
2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C
3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S
4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S
5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S
6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q
7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S
8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S
9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S
10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C
11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4,1,1,PP 9549,16.7,G6,S
12,1,1,"Bonnell, Miss. Elizabeth",female,58,0,0,113783,26.55,C103,S
13,0,3,"Saundercock, Mr. William Henry",male,20,0,0,A/5. 2151,8.05,,S
14,0,3,"Andersson, Mr. Anders Johan",male,39,1,5,347082,31.275,,S
15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14,0,0,350406,7.8542,,S
16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55,0,0,248706,16,,S
17,0,3,"Rice, Master. Eugene",male,2,4,1,382652,29.125,,Q
18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13,,S
19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31,1,0,345763,18,,S
20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C
21,0,2,"Fynney, Mr. Joseph J",male,35,0,0,239865,26,,S
22,1,2,"Beesley, Mr. Lawrence",male,34,0,0,248698,13,D56,S
23,1,3,"McGowan, Miss. Anna ""Annie""",female,15,0,0,330923,8.0292,,Q
24,1,1,"Sloper, Mr. William Thompson",male,28,0,0,113788,35.5,A6,S
25,0,3,"Palsson, Miss. Torborg Danira",female,8,3,1,349909,21.075,,S
26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38,1,5,347077,31.3875,,S
27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C
28,0,1,"Fortune, Mr. Charles Alexander",male,19,3,2,19950,263,C23 C25 C27,S
29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q
30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S
31,0,1,"Uruchurtu, Don. Manuel E",male,40,0,0,PC 17601,27.7208,,C
32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C
33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q
34,0,2,"Wheadon, Mr. Edward H",male,66,0,0,C.A. 24579,10.5,,S
35,0,1,"Meyer, Mr. Edgar Joseph",male,28,1,0,PC 17604,82.1708,,C
36,0,1,"Holverson, Mr. Alexander Oskar",male,42,1,0,113789,52,,S
37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C
38,0,3,"Cann, Mr. Ernest Charles",male,21,0,0,A./5. 2152,8.05,,S
39,0,3,"Vander Planke, Miss. Augusta Maria",female,18,2,0,345764,18,,S
40,1,3,"Nicola-Yarred, Miss. Jamila",female,14,1,0,2651,11.2417,,C
41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40,1,0,7546,9.475,,S
42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27,1,0,11668,21,,S
43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C
44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3,1,2,SC/Paris 2123,41.5792,,C
45,1,3,"Devaney, Miss. Margaret Delia",female,19,0,0,330958,7.8792,,Q
46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S
47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q
48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q
49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C
50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18,1,0,349237,17.8,,S


写在最后:预处理是艺术,也是科学

数据预处理没有放之四海而皆准的标准答案。每份数据都是独特的,需要你花时间去理解它背后的故事:

  • 缺失值该填充还是删除?要看缺失的原因
  • 异常值该修正还是保留?要看业务逻辑
  • 用哪种编码方式?要看类别是否有序

记住:好的数据科学家,不是算法用得好的人,而是能把脏数据变干净的人。


📌 核心要点回顾

  • GIGO原则:数据质量决定模型上限,预处理比调参重要10倍
  • 预处理六步法:
    • 数据探索 → 缺失值处理 → 异常值处理 → 重复值处理 → 特征编码 → 特征缩放
  • 关键技巧:
    • 缺失值:缺失率>70%直接删,数值型用中位数填充,类别型用众数填充
    • 编码:有序类别用标签编码,无序类别用独热编码
    • 缩放:正态分布用标准化,有边界用归一化
  • 避坑指南:
    • 永远先分割数据集,再做预处理,避免数据泄露
    • 每一步都检查缺失值,确保数据质量
    • 使用Pipeline让代码更优雅,减少人为错误
  • 泰坦尼克号实战要点:
    • Cabin缺失太多 → 直接删除
    • Age用中位数填充(避免受极端值影响)
    • Fare用盖帽法处理异常值
    • Sex和Embarked用独热编码,Pclass用标签编码
  • 在这里插入图片描述

    希望这篇实战指南对你有帮助!所有代码都可以直接复制运行,前提是确保titanic_train.csv文件在你的工作目录下。如果你有任何问题,欢迎在评论区留言讨论!

    赞(0)
    未经允许不得转载:171主机测评 » 进阶篇-机器学习篇-4--数据预处理与特征工程:机器学习真正的核心
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址