机器学习项目:泰坦尼克号乘客生存预测
前言
泰坦尼克生存预测是机器学习经典二分类入门项目,完整覆盖数据分析、特征工程、模型训练、超参调优整套工业流程。本文基于 Python pandas、matplotlib、scikit-learn 从零搭建预测模型,对比逻辑回归与随机森林效果,附带完整代码、运行输出与可视化分析。
一、环境与数据加载
1. 导入依赖并配置可视化
import pandas as pd
import matplotlib.pyplot as plt
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置 DataFrame 表格打印显示格式
pd.set_option('display.max_columns', None) # 设置最大显示列数:None代表全部列都显示
pd.set_option('display.width', 2000) # 设置输出表格宽度,数值调大,单位字符
pd.set_option('display.max_colwidth', 30) # 每列最大字符长度
# 获取泰坦尼克号数据
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
# 打印一些基本信息看看
print(f"数据前 5 项预览:\n{df.head()}")
print(f"\n数据集形状:", df.shape)
print(f"\n数据类型与缺失值情况:")
print(df.info())
数据前 5 项预览:
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... 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 Hea... 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
数据集形状: (891, 12)
数据类型与缺失值情况:
<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
None
2. 数值统计 & 缺失值量化统计
# 数值列统计描述
print("==== 数值特征统计描述 ====")
print(df.describe())
# 统计每列缺失值数量、缺失占比
print("\n==== 缺失值统计 ====")
missing = pd.DataFrame({
"缺失数量": df.isnull().sum(),
"缺失占比(%)": round(df.isnull().sum() / len(df) *100, 2)
})
print(missing)
==== 数值特征统计描述 ====
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.308642 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
==== 缺失值统计 ====
缺失数量 缺失占比(%)
PassengerId 0 0.00
Survived 0 0.00
Pclass 0 0.00
Name 0 0.00
Sex 0 0.00
Age 177 19.87
SibSp 0 0.00
Parch 0 0.00
Ticket 0 0.00
Fare 0 0.00
Cabin 687 77.10
Embarked 2 0.22
二、EDA可视化:特征与生存率关系
1. 客舱等级对生存率影响
plt.figure(figsize=(8, 5))
# Pclass:客舱等级 1一等,2二等,3三等
survive_pclass = df.groupby("Pclass")["Survived"].mean()
survive_pclass.plot(kind="bar", color=["#87CEEB", "#90EE90", "#FFA07A"])
plt.title("不同客舱等级的生存率")
plt.ylabel("生存率")
plt.xlabel("客舱等级Pclass")
plt.xticks([0, 1, 2], ["一等舱", "二等舱", "三等舱"], rotation=0)
plt.show()

2. 性别对生存率影响
plt.figure(figsize=(7, 4))
survive_sex = df.groupby("Sex")["Survived"].mean()
survive_sex.plot(kind="bar", color=["lightcoral", "lightblue"])
plt.title("不同性别生存率")
plt.ylabel("生存率")
plt.xlabel("性别")
plt.xticks([0, 1], ["女性", "男性"], rotation=0)
plt.show()

三、特征工程:缺失值清洗+特征衍生+编码
1. 缺失值处理 & 衍生家庭人数特征
# 1. 删除Cabin列,缺失太多无利用价值
df = df.drop("Cabin", axis=1)
# 2. Age用中位数填充
df["Age"] = df["Age"].fillna(df["Age"].median())
# 3. Embarked用众数填充
df["Embarked"] = df["Embarked"].fillna(df["Embarked"].mode()[0])
# 4. 特征衍生:新增家庭大小特征 FamilySize = SibSp + Parch +1
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
# 检查处理后是否还存在缺失
print("处理完缺失后的缺失统计:")
print(df.isnull().sum())
处理完缺失后的缺失统计:
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Embarked 0
FamilySize 0
dtype: int64
2. 删除无用字段 + 类别特征独热编码
# 1 删除无用列
drop_cols = ["PassengerId", "Name", "Ticket"]
df = df.drop(columns=drop_cols)
# 2 One‑Hot独热编码,处理文本类别特征
df = pd.get_dummies(df, columns=["Sex", "Embarked"], drop_first=True)
print("处理完特征之后的列名:")
print(df.columns.tolist())
print("\n查看前3行数据: ")
print(df.head(3))
处理完特征之后的列名:
['Survived', 'Pclass', 'Age', 'SibSp', 'Parch', 'Fare', 'FamilySize', 'Sex_male', 'Embarked_Q', 'Embarked_S']
查看前3行数据:
Survived Pclass Age SibSp Parch Fare FamilySize Sex_male Embarked_Q Embarked_S
0 0 3 22.0 1 0 7.2500 2 True False True
1 1 1 38.0 1 0 71.2833 2 False False False
2 1 3 26.0 0 0 7.9250 1 False False True
四、数据集划分(分层抽样)
from sklearn.model_selection import train_test_split
# X:全部特征,去掉目标列 Survived
X = df.drop("Survived", axis=1)
# y:预测标签,是否存活
y = df["Survived"]
# 划分训练集、测试集
# test_size=0.2: 测试集占20%
# stratify=y: 分层抽样,保证两集合存活比例一致
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, 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}")
训练集大小 X_train:(712, 9), y_train:(712,)
测试集大小 X_test:(179, 9), y_test:(179,)
五、模型一:标准化逻辑回归
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
lr_model = LogisticRegression(max_iter=200)
lr_model.fit(X_train_scaled, y_train)
y_pred = lr_model.predict(X_test_scaled)
print("========逻辑回归(标准化后)模型评估========")
print(f"测试集准确率 accuracy: {accuracy_score(y_test, y_pred)*100:.2f}%")
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
print("\n分类报告:")
print(classification_report(y_test, y_pred))
========逻辑回归(标准化后)模型评估========
测试集准确率 accuracy: 80.45%
混淆矩阵:
[[98 12]
[23 46]]
分类报告:
precision recall f1-score support
0 0.81 0.89 0.85 110
1 0.79 0.67 0.72 69
accuracy 0.80 179
macro avg 0.80 0.78 0.79 179
weighted avg 0.80 0.80 0.80 179
六、模型二:基础随机森林
from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
y_pred_rf = rf_model.predict(X_test)
print("======== 随机森林模型评估 ========")
print(f"测试集准确率 accuracy: {accuracy_score(y_test, y_pred_rf)*100:.2f}%")
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred_rf))
print("\n分类报告:")
print(classification_report(y_test, y_pred_rf))
# 输出特征重要性
print("\n==== 特征重要性(数值越大对生存预测影响越大)====")
feature_importance = pd.DataFrame({
"feature":X_train.columns,
"importance":rf_model.feature_importances_
}).sort_values("importance", ascending=False)
print(feature_importance)
======== 随机森林模型评估 ========
测试集准确率 accuracy: 81.01%
混淆矩阵:
[[95 15]
[19 50]]
分类报告:
precision recall f1-score support
0 0.83 0.86 0.85 110
1 0.77 0.72 0.75 69
accuracy 0.81 179
macro avg 0.80 0.79 0.80 179
weighted avg 0.81 0.81 0.81 179
==== 特征重要性(数值越大对生存预测影响越大)====
feature importance
4 Fare 0.273412
6 Sex_male 0.261490
1 Age 0.237682
0 Pclass 0.085662
5 FamilySize 0.050495
2 SibSp 0.033592
3 Parch 0.024457
8 Embarked_S 0.024002
7 Embarked_Q 0.009208
导出测试集预测结果
# 获取测试集预测结果
test_result = X_test.copy()
test_result["真实_Survived"] = y_test
test_result["预测_Survived"] = y_pred_rf
# 输出csv文件,保存到你当前VSCode工作目录
test_result.to_csv("1.titanic_test_predict.csv", index=False, encoding="utf_8_sig")
print("1.预测文件已保存:titanic_test_predict.csv")
七、超参调优:网格搜索GridSearchCV优化随机森林
from sklearn.model_selection import GridSearchCV
# 1、设置要搜索的参数字典
param_grid = {
"n_estimators": [50, 100, 150], # 森林中决策树棵数
"max_depth": [3, 5, 7, 9, 11] # 树最大深度,重点!
}
# 2、基础随机森林模型
rf = RandomForestClassifier(random_state=42)
# 3、网格搜索,5折交叉验证
grid_search = GridSearchCV(
estimator=rf,
param_grid=param_grid,
cv=5, # 5折交叉验证
scoring="accuracy", # 评估指标用准确率
n_jobs=-1 # n_jobs=-1 使用电脑全部CPU,加速搜索
)
# ⚠️注意:fit只用训练集!绝对不能把X_test放进来!
grid_search.fit(X_train, y_train)
# 输出最优参数、最优交叉验证得分
print("✅网格搜索得到的最优参数:")
print(grid_search.best_params_)
print(f"\n训练集上5折交叉验证最优准确率:{grid_search.best_score_*100:.2f}%")
# 获取调参完毕的最优模型
best_rf = grid_search.best_estimator_
# 使用最优模型,在【从未见过的测试集】上评估!
y_pred_best = best_rf.predict(X_test)
print("\n======== 调参后最优模型,测试集评估 ========")
print(f"测试集准确率:{accuracy_score(y_test, y_pred_best)*100:.2f}%")
print(classification_report(y_test, y_pred_best))
feature_importance = pd.DataFrame({
"feature":X_train.columns,
"importance":best_rf.feature_importances_
}).sort_values("importance", ascending=False)
print("\n调参后特征重要性:")
print(feature_importance)
✅网格搜索得到的最优参数:
{'max_depth': 3, 'n_estimators': 100}
训练集上5折交叉验证最优准确率:82.17%
======== 调参后最优模型,测试集评估 ========
测试集准确率:79.89%
precision recall f1-score support
0 0.78 0.94 0.85 110
1 0.85 0.58 0.69 69
accuracy 0.80 179
macro avg 0.82 0.76 0.77 179
weighted avg 0.81 0.80 0.79 179
调参后特征重要性:
feature importance
6 Sex_male 0.451692
4 Fare 0.177798
0 Pclass 0.161332
1 Age 0.074909
5 FamilySize 0.060430
2 SibSp 0.031093
8 Embarked_S 0.022414
3 Parch 0.012901
7 Embarked_Q 0.007431
八、项目总结与优化思路
1. 实验结论
- 数据规律:性别、舱位票价、年龄是决定生存的三大核心特征,女性、一等舱乘客存活概率更高;
- 模型对比:基础随机森林(81.01%)效果略优于标准化逻辑回归(80.45%);
- 调参效果:网格搜索仅提升训练集交叉验证精度,测试集准确率小幅下跌,出现轻微过拟合;限制树深度后性别特征区分权重大幅提升。
2. 后续优化方向
- 深度特征挖掘:从 Name 字段提取 Mr/Mrs/Miss 头衔,新增儿童/成人年龄分箱特征;
- 算法升级:尝试 XGBoost、LightGBM、SVM 等模型,搭配正则缓解过拟合;
- 工程优化:增加特征筛选、归一化对比、学习曲线绘制,直观观测过拟合程度;
- 评价指标补充:除准确率外,重点关注存活样本召回率,贴合项目业务场景。
九、最后再唠唠
由于我做的时候使用的是 Jupyter Notebook,所以代码都是一段一段的不好看,还请见谅!此外如果聪明的你发现了代码和表述有错误或者有更好的提议,还请告知,谢谢!
下面是本项目以及另外两个项目的链接(加州房价预测、鸢尾花数据集多分类),如果需要查看请自行下载:
浙公网安备 33010602011771号