Kaggle入门级赛题:泰坦尼克号生还者预测——数据分析篇
本次分享的项目来自 Kaggle 的经典赛题:泰坦尼克号生还者预测。分为数据分析和数据挖掘两部分介绍。本篇为数据分析篇。
赛题解读
比赛概述
RMS 泰坦尼克号的沉没是历史上最为人熟知的海难事件之一。 1912 年 4 月 15 日,在她的处女航中,泰坦尼克号在与冰山相撞后沉没,在船上的 2224 名乘客和机组人员中,共造成 1502 人死亡。这场耸人听闻的悲剧震惊了国际社会,从而促进了船舶安全规定的完善。
造成海难失事的原因之一是乘客和机组人员没有足够的救生艇。尽管在沉船事件中幸存下有一些运气因素,但有些人比其他人更容易存活下来,比如女人,孩子和上流社会。
在这个挑战中,要求完成哪些人可能存活下来的分析。特别的,要求运用机器学习工具来预测哪些乘客能够幸免于悲剧。
所需技巧
- 二分类 (Binary classification)
- Python 和 R 的基础 (Python and R basics)
最终目标
- 预测一名乘客是否能够在泰坦尼克号沉没事件中幸存。
- 对测试集中的每一个
PassengerId
,将其相应的Survived
变量预测为值 0 或 1(这里 1 表示幸存,0 表示遇难)。
数据分析
数据描述
首先,导入数据:
data_train = pd.read_csv('train.csv') data_test = pd.read_csv('test.csv')
数据需要进行转换能够作为模型输入。我们对训练集data_train
进行变换,同样需要对测试集data_test
进行变换,保证模型输入的一致性。
合并在一起可以进行统一变换,变换后再拆分。也可使用之前没有经过变换的data_train
。对数据集进行如下处理:
# 将目标变量 Survived 单独提取,从特征变量中移除目标变量 Survived df_target = data_train['Survived'] data_train_feature = data_train.drop(['Survived'], axis=1) # 合并 train 和 test 数据集,以方便数据处理对两个数据集同时进行 df = data_train_feature.append(data_test)
合并后的数据集如下图所示:
我们看到,共有 11 个特征:
- PassengerId: 乘客 ID
- Pclass: 舱位等级 (1 = 1st, 2 = 2nd, 3 = 3rd)
- Name: 乘客姓名
- Sex: 性别
- Age: 年龄
- SibSp: 在船上的兄弟姐妹/配偶个数
- Parch: 在船上的父母/小孩个数
- Ticket: 船票信息
- Fare: 票价
- Cabin: 客舱
- Embarked: 登船港口 (C = Cherbourg, Q = Queenstown, S = Southampton)
以上就是对比赛和数据意义的解读,下面我们对数据进行探索,来达到最终目标。
数据预处理
特征分析
我们已经在上一步查看了有哪些特征,接下来要做的是:
- 查看哪些特征存在缺失值。
- 判断哪些特征存在异常值,通过对数值型特征进行简单的描述性统计。
- 判断这些特征的数据类型,从而为后面的可视化分析找到合适的方法。
- 对数据进行可视化分析。
- 分析得出有用的结论。
1. 查看哪些特征存在缺失值
查看 train
和 test
合并后的数据:
df.info()
查看哪些特征存在缺失值,缺失程度是否严重:
print(pd.isnull(df).sum())
可以看出 age,cabin,embarked 和 Fare 四个特征有缺失值,其中 cabin 的值缺失较严重。
2. 判断哪些特征存在异常值
对数值型特征进行简单的描述性统计,包括均值,中位数,标准差,最大值,最小值等,从而判断哪些特征存在异常值。
df.describe()
观察上述值,其中 Age 的最小值为 0.17,表示的应该是婴儿的年龄,最大值为 80,年龄有些偏大。Fare 表示船票价格,它的平均值为 33.2,中位数 14,平均值比中位数大很多,说明该特征分布是严重的右偏,又有最大值约 512,所以这个值很可能是一个异常值。在 SibSp 和 Parch 中,Sibsp 最大值为 8,有可能是异常值,但 Parch 最大值也为 9。这两个特征同时出现相近的较大的数值,而又由二者所表示的含义,说明这个数值是有可能的,需要进一步的观察。
由上所述,我们看到了一些可能的异常值,但还不能确定。需要我们进一步通过可视化来清楚的显示,并结合对业务的理解来确定。
3. 判断特征的数据类型
- 定类:Name, Sex, Ticket, Embarked, Cabin
- 定序:Pclass
- 定比:Age, Fare, SibSp, Parch
根据以上对各个特征数据类型的判断,选择合适的可视化方法完成可视化。通过可视化可以:
- 可以发现事实问题,并寻找出现的原因。
- 更清晰的了解特征对目标变量的影响,有助于特征工程。
- 可以发现不易发现的特征异常值。
4. 数据可视化分析
首先,定制画布风格:
plt.style.use("bmh")
解决中文乱码问题:
plt.rcParams['font.sans-serif'] = ['SimHei'] # 或者用 plt.rc('font', family='SimHei', size=13)
定类 / 定序特征分析
cat_list = ['Pclass','Name','Sex','Embarked','Ticket','Cabin'] for n,i in enumerate(cat_list): Cabin_cat_num = df[i].value_counts().index.shape[0] print('{0}. {1}特征的类型数量是: {2}'.format(n+1,i,Cabin_cat_num))
在上面各特征值的类型中,一些比较少数量的特征如 Pclass,Sex,Embarked 等可进行可视化分析。剩下特征如 Ticket 和 Cabin 分类较多,进行可视化分析达不到想要效果,难以得到有用的结论。
先对上面 3 种容易的分类进的特征行可视化,而对于 Name,Ticket,Cabin 等到后续进行进一步分析。
f, [ax1,ax2,ax3] = plt.subplots(1,3,figsize=(20,5)) sns.countplot(x='Sex', hue='Survived', data=data_train, ax=ax1) sns.countplot(x='Pclass', hue='Survived', data=data_train, ax=ax2) sns.countplot(x='Embarked', hue='Survived', data=data_train, ax=ax3) ax1.set_title('Sex特征分析') ax2.set_title('Pclass特征分析') ax3.set_title('Embarked特征分析') f.suptitle('定类/定序数据类型特征分析', size=20, y=1.1) plt.show()
通过分别观察各特征值的分布情况和与目标变量之间的关系,得出以下结论:
- Sex:我们从 Sex 的特征分析图可以清晰的看,男性总人数大于女性总人数,但女性的存活率远远高于男性。
- Pclass:3 等舱的存活率明显比 1 等舱和 2 等舱的低很多,这是由于 3 等舱的多为普通市民,而等级越高的舱位越有可能是当时社会地位较高的人。
- Embarked:登陆港口 S 的数量最多,但获救率较低,而 C 港和 Q 港基本都有一半的人获救。
其中,Sex 和 Pclass 两个特征影响力较大。
以上是针对单独特征对生还与否的简单分析,但在实际问题中,往往是由多个因素共同决定对目标变量的影响。因此,我们需要知道在某个特定条件下的特征的影响(如 Pclass 是 1 的情况下男性和女性生还概率有何不同)才更加能帮助我们分析,即在数据集的子集内可视化变量的分布或多个变量之间的关系。这时就需要用到FacetGrid
子集数据。
在不同社会等级下,男性和女性在不同登陆港口下的数量对比:
grid = sns.FacetGrid(df, col='Pclass', hue='Sex', palette='seismic', size=4) grid.map(sns.countplot, 'Embarked', alpha=0.8) grid.add_legend()
观察得到:
- 在 Q 港口登陆的乘客中,3 等舱的乘客占了绝大多数,而其他两个舱位几乎没有乘客在该港口登陆,说明登陆港口可能与社会等级有关联。
- 在 C 港口登陆的乘客中,女性乘客占大多数,这也间接的说明在 C 港口生还率很大。
- 在 S 港口登陆的乘客中,3 等舱的乘客最多,且总的来说男性乘客占比也最多,因此可推测 S 港口生还率较小。
定距/定比特征分析
Age 特征的 kde 分布:
f,ax = plt.subplots(figsize=(10,5)) sns.kdeplot(data_train.loc[(data_train['Survived'] == 0),'Age'] , color='gray',shade=True,label='not survived') sns.kdeplot(data_train.loc[(data_train['Survived'] == 1),'Age'] , color='g',shade=True, label='survived') plt.title('Age特征分布', fontsize = 15) plt.xlabel("Age", fontsize = 15) plt.ylabel('Frequency', fontsize = 15)
根据 Age 特征得到不同性别生还与否的分布:
def plot_distribution( df , var , target , **kwargs ): row = kwargs.get( 'row' , None ) col = kwargs.get( 'col' , None ) facet = sns.FacetGrid( df , hue=target , aspect=4 , row = row , col = col ) facet.map( sns.kdeplot , var , shade= True ) facet.set( xlim=( 0 , df[ var ].max() ) ) facet.add_legend()
plot_distribution( data_train , var = 'Age' , target = 'Survived' , row = 'Sex' )
整体观察得到,0 到十几岁的孩子生还率最高,20 岁到 30 岁左右的生还率较低,其他年龄段没有太大的区别。而对于男性来说,0 到十几岁的乘客生还率明显较高,20 岁到 30 岁左右的生还率较低;而对女性来说,反而是 30 岁到 40 岁的年龄段生还率相对较高,0 到十几岁生还率并不高,其它各年龄段的生还率没有较大差别。
Fare 特征:
# 填充缺失值 data_test["Fare"].fillna(data_test["Fare"].median(), inplace=True) data_train['Fare'] = data_train['Fare'].astype(int) data_test['Fare'] = data_test['Fare'].astype(int) # 分别获得生还和遇难乘客的 Fare fare_not_survived = data_train["Fare"][data_train["Survived"] == 0] fare_survived = data_train["Fare"][data_train["Survived"] == 1] # 得到 Fare 的均值和方差 avgerage_fare = pd.DataFrame([fare_not_survived.mean(), fare_survived.mean()]) std_fare = pd.DataFrame([fare_not_survived.std(), fare_survived.std()]) data_train['Fare'].plot(kind='hist', figsize=(15,3),bins=100, xlim=(0,50)) avgerage_fare.index.names = std_fare.index.names = ["Survived"] avgerage_fare.plot(yerr=std_fare,kind='bar',legend=False)
容易观察得到 Fare 低的数量多,而 Fare 高的数量少,但生还率明显要比 Fare 低的高很多。这也符合实际情况,买得起高票价的有钱人居多,社会地位可能相对较高,更容易获救。
SibSp 和 Parch 特征:
data_train['Family'] = data_train["Parch"] + data_train["SibSp"] data_train['Family'].loc[data_train['Family'] > 0] = 1 data_train['Family'].loc[data_train['Family'] == 0] = 0 data_test['Family'] = data_test["Parch"] + data_test["SibSp"] data_test['Family'].loc[data_test['Family'] > 0] = 1 data_test['Family'].loc[data_test['Family'] == 0] = 0 # 删除 Parch 和 SibSp data_train = data_train.drop(['SibSp','Parch'], axis=1) data_test = data_test.drop(['SibSp','Parch'], axis=1) # 绘图 fig, (axis1,axis2) = plt.subplots(1,2,sharex=True,figsize=(10,5)) sns.countplot(x='Family', data=data_train, order=[1,0], ax=axis1) # 分为和家人一起、独自乘船两种情况 family_perc = data_train[["Family", "Survived"]].groupby(['Family'],as_index=False).mean() sns.barplot(x='Family', y='Survived', data=family_perc, order=[1,0], ax=axis2) axis1.set_xticklabels(["With Family","Alone"], rotation=0)
我们将 SibSp 和 Parch 这两个特征合并为了一个 Family 特征,表示该乘客是否有和家人一起乘船,来判断是否会增加生还率。
可以明显的看出独自乘船的乘客人数大于和家人一起的乘客人数,但幸存人数相对而言较少。
5. 总结
将上述 6 个特征的相互关联图进行汇总,对角线为特征自身的 kde 分布,如下图所示:
g = sns.pairplot(data_train[[u'Survived', u'Pclass', u'Sex', u'Age', u'Family', u'Fare', u'Embarked']], hue='Survived', palette = 'seismic', size=4,diag_kind = 'kde',diag_kws=dict(shade=True),plot_kws=dict(s=50) ) g.set(xticklabels=[])
数据分析部分就是这样了,根据个人的不同理解还可以有很多种不同的可视化方法,但最终目标都是为了帮助我们理解数据,进行挖掘分析。关于特征工程和建模的部分将在下一篇的数据挖掘中来介绍。
参考链接:
【Kaggle入门级竞赛top5%排名经验分享】— 分析篇
样式美化matplotlib.pyplot.style.use定制画布风格
mac下python matplotlib中文乱码解决方案
Seaborn(sns)官方文档学习笔记(第六章 绘制数据网格)
不足之处,欢迎指正。