背景描述
Titanic数据集在数据分析领域是十分经典的数据集,非常适合刚入门的小伙伴进行学习!
泰坦尼克号轮船的沉没是历史上最为人熟知的海难事件之一。1912年4月15日,在她的处女航中,泰坦尼克号在与冰山相撞后沉没,在船上的 2224 名乘客和机组人员中,共造成 1502 人死亡。这场耸人听闻的悲剧震惊了国际社会,从而促进了船舶安全规定的完善。造成海难失事的原因之一是乘客和机组人员没有足够的救生艇。尽管在沉船事件中幸存者有一些运气因素,但有些人比其他人更容易存活下来,究竟有哪些因素影响着最终乘客的生存与否呢?
数据说明
在该数据集中,共包括三个文件,分别代表训练集、测试集以及测试集的答案;
数据描述:
变量名称 | PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
变量解释 | 乘客编号 | 是否存活 | 船舱等级 | 姓名 | 性别 | 年龄 | 兄弟姐妹和配偶数量 | 父母与子女数量 | 票的编号 | 票价 | 座位号 | 登船码头 |
数据类型 | numeric | categorical | categorical | String | categorical | categorical | numeric | numeric | string | numeric | string | categorical |
显示详细信息
一 数据读取与分析
1. 数据读取
import warnings warnings.filterwarnings("ignore") import matplotlib.pyplot as plt #设置中文编码和负号的正常显示 plt.rcParams['font.family']='Microsoft YaHei' plt.rcParams['axes.unicode_minus'] = False import pandas as pd import numpy as np
data_train = pd.read_csv('train.csv') # 训练集数据 data_test = pd.read_csv('test.csv') # 测试集数据 data_train.shape, data_test.shape, data_train.columns
可以看到训练集总共有12列891行数据,其中Survived字段表示的是乘客是否获救,其余都是乘客个人信息,包括:
- Survived — 获救情况(二分类,因变量)
- PassengerId — 乘客ID
- Pclass — 乘客等级(1/2/3等舱位)
- Name — 乘客姓名
- Sex — 性别
- Age — 年龄
- SibSp — 堂兄弟/妹个数
- Parch — 父母与小孩个数
- Ticket — 船票信息
- Fare — 票价
- Cabin — 客舱
- Embarked — 登船港口
2. 简单描述性分析
看一下数据类型及缺失情况
data_train.info()
- 数据显示,训练数据集中共有891名乘客,12列特征,其中有三列数据存在缺失:
- Age(年龄):只有714条完整记录,有177条记录缺失;
- Cabin(客舱):只有204条乘客已知,有687条记录缺失,缺失较多!;
- Embarked(登船港口):只有两条记录缺失;
- 下面来看一下数据的描述性统计
data_train.describe()
- 根据表格,我们可以得出一些基本信息:
- Survived:大概有0.383838比例的人最后获救了;
- Pclass: 2号和3号舱的人要比1号舱的人多;
- Age:所有乘客的平均年龄大概再29.7岁,最小的乘客0.42岁,最大的乘客80岁;
- Fare:平均票价在32元,最高的票价在512元;
- 查看test数据集,看分布是否与训练集一致
data_test.describe()
- 训练集和测试集大致的分布相差不大;
3. 探索性分析
3.1 乘客不同特征的描述性统计
import matplotlib.pyplot as plt %matplotlib inline fig = plt.figure(figsize=(15,10)) fig.set(alpha=0.2) # 设定图标透明度 plt.subplot2grid((2,3),(0,0)) # 分为2行3列,从(0,0)算起 data_train.Survived.value_counts().plot(kind='bar') plt.title('获救情况(1为获救)') plt.ylabel('人数') plt.subplot2grid((2,3),(0,1)) data_train.Pclass.value_counts().plot(kind="bar") plt.ylabel("人数") plt.title("乘客等级分布") plt.subplot2grid((2,3),(0,2)) plt.scatter(data_train.Survived, data_train.Age) plt.ylabel("年龄") plt.grid(visible=True, which='major', axis='y') # 绘制网格线 plt.title("按年龄看获救分布 (1为获救)") plt.subplot2grid((2,3),(1,0), colspan=2) data_train.Age[data_train.Pclass == 1].plot(kind='kde') # 绘制密度图 data_train.Age[data_train.Pclass == 2].plot(kind='kde') data_train.Age[data_train.Pclass == 3].plot(kind='kde') plt.xlabel("年龄") plt.ylabel("密度") plt.title("各等级的乘客年龄分布") plt.legend(('头等舱', '2等舱', '3等舱'),loc='best') # plt.subplot2grid((2,3),(1,2)) data_train.Embarked.value_counts().plot(kind='bar') plt.title("各登船口岸上船人数") plt.ylabel("人数")
根据训练集数据绘制如上图所示,从图中我们可以得到一些信息:
- 未能获救的人有500+,而获救的人大概有300+,不到人数的一半;
- 三等舱乘客最多,接近500人,而一等和二等舱的乘客相对较少,都在200人左右;
- 从年龄分布可以看出,遇难和获救的乘客年龄分布都比较离散,跨度大;
- 三个不同舱的乘客年龄总体趋势大致相同,其中20岁左右的乘客主要集中再二三等舱,一等舱中40岁左右的最多;
- 再登船港口中,其中S港口上传人数最多,有600+人,另外两个C和Q港口,都不到200人,要远远小于C港口;
根据以上结论,提出一些假设:
- 不同舱位/乘客等级可能和财富/地位有关系,最后获救概率可能会不一样;
- 年龄对获救概率也一定是有影响的,毕竟背景知识提到,副船长还说『小孩和女士先走』呢;
- 获救概率与登船港口是不是有关系呢?也许登船港口不同,人的出身地位不同?
3.2 看看各乘客等级的获救情况
fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts() Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts() df = pd.DataFrame({u'未获救':Survived_0, u'获救':Survived_1}) df.plot(kind='bar', stacked=True) plt.title(u'不同等级乘客获救情况') plt.xlabel(u'乘客等级') plt.ylabel(u'人数') plt.show()
data_train[['Pclass', 'Survived']].groupby('Pclass').mean()
从图中可以看出,等级为1的乘客,获救的概率最大,并且随着等级的递减,获救的概率也是递减状态!所以,乘客等级这必然是一个影响乘获救的重要特征!!
3.3 查看各性别的获救情况
fig = plt.figure() fig.set(alpha=0.2) Survived_0 = data_train.Survived[data_train.Sex == 'male'].value_counts() Survived_1 = data_train.Survived[data_train.Sex == 'female'].value_counts() df = pd.DataFrame({u'男性':Survived_0, u'女性':Survived_1}) df.plot(kind='bar', stacked=True) plt.title(u'不同性别乘客获救情况') plt.xlabel(u'获救与否') plt.ylabel(u'人数') plt.show()
data_train[['Sex','Survived']].groupby('Sex').mean()
从图中可以看出,相对男性来说,女性的获救率远远高于男性,看来外国人还是比较践行女性优先的!所以,性别对于最终生存与否也是有非常重要影响的!
3.4 查看各登船港口的获救情况
泰坦尼克号从英国的南安普顿港出发,途径法国瑟堡和爱尔兰昆士敦,那么在昆士敦之前上船的人,有可能在瑟堡或昆士敦下船,这些人将不会遇到海难。
fig = plt.figure(figsize=(10,15)) fig.set(alpha=0.2) Survived_0 = data_train.Embarked[data_train.Survived == 0].value_counts() Survived_1 = data_train.Embarked[data_train.Survived == 1].value_counts() df = pd.DataFrame({u'未获救':Survived_0, u'获救':Survived_1}) df.plot(kind='bar', stacked=True) plt.title(u'不同登船港口的乘客获救情况') plt.xlabel(u'登船港口') plt.ylabel(u'人数') plt.show()
data_train[['Embarked','Survived']].groupby('Embarked').mean()
import seaborn as sns import matplotlib.pyplot as plt sns.catplot(x='Embarked', y='Survived', data=data_train, kind = 'point') plt.title(u'各登录港口乘客的获救情况') plt.show()
可以看出,再不同港口上船,生还率不同,其中C港口最高,Q次之,S港口最低;
3.5 查看携带家人数量不同的获救情况
data_train['family'] = data_train['SibSp'] + data_train['Parch'] data_train['family']
Survived_0 = data_train.family[data_train.Survived == 0].value_counts() Survived_1 = data_train.family[data_train.Survived == 1].value_counts() df = pd.DataFrame({u'未获救':Survived_0, u'获救':Survived_1}) df.plot(kind='bar', stacked=True) plt.title(u'携带不同家人数量的乘客获救情况') plt.xlabel(u'携带家人数量') plt.ylabel(u'人数') plt.show()
data_train[['family', 'Survived']].groupby('family').mean().plot.bar()
可以看到,独自一人和亲友太多,存活率都比较低
3.6 不同船舱类型的乘客获救情况
船舱的缺失值确实太多,有效值仅仅有204个,很难分析出不同的船舱和存活的关系,我们可以直接将该组特征丢弃掉,也可以简单地将数据分为是否有Cabin记录作为特征,将缺失数据归为一类,未缺失数据归为一类,一同跟与Survived进行分析
is_null = data_train.Survived[data_train.Cabin.isnull()].value_counts() not_null = data_train.Survived[data_train.Cabin.notnull()].value_counts() df = pd.DataFrame({'为空':is_null, '非空':not_null}).transpose() df.plot(kind='bar', stacked=True) plt.title('按Cabin是否为空看获救情况') plt.xlabel('Cabin是否为空') plt.ylabel('人数') plt.show()
data_train.Survived[data_train.Cabin.notnull()].value_counts(normalize=True)
data_train.Survived[data_train.Cabin.isnull()].value_counts(normalize=True)
可以看出,有cabin记录的乘客survival比例比无记录的高很多;
3.7 缺失值处理
这里只是为了进行探索性分析,具体更详细的数据处理见特征工程部分;
通常遇到缺值的情况,有下面几种处理方式:
- 如果缺值的样本占比较高,可以直接舍弃,以免作为特征加入,反倒带入噪声;
- 如果缺值的样本适中,而该属性非连续值特征属性(比如类目属性),- 那就把NaN作为一个新类别,加到类别特征中
- 如果缺值的样本适中,而该属性为连续值特征属性,可以尝试分桶处理;
- 当缺失的样本并不是特别多的时候,我们可以试着根据已有的值,拟合一下数据,补充上。
Embarked(共有三个上船地点),缺失俩值,可以用众数填充;
Cabin将缺失信息当做一个类目;
处理Embarked和数据
由于总共有1309条数据,Embarked只缺失两个,所以用众数填充即可
data_train.Embarked[data_train.Embarked.isnull() == True] = data_train.Embarked.dropna().mode().values data_train['Cabin'] = data_train.Cabin.fillna('U0') # 先简单填充Cabin
处理Age数据
facet = sns.FacetGrid(data_train,hue="Survived",aspect=4) facet.map(sns.kdeplot,'Age',shade=True) facet.set(xlim=(0,data_train['Age'].max())) facet.add_legend()
通常使用回归、随机森林等模型来预测缺失属性的值。
经过分析:Age在该数据集里是一个相当重要的特征,所以保证一定的缺失值填充准确率是非常重要的,对结果也会产生较大影响。
这里使用随机森林预测模型,选取数据集中的数值属性作为特征(因为sklearn的模型只能处理数值属性,所以这里先仅选取数值特征,但在实际的应用中需要将非数值特征转换为数值特征);
data_train.info()
from sklearn.ensemble import RandomForestRegressor cols = ['Age', 'Survived', 'Pclass', 'SibSp', 'Parch', 'Fare'] age_df = data_train[cols] X_train = age_df[age_df.Age.notnull()][cols[1:]] y_train = age_df[age_df.Age.notnull()][cols[0]] X_test = age_df[age_df.Age.isnull()][cols[1:]] rfr = RandomForestRegressor(n_estimators=1000, n_jobs=-1) #n_estimators控制随机森林决策树的数量;n_jobs=-1会使用CPU的全部内核,大幅度提升速度 rfr.fit(X_train, y_train) y_predict = rfr.predict(X_test) data_train.loc[data_train.Age.isnull(),'Age'] = y_predict # 缺失值填充 data_train.info()
3.8 不同年龄下的平均生存率
plt.figure(figsize=(18,6)) data_train['Age_int'] = data_train.Age.astype(int) rate = data_train[['Age_int', 'Survived']].groupby('Age_int', as_index=False).mean() sns.barplot(x='Age_int', y='Survived', data=rate) plt.show()
data_train['Age'].describe()
从上图可以看出,训练样本共有891份,平均年龄在29.5岁,标准差为13.7岁,最小年龄0.42岁,最大年龄80岁;根据年龄,可以将乘客划分为儿童、少年、成年、老年,分析四个群体的生还情况
bins = [0, 12, 18, 65, 100] data_train['Age_group'] = pd.cut(data_train['Age'], bins) age_group_survived_rate = data_train.groupby('Age_group')['Survived'].mean() age_group_survived_rate
age_group_survived_rate.plot(kind='bar')
从图中可以看出,0到12岁儿童的存活率是最高的,达到了50%左右,其次是少年群体,在45%以上,最低的就属于65岁到100岁的老年群体,存活率最低,在12%左右;所以看得出来,不管在哪,孩子永远都是第一要保护的对象;
3.9 不同称呼的乘客生存情况
通过观察名字数据,我们可以看出其中包括对乘客的称呼,如:Mr、Miss、Mrs等,称呼信息包含了乘客的年龄、性别,同时也包含了入社会地位等的称呼,如:Dr,Lady,Major(少校),Master(硕士,主人,师傅)等的称呼。
data_train['Name']
#方法一: data_train['Title'] = data_train['Name'].apply(lambda x : x.split(',')[1].split('.')[0].strip()) pd.crosstab(data_train['Title'],data_train['Sex'])
#方法二: data_train['Title'] =data_train['Name'].str.extract(' ([A-Za-z]+)\.',expand=False) pd.crosstab(data_train['Title'],data_train['Sex'])
不同称呼的生存率
data_train[['Title', 'Survived']].groupby('Title').mean().plot(kind='bar')
从图中可以看出,不同称呼的乘客存活情况也不尽相同,存在显著差异,其中称呼为Lady、Mlle、Mme、Ms、Sir以及the countess的人群的存活率最高,均达到了100%的存活率;
3.10 票价分布与Survived的关系
票价分布情况
plt.figure(figsize=(10, 8)) data_train['Fare'].hist(bins=70)
data_train.boxplot(column='Fare', by='Pclass', showfliers=False)
data_train['Fare'].describe()
绘制survived与票价均值和方差的关系
fare_not_survived = data_train['Fare'][data_train['Survived'] == 0] fare_survived = data_train['Fare'][data_train['Survived'] == 1] average_fare = pd.DataFrame([fare_not_survived.mean(),fare_survived.mean()]) std_fare = pd.DataFrame([fare_not_survived.std(),fare_survived.std()]) average_fare.plot(yerr=std_fare,kind='bar',legend=False)
从图中可以看出,生存者的平均票价要大于未生还者的平均票价
4. 小结
根据以上探索性分析,我们可以猜测出一些结论:
- 舱位等级:舱位越高的获救的概率最大,并且随着等级的递减,比如一等舱就比二三等舱获救概率大;
- 性别:女性的获救率远远高于男性;
- 登船港口:C港口最高,Q次之,S港口最低;(需结合具体背景解释原因)
- 携带家人数量:独自一人和亲友太多,存活率都比较低;
- 客舱:缺失值较多,很难分析出有用信息,这里简单将缺失的看成一类,未缺失的看成一类;
- 年龄:0到12岁儿童存活率最高,在50%左右,其次是少年,最低的是65岁到100岁的老年群体;
- 称呼:不同称呼的乘客存活情况也不尽相同,存在显著差异;其中,Lady、Mlle、Mme、Ms、Sir以及the countess存活率均在100%;
- 票价:生存者的平均票价要大于未生还者的平均票价;
本章只是涉及到泰坦尼克号的描述性统计分析,后续还会有关于此数据集的特征工程以及建模预测部分。
如果本文有存在不足的地方,欢迎大家在评论区留言。