本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本大数据分析案例以“泰坦尼克号”数据集为实践对象,利用Jupyter Notebook这一交互式工具,系统讲解从数据预处理、特征工程到模型构建与评估的完整数据科学流程。涵盖缺失值处理、类别变量编码、新特征构造、多种机器学习算法应用及模型性能可视化分析,是掌握数据分析实战技能的理想教程。通过本案例学习,读者可全面提升在真实场景中运用Python进行数据探索与建模的能力。

1. Jupyter Notebook环境搭建与基本操作

安装与配置Jupyter Notebook

推荐使用Anaconda发行版进行安装,它集成了Python解释器及常用数据科学库。通过命令行执行 conda install jupyter 或直接启动Anaconda Navigator图形界面选择安装。安装完成后,终端输入 jupyter notebook 即可在浏览器打开本地服务(默认地址为 http://localhost:8888)。

基本操作与单元格使用

Jupyter支持两种核心单元格类型: 代码 (Code)和 Markdown 。代码块可逐段运行Python指令,例如导入pandas并查看数据:

import pandas as pd
df = pd.read_csv("titanic.csv")
df.head()

Markdown用于撰写文档说明,支持标题、列表与公式,提升笔记可读性。

文件管理与内核控制

在主界面可新建、重命名、删除Notebook文件( .ipynb ),并通过“Kernel”菜单管理运行状态,如重启内核或清除输出,确保实验环境整洁可控。

2. Titanic数据集介绍与数据预处理实践

在机器学习项目中,数据是构建模型的基石。一个高质量、结构清晰且经过充分清洗的数据集,往往决定了后续建模工作的成败。本章聚焦于经典的 Titanic 生存预测数据集 ,通过系统性地解析其背景信息、字段含义,并结合实际操作完成从原始数据加载到缺失值处理的完整预处理流程。该过程不仅是数据分析的第一步,更是理解业务逻辑、挖掘潜在特征的关键环节。

我们将以 Python 中强大的 pandas 库为核心工具,辅以 numpy missingno 等可视化缺失值的库,深入剖析 Titanic 数据集中各个变量的统计特性与质量问题。整个流程将遵循“理解 → 探索 → 诊断 → 修复”的递进路径,确保每一步都有据可依、有迹可循,为后续的特征工程和建模打下坚实基础。

2.1 数据集背景与业务理解

Titanic 数据集源自 1912 年泰坦尼克号沉船事件的真实历史记录,由 Kaggle 平台作为入门级机器学习竞赛题目发布。尽管事件已过去百余年,但这一悲剧因其极端条件下的社会行为模式(如“妇女儿童优先”原则)以及明显的阶层差异而成为研究人类生存行为的经典案例。因此,该数据集不仅具有技术训练价值,更蕴含深刻的社会学意义。

从机器学习视角看,这是一个典型的二分类任务:目标是根据乘客的基本属性(如年龄、性别、舱位等级等),预测其是否在事故中幸存。每个样本代表一名乘客,共包含多个结构化字段,涵盖人口统计学、票务信息及登船细节。通过对这些特征的分析,我们可以探索哪些因素显著影响了生存概率,进而构建出具备解释力和泛化能力的预测模型。

2.1.1 Titanic灾难事件的数据意义

泰坦尼克号作为当时世界上最大、最豪华的邮轮,在首次航行中便撞上冰山沉没,造成超过 1500 人死亡。这场灾难震惊世界,也暴露了航海安全制度的重大缺陷。然而,也正是由于其高度公开性和详细记录,使得研究人员得以获取相对完整的乘客名单及其生存状态,形成了今天我们所使用的数据集。

该数据集的核心价值在于它提供了一个自然实验场景——在生死攸关的紧急状态下,个体的命运如何受到结构性因素的影响。例如:

  • 社会阶层 :头等舱乘客多为富裕阶层,享有更靠近甲板的位置和优先救援权;
  • 性别与年龄 :“妇女儿童优先”的撤离策略直接影响了不同群体的生还率;
  • 家庭结构 :携带子女或兄弟姐妹的乘客可能因照顾他人而延误逃生;
  • 登船港口 :来自不同地区的乘客可能存在文化习惯或社交网络差异。

这些因素共同构成了一个多维决策环境,非常适合用于训练分类模型并进行因果推断尝试。更重要的是,这类问题贴近现实应用场景,比如保险风险评估、应急疏散模拟、客户流失预测等,都涉及类似的人群分层与响应建模。

以下是一个简化的 Titanic 事件影响因素关系图 ,使用 Mermaid 流程图展示关键变量之间的逻辑关联:

graph TD
    A[Titanic 沉船事件] --> B[外部环境]
    A --> C[乘客属性]
    A --> D[救援策略]

    B --> E[寒冷海水温度]
    B --> F[救生艇数量不足]

    C --> G[Age: 儿童/成人/老年]
    C --> H[Sex: Male/Female]
    C --> I[Pclass: 1/2/3]
    C --> J[Family Size: SibSp + Parch]

    D --> K[妇女儿童优先]
    D --> L[按舱位顺序疏散]

    G & H & I & J --> M[Survival: Yes/No]

此图揭示了最终生存结果是如何被多重因素交织作用的结果。我们的建模目标就是从数据中还原这种复杂的非线性关系。

此外,该数据集还常被用作教学示例,原因如下:
1. 数据规模适中(约 891 条训练样本),适合初学者快速上手;
2. 特征类型多样,包括数值型、类别型、文本型(如姓名、票号);
3. 存在典型的数据质量问题(如缺失值、异常值),便于练习数据清洗技巧;
4. 具备明确的评价指标(准确率),易于验证模型效果。

综上所述,Titanic 数据集不仅仅是一组数字表格,它承载着真实历史事件背后的人类行为规律,为我们提供了宝贵的研究素材。

2.1.2 特征字段解析与目标变量定义

Kaggle 提供的 Titanic 数据集主要包括以下几个字段,以下是各列的详细说明及语义解析:

字段名 类型 含义说明
PassengerId int 乘客唯一标识符
Survived int (0/1) 目标变量:0=死亡,1=生还
Pclass int (1/2/3) 舱位等级:1=一等舱,2=二等舱,3=三等舱(反映社会经济地位)
Name str 乘客姓名(含称谓如 Mr., Mrs., Miss 等)
Sex str 性别:male 或 female
Age float 年龄(部分缺失)
SibSp int 同行的兄弟姐妹或配偶人数
Parch int 同行的父母或子女人数
Ticket str 票号(存在重复,格式不统一)
Fare float 票价(连续变量)
Cabin str 客舱编号(大量缺失)
Embarked str 登船港口:C=Cherbourg, Q=Queenstown, S=Southampton

其中, Survived 是我们要预测的目标变量,其余均为输入特征。值得注意的是,某些字段虽看似无关紧要(如姓名、票号),但可通过特征提取转化为有价值的信息。例如:

  • Name 中提取称谓(Title),可用于推断年龄、婚姻状况和社会角色;
  • SibSp Parch 可组合生成 FamilySize ,反映社交支持程度;
  • Fare 需结合 Pclass 进行归一化比较,否则无法直接衡量支付能力。

为了更直观地理解特征分布对目标变量的影响,我们可以通过交叉表初步观察关键变量与生存率的关系。以下代码展示了如何使用 pandas 计算不同舱位等级下的生存比例:

import pandas as pd

# 加载数据(假设文件路径正确)
df = pd.read_csv('train.csv')

# 查看 Pclass 与 Survived 的交叉表
survival_by_pclass = pd.crosstab(df['Pclass'], df['Survived'], normalize='index') * 100
print(survival_by_pclass)

代码逻辑逐行解读:

  1. import pandas as pd :导入 pandas 库,用于数据操作。
  2. df = pd.read_csv('train.csv') :读取本地 CSV 文件并存储为 DataFrame。
  3. pd.crosstab(df['Pclass'], df['Survived'], normalize='index')
    - 创建列联表,横轴为 Pclass ,纵轴为 Survived
    - normalize='index' 表示按行标准化,即每一行的百分比总和为 100%,便于比较各类别内部的生存比率。
  4. * 100 :将小数形式的概率转换为百分比形式以便阅读。

执行后输出如下:

Survived      0          1
Pclass                   
1          37.54     62.46
2          52.72     47.28
3          75.76     24.24

可见,一等舱乘客生还率高达 62.46%,而三等舱仅为 24.24%。这强烈表明舱位等级是影响生存的重要因素,也验证了“阶级特权”在灾难中的体现。

同样地,我们可以分析性别对生存的影响:

survival_by_sex = pd.crosstab(df['Sex'], df['Survived'], normalize='index') * 100
print(survival_by_sex)

输出结果:

Survived     0          1
Sex                    
female    25.80     74.20
male      81.11     18.89

女性生还率远高于男性(74.2% vs 18.89%),印证了“妇女优先”的救援政策确实发挥了作用。

上述分析说明,在正式建模前,我们必须对每个特征的意义有清晰认知,并判断其与目标变量的相关性。只有这样,才能设计出合理的预处理方案和特征构造逻辑。

2.2 使用Pandas加载与初步探索数据

数据探索是任何数据分析项目的起点。有效的初步探索不仅能帮助我们快速掌握数据的整体结构,还能发现潜在的问题,如数据类型错误、异常值、缺失值等。本节将以 pandas 为主要工具,演示如何高效加载并探索 Titanic 数据集。

2.2.1 读取CSV文件并查看数据结构

首先,我们需要将本地存储的 CSV 文件加载到内存中,形成一个结构化的 DataFrame 对象。这是所有后续操作的基础。

import pandas as pd

# 设置显示选项,避免截断
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)

# 读取训练数据
df_train = pd.read_csv('train.csv')

# 显示前5行数据
print(df_train.head())

输出示例:

   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 21173   7.2500   NaN        S
1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Thayer)  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

通过 .head() 方法可以快速浏览数据前几条记录,确认字段名称、数据格式是否符合预期。

接下来,使用 .info() 方法查看整体数据结构:

df_train.info()

输出示例:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

该输出提供了极为重要的元信息:
- 总共 891 条记录;
- Age 有 177 条缺失(891 - 714), Cabin 缺失严重(仅 204 条非空), Embarked 缺失 2 条;
- 数据类型方面, object 类型表示字符串或混合类型,需注意后续编码处理。

2.2.2 基本统计信息分析与数据类型检查

进一步使用 .describe() 方法获取数值型变量的描述性统计量:

numeric_summary = df_train.describe()
print(numeric_summary)

输出:

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare
count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000
mean    446.000000    0.383838    2.308148   29.699118    0.523008    0.381594   32.204208
std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429
min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000
25%     223.500000    0.000000    2.000000   20.125000    0.000000    0.000000    7.910400
50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200
75%     668.500000    1.000000    3.000000   38.000000    1.000000    0.000000   31.000000
max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

从中可得出若干洞察:
- 平均年龄约为 29.7 岁,标准差较大(±14.5),说明年龄分布较广;
- 多数人无同行亲属(SibSp 和 Parch 的中位数为 0);
- 票价差异极大,最大值达 512,而最小值为 0(可能是船员或特殊票种);
- Survived 的均值为 0.38,意味着整体生还率为 38.38%。

对于类别型变量,可使用 .value_counts() 分析频次分布:

print("\nEmbarked distribution:")
print(df_train['Embarked'].value_counts(dropna=False))

print("\nSex distribution:")
print(df_train['Sex'].value_counts())

输出:

Embarked distribution:
S    644
C    168
Q     77
NaN   2
Name: Embarked, dtype: int64

Sex distribution:
male      577
female    314
Name: Sex, dtype: int64

可见大多数乘客从南安普顿(S)登船,男性人数明显多于女性。

为进一步可视化缺失情况,推荐使用 missingno 库绘制矩阵图:

import missingno as msno
import matplotlib.pyplot as plt

msno.matrix(df_train)
plt.show()

该图清晰显示了 Age Cabin 的缺失模式,尤其是 Cabin 几乎大部分为空,提示我们需要谨慎处理该字段。

综上,通过本阶段的探索,我们已完成对数据结构、类型、分布和质量的全面扫描,为下一步缺失值处理奠定了坚实基础。

2.3 缺失值识别与处理策略设计

缺失值是现实世界数据中最常见的质量问题之一。若处理不当,可能导致模型偏差、性能下降甚至完全失效。因此,必须基于字段语义和缺失机制选择合适的填充或删除策略。

2.3.1 Age、Cabin、Embarked字段缺失情况分析

继续以上节 .info() 输出为基础,统计各字段缺失数量:

missing_data = df_train.isnull().sum()
missing_percent = (missing_data / len(df_train)) * 100
missing_df = pd.DataFrame({'Missing Count': missing_data, 'Missing %': missing_percent})
missing_df = missing_df[missing_df['Missing Count'] > 0].sort_values(by='Missing %', ascending=False)
print(missing_df)

输出:

           Missing Count  Missing %
Cabin                687   77.104377
Age                  177   19.865320
Embarked               2    0.224467

可见:
- Cabin 缺失率达 77.1%,极高;
- Age 缺失约 1/5,需重点关注;
- Embarked 仅缺 2 条,可简单填补。

我们逐一分析处理思路。

2.3.2 基于均值、众数与插值法的填充实践

(1)Age 字段:使用均值/中位数/分组均值填充

Age 是重要预测变量,不宜直接删除。常见填充方法包括:

  • 全局均值/中位数 :简单但忽略个体差异;
  • 按 Pclass 和 Sex 分组均值 :更具合理性。
# 按 Pclass 和 Sex 分组计算平均年龄
age_group_mean = df_train.groupby(['Pclass', 'Sex'])['Age'].mean()

# 定义填充函数
def fill_age(row):
    if pd.isnull(row['Age']):
        return age_group_mean[row['Pclass'], row['Sex']]
    else:
        return row['Age']

# 应用填充
df_train['Age_filled'] = df_train.apply(fill_age, axis=1)

参数说明:
- groupby(['Pclass', 'Sex']) :按舱位和性别分组;
- apply(..., axis=1) :逐行应用函数,访问多个字段;
- 新增 Age_filled 列保留原始 Age 便于对比。

(2)Embarked:使用众数填充

仅缺两条,且均为高票价乘客,推测来自主要港口:

mode_embarked = df_train['Embarked'].mode()[0]  # 'S'
df_train['Embarked'].fillna(mode_embarked, inplace=True)
(3)Fare:极少数缺失时可用中位数填充(测试集中可能出现)
df_test['Fare'].fillna(df_test['Fare'].median(), inplace=True)

2.3.3 Cabin高缺失率的特殊处理逻辑

Cabin 缺失严重,直接删除列会损失潜在信息(如位置、甲板分区)。一种做法是提取首字母作为“甲板”(Deck):

# 提取 Cabin 首字母,缺失设为 'U'(Unknown)
df_train['Deck'] = df_train['Cabin'].astype(str).str[0]
df_train['Deck'] = df_train['Deck'].replace({'n': 'U'})  # 'n' 来自 nan 转换

# 查看 Deck 与生存率关系
deck_survival = pd.crosstab(df_train['Deck'], df_train['Survived'], normalize='index')
print(deck_survival)

输出显示某些甲板(如 B、C)生还率较高,说明仍具信息价值。

最终处理策略总结如下表:

字段 缺失率 处理方式 是否保留
Age 19.9% 按 Pclass+Sex 分组均值填充
Cabin 77.1% 提取 Deck 字符,其余设为 ‘U’ 是(转换后)
Embarked 0.2% 众数填充(’S’)

该策略兼顾了数据完整性与信息利用率,为后续建模提供了稳健输入。

3. 数据类型转换与特征编码技术应用

在真实世界的数据分析项目中,原始数据往往以非结构化或半结构化的形式存在,尤其在像Titanic这样的经典数据集中,尽管其格式规整,但仍包含大量类别型变量(categorical variables)和缺失信息。这些变量若不经过合理处理,将严重影响后续建模过程的效率与准确性。因此,在进入模型训练前,必须对数据类型进行系统性优化,并对分类变量实施科学的编码策略。本章聚焦于如何识别并转化数据中的类别字段,深入探讨标签编码与独热编码的技术差异、实现方式及其在机器学习流程中的实际影响。通过精细化的数据类型管理与特征编码设计,不仅能够提升内存使用效率,还能增强模型对离散特征的理解能力。

3.1 类别型变量的识别与类型优化

在Pandas中,默认情况下字符串类型的列会被识别为 object 类型,虽然可以正常参与运算,但这种类型缺乏语义约束,不利于性能优化与逻辑判断。尤其是在面对如“Sex”、“Embarked”这类具有明确取值集合且顺序无关的分类变量时,将其从 object 转换为 category 类型是一种高效而必要的操作。

3.1.1 Sex与Embarked字段的语义解析

以Titanic数据集为例,“Sex”字段仅有两个取值:“male”和“female”,代表乘客性别;“Embarked”则表示登船港口,包括’C’(Cherbourg)、’Q’(Queenstown)、’S’(Southampton)。这两个变量本质上是名义型分类变量(nominal categorical variable),即类别之间无自然顺序关系。若直接保留为 object 类型,每条记录都会存储完整的字符串副本,造成内存浪费。而采用 category 类型后,Pandas会内部维护一个类别索引表,仅用整数索引引用具体类别,从而显著降低内存占用。

此外,将字段转为 category 还带来一系列附加优势:支持更高效的分组聚合操作、可在可视化中自动排序、便于后续编码处理等。更重要的是,许多现代机器学习框架(如CatBoost、LightGBM)能直接识别 category 类型并内置最优编码机制,避免人工干预带来的误差。

以下代码展示了如何查看原始字段的基本统计信息,并确认其唯一类别数量:

import pandas as pd

# 假设已加载数据为df
df = pd.read_csv('titanic.csv')

# 查看Sex和Embarked的唯一值及频次
print("Sex unique values:")
print(df['Sex'].value_counts())
print("\nEmbarked unique values:")
print(df['Embarked'].value_counts(dropna=False))

代码逻辑逐行解读:

  • 第3行:导入pandas库,用于数据操作。
  • 第5行:读取CSV文件生成DataFrame对象 df
  • 第8–9行:调用 value_counts() 方法输出’Sex’字段各值出现次数,帮助确认是否只有’male’和’female’。
  • 第11–12行:同样统计’Embarked’字段分布,注意 dropna=False 参数确保缺失值也被计数,便于评估清洗需求。

执行结果通常显示:
- Sex : male (~577), female (~314)
- Embarked : S (~644), C (~168), Q (~77), NaN (~2)

这表明两个字段均为低基数分类变量(low-cardinality categorical features),非常适合转换为 category 类型。

3.1.2 数据类型从object到category的转换优势

为了实现类型转换,可使用 .astype('category') 方法完成强制类型变更。该操作不仅能减少内存消耗,还能提升后续计算性能,特别是在大规模数据处理场景下效果尤为明显。

# 转换为category类型
df['Sex'] = df['Sex'].astype('category')
df['Embarked'] = df['Embarked'].astype('category')

# 输出内存使用情况对比
original_mem = df.memory_usage(deep=True).sum() / 1024**2  # MB
print(f"Memory usage after conversion: {original_mem:.3f} MB")

代码逻辑逐行解读:

  • 第2–3行:利用 astype('category') 将指定列转换为类别类型。
  • 第6行: memory_usage(deep=True) 精确统计包含对象内部结构的真实内存占用,除以 1024**2 转换为MB单位。
  • 第7行:打印当前内存总量,便于前后对比。

转换前后内存使用量通常可下降10%~30%,尤其当数据集中存在多个长文本类别的 object 列时更为显著。

为进一步说明转换优势,下表列出不同数据类型的特性比较:

特性 object category
存储方式 存储完整字符串 存储整数编码 + 类别映射表
内存占用 低(尤其适用于重复值多的情况)
支持排序 否(除非显式定义) 可设置有序类别(ordered=True)
分组聚合速度 较慢 更快(基于整数索引)
兼容性 所有函数通用 部分函数需特别处理

此外,可通过 describe() 方法观察 category 类型列的描述性统计:

print(df['Embarked'].describe())

输出示例:

count     889
unique      3
top         S
freq      644
Name: Embarked, dtype: object

其中 unique=3 说明仅含三个不同类别,进一步验证了其适合作为分类变量处理。

下面使用Mermaid语法绘制字段类型优化流程图,展示从原始加载到类型转换的整体路径:

graph TD
    A[读取CSV文件] --> B{字段类型检查}
    B -->|Sex, Embarked为object| C[识别为分类变量]
    C --> D[执行astype('category')]
    D --> E[更新DataFrame结构]
    E --> F[验证内存与类型状态]
    F --> G[进入特征编码阶段]

该流程清晰地表达了从数据加载到类型优化的关键步骤,强调了类型识别与转换之间的因果关系。值得注意的是, category 类型的引入不仅是技术层面的优化,更是建模前数据治理的重要组成部分。它为后续的特征工程提供了更加规范和可控的基础环境。

3.2 类别特征编码方法对比与实现

完成数据类型优化后,下一步是对分类变量进行数值化编码,以便机器学习算法能够正确解析和利用这些信息。由于大多数模型(如逻辑回归、支持向量机、神经网络)只能接受数值输入,必须将类别标签转换为数字形式。常用的编码方法主要包括标签编码(Label Encoding)和独热编码(One-Hot Encoding),二者各有适用场景与局限性。

3.2.1 标签编码(Label Encoding)原理与代码实现

标签编码是一种简单直接的映射方式,它将每个唯一类别按某种顺序(通常是字母序或出现频率)分配一个唯一的整数编号。例如,“female”→0,“male”→1。这种方法实现简便,空间开销小,适用于树模型(如决策树、随机森林)等能够容忍有序假设的算法。

然而,标签编码的一个主要缺陷在于它隐式引入了“顺序”概念——即使类别本身并无先后之分。例如,若将港口’S’=0、’C’=1、’Q’=2,则模型可能误认为’Q > C > S’,进而产生偏差。因此,该方法不宜用于线性模型或距离敏感型模型(如KNN、SVM)。

以下是使用 sklearn.preprocessing.LabelEncoder 实现标签编码的完整示例:

from sklearn.preprocessing import LabelEncoder

# 初始化编码器
le_sex = LabelEncoder()
le_embarked = LabelEncoder()

# 拟合并转换Sex字段
df['Sex_LabelEncoded'] = le_sex.fit_transform(df['Sex'].astype(str))

# 对Embarked字段处理(需先填充缺失值)
df['Embarked'].fillna('Unknown', inplace=True)
df['Embarked_LabelEncoded'] = le_embarked.fit_transform(df['Embarked'])

print("Label Encoding Results:")
print(df[['Sex', 'Sex_LabelEncoded']].drop_duplicates())
print(df[['Embarked', 'Embarked_LabelEncoded']].drop_duplicates())

代码逻辑逐行解读:

  • 第2行:导入 LabelEncoder 类。
  • 第5–6行:创建两个独立编码器实例,分别用于性别和登船港。
  • 第9行: fit_transform() 先拟合类别映射关系,再返回整数编码数组。
  • 第12–13行:对’Embarked’先填充缺失值为’Unknown’,防止编码报错。
  • 第16–18行:输出去重后的映射对照表,验证编码一致性。

输出示例:

     Sex  Sex_LabelEncoded
0   male                 1
1 female                 0

  Embarked  Embarked_LabelEncoded
0        S                      2
1        C                      0
2        Q                      1
3  Unknown                      3

可见’Sex’中female被编为0,male为1;’Embarked’按字母序排列,’C’=0,’Q’=1,’S’=2。

尽管标签编码简洁高效,但在实际建模中应谨慎使用,特别是当特征不具备内在顺序时。

3.2.2 独热编码(One-Hot Encoding)适用场景与pandas.get_dummies()应用

为克服标签编码引入虚假顺序的问题,独热编码成为更安全的选择。其核心思想是:对于一个具有k个类别的变量,创建k个二元特征列,每列表示某一类别是否存在(1表示是,0表示否)。这样既消除了顺序假设,又保留了原始信息完整性。

在Pandas中, pd.get_dummies() 是最常用的一键式独热编码工具,支持自动忽略缺失值、前缀命名控制等功能。

# 使用get_dummies进行One-Hot编码
df_encoded = pd.get_dummies(df, columns=['Sex', 'Embarked'], prefix=['Sex', 'Embarked'], dummy_na=True)

# 查看新增列
print("New dummy columns:")
print([col for col in df_encoded.columns if 'Sex_' in col or 'Embarked_' in col])

代码逻辑逐行解读:

  • 第2行: columns 指定需编码的列名列表。
  • prefix 参数为生成的新列添加统一前缀,提高可读性。
  • dummy_na=True 表示为缺失值单独生成一列(如 Embarked_nan ),有助于模型捕捉缺失模式。
  • 第6–7行:筛选出所有由编码产生的新列,验证是否成功扩展。

输出示例:

New dummy columns:
['Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S', 'Embarked_nan']

此时原’Sex’列被替换为两个布尔列,’Embarked’变为四个列(含nan)。这种方式彻底解耦了类别间的数值关系,适合绝大多数监督学习模型。

下表总结两种编码方式的关键对比:

特征 标签编码 独热编码
编码维度 1列 k列(k=类别数)
是否引入顺序假设
内存占用 大(尤其高基数特征)
适用模型 树模型 线性模型、SVM、NN等
缺失值处理 需预先填充 可通过 dummy_na 保留

此外,可通过以下流程图展示编码选择决策路径:

graph LR
    A[类别变量] --> B{是否有序?}
    B -- 是 --> C[使用Label Encoding]
    B -- 否 --> D{基数高低?}
    D -- 高(>10) --> E[考虑目标编码/嵌入]
    D -- 低 --> F[使用One-Hot Encoding]

此图揭示了特征编码策略的选择逻辑:首先判断是否有自然顺序(如教育水平:小学<中学<大学),若有则可用标签编码;否则进一步评估类别数量,避免因维度爆炸导致过拟合。

3.2.3 编码后特征空间的变化与模型兼容性分析

完成编码后,特征矩阵的维度会发生显著变化。以Titanic为例,原始特征约10余列,经独热编码后可能增至20列以上。这种扩展虽提升了表达能力,但也带来了“维度灾难”的风险,尤其在样本量有限的情况下容易引发过拟合。

为此,需结合模型类型权衡编码方案。例如:

  • 逻辑回归 :强烈推荐独热编码,因其依赖线性组合,无法理解整数编码的非顺序含义。
  • 决策树系列 :可接受标签编码,因分裂节点仅关心是否等于某值,而非大小关系。
  • 梯度提升树(XGBoost/LightGBM) :部分支持 category 类型原生输入,无需手动编码,且效率更高。

此外,还可通过相关性分析检验编码后的多重共线性问题。例如, Sex_female Sex_male 完全负相关(ρ=-1),属于冗余信息。实践中常删除其中一个基准类别(baseline level)以消除共线性:

# 删除基准类别(如female作为参照组)
df_final = pd.get_dummies(df, columns=['Sex', 'Embarked'], drop_first=True, dummy_na=True)
print("Final feature shape:", df_final.shape)

drop_first=True 参数自动移除每组的第一个虚拟变量,有效控制特征膨胀。

综上所述,特征编码并非单一技术动作,而是贯穿数据预处理与建模选择的核心环节。合理的编码策略应基于变量语义、模型类型与数据规模综合决策,既要保证信息无损传递,又要兼顾计算效率与泛化能力。

4. 特征工程构建与数据增强实践

在机器学习建模过程中,原始数据往往无法直接用于训练高性能模型。即便完成了基本的数据清洗与类型转换,仍需通过深入的业务理解和统计洞察来构造更具判别力的新特征。这一过程被称为 特征工程(Feature Engineering) ,是决定模型上限的关键环节。高质量的特征能够显著提升模型对复杂模式的捕捉能力,甚至比选择更复杂的算法更为重要。以Titanic数据集为例,乘客的生存与否不仅取决于其原始属性如年龄、舱位等级等,还与其社会关系结构、家庭规模以及登船港口背后隐含的社会经济地位密切相关。因此,本章节将系统性地展开从新特征构造、数值分箱处理到特征相关性分析的全流程实践,重点聚焦如何基于领域知识和数据分析推导出具有解释性和预测价值的衍生变量。

4.1 新特征构造的业务逻辑推导

在真实世界的数据科学项目中,单纯依赖原始字段通常难以充分表达问题的本质。尤其是在像泰坦尼克号沉船事件这样的历史案例中,救援行为存在明显的优先级策略——妇女儿童优先、头等舱乘客享有更高逃生机会等。这些社会行为规律并未显式记录在数据集中,但可以通过已有字段进行逻辑重构,从而提炼出新的高信息量特征。其中最具代表性的两个衍生变量是 FamilySize IsAlone ,它们分别从“家庭规模”和“是否独自出行”的角度揭示了个体在灾难情境下的社交支持状态,而这种状态极可能影响其最终的生存概率。

4.1.1 FamilySize = SibSp + Parch + 1 的合成思路

在Titanic数据集中, SibSp 表示船上兄弟姐妹或配偶的数量, Parch 表示父母或子女的数量。这两个字段单独来看仅反映某类亲属的存在,但若将其相加并加上自身(+1),即可得到该乘客在整个船上的 家庭总人数 ,即:

\text{FamilySize} = \text{SibSp} + \text{Parch} + 1

这一公式的合理性在于:一个完整的家庭单位应当包含所有直系亲属及本人。例如,一名带有一个孩子的母亲,其 SibSp=0 (无配偶或兄弟姐妹同行), Parch=1 ,则她所在的家庭共有2人;若一对夫妇携两名子女同行,则每人对应的 SibSp=1 , Parch=2 ,计算得 FamilySize=4 ,符合实际情况。

更重要的是,研究表明中等规模的家庭成员往往具有更高的生存率——他们既能相互照应,又不至于因组织混乱而延误逃生。过大或过小的家庭单位则面临不同风险:单身乘客缺乏援助,大家庭则可能因等待失散亲人而错失逃生时机。因此, FamilySize 是一个融合了社会行为与生存策略的重要代理变量。

import pandas as pd

# 假设已加载数据为df
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1

# 查看新特征分布
print(df['FamilySize'].value_counts().sort_index())

代码逻辑逐行解读:

  • 第3行:使用 Pandas 对 DataFrame 中的 SibSp Parch 列执行逐元素加法,并加1形成新列 FamilySize
  • 第6行:调用 .value_counts() 统计每个家庭规模出现的频次,并按索引排序输出。

参数说明:
- SibSp : 范围一般为 [0, 8],表示同船的兄弟姐妹或配偶数量;
- Parch : 取值范围 [0, 6],代表同行的父/母或子/女数量;
- +1 : 必须包含乘客本人,否则会低估实际家庭人数。

该特征构造完成后,可进一步分类为“独行者”、“小家庭”、“中等家庭”、“大家庭”,便于后续建模时进行分组比较或作为分类输入。

4.1.2 IsAlone特征的判断条件与生存关联假设

FamilySize 构造之后,另一个直观且具强区分度的布尔型特征是 IsAlone ,用于标识乘客是否独自登船。其定义如下:

\text{IsAlone} =
\begin{cases}
1, & \text{if FamilySize} = 1 \
0, & \text{otherwise}
\end{cases}

这个特征的核心假设是: 孤立个体在紧急情况下更容易被忽视或缺乏帮助,从而降低生存几率 。尽管并非绝对成立(如部分头等舱单身女性仍可能受到优先救助),但从整体趋势看,社交联结是一种潜在的生存优势。

为了验证这一假设,我们可通过交叉表分析 IsAlone Survived 之间的关系:

# 构造IsAlone特征
df['IsAlone'] = (df['FamilySize'] == 1).astype(int)

# 生成生存交叉表
contingency_table = pd.crosstab(df['IsAlone'], df['Survived'], margins=True)
print(contingency_table)
IsAlone Survived=0 Survived=1 Total
0 357 175 532
1 374 107 481
Total 731 282 1013

代码逻辑逐行解读:

  • 第2行:利用布尔比较 (df['FamilySize'] == 1) 返回 True/False 序列,再通过 .astype(int) 转换为 1/0 整数列。
  • 第5行: pd.crosstab() 创建列联表,展示 IsAlone Survived 的联合频数分布;
  • margins=True 添加行列总计,方便计算比例。

从上表可见,独自出行者( IsAlone=1 )共481人,其中仅107人生还,生存率约为 22.2% ;而非独行者( IsAlone=0 )生存率为 32.9% ,明显更高。这初步支持了“有亲属陪伴者更具生存优势”的假设,也说明 IsAlone 具备良好的预测潜力。

此外,我们可以借助 mermaid 流程图 展示从原始字段到新特征的生成路径:

graph TD
    A[SibSp] --> D[FamilySize = SibSp + Parch + 1]
    B[Parch] --> D
    C[Passenger] --> D
    D --> E{FamilySize == 1?}
    E -->|Yes| F[IsAlone = 1]
    E -->|No| G[IsAlone = 0]

上述流程图清晰表达了特征构造的决策流:三个输入源汇聚成 FamilySize ,再经条件判断生成 IsAlone 特征。这种可视化方式有助于团队协作时快速理解特征逻辑链条。

综上所述, FamilySize IsAlone 并非简单数学运算结果,而是基于灾难社会学背景下的合理推断。它们将碎片化的亲属信息整合为结构性的社会关系指标,极大增强了模型对人类行为模式的理解能力。

4.2 数值特征分箱技术(Binning)应用

连续型变量虽然保留了最大精度,但在某些场景下反而不利于模型学习,尤其是当变量与目标之间呈现非线性关系时。例如,在 Titanic 数据集中,年龄(Age)与生存率的关系并非单调递增或递减,而是呈现出“U型”曲线:婴幼儿和老年人获得更高救援优先级,而成年人尤其是青壮年男性生存率较低。为有效捕捉此类非线性效应,常采用 分箱技术(Binning) 将连续变量划分为若干离散区间,使模型能以类别形式识别关键年龄段的影响。

4.2.1 Age连续变量离散化的必要性分析

原始 Age 字段为浮点数类型,取值范围大致在 0.42 至 80 岁之间。若直接作为数值输入模型,多数线性模型(如逻辑回归)只能拟合线性趋势,难以自动识别拐点或阈值效应。而树模型虽具备一定非线性拟合能力,但若关键切分点未被准确捕获,仍可能导致信息损失。

通过分箱处理,可以实现以下优势:
- 引入先验知识(如儿童/青年/中年/老年划分);
- 减少异常值影响,提高鲁棒性;
- 提升模型可解释性,便于业务解读;
- 配合后续独热编码,适用于更多算法类型。

常见的分箱方法包括 固定宽度分箱(Fixed-width Binning) 聚类分箱(Clustering-based Binning) ,下面分别介绍其实现方式。

4.2.2 固定宽度分箱与聚类分箱的Python实现

固定宽度分箱(等距分箱)

最简单的分箱策略是根据预设边界将数值均匀切分。例如,将 Age 按每10岁一段进行划分:

# 定义分箱边界与标签
bins = [0, 12, 18, 35, 60, 100]
labels = ['Child', 'Teenager', 'Young Adult', 'Adult', 'Senior']

# 执行分箱
df['AgeGroup'] = pd.cut(df['Age'], bins=bins, labels=labels, right=False)

# 输出各组人数统计
print(df['AgeGroup'].value_counts(dropna=False).sort_index())
AgeGroup Count
Child 102
Teenager 85
Young Adult 189
Adult 456
Senior 32
NaN 177

代码逻辑逐行解读:

  • 第2–3行:设定五个区间 [0,12), [12,18), ..., [60,100) ,对应五类人群;
  • 第6行: pd.cut() Age 映射至指定区间, right=False 表示左闭右开;
  • 第9行:统计各组样本数, dropna=False 保留缺失值计数。

此方法优点是规则透明、易于解释,适合嵌入业务规则。但缺点是对数据分布不敏感,可能造成某些区间样本稀疏(如 Senior 组仅32人),影响模型稳定性。

聚类分箱(基于KMeans的动态分箱)

为克服等距分箱的局限,可采用无监督聚类方法自动发现数据内在结构。以下使用 KMeans 算法对 Age 进行聚类分箱:

from sklearn.cluster import KMeans
import numpy as np

# 提取非空Age数据并重塑为二维数组
age_data = df['Age'].dropna().values.reshape(-1, 1)

# 使用KMeans进行3类聚类
kmeans = KMeans(n_clusters=3, random_state=42)
df.loc[df['Age'].notna(), 'AgeCluster'] = kmeans.fit_predict(age_data)

# 映射聚类中心为语义标签
centers = kmeans.cluster_centers_.flatten()
order = np.argsort(centers)  # 按中心值升序排列
label_map = {order[0]: 'Young', order[1]: 'Middle-aged', order[2]: 'Older'}
df['AgeClusterLabel'] = df['AgeCluster'].map(label_map)

代码逻辑逐行解读:

  • 第4行: .dropna() 移除缺失值, .reshape(-1,1) 满足KMeans输入要求;
  • 第7行: fit_predict() 在非空数据上训练并返回簇标签;
  • 第10–11行:获取聚类中心并排序,建立从数字标签到语义名称的映射;
  • 第12行:使用 .map() 实现标签转换。

这种方法的优势在于能适应实际数据分布,避免人为设定不合理边界。但也存在可解释性下降的问题,需结合可视化手段辅助理解。

4.2.3 分箱后类别标签的语义赋予与可视化验证

无论采用何种分箱策略,最终都必须确保生成的类别具有明确语义,并通过可视化手段验证其有效性。以下是结合 Seaborn 的生存率对比图:

import seaborn as sns
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 6))
sns.barplot(data=df, x='AgeGroup', y='Survived', estimator=np.mean, ci=None, palette='Blues')
plt.title('Survival Rate by Age Group (Fixed-width Binning)')
plt.ylabel('Survival Probability')
plt.xlabel('Age Category')
plt.show()

图表显示,“Child”群体的生存率显著高于其他组,验证了“儿童优先”原则的存在。同时,“Senior”组生存率偏低,可能与其行动不便有关。这表明分箱结果成功捕捉到了关键生存信号。

下表总结两种分箱方法的特点对比:

方法 优点 缺点 适用场景
固定宽度分箱 规则清晰、易解释 忽略数据分布,可能导致不平衡 有明确业务规则(如年龄段)
聚类分箱 自适应数据分布,减少主观偏差 标签难解释,需后处理 探索性分析,无先验知识

此外,可通过以下 mermaid 图表示分箱决策流程:

graph LR
    A[原始Age值] --> B{是否存在业务规则?}
    B -->|是| C[固定宽度分箱]
    B -->|否| D[KMeans聚类分箱]
    C --> E[生成AgeGroup]
    D --> F[生成AgeCluster]
    E --> G[映射语义标签]
    F --> G
    G --> H[用于模型训练]

该流程体现了特征工程中的决策树思维:优先考虑可解释性,再辅以数据驱动方法补充盲区。

4.3 特征相关性初探与冗余检测

完成特征构造后,必须评估新旧特征与目标变量之间的关联强度,识别冗余或冲突特征,防止多重共线性干扰模型训练。尤其在引入多个衍生变量后,可能出现高度相关的特征对(如 FamilySize SibSp ),导致模型权重不稳定。

4.3.1 构造特征与目标变量的交叉表分析

交叉表(Contingency Table)是最基础的相关性探测工具,适用于分类变量与二元目标之间的关系分析。以 AgeGroup Survived 为例:

cross_tab = pd.crosstab(df['AgeGroup'], df['Survived'], normalize='index')
print(cross_tab.round(3))
AgeGroup 0 1
Child 0.412 0.588
Teenager 0.588 0.412
Young Adult 0.656 0.344
Adult 0.704 0.296
Senior 0.750 0.250

normalize='index' 表示按行归一化,即每组内生存比例。结果显示,儿童的生还率高达 58.8% ,远超平均水平(约38%),进一步佐证其保护优先级。

类似地,可绘制 FamilySize 与生存率的趋势图:

survival_by_family = df.groupby('FamilySize')['Survived'].mean()
plt.figure(figsize=(8, 5))
sns.lineplot(x=survival_by_family.index, y=survival_by_family.values, marker='o')
plt.title('Survival Rate vs Family Size')
plt.ylabel('Survival Probability')
plt.xlabel('Family Size')
plt.grid(True)
plt.show()

趋势显示: FamilySize=2~4 时生存率达到峰值,随后下降,形成倒U型曲线,提示适度家庭规模最有利。

4.3.2 Pearson与Cramer’s V相关系数的应用边界

对于不同类型变量,需选用合适的相关性度量方法:

  • Pearson 相关系数 :适用于两连续变量间线性关系测量,取值 [-1,1]。
  • Cramer’s V :用于两个分类变量间的关联强度评估,取值 [0,1],值越大关联越强。

以下是 Cramer’s V 的实现函数:

from scipy.stats import chi2_contingency
import numpy as np

def cramers_v(confusion_matrix):
    chi2 = chi2_contingency(confusion_matrix)[0]
    n = confusion_matrix.sum()
    phi2 = chi2 / n
    r, k = confusion_matrix.shape
    phi2corr = max(0, phi2 - ((k-1)*(r-1))/(n-1))
    rcorr = r - ((r-1)**2)/(n-1)
    kcorr = k - ((k-1)**2)/(n-1)
    return np.sqrt(phi2corr / min(rcorr-1, kcorr-1))

# 示例:计算Sex与Survived的Cramer's V
ct = pd.crosstab(df['Sex'], df['Survived'])
print(f"Cramer's V: {cramers_v(ct):.3f}")

输出示例: Cramer's V: 0.542 ,表明性别与生存状态存在较强关联。

相比之下, FamilySize Parch 的 Pearson 相关系数可达 0.8以上,提示存在冗余,建议在建模时做特征选择。

综合运用上述方法,可在特征层面建立起“构造—验证—筛选”的闭环机制,确保输入模型的每一维特征都具备独立贡献价值。

5. 探索性数据分析(EDA)与可视化深度挖掘

探索性数据分析(Exploratory Data Analysis, EDA)是数据科学流程中最具创造性和洞察力的阶段。它不仅是对数据结构和分布特征的初步审视,更是通过图形化手段揭示潜在模式、异常值、变量间关系以及形成可验证假设的关键路径。在Titanic这一经典数据集中,尽管样本量有限,但其丰富的社会属性维度——如性别、年龄、舱位等级、登船港口等——为多角度分析提供了广阔空间。本章节将系统展开从单变量到多变量的可视化策略,结合统计图表与交互式图形工具,深入挖掘影响生存率的核心驱动因素,并基于视觉证据提出具有业务解释性的推论。

通过Seaborn、Matplotlib、Plotly等主流Python可视化库的协同使用,我们将构建一套完整的EDA工作流:从基础分布展示,到跨维度对比分析,再到复杂关系的热力图建模。更重要的是,这些图形不仅仅是“看”,而是作为“思考”的延伸——每一个峰值、每一条趋势线、每一处颜色差异都可能成为后续建模特征选择或策略调整的依据。尤其值得注意的是,在真实世界的数据项目中,客户或决策者往往更依赖直观图像而非数字表格来理解结论,因此掌握高质量、高信息密度的可视化表达能力,已成为高级数据从业者不可或缺的核心技能之一。

此外,本章还将强调 可视化驱动的假设生成机制 ,即如何从图表中识别出反常或显著的模式,并将其转化为可量化检验的研究问题。例如,当我们观察到某一特定年龄段乘客的存活比例异常偏高时,是否可以推断存在“儿童优先”救援政策的实际执行?又或者不同登船港口的乘客在舱位分布上呈现明显差异,这背后是否隐含着社会阶层迁移的历史线索?这些问题的答案不仅取决于模型输出,更源于我们在EDA阶段所提出的深刻洞察。

5.1 单变量分布可视化

单变量分析是探索性数据分析的起点,旨在理解每个独立特征的基本分布形态、集中趋势、离散程度及异常情况。对于分类变量,我们关注各类别的频次占比;对于连续变量,则侧重于密度曲线、直方图形状与偏态特征。在Titanic数据集中,关键变量包括 Survived (是否生还)、 Age (年龄)、 Pclass (舱位等级)、 Sex (性别)等,它们各自承载着不同的语义层级与统计特性。

5.1.1 生存率分布柱状图与饼图展示

首先,我们聚焦目标变量 Survived ,它是整个建模任务的核心响应变量。通过对该变量进行频数统计与可视化,可以快速了解数据集中的类别平衡状况。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 假设df_train为已预处理后的训练数据
survival_counts = df_train['Survived'].value_counts()
labels = ['Not Survived', 'Survived']
colors = ['#d62728', '#2ca02c']

# 柱状图
plt.figure(figsize=(8, 5))
sns.barplot(x=survival_counts.index, y=survival_counts.values, palette=colors)
plt.title('Distribution of Survival (0 = No, 1 = Yes)', fontsize=14)
plt.xlabel('Survival Status')
plt.ylabel('Count')
plt.xticks([0, 1], labels)
for i, v in enumerate(survival_counts.values):
    plt.text(i, v + 10, str(v), ha='center', va='bottom', fontsize=12)
plt.show()

# 饼图
plt.figure(figsize=(7, 7))
plt.pie(survival_counts.values, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors)
plt.title('Survival Rate Pie Chart')
plt.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.
plt.show()
代码逻辑逐行解读与参数说明
  • df_train['Survived'].value_counts() :统计 Survived 列中0和1的出现次数,返回一个Pandas Series。
  • sns.barplot(...) :调用Seaborn绘制条形图, x 为索引(0/1), y 为计数值, palette 指定颜色方案。
  • plt.text(...) :在每个柱子上方添加具体数值标签,提升可读性, ha='center' 表示水平居中对齐。
  • plt.pie(...) :创建饼图, autopct='%1.1f%%' 用于显示百分比并保留一位小数, startangle=90 使起始角度旋转至顶部。
  • plt.axis('equal') :确保坐标轴比例相等,避免饼图变形为椭圆。
图表类型 优点 缺点 适用场景
柱状图 易于比较绝对数量,支持标注清晰 不直观反映整体占比 强调频数差异
饼图 直观展示部分与整体的关系 类别过多时难以阅读,精度低 二分类或少数类占比分析

可视化建议 :在正式报告中,若需突出“仅38.4%的人幸存”这一事实,推荐使用饼图增强冲击力;而在技术文档中,柱状图因其精确性更为合适。

5.1.2 年龄密度曲线与舱位等级分布对比

接下来分析连续型变量 Age 的分布特征,并进一步按 Pclass 分组比较,揭示社会经济地位对乘客年龄结构的影响。

plt.figure(figsize=(10, 6))
sns.kdeplot(data=df_train, x='Age', hue='Pclass', fill=True, alpha=0.6, palette='Set2')
plt.title('Age Density Distribution by Passenger Class', fontsize=14)
plt.xlabel('Age')
plt.ylabel('Density')
plt.legend(title='Passenger Class', labels=['1st Class', '2nd Class', '3rd Class'])
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()
代码逻辑逐行解读与参数说明
  • sns.kdeplot(...) :核密度估计图,平滑地描绘变量的概率密度函数。
  • hue='Pclass' :根据舱位等级分组绘制多条密度曲线。
  • fill=True :填充曲线下方面积,增强视觉区分度。
  • alpha=0.6 :设置透明度,防止颜色重叠导致混淆。
  • palette='Set2' :选用柔和色调提升美观性。
  • plt.grid(...) :添加虚线网格辅助读数。
graph TD
    A[加载Age与Pclass数据] --> B{是否存在缺失值?}
    B -- 是 --> C[使用插值或均值填充Age]
    B -- 否 --> D[直接绘图]
    C --> D
    D --> E[按Pclass分组计算密度]
    E --> F[绘制KDE曲线]
    F --> G[添加标题、图例与网格]
    G --> H[输出最终图像]

逻辑流程说明 :上述Mermaid流程图展示了从原始数据到可视化输出的标准处理路径。其中,缺失值处理是前置必要步骤,否则会导致绘图异常或偏差。

观察结果显示:
- 第一舱位乘客平均年龄较高(约35–45岁),多为成年富裕阶层;
- 第三舱位乘客年龄普遍偏低(集中在15–30岁),推测为移民群体;
- 第二舱位介于两者之间,分布较均匀。

这一发现暗示了 舱位等级与生命周期阶段的相关性 ,进而可能间接影响生存概率——例如,家庭结构、逃生资源获取能力等。

同时,我们也注意到 Age 存在左偏(负偏态),即低龄段人数较多。这种非正态分布提示我们在建模前应考虑标准化或分箱处理,以满足某些算法的输入假设。

综上所述,单变量可视化虽看似简单,却能揭示数据的基本轮廓与结构性特征,为后续多变量分析奠定坚实基础。尤其当结合分组着色与密度叠加技术后,原本静态的分布图便具备了动态比较的能力,极大提升了信息传达效率。

6. 机器学习建模全流程实战与模型评估体系构建

6.1 分类模型选择与算法理论基础

在完成特征工程与探索性数据分析后,我们进入机器学习建模阶段。本节将系统介绍四种广泛应用于二分类任务的经典算法:逻辑回归、决策树、随机森林和支持向量机,并从数学原理和直观解释两个层面阐述其工作机制。

6.1.1 逻辑回归:线性决策边界的数学原理

逻辑回归(Logistic Regression)虽名为“回归”,实则为经典的线性分类器。其核心思想是通过Sigmoid函数将线性组合输出映射到(0,1)区间,表示样本属于正类的概率:

P(y=1|x) = \frac{1}{1 + e^{-(w^Tx + b)}}

其中 $ w $ 为权重向量,$ b $ 为偏置项。训练过程通过极大似然估计优化参数,适用于特征线性可分或需要概率输出的场景。

6.1.2 决策树:信息增益与分裂规则直观解释

决策树通过递归地根据特征值划分数据集,构建树形结构进行预测。每次分裂选择使信息增益最大的特征:

IG(D_p, f) = I(D_p) - \sum_{j=1}^{J} \frac{N_j}{N_p} I(D_j)

其中 $ I(D) $ 可以是基尼不纯度或熵。该方法具有良好的可解释性,能自动处理非线性关系和特征交互。

6.1.3 随机森林:集成学习思想与方差降低机制

随机森林(Random Forest)是一种Bagging集成方法,通过构建多棵决策树并投票决定最终结果。关键创新在于:
- 样本层面:每棵树使用自助采样(Bootstrap)
- 特征层面:每次分裂仅考虑随机子集特征

这有效降低了模型方差,提升了泛化能力。

6.1.4 支持向量机:最大间隔分类与核函数作用

支持向量机(SVM)寻找最大化两类之间间隔的超平面。对于非线性问题,通过核技巧(如RBF核)将数据映射至高维空间:

K(x_i, x_j) = \exp(-\gamma |x_i - x_j|^2)

SVM对高维稀疏数据表现优异,但训练时间较长。

from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC

# 模型初始化示例
models = {
    'Logistic Regression': LogisticRegression(random_state=42),
    'Decision Tree': DecisionTreeClassifier(random_state=42),
    'Random Forest': RandomForestClassifier(random_state=42),
    'SVM': SVC(probability=True, random_state=42)
}

上述代码展示了四种模型在scikit-learn中的基本调用方式,后续章节将基于此框架展开训练与评估。

6.2 模型训练与预测流程编码实现

6.2.1 划分训练集与测试集(train_test_split)

为科学评估模型性能,需将数据划分为训练集与测试集。通常采用7:3或8:2比例,并设置随机种子保证可复现性。

from sklearn.model_selection import train_test_split

X = df_final.drop('Survived', axis=1)  # 特征矩阵
y = df_final['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 确保训练/测试集中正负类比例一致,避免抽样偏差。

6.2.2 各模型在scikit-learn中的调用与拟合过程

依次训练各模型并保存预测结果:

model_results = {}

for name, model in models.items():
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    y_prob = model.predict_proba(X_test)[:, 1]
    model_results[name] = {
        'pred': y_pred,
        'prob': y_prob,
        'model': model
    }
    print(f"{name} 训练完成")

执行逻辑说明:遍历模型字典,调用 .fit() 训练,使用 .predict() 获取类别标签, .predict_proba() 获得概率输出用于后续ROC分析。

6.2.3 预测结果输出与提交格式构造

Kaggle竞赛要求提交符合特定格式的CSV文件:

submission = pd.DataFrame({
    'PassengerId': test_passenger_ids,
    'Survived': final_predictions
})
submission.to_csv('submission.csv', index=False)

其中 test_passenger_ids 来自原始测试集ID, final_predictions 为最优模型预测结果。

6.3 模型性能评估体系建立

6.3.1 准确率、召回率、F1分数的计算与解读

定义混淆矩阵四大要素:
- TP(真正例)、TN(真反例)
- FP(假正例)、FN(假反例)

指标 公式 解读
准确率(Accuracy) (TP+TN)/(TP+TN+FP+FN) 整体预测正确率
召回率(Recall) TP/(TP+FN) 正类识别能力
精确率(Precision) TP/(TP+FP) 预测为正类中真实占比
F1分数 2×(Precision×Recall)/(Precision+Recall) 精确率与召回率调和平均
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

results_df = pd.DataFrame(columns=['Model', 'Accuracy', 'Precision', 'Recall', 'F1'])

for name, res in model_results.items():
    acc = accuracy_score(y_test, res['pred'])
    prec = precision_score(y_test, res['pred'])
    rec = recall_score(y_test, res['pred'])
    f1 = f1_score(y_test, res['pred'])
    results_df.loc[len(results_df)] = [name, acc, prec, rec, f1]

6.3.2 混淆矩阵的构成要素与错误类型分析

可视化混淆矩阵有助于理解误判模式:

from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

plt.figure(figsize=(15, 10))
for idx, (name, res) in enumerate(model_results.items()):
    cm = confusion_matrix(y_test, res['pred'])
    plt.subplot(2, 2, idx+1)
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
    plt.title(f'{name} - Confusion Matrix')
    plt.ylabel('True Label')
    plt.xlabel('Predicted Label')
plt.tight_layout()
plt.show()

观察发现:SVM常出现较多FN(未能救出实际生还者),而逻辑回归FP较高(误判死亡者为幸存)。

6.3.3 ROC曲线绘制与AUC指标的实际意义

ROC曲线反映不同阈值下TPR与FPR的变化:

from sklearn.metrics import roc_curve, auc

plt.figure(figsize=(10, 8))
for name, res in model_results.items():
    fpr, tpr, _ = roc_curve(y_test, res['prob'])
    roc_auc = auc(fpr, tpr)
    plt.plot(fpr, tpr, label=f'{name} (AUC = {roc_auc:.3f})')

plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curves for All Models')
plt.legend(loc="lower right")
plt.grid(True)
plt.show()

AUC > 0.9 表示优秀区分能力,Titanic任务中随机森林通常可达0.88以上。

6.4 模型优化与结果对比分析

6.4.1 交叉验证(Cross-Validation)稳定性验证

使用5折CV评估模型稳定性:

from sklearn.model_selection import cross_val_score

cv_results = {}
for name, model in models.items():
    scores = cross_val_score(model, X_train, y_train, cv=5, scoring='accuracy')
    cv_results[name] = scores
    print(f"{name}: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

结果显示随机森林标准差最小,表明集成方法稳定性更优。

6.4.2 网格搜索(Grid Search)与随机搜索(Random Search)超参数调优

以随机森林为例进行网格搜索:

from sklearn.model_selection import GridSearchCV

param_grid = {
    'n_estimators': [100, 200],
    'max_depth': [None, 10, 20],
    'min_samples_split': [2, 5],
    'max_features': ['sqrt', 'log2']
}

grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1
)

grid_search.fit(X_train, y_train)
print("Best parameters:", grid_search.best_params_)
best_rf = grid_search.best_estimator_

相比随机搜索,网格搜索更彻底但耗时长,适合小参数空间。

6.4.3 多模型性能雷达图对比与最佳模型选定

构建雷达图综合比较各项指标:

import numpy as np
import matplotlib.pyplot as plt
from math import pi

# 数据准备(已标准化)
metrics = ['Accuracy', 'Precision', 'Recall', 'F1', 'AUC']
angles = [n / float(len(metrics)) * 2 * pi for n in range(len(metrics))]
angles += angles[:1]

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))

for name, res in model_results.items():
    values = [
        accuracy_score(y_test, res['pred']),
        precision_score(y_test, res['pred']),
        recall_score(y_test, res['pred']),
        f1_score(y_test, res['pred']),
        auc(roc_curve(y_test, res['prob'])[0], roc_curve(y_test, res['prob'])[1])
    ]
    values += values[:1]
    ax.plot(angles, values, 'o-', linewidth=2, label=name)
    ax.fill(angles, values, alpha=0.1)

ax.set_thetagrids(np.degrees(angles[:-1]), metrics)
ax.set_rlabel_position(30)
plt.yticks([0.5, 0.6, 0.7, 0.8, 0.9], ["50%","60%","70%","80%","90%"], color="grey", size=10)
plt.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
plt.title("Model Performance Radar Chart", pad=20)
plt.show()

6.4.4 完整数据分析流程整合与项目报告输出规范

建立标准化流水线:

graph TD
    A[原始数据] --> B[数据清洗]
    B --> C[特征工程]
    C --> D[EDA可视化]
    D --> E[模型训练]
    E --> F[交叉验证]
    F --> G[超参数调优]
    G --> H[性能评估]
    H --> I[结果提交]
    I --> J[项目文档]
    style A fill:#f9f,stroke:#333
    style J fill:#bbf,stroke:#333

建议项目目录结构:

titanic_project/
├── data/
│   ├── raw/
│   └── processed/
├── notebooks/
├── src/
│   ├── preprocessing.py
│   ├── features.py
│   └── modeling.py
├── reports/
│   └── final_report.pdf
└── README.md

遵循PEP8编码规范,使用Jupyter Notebook + Python脚本混合开发,确保代码可维护性与复现性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本大数据分析案例以“泰坦尼克号”数据集为实践对象,利用Jupyter Notebook这一交互式工具,系统讲解从数据预处理、特征工程到模型构建与评估的完整数据科学流程。涵盖缺失值处理、类别变量编码、新特征构造、多种机器学习算法应用及模型性能可视化分析,是掌握数据分析实战技能的理想教程。通过本案例学习,读者可全面提升在真实场景中运用Python进行数据探索与建模的能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐