☰
Python泰坦尼克号生存预测实战:从数据清洗到模型调优的完整指南
2026/10/1 20:21:45 网站建设 项目流程

简介:这份资源是面向数据科学初学者与Kaggle竞赛入门者的Python实战项目包,以经典泰坦尼克号生存预测为场景,帮助读者打通从数据加载、探索性分析到模型训练评估的完整流程。压缩包共4个文件,包含2个csv数据集、1个py实验代码和1份doc实验报告,整体约2.35MB,其中csv用于训练与测试建模,py脚本承载核心分析逻辑,doc则记录实验过程与结论。目前已有5207人学习下载,热度较高。项目覆盖Pandas缺失值处理与分组分析、Numpy数值统计、Matplotlib与Seaborn可视化,并延伸至特征工程、逻辑回归与随机森林等模型构建、K折交叉验证及网格搜索调优,实验报告还总结了模型评估与结果解释。对于希望系统练习Python数据分析与机器学习建模的读者,这是一份结构完整、可直接复现的学习范例。

1. 泰坦尼克号生存预测:一份能直接跑通的 Python 实战包

很多人第一次打开 Kaggle 的 Titanic 竞赛页面,看到 891 行训练数据、十几个字段,觉得不过是个二分类问题,随手fit一下就完事。真跑起来才发现:Age 缺了 177 个、Cabin 缺了将近 700 个、Embarked 还漏了 2 个,性别和登船港口是字符串,模型根本喂不进去。这份python泰坦尼克号.zip就是冲着这些具体麻烦来的——它把数据清洗、特征工程、模型训练到结果输出串成一条能直接跑的链路,适合刚学完 pandas 想找个完整项目练手的人,也适合想拿它当模板改造成自己业务数据集的从业者。下面我按拆包后的实际使用顺序,把每一步的参数、坑和验证方法讲清楚。

2. 拆包先看结构:数据、脚本、依赖怎么摆

2.1 压缩包里的文件分工

拿到压缩包别急着解压完就双击运行,先看清楚里面有什么。这类实战包通常包含三类东西:原始数据、处理脚本、依赖说明。我拆过的同类包里,目录结构大致是这样:

文件/目录作用使用顺序
train.csv训练集,含 Survived 标签第一步读取
test.csv测试集,无标签,用于生成提交训练后预测
titanic.py或main.py主流程脚本核心入口
requirements.txt依赖库及版本环境配置时先装
submission.csv输出结果模板最后生成

如果包里没有requirements.txt,常见做法是手动确认 pandas、numpy、scikit-learn 三个库是否齐全。这三个是跑通的最小集合,缺一个都会在 import 阶段直接报错。

2.2 环境准备与依赖安装

新手最容易卡在环境上。我一般建议用虚拟环境隔离,避免和系统里其他项目的库版本打架。Windows 下打开 PowerShell,macOS 或 Linux 下打开终端,依次执行:

# 创建虚拟环境,python 版本建议 3.8 以上 python -m venv titanic_env # 激活环境:Windows titanic_env\Scripts\activate # 激活环境:macOS / Linux source titanic_env/bin/activate # 安装核心依赖 pip install pandas numpy scikit-learn

这里有个参数值得说:python -m venv后面的目录名随便起,但建议和项目名区分开,否则以后多个项目混在一起分不清。激活成功后命令行前面会出现(titanic_env)前缀,看到这个前缀才说明装库装对了地方。如果pip install报权限错误,Windows 下用管理员身份重开终端,Linux 下检查是不是误用了系统 Python。

提示:不要用pip install直接往系统 Python 里装库。我见过太多人把系统环境搞乱,最后连python命令都启动不了,只能重装。

2.3 先跑通再优化

环境好了之后,第一件事不是改代码,是原封不动跑一遍,确认能产出submission.csv。这一步的意义在于建立基线——知道原始脚本的准确率大概在什么水平,后面每改一处才知道是变好还是变坏。跑的命令通常是:

python titanic.py

如果脚本里用了相对路径读train.csv,就必须在脚本所在目录下执行,否则会报FileNotFoundError。这是新手翻车率最高的地方之一,和代码逻辑无关,纯粹是工作目录问题。

3. 数据清洗与特征工程:把脏数据变成模型能吃的格式

3.1 缺失值处理的三种策略

Titanic 数据集的缺失集中在 Age、Cabin、Embarked 三列,但处理方式不能一刀切。先看缺失比例:

import pandas as pd train = pd.read_csv('train.csv') # 统计每列缺失数量和比例 missing = train.isnull().sum() missing_pct = (missing / len(train) * 100).round(2) print(pd.DataFrame({'缺失数': missing, '缺失比例%': missing_pct}))

跑完你会看到 Cabin 缺失接近 77%,Age 约 20%,Embarked 只有 2 个。策略上我一般这么分:

  • Embarked:缺失极少,用众数填充即可,train['Embarked'].fillna(train['Embarked'].mode()[0], inplace=True)。
  • Age:缺失 20%,直接填均值会抹平分布。常见做法是按 Pclass 和 Sex 分组后取组内中位数填充,保留不同舱位、性别的年龄差异。
  • Cabin:缺失太多,硬填没意义。但 Cabin 的首字母代表甲板区域,可能和生存率相关,所以提取首字母作为一个新特征,再把原列丢掉。
# Age 按舱位和性别分组填充中位数 train['Age'] = train.groupby(['Pclass', 'Sex'])['Age'].transform( lambda x: x.fillna(x.median()) ) # Cabin 提取首字母,缺失的标记为 'U'(Unknown) train['Deck'] = train['Cabin'].str[0].fillna('U') train.drop('Cabin', axis=1, inplace=True)

transform和apply的区别这里值得记一下:transform返回和原列等长的结果,能直接赋值回去;apply在分组场景下返回的是聚合后的结构,赋值容易出形状不匹配的错。这个坑我踩过,报错信息还特别不直观。

3.2 类别变量编码与衍生特征

Sex、Embarked、Deck 都是字符串,模型读不了。最直接的是用pd.get_dummies做独热编码:

# 对类别列做独热编码,drop_first 避免共线性 df = pd.get_dummies(train, columns=['Sex', 'Embarked', 'Deck'], drop_first=True)

drop_first=True的作用是去掉每个类别组的第一列,避免虚拟变量陷阱(多个列线性相关导致模型系数不稳定)。对于逻辑回归这类对共线性敏感的模型,这个参数建议开着;树模型其实无所谓,但统一处理省心。

除了编码,还可以从现有字段里挖衍生特征。比如 Name 列里藏着称呼(Mr、Mrs、Miss、Master),不同称呼的生存率差异很大,尤其是 Master(小男孩)生存率明显偏高:

# 从姓名中提取称呼 df['Title'] = df['Name'].str.extract(r' ([A-Za-z]+)\.', expand=False) # 把低频称呼归为 Rare title_counts = df['Title'].value_counts() rare_titles = title_counts[title_counts < 10].index df['Title'] = df['Title'].replace(rare_titles, 'Rare') df = pd.get_dummies(df, columns=['Title'], drop_first=True)

正则r' ([A-Za-z]+)\.'的意思是:匹配一个空格,后面跟一串字母,再跟一个点。这样能从 "Braund, Mr. Owen Harris" 里抓出 "Mr"。低频称呼合并成 Rare 是为了防止某个称呼只有一两个样本,模型学不到规律反而过拟合。

3.3 特征缩放与训练集划分

如果后面用逻辑回归或 SVM,数值特征需要标准化;用随机森林或 XGBoost 则可以跳过。常见做法是先把无关列丢掉,再划分:

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 丢掉对预测无用的列 drop_cols = ['PassengerId', 'Name', 'Ticket', 'Survived'] X = df.drop(drop_cols, axis=1) y = df['Survived'] # 划分训练集和验证集,stratify 保证标签比例一致 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 标准化 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_val_scaled = scaler.transform(X_val)

stratify=y这个参数很关键。Titanic 的生存率大约 38%,如果不分层,随机划分可能让验证集里生存比例偏得离谱,评估结果就不可信了。random_state=42是固定随机种子,保证每次划分结果一致,方便复现和对比。注意fit_transform只在训练集上用,验证集用transform,否则会引入验证集的信息,造成数据泄露。

4. 模型训练与结果输出:从基线到可提交文件

4.1 选哪个模型:先逻辑回归再树模型

很多人一上来就上 XGBoost,结果调参调到怀疑人生。我的习惯是先跑逻辑回归建立基线,再换随机森林看提升空间,最后才考虑梯度提升。逻辑回归在这个数据集上通常能到 0.78 到 0.80 的准确率,随机森林能到 0.80 到 0.83,差距没有想象中大。

from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report # 逻辑回归基线 lr = LogisticRegression(max_iter=1000, random_state=42) lr.fit(X_train_scaled, y_train) lr_pred = lr.predict(X_val_scaled) print('逻辑回归准确率:', accuracy_score(y_val, lr_pred)) # 随机森林 rf = RandomForestClassifier( n_estimators=200, # 树的数量 max_depth=6, # 最大深度,防止过拟合 min_samples_leaf=3, # 叶节点最少样本数 random_state=42 ) rf.fit(X_train, y_train) rf_pred = rf.predict(X_val) print('随机森林准确率:', accuracy_score(y_val, rf_pred))

max_iter=1000是因为逻辑回归默认迭代次数可能不够,数据标准化后仍不收敛时会报警告,调大就行。随机森林的max_depth和min_samples_leaf是两个主要防过拟合的旋钮:深度越大越容易记住训练集噪声,叶节点样本太少则每个叶子学到的是个别样本而非规律。我一般从max_depth=5到8之间试,min_samples_leaf从 1 试到 5。

4.2 交叉验证看稳定性

单次划分的准确率有偶然性,换一个random_state可能差两三个百分点。用交叉验证看均值更靠谱:

from sklearn.model_selection import cross_val_score # 5 折交叉验证 cv_scores = cross_val_score(rf, X, y, cv=5, scoring='accuracy') print('各折准确率:', cv_scores) print('平均准确率: %.4f (+/- %.4f)' % (cv_scores.mean(), cv_scores.std()))

cv=5表示把数据分成 5 份,轮流用 4 份训练、1 份验证。输出里的标准差反映模型稳定性,标准差超过 0.03 就说明模型对数据划分比较敏感,可能需要简化模型或增加数据。这一步比单次验证费时间,但能避免被一次好运的结果骗到。

4.3 生成提交文件

确认模型后,用同样的流程处理测试集,生成提交文件:

# 读取测试集,做和训练集完全一致的处理 test = pd.read_csv('test.csv') test['Age'] = test.groupby(['Pclass', 'Sex'])['Age'].transform( lambda x: x.fillna(x.median()) ) test['Fare'].fillna(test['Fare'].median(), inplace=True) # 测试集 Fare 有 1 个缺失 test['Deck'] = test['Cabin'].str[0].fillna('U') test.drop('Cabin', axis=1, inplace=True) test['Title'] = test['Name'].str.extract(r' ([A-Za-z]+)\.', expand=False) test['Title'] = test['Title'].replace(rare_titles, 'Rare') test = pd.get_dummies(test, columns=['Sex', 'Embarked', 'Deck', 'Title'], drop_first=True) # 对齐训练集和测试集的列,缺失的列补 0 test = test.reindex(columns=X.columns, fill_value=0) # 预测并输出 predictions = rf.predict(test) submission = pd.DataFrame({ 'PassengerId': pd.read_csv('test.csv')['PassengerId'], 'Survived': predictions }) submission.to_csv('submission.csv', index=False)

这里有个必须注意的点:测试集处理完独热编码后,列的顺序和数量可能和训练集不一致(比如测试集里某个 Deck 类别没出现)。reindex(columns=X.columns, fill_value=0)就是用来对齐列的,缺的补 0,多的丢掉。不做这一步,predict会直接报特征数量不匹配。index=False是写 CSV 时不加行号,Kaggle 提交要求格式干净。

5. 避坑与排查:那些让脚本跑不起来的细节

5.1 报错 FileNotFoundError

现象:运行脚本立刻报找不到train.csv。原因:工作目录不是脚本所在目录,相对路径解析到了别处。解决:cd到脚本目录再执行,或者把读取路径改成绝对路径。我一般会在脚本开头加import os; os.chdir(os.path.dirname(os.path.abspath(__file__))),强制切到脚本目录。

5.2 独热编码后训练集测试集列不一致

现象:predict时报特征数量不匹配,或者结果全是一个类别。原因:测试集里某些类别在训练集没出现,get_dummies生成的列不同。解决:用reindex(columns=X.columns, fill_value=0)对齐,确保两边列完全一致。这个坑在真实业务数据里更常见,因为线上数据分布和训练数据往往有差异。

5.3 准确率虚高但提交分数很低

现象:本地验证准确率 0.85,提交到平台只有 0.75。原因:验证集划分时用了random_state但没做分层,或者特征工程里用到了标签信息造成泄露。解决:检查train_test_split是否加了stratify=y,检查有没有在划分前就用全量数据算均值、中位数等统计量。正确顺序是先划分,再在训练集上算统计量,最后应用到验证集。

5.4 Age 填充后分布失真

现象:填充后 Age 的直方图出现一个尖峰。原因:用了全局均值或中位数填充,大量缺失值被填成同一个数。解决:改用分组中位数填充,或者用模型预测填充(比如用 Pclass、Sex、Fare 预测 Age)。分组填充已经能缓解大部分问题,模型填充更精细但代码复杂度高,看需求取舍。

5.5 随机森林 n_estimators 越大越好吗

现象:把n_estimators从 100 调到 1000,准确率没提升,训练时间翻倍。原因:树的数量增加到一定程度后,模型性能趋于平稳,继续加只是浪费算力。解决:画学习曲线找拐点,通常 200 到 500 之间就够。我一般先用 100 快速试,确认方向对了再调到 300 左右做最终训练。

6. 进阶技巧:用特征重要性反推业务逻辑

跑通基线之后,别急着调参。我习惯先看随机森林的feature_importances_,它能告诉你模型到底在依赖哪些特征:

import matplotlib.pyplot as plt # 获取特征重要性并排序 importances = pd.Series(rf.feature_importances_, index=X.columns) importances.sort_values(ascending=True).plot(kind='barh', figsize=(8, 6)) plt.title('Feature Importances') plt.tight_layout() plt.show()

跑完通常会看到 Sex、Fare、Age、Title 排在前列。这符合历史事实——妇女儿童优先登救生艇,头等舱乘客离甲板近、逃生机会大。如果某个你精心构造的特征重要性接近 0,说明它没提供有效信息,可以考虑删掉简化模型。

更进一步,可以用permutation_importance做更可靠的评估,它通过打乱某一列的值看模型性能下降多少来判断重要性,比基于分裂次数的feature_importances_更稳:

from sklearn.inspection import permutation_importance result = permutation_importance( rf, X_val, y_val, n_repeats=10, random_state=42, scoring='accuracy' ) perm_imp = pd.Series(result.importances_mean, index=X.columns) print(perm_imp.sort_values(ascending=False).head(10))

n_repeats=10表示每列重复打乱 10 次取平均,次数越多结果越稳但越慢。这个方法在验证集上做,反映的是特征对泛化性能的贡献,比训练集上的重要性更可信。

还有一个容易被忽略的点:阈值调整。默认分类阈值是 0.5,但 Titanic 的评估指标是准确率,调整阈值可能带来小幅提升:

# 获取正类概率 probs = rf.predict_proba(X_val)[:, 1] # 试不同阈值 for thresh in [0.4, 0.45, 0.5, 0.55, 0.6]: preds = (probs >= thresh).astype(int) acc = accuracy_score(y_val, preds) print(f'阈值 {thresh}: 准确率 {acc:.4f}')

如果 0.45 比 0.5 好,说明模型偏保守,适当降低阈值能让更多样本被判为正类。但要注意,在验证集上调阈值也有过拟合风险,最好用交叉验证确认。

从那以后我每次拿到新数据集,都强制先跑一遍isnull().sum()和feature_importances_,再动手写模型。这两个动作花不了五分钟,但能省下后面几个小时盲目调参的时间。希望这份实战包和上面的拆解,能帮你把 Titanic 这个经典项目真正跑通、吃透。

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

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

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

立即咨询