Kaggle Titanic生存预测:从数据清洗到特征工程,一个菜鸟的完整踩坑实录
凌晨两点,我的第三杯咖啡已经见底。屏幕上的数据集像一团乱麻——这就是传说中的Kaggle入门神题Titanic生存预测?作为一个刚转行数据科学的Java程序员,我盯着train.csv里那些缺失的年龄值和意义不明的船舱编号,第一次体会到什么叫"从入门到放弃"的真实含义。
1. 数据观察:当理想照进现实
打开训练集的第一眼,我就被现实狠狠教育了。教科书里整洁的Iris数据集根本不存在于真实世界,这里只有:
import pandas as pd train = pd.read_csv('train.csv') print(train.isnull().sum()) # 输出结果: # Age 177 # Cabin 687 # Embarked 2三个致命发现:
- 年龄缺失近20%,船舱号缺失77%——这数据能要?
- 票价(Fare)的分布跨度从0到512美元,标准差是49.7
- 姓名列里居然藏着贵族头衔(Mr/Miss/Dr等)
提示:真实项目的数据清洗会占用70%时间,这个认知让我放弃了"一夜搞定"的幻想
2. 数据清洗:与缺失值的斗智斗勇
2.1 船舱数据的"摆烂式"处理
面对687个缺失的Cabin值,我尝试了三种策略:
| 处理方式 | 代码实现 | 适用场景 |
|---|---|---|
| 直接填充'U' | df['Cabin'] = df['Cabin'].fillna('U') | 低重要性特征 |
| 提取首字母 | df['Deck'] = df['Cabin'].str[0] | 有规律的结构化缺失 |
| 完全丢弃 | df.drop('Cabin', axis=1) | 缺失率>50%且无分析价值 |
最终选择方案一,因为后续发现船舱甲板位置(Deck)与生存率存在关联:
sns.barplot(x='Deck', y='Survived', data=train[train['Deck']!='U'])2.2 年龄预测的随机森林魔法
年龄缺失的处理让我第一次体会到特征工程的魅力。通过构建包含以下特征的临时数据集:
- Pclass (船舱等级)
- Title (从姓名提取的头衔)
- FamilySize (家庭成员数)
用随机森林预测缺失年龄:
from sklearn.ensemble import RandomForestRegressor # 构建训练集 age_train = df[df['Age'].notnull()] age_test = df[df['Age'].isnull()] # 训练预测模型 rfr = RandomForestRegressor(n_estimators=200) rfr.fit(age_train[features], age_train['Age']) # 填充预测值 df.loc[df['Age'].isnull(), 'Age'] = rfr.predict(age_test[features])这个操作让我的模型准确率直接提升了3个百分点——原来这就是"用数据创造数据"的奥义。
3. 特征工程:从原始数据中"榨取"价值
3.1 姓名中的隐藏金矿
原始Name列看起来毫无用处,直到我发现可以用正则表达式提取头衔:
df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)统计后惊讶发现:
- 头衔与生存率强相关(女性头衔生存率72%,男性仅18%)
- 罕见头衔如'Lady'生存率100%
于是进行了头衔归类:
title_mapping = { 'Mr': 'AdultMale', 'Mrs': 'AdultFemale', 'Miss': 'YoungFemale', 'Master': 'Boy', 'Dr': 'Professional', 'Rev': 'Professional', 'Col': 'Military' }3.2 家庭关系的生存密码
将SibSp(兄弟姐妹/配偶)和Parch(父母/子女)组合成新特征:
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1 df['IsAlone'] = (df['FamilySize'] == 1).astype(int)可视化结果令人震惊——独身旅客生存率仅30%,而2-4人家庭生存率超过50%:
4. 模型构建:从盲目调包到有的放矢
4.1 算法选型踩坑记
第一次尝试时,我直接照搬教程里的随机森林:
from sklearn.ensemble import RandomForestClassifier rfc = RandomForestClassifier() rfc.fit(X_train, y_train)结果Kaggle得分只有0.765——比基准线还低。通过交叉验证对比才发现梯度提升树(GradientBoosting)更适合这个数据集:
| 算法 | 交叉验证准确率 | 训练时间(s) |
|---|---|---|
| 随机森林 | 0.812 | 3.2 |
| 逻辑回归 | 0.798 | 1.1 |
| 梯度提升树 | 0.832 | 4.7 |
| SVM | 0.803 | 8.3 |
4.2 同组效应:数据中的"潜规则"
分析中发现一个有趣现象:某些家族的生存呈现"全活"或"全灭"模式。于是增加特征:
# 计算每个姓氏的平均生存率 surname_survival = df.groupby('Surname')['Survived'].mean() # 标记特殊家族 df['FamilySurvival'] = df['Surname'].map(surname_survival) df['FamilySurvival'] = df['FamilySurvival'].fillna(0.5) # 默认值这个操作让我的最终排名进入了前15%——原来历史数据中真的藏着"妇女儿童优先"的执行痕迹。
5. 经验总结:新手避坑指南
- 不要急于建模:我前三次提交失败都是因为没处理好Embarked缺失值
- 可视化是神器:
seaborn.pairplot()帮我发现了Fare的异常值 - 业务理解决定上限:知道"头等舱优先救援"的历史事实后,Pclass特征的重要性突然明朗
凌晨四点,当我第17次提交后看到"Your score: 0.843"时,终于理解了为什么说Titanic是机器学习界的"Hello World"——它用最温柔的方式教会我:数据科学不是写代码,而是用数据讲故事。