Python 机器学习实战:泰坦尼克号生还者预测 (从数据探索到模型构建)
引言:挑战介绍
泰坦尼克号的沉没是历史上最著名的海难之一。除了其悲剧色彩,它还为数据科学提供了一个经典且引人入胜的入门项目。Kaggle 平台上的 “Titanic: Machine Learning from Disaster” 竞赛,要求我们利用乘客数据来预测哪些人更有可能在这场灾难中幸存。
这是一个典型的二元分类问题:目标变量
Survived
只有两个值,0(遇难)或 1(生还)。这个项目之所以经典,是因为它涵盖了数据科学项目的完整生命周期:从数据探索、清洗、特征工程到模型构建和评估。它将全面检验我们之前学到的所有技能。
数据集分为两部分:
train.csv
: 包含乘客特征和他们的生还情况(标签),用于训练我们的模型。test.csv
: 只包含乘客特征,我们需要用训练好的模型来预测这些乘客的生还情况,并生成提交文件。- 数据集下载地址
步骤一:探索性数据分析 (EDA)
EDA 的目的是深入理解数据,发现其中的模式、异常和变量间的关系,为后续的特征工程和建模提供方向。
加载与初步检查
首先,我们使用 Pandas 加载数据,并进行初步检查。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns# 加载数据
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')# 查看数据前几行
print(train_df.head())# 查看数据基本信息,特别是缺失值
print("\n训练集信息:")
train_df.info()
通过
.info()
,我们会发现Age
、Cabin
和Embarked
列存在缺失值。Cabin
列的缺失尤为严重。
可视化分析
利用 Seaborn 和 Matplotlib,我们可以直观地探索哪些因素与生还率相关。
-
性别与生还率的关系
sns.countplot(x='Survived', hue='Sex', data=train_df) plt.title('Survival Count by Sex') plt.show()
图表会清晰地显示,女性的生还率远高于男性,这符合“妇女和儿童优先”的历史背景。
-
船票等级与生还率的关系
sns.countplot(x='Survived', hue='Pclass', data=train_df) plt.title('Survival Count by Pclass') plt.show()
可以看到,一等舱(Pclass=1)乘客的生还率最高,而三等舱(Pclass=3)最低,这揭示了社会经济地位对生还的影响。
-
年龄分布与生还率
sns.histplot(data=train_df, x='Age', hue='Survived', kde=True, multiple="stack") plt.title('Age Distribution of Survivors vs. Non-survivors') plt.show()
这个图表可能显示儿童的生还率相对较高。
步骤二:数据清洗与特征工程
这是决定模型成败的关键一步。我们需要处理缺失值,并将原始数据转换为对机器学习模型有用的特征。
处理缺失值
-
Embarked
(登船港口): 只有两个缺失值,最简单的处理方法是用出现次数最多的港口(众数)来填充。# 用众数填充 most_common_port = train_df['Embarked'].mode()[0] train_df['Embarked'].fillna(most_common_port, inplace=True)
-
Cabin
(船舱号): 缺失值太多(约77%)。对于初级模型,最稳妥的做法是直接丢弃该列。train_df.drop('Cabin', axis=1, inplace=True) test_df.drop('Cabin', axis=1, inplace=True)
-
Age
(年龄): 缺失值较多,直接用全体平均值填充会引入较大偏差。一个更精细的方法是,用分组后的中位数来填充。# 使用 Pclass 和 Sex 分组后的年龄中位数来填充缺失值 for df in [train_df, test_df]:df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].transform(lambda x: x.fillna(x.median()))
特征工程 (Feature Engineering)
-
创建
FamilySize
和IsAlone
: 将SibSp
(兄弟姐妹/配偶数) 和Parch
(父母/子女数) 合并成一个更有意义的特征FamilySize
。for df in [train_df, test_df]:df['FamilySize'] = df['SibSp'] + df['Parch'] + 1df['IsAlone'] = 0df.loc[df['FamilySize'] == 1, 'IsAlone'] = 1
-
从
Name
中提取Title
: 乘客姓名中的头衔(如Mr.
,Mrs.
,Miss.
,Master.
)蕴含了丰富信息。for df in [train_df, test_df]:df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) # 将稀有头衔归为一类 for df in [train_df, test_df]: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')
转换分类特征
机器学习模型只能处理数字。我们需要将文本类别的特征转换为数值。
-
映射 (Mapping): 对于有序或二元类别,可以直接映射。
# 映射 Sex for df in [train_df, test_df]:df['Sex'] = df['Sex'].map({'female': 1, 'male': 0}).astype(int)# 映射 Title title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5} for df in [train_df, test_df]:df['Title'] = df['Title'].map(title_mapping).fillna(0)
-
独热编码 (One-Hot Encoding): 对于像
Embarked
这样的无序分类变量,使用pd.get_dummies
是更好的选择。# 对 Embarked 进行独热编码 train_df = pd.get_dummies(train_df, columns=['Embarked'], prefix='Embarked') test_df = pd.get_dummies(test_df, columns=['Embarked'], prefix='Embarked')
步骤三:模型构建与预测
数据准备就绪后,我们就可以训练模型了。
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score# 删除不再需要的列
train_df = train_df.drop(['Name', 'Ticket', 'PassengerId', 'SibSp', 'Parch'], axis=1)
test_passenger_ids = test_df['PassengerId']
test_df = test_df.drop(['Name', 'Ticket', 'PassengerId', 'SibSp', 'Parch'], axis=1)# 分离特征和标签
train_labels = train_df['Survived']
train_features = train_df.drop('Survived', axis=1)
test_features = test_df# 对齐列,确保测试集和训练集有相同的列(独热编码可能导致列不匹配)
final_train, final_test = train_features.align(test_features, join='left', axis=1, fill_value=0)# 划分训练集和验证集 (用训练数据的一部分做验证)
X_train, X_val, y_train, y_val = train_test_split(final_train, train_labels, test_size=0.2, random_state=42
)# 训练决策树模型
decision_tree = DecisionTreeClassifier(random_state=42)
decision_tree.fit(X_train, y_train)# 在验证集上评估
y_pred_val = decision_tree.predict(X_val)
print(f"Validation Accuracy: {accuracy_score(y_val, y_pred_val):.4f}")
步骤四:创建提交文件
最后,我们用全部训练数据重新训练模型,并对处理过的测试集进行预测,生成提交文件。
# 使用全部训练数据重新训练模型
decision_tree.fit(final_train, train_labels)# 对测试集进行预测
test_predictions = decision_tree.predict(final_test)# 创建提交文件
submission = pd.DataFrame({"PassengerId": test_passenger_ids,"Survived": test_predictions
})
submission.to_csv('submission.csv', index=False)
总结与解读
通过这个项目,我们实践了一个完整的数据科学流程。EDA 告诉我们性别和阶级是重要因素,特征工程则帮助我们量化了这些信息(如 Title
特征)。我们还处理了棘手的缺失值问题,并最终构建了一个能够预测生还率的模型。
这个决策树模型只是一个起点。你可以尝试 LogisticRegression
、RandomForestClassifier
等更复杂的模型,或者进行更精细的特征工程和超参数调优,以在 Kaggle 排行榜上获得更高的分数。
接下来,我们将挑战一个回归问题——预测房价,这将让我们接触到一套不同的评估指标和建模思路。