☰
泰坦尼克号幸存者预测Python代码与文档说明:从数据清洗到模型集成的完整实战
2026/10/2 14:44:30 网站建设 项目流程

简介:这是一份面向高校学生与Python初学者的泰坦尼克号幸存者预测完整项目资料,可作为期末大作业、课程设计或数据挖掘入门实战使用。资源围绕经典Kaggle赛题展开,涵盖数据读取、特征处理、模型训练与结果预测等完整流程,代码注释详尽,新手也能快速理解并部署运行。压缩包共5个文件,约33KB,包含py脚本、csv数据集、ipynb交互式笔记本、gitignore配置及md说明文档,分别对应核心代码、原始数据、分步实验记录与项目说明,结构清晰便于按模块学习。目前已有746人学习下载,适合需要一份可直接参考的完整方案来完成作业或入门机器学习分类任务。通过该资料,读者可掌握数据探索、可视化分析与幸存者预测建模的基本思路,并借鉴其代码组织方式与注释风格,快速搭建自己的课程设计项目。

1. 泰坦尼克号幸存者预测到底在练什么:从一份结构化数据说起

泰坦尼克号幸存者预测是 Kaggle 上最经典的入门赛题,也是国内高校数据挖掘、机器学习大作业里出现频率最高的题目之一。很多人第一次拿到train.csv的时候会有点懵:891 行、12 列,字段看着都认识,但真让你从零写出一份能跑通、能解释、能交作业的 Python 代码,还是会卡在缺失值怎么填、类别特征怎么编码、模型怎么选这些具体问题上。这篇笔记就围绕「泰坦尼克号幸存者预测【Python代码&文档说明】大作业」这个标题,把一份完整可复现的方案拆开讲清楚:数据长什么样、每一步为什么这么做、参数怎么调、哪些地方最容易翻车。适合正在做课程大作业的学生,也适合想用一个小数据集把 Python 数据分析与可视化流程走一遍的入门者。整条链路走完,你会得到一份结构化的预测代码和一份能直接写进文档的说明思路。

2. 数据到手先别急着建模:泰坦尼克号字段的清洗与特征工程

2.1 先看清 12 个字段里哪些是噪音

拿到数据的第一步不是fit,而是把每个字段的含义和分布过一遍。泰坦尼克号训练集常见字段包括PassengerId、Survived、Pclass、Name、Sex、Age、SibSp、Parch、Ticket、Fare、Cabin、Embarked。其中PassengerId是纯索引,对预测没有信息量,直接丢掉;Name里其实藏着称呼(Mr、Mrs、Miss、Master),这个称呼和性别、年龄、社会地位强相关,是值得提取的;Ticket编号杂乱,但前缀有时能反映舱位等级,属于可挖可不挖的字段;Cabin缺失率超过 70%,直接整列用风险很大,通常只取首字母作为甲板区域特征。

我一般会先跑一段概览代码,把缺失情况和类型看清楚再动手:

import pandas as pd import numpy as np train = pd.read_csv('train.csv') test = pd.read_csv('test.csv') # 看缺失比例,决定每列是删是填 print(train.isnull().sum().sort_values(ascending=False)) print(train.dtypes) print(train['Survived'].value_counts(normalize=True))

这段代码做三件事:isnull().sum()按缺失数量排序,让你一眼看到Cabin、Age、Embarked三列的问题;dtypes确认哪些是 object 需要编码;value_counts(normalize=True)看标签分布,泰坦尼克号幸存率大约在 38% 左右,属于轻度不平衡,暂时不需要上采样,但评估时不能只看准确率。

参数上没什么可调的,重点是看结果。如果Cabin缺失超过 70%,我的处理是只保留首字母,缺失的填'U'(Unknown);Age缺失约 20%,用分组中位数填比全局中位数更合理;Embarked只缺两三个,用众数填即可。

2.2 缺失值填充:为什么 Age 要按性别和舱位分组填

Age的缺失填充是这份作业里第一个能体现水平的地方。直接填全局中位数(约 28 岁)会把所有缺失乘客都当成同一个人,但事实上不同舱位、不同性别的年龄分布差别很大。头等舱乘客整体偏年长,三等舱里有大量年轻男性。按Sex和Pclass分组取中位数,能让填充值更贴近真实分布。

# 按性别和舱位分组填充年龄,比全局中位数更贴近真实分布 train['Age'] = train.groupby(['Sex', 'Pclass'])['Age'].transform( lambda x: x.fillna(x.median()) ) test['Age'] = test.groupby(['Sex', 'Pclass'])['Age'].transform( lambda x: x.fillna(x.median()) ) # Embarked 只缺极少数,用众数填 train['Embarked'] = train['Embarked'].fillna(train['Embarked'].mode()[0]) # Fare 在测试集有缺失,用同舱位中位数填 test['Fare'] = test.groupby('Pclass')['Fare'].transform( lambda x: x.fillna(x.median()) )

transform和apply的区别这里要强调一下:transform返回和原列等长的结果,能直接赋值回去;apply在某些写法下会返回聚合后的短表,赋值会报长度不匹配。这是新手很常见的翻车点。分组键选['Sex', 'Pclass']而不是只选Pclass,是因为同一舱位里男女年龄差异明显,分组越细填充越准,但也不能细到每个组合只剩几个人,那样中位数就不稳定了。

2.3 从 Name 和 Cabin 里榨出可用特征

Name和Cabin看着像要丢的列,其实能挖。Name里的称呼可以直接正则提取,Cabin的首字母代表甲板位置,和生存率有一定关联。

import re # 从姓名里提取称呼,这是性别和社会地位的浓缩 def get_title(name): title_search = re.search(r' ([A-Za-z]+)\.', name) if title_search: return title_search.group(1) return '' for df in [train, test]: df['Title'] = df['Name'].apply(get_title) # 把低频称呼归并,避免类别过多 df['Title'] = df['Title'].replace( ['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare') df['Title'] = df['Title'].replace('Mlle', 'Miss') df['Title'] = df['Title'].replace('Ms', 'Miss') df['Title'] = df['Title'].replace('Mme', 'Mrs') # 舱位首字母作为甲板特征 df['Deck'] = df['Cabin'].str[0].fillna('U')

正则r' ([A-Za-z]+)\.'匹配的是空格后一串字母再加一个点,正好对应Braund, Mr. Owen Harris里的Mr。归并低频称呼是为了防止编码后出现大量只出现一两次的类别,这类类别对模型没有泛化价值,反而容易过拟合。Deck取首字母后缺失填'U',这样既保留了甲板信息,又没有因为大面积缺失而丢样本。

2.4 类别编码与特征缩放:独热编码和标准化的取舍

清洗完之后,剩下的类别特征(Sex、Embarked、Title、Deck)需要转成数值。常见做法有两种:标签编码和独热编码。Sex只有男女两类,标签编码成 0/1 就够了;Embarked、Title、Deck类别稍多,用独热编码更稳妥,避免模型误以为类别之间有大小顺序。

from sklearn.preprocessing import StandardScaler # 性别二值化 for df in [train, test]: df['Sex'] = df['Sex'].map({'male': 0, 'female': 1}) # 多类别做独热编码,drop_first 避免共线性 train = pd.get_dummies(train, columns=['Embarked', 'Title', 'Deck'], drop_first=True) test = pd.get_dummies(test, columns=['Embarked', 'Title', 'Deck'], drop_first=True) # 对齐训练集和测试集的列,防止独热后列数不一致 train, test = train.align(test, join='left', axis=1, fill_value=0) # 对 Age 和 Fare 做标准化 scaler = StandardScaler() for col in ['Age', 'Fare']: train[[col]] = scaler.fit_transform(train[[col]]) test[[col]] = scaler.transform(test[[col]])

align这一步是血泪经验:训练集和测试集分别做独热编码后,如果某个类别只在一边出现,两边的列数会对不上,直接喂给模型就报错。align以训练集为准对齐,缺失的列补 0。标准化用fit_transform处理训练集、transform处理测试集,绝不能对测试集重新fit,否则就是把测试集的信息泄露进了训练过程,这是数据泄露里最隐蔽的一种。

3. 模型选型与训练:从逻辑回归到集成模型的对比实验

3.1 为什么先用逻辑回归做基线

很多人一上来就上 XGBoost,结果调参调到怀疑人生,最后连基线是多少都不知道。我的习惯是先跑一个逻辑回归,因为它训练快、可解释、不容易过拟合,能给你一个可靠的参照线。泰坦尼克号这种小数据集,逻辑回归配上合理的特征工程,准确率通常能到 0.78 到 0.82 之间。

from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 选好特征列,丢掉无用的文本和索引 features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare'] + \ [c for c in train.columns if c.startswith(('Embarked_', 'Title_', 'Deck_'))] X = train[features] y = train['Survived'] # 5 折交叉验证,比单次划分更稳 lr = LogisticRegression(max_iter=1000, C=1.0) scores = cross_val_score(lr, X, y, cv=5, scoring='accuracy') print('逻辑回归 CV 准确率: %.4f (+/- %.4f)' % (scores.mean(), scores.std()))

max_iter=1000是因为默认的 100 次迭代在特征标准化后有时不收敛,会弹警告。C是正则化强度的倒数,越小正则越强,默认 1.0 通常够用,如果交叉验证发现训练集准确率远高于验证集,可以调小C。cross_val_score用 5 折,比单次train_test_split的结果更可信,因为每个样本都当过验证集。

3.2 随机森林和梯度提升:参数怎么设才不玄学

基线跑通后,可以上树模型。随机森林和梯度提升是两种常见选择,前者抗过拟合、对参数不敏感,后者精度上限更高但更容易调崩。我一般两个都跑,用交叉验证对比。

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier # 随机森林:树多一点、深度限制一下防过拟合 rf = RandomForestClassifier( n_estimators=300, # 树的数量,300 通常够用 max_depth=6, # 限制深度,防止每棵树记死训练集 min_samples_leaf=3, # 叶子最少样本,进一步防过拟合 random_state=42 ) rf_scores = cross_val_score(rf, X, y, cv=5, scoring='accuracy') print('随机森林 CV 准确率: %.4f (+/- %.4f)' % (rf_scores.mean(), rf_scores.std())) # 梯度提升:学习率小一点、树多一点 gb = GradientBoostingClassifier( n_estimators=200, learning_rate=0.05, # 学习率,越小越稳但需要更多树 max_depth=3, # 树浅一点,防止单棵树过强 random_state=42 ) gb_scores = cross_val_score(gb, X, y, cv=5, scoring='accuracy') print('梯度提升 CV 准确率: %.4f (+/- %.4f)' % (gb_scores.mean(), gb_scores.std()))

n_estimators和learning_rate是一对需要一起看的参数:学习率降到 0.05,树的数量就要相应增加,否则模型还没学够。max_depth=3对梯度提升是常见起点,树太深会让每棵树都去拟合噪声。min_samples_leaf=3是随机森林里很有效的防过拟合手段,叶子节点样本太少时,模型会把训练集里的偶然规律当成真规律。

3.3 用网格搜索找参数:别把搜索空间开太大

参数调优不是把范围开得越宽越好,搜索空间一大,跑一晚上都出不来结果,而且容易过拟合验证集。我的做法是先粗后细,先在小范围里找到大致方向,再在附近细化。

from sklearn.model_selection import GridSearchCV # 先粗搜,范围不要开太大 param_grid = { 'n_estimators': [200, 300, 400], 'max_depth': [4, 6, 8], 'min_samples_leaf': [1, 3, 5] } grid = GridSearchCV( RandomForestClassifier(random_state=42), param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid.fit(X, y) print('最佳参数:', grid.best_params_) print('最佳 CV 准确率: %.4f' % grid.best_score_)

n_jobs=-1表示用满所有 CPU 核心,能明显缩短搜索时间。cv=5配合 3×3×3 共 27 组参数,总共要训练 135 次,小数据集上几分钟能跑完。如果发现最佳参数落在搜索范围的边界上(比如max_depth最优是 8,而你的范围是 4 到 8),说明范围开小了,需要往外扩再搜一轮。

3.4 特征重要性:哪些字段真正在起作用

树模型训练完可以直接看特征重要性,这比逻辑回归的系数更直观,也方便你写文档说明。

import matplotlib.pyplot as plt # 用最佳参数重新训练 best_rf = grid.best_estimator_ importances = pd.Series(best_rf.feature_importances_, index=X.columns) importances.sort_values(ascending=False).head(10).plot(kind='barh') plt.title('Top 10 Feature Importances') plt.tight_layout() plt.show()

通常排在前面的会是Sex、Title_Mr、Fare、Age、Pclass这几个。Sex排第一不意外,泰坦尼克号上女性生存率远高于男性。Title_Mr和Sex有信息重叠,但称呼还额外携带了年龄和婚姻状况的信息。如果发现某个独热出来的列重要性异常高,要警惕是不是数据泄露,比如把Survived相关的信息不小心编进了特征。

4. 避坑与排查:泰坦尼克号作业里最容易翻车的五个地方

4.1 训练集和测试集列对不齐,模型直接报错

现象:pd.get_dummies分别处理训练集和测试集后,喂给模型时报feature_names mismatch或者维度不一致。原因:某个类别只在训练集或只在测试集出现,独热后两边列数不同。解决:独热之后立刻用train, test = train.align(test, join='left', axis=1, fill_value=0)对齐,以训练集列为准,测试集缺的列补 0。更稳妥的做法是把训练集和测试集拼起来一起做独热,再拆回去。

4.2 标准化时对测试集重新 fit,造成数据泄露

现象:交叉验证分数很高,但实际提交后分数掉一大截。原因:对测试集调用了scaler.fit_transform而不是transform,测试集的均值和方差信息泄露进了预处理。解决:标准化、归一化、填充中位数这些操作,统计量一律只在训练集上算,测试集只做变换。写代码时养成习惯:fit_transform只出现在训练集,transform只出现在测试集。

4.3 用准确率评估不平衡数据,忽略了召回率

现象:模型准确率 0.82 看着不错,但一看混淆矩阵,几乎把所有乘客都预测成遇难。原因:幸存率只有 38% 左右,模型只要全猜多数类就能拿到 0.62 的准确率,0.82 里有一部分是蒙的。解决:评估时同时看precision、recall和f1,用classification_report一次性输出。如果更关心找出幸存者,可以调class_weight='balanced'让模型对少数类更敏感。

4.4 Age 填充用了全局中位数,特征区分度被抹平

现象:填充完 Age 后,模型对年龄的利用很弱,特征重要性排得很靠后。原因:全局中位数把所有缺失乘客填成同一个值,人为制造了一堆重复值,年龄的分布被压扁了。解决:按Sex和Pclass分组填中位数,让填充值随群体变化。如果缺失比例很高,还可以考虑加一列Age_is_null标记是否缺失,让模型自己决定怎么用这个信息。

4.5 交叉验证用了默认的 KFold,分类数据上分层失效

现象:交叉验证分数波动很大,不同折之间差好几个百分点。原因:默认KFold不保证每折里正负样本比例一致,小数据集上某一折可能幸存者特别少,导致评估不稳定。解决:分类任务用StratifiedKFold,或者直接在cross_val_score里传cv=5时确保用的是分层策略。GridSearchCV和cross_val_score在分类器下默认就是分层折,但如果你手动传了KFold对象,分层就没了。

5. 把结果讲清楚:文档说明怎么写、提交文件怎么生成

5.1 一份能交差的文档说明包含哪些内容

大作业的文档说明不是把代码贴一遍就完事,评阅人想看的是你的思路链条。我一般按这个结构写:数据概况(样本量、字段含义、缺失情况)、清洗决策(每个缺失值为什么这么填、每个特征为什么这么构造)、模型对比(基线是什么、最终选了什么、交叉验证分数多少)、特征重要性分析(哪些字段在起作用、和业务直觉是否一致)、结论与局限(模型能做什么、不能做什么)。其中清洗决策和模型对比是重点,要写出「为什么选 A 不选 B」,而不是只写「我用了 A」。

5.2 生成提交文件并检查格式

Kaggle 提交要求两列:PassengerId和Survived,Survived是 0 或 1 的整数。生成时注意索引和列名,别把 DataFrame 的索引也写进去。

# 用最佳模型对测试集预测 test_features = test[features] predictions = best_rf.predict(test_features) # 组装提交文件,列名和顺序必须和样例一致 submission = pd.DataFrame({ 'PassengerId': test['PassengerId'], 'Survived': predictions.astype(int) }) submission.to_csv('submission.csv', index=False) # 自检:行数、列名、标签分布 print(submission.shape) print(submission.columns.tolist()) print(submission['Survived'].value_counts())

index=False是必须的,否则会多出一列索引,提交系统直接判格式错误。astype(int)确保标签是整数而不是浮点,有些提交系统对 0.0/1.0 也会报错。最后打印行数和标签分布做自检,测试集通常是 418 行,幸存预测比例应该和训练集幸存率大致接近,如果差太远说明模型有问题。

5.3 用混淆矩阵和 ROC 曲线验证模型真实水平

准确率只是一个数,混淆矩阵和 ROC 曲线能告诉你模型错在哪、阈值怎么调。

from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix, classification_report, roc_auc_score # 留出一部分数据做最终验证 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) best_rf.fit(X_train, y_train) y_pred = best_rf.predict(X_val) y_prob = best_rf.predict_proba(X_val)[:, 1] print(confusion_matrix(y_val, y_pred)) print(classification_report(y_val, y_pred)) print('AUC: %.4f' % roc_auc_score(y_val, y_prob))

stratify=y保证划分后训练集和验证集的幸存比例一致,小数据集上这一步很关键。predict_proba返回的是概率,取第二列(索引 1)是幸存概率,用来算 AUC。AUC 在 0.85 以上算不错,0.80 到 0.85 是正常水平,低于 0.80 说明特征工程还有提升空间。混淆矩阵重点看假阴性和假阳性哪个多,如果假阴性多,说明模型漏掉了太多实际幸存的人,可以考虑降低分类阈值。

5.4 一个提升分数的小技巧:把多个模型的预测概率平均

单模型调到头之后,集成是性价比最高的提升手段。不需要复杂的 stacking,把逻辑回归、随机森林、梯度提升的预测概率简单平均,往往就能涨一两个百分点。

# 三个模型各自预测概率,然后平均 lr_prob = lr.fit(X_train, y_train).predict_proba(X_val)[:, 1] rf_prob = best_rf.predict_proba(X_val)[:, 1] gb_prob = gb.fit(X_train, y_train).predict_proba(X_val)[:, 1] ensemble_prob = (lr_prob + rf_prob + gb_prob) / 3 ensemble_pred = (ensemble_prob > 0.5).astype(int) print('集成后 AUC: %.4f' % roc_auc_score(y_val, ensemble_prob)) print(classification_report(y_val, ensemble_pred))

三个模型平均的前提是它们各有各的错法,如果三个模型高度相关,平均的收益就有限。逻辑回归是线性模型,随机森林是 bagging,梯度提升是 boosting,三者原理不同,互补性通常不错。阈值 0.5 不是固定的,如果更看重召回率,可以降到 0.4 左右,具体看业务需求。这套流程走下来,一份泰坦尼克号幸存者预测的 Python 代码和文档说明就完整了。我自己做这类作业最大的教训是:别急着上复杂模型,先把数据清洗和特征工程做扎实,基线的分数往往比想象中高,而后面所有的提升都建立在这条基线之上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询