太空泰坦尼克号乘客传送预测
问题背景
本次竞赛任务是预测太空泰坦尼克号乘客是否被传送到另一个维度。数据集包含8693条训练记录和4277条测试记录,需要通过特征工程和CART决策树算法实现的预测流程。
相关数据集可从kaggle下载- 泰坦尼克号数据集
如果不知道如何下载可以跳转至--如何使用Kaggle下载数据集?
数据集说明
| 特征名 | 类型 | 说明 |
|---|---|---|
PassengerId |
字符串 | 格式为gggg_pp,gggg表示团体ID,pp表示团体中的编号 |
HomePlanet |
分类 | 乘客出发星球(Earth/Europa/Mars) |
CryoSleep |
布尔 | 是否进入冷冻睡眠 |
Cabin |
字符串 | 格式为Deck/Num/Side(甲板/舱号/舷侧) |
Destination |
分类 | 目的地星球 |
Age |
数值 | 乘客年龄 |
VIP |
布尔 | 是否为VIP乘客 |
RoomService~VRDeck |
数值 | 各设施消费金额 |
Name |
字符串 | 乘客姓名 |
Transported |
布尔 | 目标变量:是否被传送 |
代码精讲流程
环境配置与依赖导入
python
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
import warnings
warnings.filterwarnings('ignore')
| 代码行 | 功能说明 | 技术要点 |
|---|---|---|
import pandas as pd |
导入数据分析核心库 | pandas是数据处理的事实标准,提供DataFrame数据结构 |
import numpy as np |
导入数值计算库 | numpy提供高效的数组操作和数学函数 |
from sklearn.tree import DecisionTreeClassifier |
导入CART决策树分类器 | scikit-learn中决策树的核心实现,默认使用Gini指数 |
from sklearn.preprocessing import OneHotEncoder |
导入独热编码器 | 将分类变量转换为数值型,避免引入顺序关系 |
from sklearn.impute import SimpleImputer |
导入缺失值填充器 | 支持多种填充策略(均值、中位数、众数) |
from sklearn.model_selection import train_test_split, GridSearchCV |
导入模型选择工具 | train_test_split划分数据集,GridSearchCV网格搜索调参 |
from sklearn.metrics import accuracy_score |
导入准确率指标 | 分类问题最常用的评估指标 |
from sklearn.compose import ColumnTransformer |
导入列转换器 | 实现不同特征列的差异化预处理 |
from sklearn.pipeline import Pipeline |
导入管道 | 将预处理和模型串联成统一流程,防止数据泄漏 |
warnings.filterwarnings('ignore') |
忽略警告信息 | 避免不必要的警告干扰输出,建议在调试阶段关闭 |
数据加载与初步探索
python
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
print(f'训练集形状: {train_df.shape}')
print(f'测试集形状: {test_df.shape}')
train_df.head()
| 代码行 | 功能说明 | 技术要点 |
|---|---|---|
train_df = pd.read_csv('train.csv') |
读取训练集数据 | read_csv是pandas读取CSV文件的标准方法,自动解析表头 |
test_df = pd.read_csv('test.csv') |
读取测试集数据 | 测试集比训练集少一列Transported(目标变量) |
print(f'训练集形状: {train_df.shape}') |
输出训练集维度 | f-string格式化输出,shape返回(行数, 列数)元组 |
print(f'测试集形状: {test_df.shape}') |
输出测试集维度 | 训练集(8693, 14),测试集(4277, 13) |
train_df.head() |
显示前5行数据 | 快速了解数据结构、特征类型和取值范围 |
特征工程(核心环节)
python
def feature_engineering(df):
df = df.copy()
df['GroupId'] = df['PassengerId'].str.split('_').str[0].astype(int)
df['GroupSize'] = df.groupby('GroupId')['PassengerId'].transform('count')
cabin_split = df['Cabin'].str.split('/', expand=True)
df['Deck'] = cabin_split[0]
df['CabinNum'] = cabin_split[1].astype(float)
df['Side'] = cabin_split[2]
spend_cols = ['RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck']
df['TotalSpend'] = df[spend_cols].sum(axis=1)
df['HasSpend'] = (df['TotalSpend'] > 0).astype(int)
df['AgeGroup'] = pd.cut(df['Age'], bins=[0, 12, 18, 35, 50, 100],
labels=['Child', 'Teen', 'Adult', 'Middle', 'Senior'])
return df
train_df = feature_engineering(train_df)
test_df = feature_engineering(test_df)
train_df.head()
函数定义与数据拷贝
python
def feature_engineering(df):
df = df.copy()
def feature_engineering(df)::定义特征工程函数,接收DataFrame作为输入df = df.copy():创建数据副本,避免原地修改原始数据。这是一个重要的安全习惯,防止后续操作污染原始数据集
特征提取
python
df['GroupId'] = df['PassengerId'].str.split('_').str[0].astype(int)
df['GroupSize'] = df.groupby('GroupId')['PassengerId'].transform('count')
| 代码行 | 功能说明 | 技术要点 |
|---|---|---|
df['PassengerId'].str.split('_') |
按下划线分割字符串 | 返回Series对象,每个元素是分割后的列表 |
.str[0] |
提取第一个元素 | 获取团体ID部分(如0001_01→0001) |
.astype(int) |
转换为整数类型 | 字符串ID转换为数值型便于后续计算 |
df.groupby('GroupId') |
按团体分组 | groupby是pandas强大的分组聚合工具 |
.transform('count') |
计算每组大小 | transform保持原始数据形状,将聚合结果映射回每一行 |
为什么要提取团体特征?
团体中的乘客通常是家人或同行者,他们被传送的概率可能存在关联。团体大小反映了社交关系的紧密程度。
舱位信息拆分
python
cabin_split = df['Cabin'].str.split('/', expand=True)
df['Deck'] = cabin_split[0]
df['CabinNum'] = cabin_split[1].astype(float)
df['Side'] = cabin_split[2]
| 代码行 | 功能说明 | 技术要点 |
|---|---|---|
.str.split('/', expand=True) |
按斜杠分割并展开为多列 | expand=True将结果展开为DataFrame(3列) |
cabin_split[0] |
提取甲板信息 | 如B/0/P→B,甲板位置可能影响传送概率 |
cabin_split[1].astype(float) |
提取舱号并转为浮点 | 舱号是数值型特征,浮点类型支持缺失值(NaN) |
cabin_split[2] |
提取舷侧信息 | P(左舷)或S(右舷),可能与事故位置相关 |
注意事项
Cabin字段可能存在缺失值,str.split处理缺失值时会返回NaN,后续需要通过SimpleImputer处理。
消费特征构造
python
spend_cols = ['RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck']
df['TotalSpend'] = df[spend_cols].sum(axis=1)
df['HasSpend'] = (df['TotalSpend'] > 0).astype(int)
| 代码行 | 功能说明 | 技术要点 |
|---|---|---|
spend_cols = [...] |
定义消费相关列名列表 | 5个消费特征:客房服务、美食广场、购物中心、水疗、VR体验 |
.sum(axis=1) |
按行求和 | axis=1表示横向求和(跨行),计算每位乘客的总消费 |
(df['TotalSpend'] > 0) |
生成布尔掩码 | 判断是否有消费行为 |
.astype(int) |
转换为整数 | True→1,False→0,便于模型处理 |
年龄分组
python
df['AgeGroup'] = pd.cut(df['Age'], bins=[0, 12, 18, 35, 50, 100],
labels=['Child', 'Teen', 'Adult', 'Middle', 'Senior'])
| 参数 | 说明 |
|---|---|
bins=[0, 12, 18, 35, 50, 100] |
年龄区间划分:0-12儿童、12-18青少年、18-35成年、35-50中年、50+老年 |
labels=[...] |
对应区间的标签名称 |
为什么要对年龄分组?
年龄与传送概率可能是非线性关系。直接使用原始年龄值,模型需要学习复杂的非线性模式;分组后将连续值离散化,更容易捕捉年龄带来的差异。
函数调用
python
train_df = feature_engineering(train_df)
test_df = feature_engineering(test_df)
train_df.head()
- 训练集和测试集使用相同的特征工程函数:确保特征处理逻辑一致,避免数据泄漏
train_df.head():查看特征工程后的数据集,确认新特征已正确添加(从14列扩展到22列)
数据预处理与数据集划分
python
numerical_cols = ['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck',
'TotalSpend', 'HasSpend', 'CabinNum', 'GroupSize']
categorical_cols = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Deck', 'Side', 'AgeGroup']
numerical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median'))
])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(drop='first', sparse=False))
])
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_cols),
('cat', categorical_transformer, categorical_cols)
])
X = train_df.drop(['PassengerId', 'Name', 'Cabin', 'Transported'], axis=1)
y = train_df['Transported'].astype(int)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
特征分类
python
numerical_cols = ['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck',
'TotalSpend', 'HasSpend', 'CabinNum', 'GroupSize']
categorical_cols = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Deck', 'Side', 'AgeGroup']
- 数值特征 :包含原始数值列和新构造的
TotalSpend、HasSpend、CabinNum、GroupSize - 分类特征 :包含原始分类列和新构造的
Deck、Side、AgeGroup
HasSpend特征的作用
HasSpend是一个0/1二元特征,表示乘客是否有消费行为。虽然TotalSpend已经包含了消费金额信息,但HasSpend能够明确区分"无消费"和"有消费"两类人群,提供额外的分类信号,帮助模型捕捉消费行为的有无对传送概率的影响。
数值特征预处理
python
numerical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median'))
])
| 参数 | 说明 |
|---|---|
steps=[...] |
定义预处理步骤列表,按顺序执行 |
('imputer', ...) |
步骤名称为imputer,便于后续引用 |
SimpleImputer(strategy='median') |
使用中位数填充缺失值 |
为什么选择中位数?
数值特征可能存在异常值(如消费金额的极端值),中位数比均值更具鲁棒性,不受极端值影响。
分类特征预处理
python
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(drop='first', sparse=False))
])
| 参数 | 说明 |
|---|---|
SimpleImputer(strategy='most_frequent') |
使用众数填充缺失值,适合分类特征 |
OneHotEncoder(drop='first') |
独热编码,drop='first'避免虚拟变量陷阱 |
sparse=False |
返回密集数组而非稀疏矩阵 |
列转换器
python
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_cols),
('cat', categorical_transformer, categorical_cols)
])
| 参数 | 说明 |
|---|---|
transformers=[...] |
定义多个转换器,每个转换器处理不同的特征列 |
('num', numerical_transformer, numerical_cols) |
名称为num,使用numerical_transformer处理numerical_cols |
('cat', categorical_transformer, categorical_cols) |
名称为cat,使用categorical_transformer处理categorical_cols |
ColumnTransformer的优势
可以为不同类型的特征列指定不同的预处理逻辑,避免手动拆分和合并数据,代码更简洁且不易出错。
特征矩阵与目标变量
python
X = train_df.drop(['PassengerId', 'Name', 'Cabin', 'Transported'], axis=1)
y = train_df['Transported'].astype(int)
| 代码行 | 功能说明 |
|---|---|
train_df.drop(['PassengerId', 'Name', 'Cabin'], axis=1) |
删除无预测价值的列:ID、姓名、原始舱位字符串 |
y = train_df['Transported'].astype(int) |
将布尔目标变量转换为整数(True→1,False→0) |
数据集划分
python
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
| 参数 | 说明 |
|---|---|
test_size=0.2 |
验证集占比20%,训练集占比80% |
random_state=42 |
设置随机种子,保证实验可复现 |
为什么需要验证集?
验证集用于评估模型在未见过的数据上的泛化能力,帮助检测过拟合现象。
模型构建与参数调优
python
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', DecisionTreeClassifier(criterion='gini', random_state=42))
])
param_grid = {
'classifier__max_depth': [5, 6, 7, 8, 9, 10, 11, 12],
'classifier__min_samples_split': [2, 3, 4, 5],
'classifier__min_samples_leaf': [1, 2, 3]
}
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=1)
grid_search.fit(X_train, y_train)
print(f'最佳参数: {grid_search.best_params_}')
print(f'最佳交叉验证准确率: {grid_search.best_score_:.4f}')
完整管道构建
python
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', DecisionTreeClassifier(criterion='gini', random_state=42))
])
| 参数 | 说明 |
|---|---|
('preprocessor', preprocessor) |
第一步:使用预定义的预处理管道 |
('classifier', DecisionTreeClassifier(...)) |
第二步:使用CART决策树分类器 |
criterion='gini' |
使用Gini指数作为分裂准则,这是CART算法的标准配置 |
random_state=42 |
设置随机种子,保证决策树结构可复现 |
Pipeline的优势
- 将预处理和模型训练合并为一个步骤,避免手动处理数据
- 在交叉验证时自动处理数据泄漏问题(每个fold使用独立的预处理)
参数网格定义
python
param_grid = {
'classifier__max_depth': [5, 6, 7, 8, 9, 10, 11, 12],
'classifier__min_samples_split': [2, 3, 4, 5],
'classifier__min_samples_leaf': [1, 2, 3]
}
| 参数 | 说明 | 调优范围 |
|---|---|---|
classifier__max_depth |
决策树最大深度 | 5-12,防止过拟合 |
classifier__min_samples_split |
节点分裂所需的最小样本数 | 2-5,控制分裂粒度 |
classifier__min_samples_leaf |
叶节点所需的最小样本数 | 1-3,防止生成过于复杂的树 |
参数命名规则
classifier__max_depth表示管道中classifier步骤的max_depth参数,双下划线用于嵌套访问。
网格搜索
python
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=1)
grid_search.fit(X_train, y_train)
| 参数 | 说明 |
|---|---|
cv=5 |
5折交叉验证,评估模型稳定性 |
scoring='accuracy' |
使用准确率作为评估指标 |
n_jobs=1 |
单进程运行 |
Windows多进程问题
在Windows环境下,n_jobs=-1(使用所有CPU核心)可能导致AttributeError: module '_winapi' has no attribute 'SYNCHRONIZE'错误。解决方案是将n_jobs设置为1,使用单进程运行。
模型验证
python
best_model = grid_search.best_estimator_
y_pred_val = best_model.predict(X_val)
val_accuracy = accuracy_score(y_val, y_pred_val)
print(f'验证集准确率: {val_accuracy:.4f}')
| 代码行 | 功能说明 |
|---|---|
grid_search.best_estimator_ |
获取网格搜索找到的最佳模型(包含完整的预处理管道) |
best_model.predict(X_val) |
对验证集进行预测,返回预测结果数组 |
accuracy_score(y_val, y_pred_val) |
计算验证集准确率,评估模型泛化能力 |
完整代码汇总
python
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
import warnings
warnings.filterwarnings('ignore')
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
def feature_engineering(df):
df = df.copy()
df['GroupId'] = df['PassengerId'].str.split('_').str[0].astype(int)
df['GroupSize'] = df.groupby('GroupId')['PassengerId'].transform('count')
cabin_split = df['Cabin'].str.split('/', expand=True)
df['Deck'] = cabin_split[0]
df['CabinNum'] = cabin_split[1].astype(float)
df['Side'] = cabin_split[2]
spend_cols = ['RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck']
df['TotalSpend'] = df[spend_cols].sum(axis=1)
df['HasSpend'] = (df['TotalSpend'] > 0).astype(int)
df['AgeGroup'] = pd.cut(df['Age'], bins=[0, 12, 18, 35, 50, 100],
labels=['Child', 'Teen', 'Adult', 'Middle', 'Senior'])
return df
train_df = feature_engineering(train_df)
test_df = feature_engineering(test_df)
numerical_cols = ['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck',
'TotalSpend', 'HasSpend', 'CabinNum', 'GroupSize']
categorical_cols = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Deck', 'Side', 'AgeGroup']
numerical_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='median'))])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(drop='first', sparse=False))
])
preprocessor = ColumnTransformer(transformers=[
('num', numerical_transformer, numerical_cols),
('cat', categorical_transformer, categorical_cols)
])
X = train_df.drop(['PassengerId', 'Name', 'Cabin', 'Transported'], axis=1)
y = train_df['Transported'].astype(int)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', DecisionTreeClassifier(criterion='gini', random_state=42))
])
param_grid = {
'classifier__max_depth': [5, 6, 7, 8, 9, 10, 11, 12],
'classifier__min_samples_split': [2, 3, 4, 5],
'classifier__min_samples_leaf': [1, 2, 3]
}
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=1)
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
y_pred_val = best_model.predict(X_val)
print(f'验证集准确率: {accuracy_score(y_val, y_pred_val):.4f}')
X_test = test_df.drop(['PassengerId', 'Name', 'Cabin'], axis=1)
test_predictions = best_model.predict(X_test)
submission = pd.DataFrame({
'PassengerId': test_df['PassengerId'],
'Transported': test_predictions.astype(bool)
})
submission.to_csv('submission.csv', index=False)
print('提交文件已生成!')
结语:本文详细讲解了使用CART决策树算法解决太空泰坦尼克号乘客传送预测问题的完整流程。特征工程是提升模型性能的关键,合理的预处理管道和参数调优能够有效防止过拟合。希望这篇文章能帮助你掌握机器学习实战的核心技能!