本文承接上一篇
1. 填补缺失值
分析 “Group”(乘客组)与 “Cabin”(舱位)之间的关系
# Joint distribution of Group and Cabin features
GCD_gb = data[data['Group_size']>1].groupby(['Group', 'Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)
GCN_gb = data[data['Group_size']>1].groupby(['Group', 'Cabin_number'])['Cabin_number'].size().unstack().fillna(0)
GCS_gb = data[data['Group_size']>1].groupby(['Group', 'Cabin_side'])['Cabin_side'].size().unstack().fillna(0)
- 只保留 Group_size > 1 的乘客,即属于某个群组(Group)的乘客。单独旅行的乘客 (Group_size == 1) 会被排除。
- .groupby([‘Group’, ‘Cabin_deck’])[‘Cabin_deck’].size() 计算每个 Group 在不同 Cabin_deck 的乘客数。
- .unstack() 将 Cabin_deck 转为列索引
GCD_unique = (GCD_gb>0).sum(axis=1)
GCN_unique = (GCN_gb>0).sum(axis=1)
GCS_unique = (GCS_gb>0).sum(axis=1)
# Countplots
fig=plt.figure(figsize=(16,4))
plt.subplot(1,3,1)
sns.countplot(x=GCD_unique)
plt.title('#Unique cabin decks per group')
plt.subplot(1,3,2)
sns.countplot(x=GCN_unique)
plt.title('#Unique cabin numbers per group')
plt.subplot(1,3,3)
sns.countplot(x=GCS_unique)
plt.title('#Unique cabin sides per group')
fig.tight_layout()
# Missing values before
CS_bef = data['Cabin_side'].isna().sum()
# Passengers with missing Cabin side and in a group with known Cabin side
GCS_index = data[data['Cabin_side'].isna()][(data[data['Cabin_side'].isna()]['Group']).isin(GCS_gb.index)].index
# Fill corresponding missing values
data.loc[GCS_index, 'Cabin_side']=data.iloc[GCS_index,:]['Group'].map(lambda x: GCS_gb.idxmax(axis=1)[x])
# Print number of missing values left
print('#Cabin_side missing values before:', CS_bef)
print('#Cabin_side missing values after:', data['Cabin_side'].isna().sum())
- data[‘Cabin_side’].isna().sum() 计算 Cabin_side 缺失的行数,存入变量 CS_bef。
- 属于某个 Group,且该 Group 其他成员的 Cabin_side 已知(即 Group 存在于 GCS_gb.index 中)。
- GCS_gb 之前计算了每个 Group 在不同 Cabin_side(左舷/右舷)的分布情况,所以这里确保该 Group 里至少有一个乘客的 Cabin_side 是已知的。
- GCS_gb.idxmax(axis=1) 返回 每个 Group 最多成员所在的 Cabin_side
- data[‘Group’].map(…) 查找该乘客所在 Group 的多数 Cabin_side,并填充到 Cabin_side 列。
#Cabin_side missing values before: 299
#Cabin_side missing values after: 162
计算姓氏 (Surname) 和舱位方向 (Cabin_side) 之间的联合分布,即 统计同姓乘客在不同舱位方向上的人数。
# Joint distribution of Surname and Cabin side
SCS_gb = data[data['Group_size']>1].groupby(['Surname', 'Cabin_side'])['Cabin_side'].size().unstack().fillna(0)
SCS_gb
- 只考虑 Group_size 大于 1 的乘客,即 那些属于一个家庭或团体的乘客。
- 计算 每个 Surname 在不同 Cabin_side 的人数。
- 将 Cabin_side(第二级索引)转换为列名
- 如果某个 Surname 只出现在 P 或 S,另一个方向可能会是 NaN。
- 用 0 填充 NaN,表示该姓氏在该 Cabin_side 没有乘客。
# Ratio of sides
SCS_gb['Ratio'] = SCS_gb['P']/(SCS_gb['P']+SCS_gb['S'])
# Histogram of ratio
plt.figure(figsize=(10,4))
sns.histplot(SCS_gb['Ratio'], kde=True, binwidth=0.05)
plt.title('Ratio of cabin side by surname')
- 该姓氏乘客在 P 舱位的比例,范围在 0 到 1 之间:
- 0:该姓氏的所有乘客 都在 S(右舷)。
- 1:该姓氏的所有乘客 都在 P(左舷)。
- kde=True:绘制核密度估计曲线(KDE),显示数据的平滑分布。
SCS_gb
# Print proportion
print('Percentage of families all on the same cabin side:', 100*np.round((SCS_gb['Ratio'].isin([0,1])).sum()/len(SCS_gb),3), '%')
Percentage of families all on the same cabin side: 76.7 %
来自同一个家庭的乘客大部分都在舱板的同一侧。
SCS_gb.drop('Ratio', axis=1, inplace=True)
# Missing values before
CS_bef = data['Cabin_side'].isna().sum()
# Passengers with missing Cabin side and in a family with known Cabin side
SCS_index = data[data['Cabin_side'].isna()][(data[data['Cabin_side'].isna()]['Surname']).isin(SCS_gb.index)].index
# Fill corresponding missing values
data.loc[SCS_index, 'Cabin_side'] = data.iloc[SCS_index, :]['Surname'].map(lambda x: SCS_gb.idxmax(axis=1)[x])
# Print number of missing values left
print('#Cabin_side missing values before:', CS_bef)
print('#Cabin_side missing values after:', data['Cabin_side'].isna().sum())
- 计算 Cabin_side 列中缺失值 (NaN) 的数量,存入 CS_bef 变量。
- 选取 Cabin_side 为空 (NaN) 的乘客。
- 获取这些乘客的姓氏 (Surname)。
- 过滤出姓氏 (Surname) 在 SCS_gb 这个数据集中 的乘客。
- SCS_gb.index 是之前计算的按姓氏 (Surname) 统计 Cabin_side 分布的表,只包含有 Cabin_side 信息的姓氏。
- 获取索引 SCS_index 处乘客的 Surname。
- SCS_gb.idxmax(axis=1) 找到 每个姓氏 (Surname) 最常见的 Cabin_side
- 通过 Surname 查询该姓氏最常见的 Cabin_side 并填充。
#Cabin_side missing values before: 162
#Cabin_side missing values after: 66
# Drop surname, we don't need it anymore
data.drop('Surname', axis=1, inplace=True)
data['Cabin_side'].value_counts()
Cabin_side
S 6504
P 6400
Name: count, dtype: int64
# Missing values before
CS_bef = data['Cabin_side'].isna().sum()
# Fill remaining missing values with outlier
data.loc[data['Cabin_side'].isna(), 'Cabin_side'] = 'Z'
# Print number of missing values left
print('#Cabin_side missing values before:', CS_bef)
print('#Cabin_side missing values after:', data['Cabin_side'].isna().sum())
- 将所有剩余的 Cabin_side 缺失值填充为 ‘Z’(表示异常值)
#Cabin_side missing values before: 66
#Cabin_side missing values after: 0
# Missing values before
CD_bef = data['Cabin_deck'].isna().sum()
# Passengers with missing cabin deck and in a group with known majority cabin deck
GCD_index = data[data['Cabin_deck'].isna()][(data[data['Cabin_deck'].isna()]['Group']).isin(GCD_gb.index)].index
# Fill corresponding missing values
data.loc[GCD_index, 'Cabin_deck'] = data.iloc[GCD_index, :]['Group'].map(lambda x: GCD_gb.idxmax(axis=1)[x])
# Print number of missing values left
print('#Cabin_deck missing values before:', CD_bef)
print('#Cabin_deck missing values after:', data['Cabin_deck'].isna().sum())
- 统计 Cabin_deck 缺失值
- data[‘Cabin_deck’].isna():找到 Cabin_deck 为空 (NaN) 的乘客。
- data[data[‘Cabin_deck’].isna()][‘Group’]:获取这些乘客的 Group(分组信息)。
- GCD_gb 是按 Group 和 Cabin_deck 计算的联合分布表,表示每个 Group 里不同 Cabin_deck 的出现次数。
- 这里检查 Group 是否在 GCD_gb 里,如果是,说明该组的 Cabin_deck 有多数信息,可以填充。
- 取出 GCD_index 对应行的 Group,即每个缺失 Cabin_deck 的乘客的组别。
- 计算每个 Group 内出现最多的 Cabin_deck(即该组的多数甲板)
- idxmax(axis=1) 找出 GCD_gb 每一行(即 Group)中数量最多的 Cabin_deck。
- .map(…):用 Group 去查找对应的多数 Cabin_deck 并填充。
#Cabin_deck missing values before: 299
#Cabin_deck missing values after: 162
按照 HomePlanet(出发星球)、Destination(目的地)、Solo(是否独自旅行)分组,然后计算 Cabin_deck 的众数(mode,即最常见的值)
# Joint distribution
data.groupby(['HomePlanet', 'Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)
data.groupby(['HomePlanet', 'Solo', 'Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)
data.groupby(['HomePlanet', 'Destination', 'Solo', 'Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)
# Missing values before
CD_bef = data['Cabin_deck'].isna().sum()
# Fill missing values using the mode
na_rows_CD = data.loc[data['Cabin_deck'].isna(), 'Cabin_deck'].index
data.loc[data['Cabin_deck'].isna(), 'Cabin_deck'] = data.groupby(
['HomePlanet', 'Destination', 'Solo'])['Cabin_deck'].transform(
lambda x: x.fillna(pd.Series.mode(x)[0]))[na_rows_CD]
# Print number of missing values left
print('#Cabin_deck missing values before:', CD_bef)
print('#Cabin_deck missing values after:', data['Cabin_deck'].isna().sum())
- 找出 Cabin_deck 为空 (NaN) 的行索引,并存入 na_rows_CD,稍后用于选择填充数据。
- 假设同一 HomePlanet 的旅客前往相同 Destination,且 Solo 状态相同,那么他们的 Cabin_deck 可能相似。
- pd.Series.mode(x) 计算该组 Cabin_deck 的众数(mode)
- [0] 取最常见的 Cabin_deck(如果有多个众数,取第一个)。
- .fillna(…) 用该众数填充该组中的 NaN。
- transform() 让填充后的数据保持原始数据的形状,不会丢失索引信息。
- [na_rows_CD] 只选取原本缺失的行进行填充,防止影响已有数据。
#Cabin_deck missing values before: 162
#Cabin_deck missing values after: 0
利用 Group 预测 Cabin_number,利用线性回归拟合
# Scatterplot
plt.figure(figsize=(10,4))
sns.scatterplot(
x=data['Cabin_number'],
y=data['Group'],
c=LabelEncoder().fit_transform(data.loc[~data['Cabin_number'].isna(), 'Cabin_deck']),
cmap='tab10'
)
plt.title('Cabin_number vs group coloured by group')
- LabelEncoder().fit_transform(…) 将 Cabin_deck 转换为数值,这样 c 参数可以使用它来区分颜色。
- 只选取 Cabin_number 非空的行,确保数据完整。
- tab10 是 matplotlib 的颜色映射表,适用于分类数据(最多支持 10 种颜色)。
- Cabin_number(舱位编号)和 Group(乘客组编号)在同一甲板(deck)上存在线性关系,因此可以使用线性回归来填补缺失的舱位编号。
cabin_deck_list
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'T']
cabin_deck_list_new = [x for x in cabin_deck_list if x != 'T']
cabin_deck_list_new
['A', 'B', 'C', 'D', 'E', 'F', 'G']
# Missing values before
CN_bef = data['Cabin_number'].isna().sum()
# Extrapolate linear relationship on a deck by deck basis
for deck in cabin_deck_list_new:
X_CN = data.loc[~(data['Cabin_number'].isna()) & (data['Cabin_deck']==deck), 'Group']
y_CN = data.loc[~(data['Cabin_number'].isna()) & (data['Cabin_deck']==deck), 'Cabin_number']
X_test_CN = data.loc[(data['Cabin_number'].isna()) & (data['Cabin_deck']==deck), 'Group']
# Linear regression
model_CN = LinearRegression()
model_CN.fit(X_CN.values.reshape(-1, 1), y_CN)
preds_CN = model_CN.predict(X_test_CN.values.reshape(-1, 1))
# Fill missing values with predictions
data.loc[(data['Cabin_number'].isna()) & (data['Cabin_deck']==deck), 'Cabin_number'] = preds_CN.astype(int)
# Print number of missing values left
print('#Cabin_number missing values before:', CN_bef)
print('#Cabin_number missing values after:', data['Cabin_number'].isna().sum())
- 遍历 cabin_deck_list_new,对 每个甲板(deck)单独拟合回归模型,确保不同甲板上的模式不会相互干扰。
- 获取已知 Cabin_number 数据(作为训练集)
- 获取缺失 Cabin_number 数据(作为测试集)
- X_test_CN:对于 Cabin_number 为空的行,我们使用 Group 作为 X,以便用模型预测对应的 Cabin_number。
- X_CN.values.reshape(-1,1):转换 X_CN 为二维数组,适应 scikit-learn 训练格式
- 用预测的 Cabin_number 填补缺失值
#Cabin_number missing values before: 299
#Cabin_number missing values after: 0
# One-hot encode cabin regions
data['Cabin_region1'] = (data['Cabin_number']<300).astype(int)
data['Cabin_region2'] = ((data['Cabin_number']>=300) & (data['Cabin_number']<600)).astype(int)
data['Cabin_region3'] = ((data['Cabin_number']>=600) & (data['Cabin_number']<900)).astype(int)
data['Cabin_region4'] = ((data['Cabin_number']>=900) & (data['Cabin_number']<1200)).astype(int)
data['Cabin_region5'] = ((data['Cabin_number']>=1200) & (data['Cabin_number']<1500)).astype(int)
data['Cabin_region6'] = ((data['Cabin_number']>=1500) & (data['Cabin_number']<1800)).astype(int)
data['Cabin_region7'] = (data['Cabin_number']>=1800).astype(int)
data.head()