# 水印魔术命令
%load_ext watermark
%watermark -a "Lingjian" -u -d -v -p numpy,pandas,matplotlib
# 可视化显示魔术命令
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 可视化中文字体显示设置
plt.rc('font', family='SimHei', size=13)
# 可视化负号显示设置
plt.rc('axes', unicode_minus=False)
# 用 pandas 读取 csv 文件,加载到数据框(DataFrame)
raw_passengers_df = pd.read_csv('titanic-data.csv')
# 原始数据预览
raw_passengers_df.head()
# 原始数据基本信息
raw_passengers_df.info()
基本信息显示,数据集有891条记录,有PassengerId、Survived、Pclass、Name、Sex、Age等12个字段。
其中,有714条 Age 非空值记录(也就是有177条 Age 为空值的记录),Cabin 为非空值的只有少量记录;
有数据类型为 float64 的字段有2个,为 int64 的有5个,为 object 的有5个。
PassengerId:乘客编号
Survived:是否幸存 0 = 遇难,1 = 幸存
Pclass:船舱等级 1 = 头等舱,2 = 二等舱,3 = 三等舱
Name:乘客姓名
Sex:性别
Age:年龄
SibSp:登船的兄弟姐妹或配偶个数
Parch:登船的父母或小孩个数
Ticket:船票号码
Fare:船票价格
Cabin:船舱号
Embarked:登船港口
# 数据预处理用的自定义函数
# 年龄分段
def age_to_group(age):
if age >= 70:
return '70+'
elif age >= 60:
return '60+'
elif age >= 50:
return '50+'
elif age >= 40:
return '40+'
elif age >= 30:
return '30+'
elif age >= 20:
return '20+'
elif age >= 10:
return '10+'
elif age >= 0:
return '0+'
else:
return '未知'
# 年龄分段改进,对年龄缺失的部分乘客按称谓做年龄分段
def age_to_group_fix(age_name_df):
if age_name_df['Age'] >= 70:
return '70+'
elif age_name_df['Age'] >= 60:
return '60+'
elif age_name_df['Age'] >= 50:
return '50+'
elif age_name_df['Age'] >= 40:
return '40+'
elif age_name_df['Age'] >= 30:
return '30+'
elif age_name_df['Age'] >= 20:
return '20+'
elif age_name_df['Age'] >= 10:
return '10+'
elif age_name_df['Age'] >= 0:
return '0+'
elif 'Master'in age_name_df['Name']: # Master 称谓的乘客平均年龄是 4.574167
return '0+'
elif 'Miss'in age_name_df['Name']: # Miss 称谓的乘客平均年龄是 21.773973
return '20+'
else:
return '未知'
# 性别的中文名称,便于可视化展示
def sex_to_chn(sex):
if sex == 'male':
return '男性'
elif sex == 'female':
return '女性'
else:
return '未知'
# 性别的有序编码,便于计算
def sex_to_code(sex):
if sex == 'male':
return 0
elif sex == 'female':
return 1
else:
return -1
# 船舱等级的中文名称,便于可视化展示
def pclass_to_chn(pclass):
if pclass == 1:
return '头等舱'
elif pclass == 2:
return '二等舱'
elif pclass == 3:
return '三等舱'
else:
return '未知'
# 船舱等级的有序编码,便于计算
def pclass_to_code(pclass):
if pclass == 1:
return 2
elif pclass == 2:
return 1
elif pclass == 3:
return 0
else:
return -1
# 幸存与否的中文名称,便于可视化展示
def survived_to_chn(survived):
if survived == 0:
return '遇难者'
elif survived == 1:
return '幸存者'
else:
return '未知'
# 深拷贝,创建新数据框,用于后续数据预处理及数据探索分析
passengers_df = raw_passengers_df.copy()
# 为了方便后续相关指标的可视化展示或计算,在新数据框中增加下面字段
passengers_df.insert(6,'年龄段',passengers_df['Age'].apply(age_to_group))
passengers_df.insert(6,'年龄段_改进后',passengers_df[['Age', 'Name']].apply(age_to_group_fix, axis=1))
passengers_df.insert(5,'性别',passengers_df['Sex'].apply(sex_to_chn))
passengers_df.insert(5,'SexCode',passengers_df['Sex'].apply(sex_to_code))
passengers_df.insert(3,'船舱等级',passengers_df['Pclass'].apply(pclass_to_chn))
passengers_df.insert(3,'PclassCode',passengers_df['Pclass'].apply(pclass_to_code))
passengers_df.insert(2,'是否幸存',passengers_df['Survived'].apply(survived_to_chn))
# 预处理后数据预览
passengers_df.head()
# 分组幸存者比例及可视化 自定义函数
def groups_survived_rate_charts(groupby_c1=None, groupby_c2=None):
'''
按输入的一个或两个字段名称 分组求遇难者、幸存者人数及幸存者比例,并进行可视化。
返回数据框(DataFrame),其中包含各个组别的遇难者、幸存者人数及幸存者比例。
'''
# 可视化 x 轴坐标标签方向
var_rot = 'horizontal'
# 确定分组字段
if groupby_c1 == None and groupby_c2 == None:
return print('请至少输入一个分组字段')
elif groupby_c1 != None and groupby_c2 == None:
groupby_cs = [groupby_c1, '是否幸存']
elif groupby_c1 == None and groupby_c2 != None:
groupby_cs = [groupby_c2, '是否幸存']
else:
groupby_cs = [groupby_c1, groupby_c2, '是否幸存']
var_rot = 'vertical'
# 按分组字段汇总人数
groups_data = passengers_df.groupby(groupby_cs).size()
# 最后一层索引(即“是否幸存”)人数进行 行列转换
groups_data_df = groups_data.unstack()
# 按行计算幸存者比例,并插入到新列
groups_data_df.insert(2, '幸存者比例', groups_data_df['幸存者'] / groups_data_df.sum(axis=1))
groups_data_df.columns.name = None # 为了数据框数据显示明确,这里设为空
# 分组人数堆叠柱状图
groups_data_df[['遇难者', '幸存者']].plot(kind='bar', stacked=True, alpha=0.6)
plt.ylabel("人数")
# 幸存者比例折线图
groups_data_df['幸存者比例'].plot(secondary_y=True, kind='line', style='gd-', rot=var_rot)
plt.ylabel("幸存者比例")
plt.show()
# 返回数据框
return groups_data_df
# 全部样本乘客求遇难者、幸存者人数及幸存者比例
all_passengers_group_data = passengers_df.groupby(['是否幸存']).size() # 求人数
all_passengers_group_data_df = all_passengers_group_data.to_frame().T # 转换成数据框
all_passengers_group_data_df.insert(2, '幸存者比例', all_passengers_group_data_df['幸存者'] / all_passengers_group_data_df.sum(1)) # 求幸存者比例
all_passengers_survived_rate_df = all_passengers_group_data_df.set_index([['全部样本乘客']]) # 为了数据框数据显示明确,建该名称的索引
all_passengers_survived_rate_df.columns.name = None # 为了数据框数据显示明确,这里设为空
all_passengers_survived_rate_df
问题(1)的探索结果是 全部样本乘客幸存者比例是0.38,为了描述方便,我在下文将此比例称为“样本平均水平”。
# 按性别 分组求遇难者、幸存者人数及幸存者比例
groups_survived_rate_charts('性别')
问题(2)的探索结果是 女性乘客当中幸存者比例远大于男性当中幸存者比例,且比样本平均水平高出近1倍。
# 按船舱等级 分组求遇难者、幸存者人数及幸存者比例
groups_survived_rate_charts('船舱等级')
问题(3)的探索结果是 船舱等级越高幸存者比例也越高,且三等舱低于样本平均水平,二等舱、头等舱皆高于样本平均水平。
# 按性别、船舱等级 分组求遇难者、幸存者人数及幸存者比例
groups_survived_rate_charts('性别', '船舱等级')
问题(2)、(3)的综合探索结果是:
头等舱女性当中幸存者比例最高(接近1),而三等舱男性当中幸存者比例最低(约0.13)。
整体趋势是女性在不同等级的船舱中随着船舱等级的变高幸存者比例也变高,且头等舱比二等舱略高,但比三等舱高出近1倍;
同样男性在不同等级的船舱中随着船舱等级的变高幸存者比例也变高,且头等舱比二等舱高出1倍,接近样本平均水平,而二等舱略高于三等舱;
比较有趣的一点是女性在3个不同等级的船舱中幸存者比例皆高于男性。
这次综合探索结果更加印证了问题(2)、问题(3)的单独探索结果。
# 按年龄段 分组求遇难者、幸存者人数及幸存者比例
groups_survived_rate_charts('年龄段')
我们发现未知年龄的乘客人数较多,有没有办法对年龄缺失问题进行适当处理呢?
查看源数据发现,Name字段含有对乘客称谓的信息(比如 Master. Miss. Mr. Mrs. Ms.),
根据常识,Miss.是对未婚女子的称呼,另查资料发现 Master.是20世纪初对未成年男子的一种称呼,
根据这些信息我们可以将缺失年龄值的这两类人划分到相应年龄段(而其余称谓的年龄跨度太大,我们不做缺失值处理)。
# Master.称谓乘客的年龄分布情况
master_passengers = passengers_df['Name'].str.contains('Master.')
master_passengers_df = passengers_df[master_passengers]
master_passengers_df['Age'].describe()
# Miss.称谓乘客的年龄分布情况
miss_passengers = passengers_df['Name'].str.contains('Miss.')
miss_passengers_df = passengers_df[miss_passengers]
miss_passengers_df['Age'].describe()
以上统计发现,
Master.称谓乘客的平均年龄约4.57,
Miss.称谓乘客的平均年龄约21.77,
所以根据均值插入法我将年龄缺失的Master.称谓乘客归到 0+(即0至10(不含))年龄段,
将年龄缺失的Miss.称谓乘客归到 20+(即20至30(不含))年龄段。
下面是改进后年龄段分组统计情况:
# 按改进后年龄段 分组求遇难者、幸存者人数及幸存者比例
groups_survived_rate_charts('年龄段_改进后')
改进后,未知年龄段人数从 177(52+125)人下降到 137(28+109)人;
相应的,0+(即0至10(不含))年龄段人数从 62(38+24)人增加到 66(40+26)人,
20+(即20至30(不含))年龄段人数从 220(77+143)人增加到 256(99+157)人。
问题(4)的探索结果是:
0+(即0至10(不含))年龄段乘客当中幸存者比例最高(约0.6,1.5倍于样本平均水平),
10+、30+、50+ 年龄段乘客当中幸存者比例高于略高于样本平均水平,
20+、40+ 年龄段乘客当中幸存者比例持平于样本平均水平,
60+、70+ 以及未知年龄段乘客当中幸存者比例低于样本平均水平,
其中70+(即大于70(含))年龄段乘客当中幸存者比例最低(约0.14),
所以从整体趋势看是随着年龄段变高,幸存者比例变小。
# 全部样本乘客各个属性的皮尔逊相关系数计算
corr_df = passengers_df[['Survived', 'PclassCode', 'SexCode', 'Age', 'SibSp', 'Parch', 'Fare']].corr()
plt.matshow(corr_df)
plt.colorbar()
plt.show()
corr_df
问题(5)的探索结果是:
乘客各个属性与幸存(值定义为1)的皮尔逊相关系数值高低依次是:
性别[SexCode]、
船舱等级[PclassCode]、
船票价格[Fare]、
登船的父母或小孩个数[Parch]、
登船的兄弟姐妹或配偶个数[SibSp]、
年龄[Age]
其中 性别[SexCode]、船舱等级[PclassCode]是与幸存呈正相关程度最高的前两个属性,也就是说越是女性乘客越可能幸存,船舱等级越高越可能幸存,这与前面问题(2)、(3)的探索结果吻合。
另外,年龄[Age]、登船的兄弟姐妹或配偶个数[SibSp]与幸存呈负相关(不过负相关程度都比较低),也就是说年龄越大越不可能幸存(与问题(4)的探索结果吻合),登船的兄弟姐妹或配偶个数越多越不可能幸存(这有点出乎意料!)。
根据以上探索分析结果,我们可以得出结论:
当然,这些结论只是暂时的,因为我们分析的只是泰坦尼克号上部分乘客的样本数据,样本数据并未说明取样方式是随机的还是人为选择性的,如果样本数据不能反映总体情况的话,我们的结论也不能反映总体情况。在样本数据能反映整体情况的前提下,如果要判断数据差异是真正的差异还是数据中的噪音所导致,我们还需利用统计学来严格检查偶然得出这些结果的可能性。
另外,我们不能得出因为是女性乘客所以幸存可能性高,或者 因为是船舱等级高所以幸存可能性高,又或者 因为年龄小所以幸存可能性高。因为变量间相关性并不能直接表明因果关系,有可能是其他因素引起的。针对本项目中的结论,我觉得可能的其他因素是人类在灾难面前对女性、儿童的本能爱护有关;还有个人的社会身份地位、经济实力等因素影响着个人的抗灾能力。
我想,除了靠人性本能和个人能力外,提高灾难发生时幸存可能性在根本上还需要靠在灾难发生前要有合理的预案、充足的救灾人员物资,或许有一些灾难还可以从其他一些方面来努力避免。