太空泰坦尼克号乘客传送预测

太空泰坦尼克号乘客传送预测

问题背景

本次竞赛任务是预测太空泰坦尼克号乘客是否被传送到另一个维度。数据集包含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_010001
.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/PB,甲板位置可能影响传送概率
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']
  • 数值特征 :包含原始数值列和新构造的TotalSpendHasSpendCabinNumGroupSize
  • 分类特征 :包含原始分类列和新构造的DeckSideAgeGroup

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决策树算法解决太空泰坦尼克号乘客传送预测问题的完整流程。特征工程是提升模型性能的关键,合理的预处理管道和参数调优能够有效防止过拟合。希望这篇文章能帮助你掌握机器学习实战的核心技能!