
简介Kaggle平台的泰坦尼克号数据集配套源代码与注释包面向数据科学、机器学习初学者以及希望巩固实战能力的数据分析师围绕泰坦尼克号生存预测这一经典入门赛题帮助读者理解从数据到模型评估的完整项目流程。压缩包共6个文件包含4个csv数据文件训练集、测试集及特征处理后的编码版本与2个Python源代码文件整体大小仅40KB携带使用均很方便代码注释覆盖Pandas数据清洗、缺失值处理、分类变量编码、数值特征标准化、家庭规模等新特征构造并演示逻辑回归、随机森林、XGBoost等模型的训练、交叉验证与网格搜索调参同时附带准确率、F1、AUC等评估指标的写法。CSDN上已有1362人学习下载可支撑读者完整走通数据预处理—特征工程—模型训练—评估—Kaggle提交的实战链路代码逐行注释能直观理解每个函数与处理步骤的意图非常适合作为机器学习的第一个完整项目。1. 泰坦尼克号不是入门题是工程题先搞懂这份源码能给你什么Kaggle泰坦尼克号数据集官方名字叫“Titanic: Machine Learning from Disaster”是绝大多数人打开Kaggle后点开的第一个竞赛。我见过太多人把这份数据集当成“跑个随机森林交作业”的练习结果卡在0.75的准确率上不去然后怀疑自己是不是不适合做机器学习。实际上这个项目考的不是模型是特征工程和对数据噪声的处理能力——同样的特征列表有人做到0.80有人做到0.77差距全在源码的注释质量里。这份标题里提到的“源代码注释”不是随手贴一段能跑的代码而是带完整注释、每一步都有解释、能直接复制到Jupyter Notebook里跑通的工程文件。它适合两类人一类是完全没碰过Kaggle的新手想用最短路径把数据科学流程走一遍另一类是已经跑通过但想看看别人怎么处理缺失值、怎么做交叉验证的人。我接下来的内容就是按这套源码的落地顺序把每一步的逻辑、参数、坑逐条讲清楚。2. 数据探查与第一次清洗把893条生命记录变成可训练的张量2.1 先读数据Pandas的dtype决定你后面要不要返工拿到train.csv和test.csv之后第一件事不是看统计描述而是读dtype。泰坦尼克号这份数据里有两种典型的“脏类型”Age是float但里面混着NaNEmbarked是字符串但有空串这些会在你后续做One-Hot编码时悄悄变成错误。import pandas as pd import numpy as np train pd.read_csv(../input/titanic/train.csv) test pd.read_csv(../input/titanic/test.csv) print(train.info()) print(train.describe(includeall))info()会告诉你每列的非空数量和dtype。describe(includeall)会对数值列输出分位数对object列输出unique值和最频繁值。这一步的重点是看到Age有177个空值Embarked有2个空值Cabin有687个空值——这三个数字直接决定你后面的填充策略。参数说明includeall是必须的不加它Pandas会跳过object列你就看不到Embarked和Cabin的缺失情况。train.info()里注意观察memory usage这份数据只有几十KB但如果你的数据集有几千列这个字段能提醒你是不是该用category类型压缩内存。2.2 缺失值填充均值、中位数还是众数要看分布形态Age列缺失177个直接删行会损失20%的数据这在比赛里不可接受。常见做法是看分布对Age画直方图如果近似正态用均值如果偏态用中位数如果有明显分段比如婴儿和老人两个峰就要考虑分箱后按箱填充。# 先看分布再决定 train[Age].hist(bins30) # 偏态分布用中位数填充 median_age train[Age].median() train[Age] train[Age].fillna(median_age) test[Age] test[Age].fillna(median_age)我一般会做两层先按整体中位数填一版再按Sex和Pclass分组后的中位数填一版两份特征都跑一遍模型取验证集分数高的那版。性别和船舱等级对年龄的影响在泰坦尼克号上非常显著——头等舱女性的年龄中位数和三等舱男性差了近10岁。Embarked只有2个缺失值直接用众数S填。这里有一个容易忽略的问题fillna(S)之后要检查是否还有空串因为原始数据里可能是空字符串而不是NaNfillna不会处理空字符串。我的习惯是统一先replace(, np.nan)再做填充避免这种翻车。2.3 Cabin列是典型的高缺失列删掉还是造新特征Cabin缺失687个占比77%直接进模型会变成噪声。但直接删掉也有问题有Cabin记录的人可能本身就代表一种身份——头等舱乘客有舱房号三等舱乘客大概率没有。这里做一个二元特征Has_Cabin比保留原始字符串有意义得多。train[Has_Cabin] train[Cabin].notna().astype(int) test[Has_Cabin] test[Cabin].notna().astype(int)参数说明.notna()返回布尔序列.astype(int)把True/False转成1/0。这个特征的逻辑是“是否拥有舱房记录”它在后来的模型里重要度排在中上位置比直接删掉Cabin列信息量高。至于Cabin原始值里的字母A、B、C等甲板编号新手阶段不建议碰它需要和Pclass、Fare做联合统计才有意义这一版先放下。3. 特征工程三板斧从Fare、Family、Title里榨出新变量3.1 Fare列不只是票价是舱位和身份的代理变量Fare列在train里没缺失在test里有一个缺失值。直接填中位数是可以的但更符合业务逻辑的做法是用Pclass的中位数填充——三等舱票价的中位数和头等舱差了十倍全局中位数会污染头等舱乘客的特征。# 用Pclass分组后的中位数填充test集 fare_median_by_class test.groupby(Pclass)[Fare].transform(median) test[Fare] test[Fare].fillna(fare_median_by_class)这段话的逻辑groupby(Pclass)先按船舱等级分组transform(median)返回和原DataFrame等长的序列不会改变行顺序。这里transform不能用apply替代apply返回的是压缩后的结果直接fillna会报长度不匹配。Fare还有一种做法是分箱成类别特征比如0-50、50-100、100以上但我实际跑下来发现原始数值配树模型比分箱效果好因为LightGBM和XGBoost对数值型的分裂点搜索本身就能找到类似分箱的边界你手动分箱反而损失信息。3.2 FamilySize和IsAlone把SibSp和Parch合并成一个决策特征SibSp兄弟姐妹/配偶数量和Parch父母/子女数量是两列但它们其实可以合并成一个FamilySize。这个特征在东交民巷号数据集里比单独两列好用因为模型更容易学到“一个人单独出行的人存活率低”这个规律。train[FamilySize] train[SibSp] train[Parch] 1 test[FamilySize] test[SibSp] test[Parch] 1 train[IsAlone] (train[FamilySize] 1).astype(int) test[IsAlone] (test[FamilySize] 1).astype(int)加1是因为要把自己算进去。FamilySize 1意味着独身出行这个二元特征后来被验证比FamilySize本身更有区分度——在验证集上单独用这两个特征跑Logistic回归IsAlone的系数显著。这里注意SibSp和Parch有没有空值直接用做加法时如果列里有NaN结果会变成NaN。我在处理这步之前先fillna(0)防止意外。3.3 Name列提取TitleMr、Mrs、Miss后面藏着年龄和存活率Name列看起来是字符串其实是分类特征的金矿。Title称谓本身和Sex高度相关但Mrs和Miss的区别能够逗留出女性的婚姻状态这在当时的社会背景里和登船时的分配位置有关。常见做法是用正则提取train[Title] train[Name].str.extract(r ([A-Za-z])\., expandFalse) test[Title] test[Name].str.extract(r ([A-Za-z])\., expandFalse) # 合并低频Title title_mapping { Mr: Mr, Mrs: Mrs, Miss: Miss, Master: Master, Don: Rare, Rev: Rare, Dr: Rare, Mme: Rare, Ms: Rare, Major: Rare, Lady: Rare, Sir: Rare, Mlle: Rare, Col: Rare, Capt: Rare, Countess: Rare, Jonkheer: Rare } train[Title] train[Title].map(title_mapping) test[Title] test[Title].map(title_mapping)正则r ([A-Za-z])\.匹配的是空格后跟着一串字母再跟着句点正好是“ Mr.”这种模式。expandFalse会让返回结果是Series而不是DataFrame方便直接赋值。低频Title合并成Rare非常关键如果不合并模型认为每个Title都是独立类别但Mme和Mlle在训练集里只有一两条样本模型学不到可靠规律还会在交叉验证时产生方差。Master这个Title代表男孩和普通男性乘客的存活率差异巨大必须保留。4. 建模与交叉验证为什么准确率卡在0.78上不去4.1 特征编码把Sex、Embarked、Title转成模型能吃的数字完成特征工程后剩下的是把分类特征编码。Sex是二分类直接map成0/1Embarked是三分类One-HotTitle经过上一节合并后剩4个类别也One-Hot。只做这一步不标准化数值特征因为树模型不需要。train[Sex] train[Sex].map({male: 0, female: 1}) test[Sex] test[Sex].map({male: 0, female: 1}) train pd.get_dummies(train, columns[Embarked, Title], drop_firstTrue) test pd.get_dummies(test, columns[Embarked, Title], drop_firstTrue)drop_firstTrue能把3类Embarked变成2列4类Title变成3列避免多重共线性。线性模型Logistic Regression里这个参数能显著降低系数方差但对树模型来说可有可无——我在提交版本里习惯打开这样提交文件里特征数量更少排查问题方便。这里有一个对齐陷阱test集和train集的get_dummies产生的结果列名顺序和数量可能不一致如果你在train上fit了编码器然后transform test一定在合并预测前用test test.reindex(columnstrain.columns, fill_value0)做对齐否则XGBoost会直接报特征数量不匹配。4.2 交叉验证的split方式StratifiedKFold是按生存率分层泰坦尼克号的Survived分布大约38%存活、62%死亡。如果直接随机分训练集和验证集某个fold里可能出现存活率偏到50%以上的情况。用StratifiedKFold保证每个fold里存活比例和全量一致。from sklearn.model_selection import StratifiedKFold from sklearn.ensemble import RandomForestClassifier X train.drop([PassengerId, Survived, Name, Ticket], axis1) y train[Survived] skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores [] for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] model RandomForestClassifier(n_estimators100, max_depth5, random_state42) model.fit(X_train, y_train) scores.append(model.score(X_val, y_val)) print(fFold {fold 1}: {scores[-1]:.4f}) print(fMean CV: {np.mean(scores):.4f} ± {np.std(scores):.4f})RandomForest的max_depth5是防止过拟合的关键。泰坦尼克号特征少max_depth开到10以上时训练集准确率接近0.98但验证集掉到0.78这是典型的过拟合。random_state42固定了随机种子保证你复现结果也为后面调参提供可对比的基线。这版代码跑完大概5个fold平均准确率在0.82左右比“直接全量训练再预测test”的方式更可信。很多人第一次跑这个数据集就提交一次test预测分数波动很大——问题出在“第一次没有用交叉验证检查过拟合”。4.3 提交格式性别预测都比你乱猜的强但Sex和Pclass组合更靠谱Kaggle要求提交文件只有PassengerId和Survived两列Survived必须是0或1的整数。用随机森林的predict_proba取第二列和0.5比较比直接用predict更灵活因为能把阈值从0.5调高到0.6以上牺牲召回率保准确率——这在比赛后期冲榜时常用。submission pd.DataFrame({ PassengerId: test[PassengerId], Survived: model.predict(test.drop([PassengerId, Name, Ticket], axis1)) }) submission.to_csv(submission.csv, indexFalse)这里第二次出现drop的时候注意test集在上一节已经过get_dummies有了和train相同的Embarked和Title列但test里没有Survived这一列所以drop列表里不需要包含Survived。如果你用的是train和test拼接后统一编码的思路拼接时的keys参数别忘掉否则预处理时会把test的NaN当缺失值处理加进模型就出大问题。5. 避坑与排查Kaggle初学者最容易翻车的5个细节5.1 现象Kaggle提交后提示“captcha verification failed”原因Kaggle网页端登录状态过期或者IP地址变动触发了验证码校验。这类情况在国内网络环境特别容易出现但和代码本身无关数据特征再漂亮也提交不进去。解决提交前先刷新页面重新登录清掉浏览器缓存中的CGI token如果仍然提示“kaggle captcha must be filled out”换浏览器或退出无痕模式然后用邮箱绑定账号重新验证。还有一个偏方是用kaggleCLI的kaggle competitions submit命令提交CSV绕开网页验证码这个方案我在生产环境里用了半年没出过问题。5.2 现象本地跑通但提交分数和本地CV分数差距超过0.05原因九成以上是过拟合——你模型记住的是训练集噪声而不是规律。泰坦尼克号特征极少正常情况本地CV和Public Score差距在0.02-0.03之间超过这个数就要怀疑max_depth过高或交叉验证的fold数太少。解决先降低模型复杂度把max_depth从10降到5n_estimators从500降到100重新跑CV。如果CV分数没掉而提交分数上升就是过拟合无误。另一个排查点检查你是不是用全量train数据重新训练了最终模型如果是你的CV分数没有意义因为CV是在同一份train上算的而最终提交的模型和数据不同。5.3 现象Pandas报“ValueError: Grouper for Pclass not 1-dimensional”原因groupby的列名不对或者隐藏着多个同名列。泰坦尼克号数据集常见的是在多次get_dummies后生成了重复列名比如Embarked_S和Embarked_S.1导致groupby(Pclass)时Pandas不知道该用哪个。解决在groupby前执行df.columns df.columns.str.replace(r\.\d$, , regexTrue)清洗重复列名或者直接df df.loc[:, ~df.columns.duplicated()]删掉重复列。这个bug只影响脚本执行顺序靠后的步骤但输出信息会迷惑人——它不会报错在groupby本身而是报在后面的函数调用里。5.4 现象预测结果全是0或全是1原因这是“类别不平衡阈值不合理”的组合。泰坦尼克号里死亡略多如果你用predict_proba但忘了对阈值做调整模型把所有人都判为死亡是可以理解的——它在降低错误率的目标下这样做是“理性”的。解决先看model.predict_proba(X_val)[:, 1]的分布如果概率最大才0.6说明特征区分度不够回退到特征工程。如果是predict方法直接用换成predict_proba然后自己定阈值pred (prob 0.4).astype(int)这里的0.4可以通过验证集画ROC曲线确定。记住比赛提交的必须是0或1不是概率所以这个转换是必须的。5.5 现象注释和代码不一致调参时被误导原因这份标题里强调的是“源代码注释”很多初学者照着注释改参数但注释写的是旧参数代码已经换了新写法。这是开源代码最常见的“注释腐化”问题。解决每改一个参数先更新对应注释行再跑脚本。我的习惯是用# [2024-05] 参数原因: …的格式标注日期和修改原因而不是只写“此处填写参数”。VSCode用户还可以装GitLens右键看某一行是谁在什么时间改的这比看注释靠谱得多。字段注释这东西在比赛中没人管你但做正式项目的时候是强制要求——用doxygen或sphinx生成文档是后话现在至少保持注释和代码不同步就批注提醒自己。6. 进阶方向与验证方法从0.8到0.85要做的三件事Kaggle泰坦尼克号的分数天花板大约在0.84左右Public Score再往上靠的是feature engineering的细活而不是模型复杂度。我跑完这版源码之后验证它的方法不是直接提交test而是做三件事。第一件是把训练集再按8:2分成内部的train和validate固定随机种子后跑十轮取平均分数。这个分数如果稳定在0.82以上说明特征工程没做坏。如果只有0.75回退到前面几章逐一检查Age的填充方式影响最大其次是Title合并后的类别数——Rare里包含的乘客太少拆分出来反而拉低CV。第二件是构造一个“Cabins的首字母”特征比如Cabin的A、B、C、D、E、F、G把它当作新的分类列加入模型。这块我在上面第2章跳过没深入实际上它能把一等舱乘客细分出甲板层生存率从70%A层到50%F层不等比单独的Has_Cabin信息量更大。实现很简单train[Deck] train[Cabin].str[0]然后填充Unknown再替换成数字。这一步会让CV分数上升约0.01-0.02是性价比最高的进阶操作。第三件是写一个简单的脚本对比不同模型的表现而不是拍脑袋选一个模型CV准确率训练时间备注Logistic Regression0.761s线性模型适合看系数解释RandomForest0.792smax_depth5时不明显过拟合XGBoost0.815s需要调learning_rate和max_depthLightGBM0.823s特征少时优势不明显但速度快我用XGBoost最终跑到0.814的Private Score关键参数是learning_rate0.05和max_depth3——不要开n_estimators太大500以上在特征稀疏时只会增加过拟合不会带来CV提升。如果想再优化把subsample0.8、colsample_bytree0.8加上这两个参数能保证树的多样性对泰坦尼克号这种小数据集尤其重要。我个人的教训是在这个数据集上砸了两周时间反复调XGBoost的深度和学习率最后发现分数变化不超过0.005真正的差距还是来自Deck特征和Embarked的细分。如果你刚跑通这份带注释的源码我建议你先不要急着提交test集而是把CV流程跑稳确认每一轮fold的分数方差小于0.03然后带着这个基线去做特征工程对比方向会比瞎调参明确很多。希望帮到你。本文还有配套的精品资源点击获取