对KAGGLE经典泰坦尼克号问题的解析(一)

深入解析那些高赞回答(一)

谨以此篇文章记录小菜鸡作者向数据分析大佬的仰望。如有冒犯,纯属无意
这次主要分析的是Titanic Data Science Solutions这一篇文章
正如文章开头所说的

The notebook walks us through a typical workflow for solving data science competitions at sites like Kaggle.

这篇文章对于我这种刚刚入门的小白来讲还是很有帮助的。
下面我就主要结合自己的理解并带上自己刚入门的疑问来“翻译下下”大佬对此类问题的解决建议叭~
如果文章有些菜鸡的地方,希望大佬们不要介意~

工作流程

解决一般的类似KAGGLE竞赛,主要有七步。

  1. 问题定义的理解
  2. 下载并导入训练集和测试集
  3. 对数据进行预处理(如数据清洗、对空值的处理等)
  4. 分析数据中的关系
  5. 建模并解决问题
  6. 可视化,完成问题解决步骤,呈现最终答案
  7. 提交结果

当然,这只是一般的流程。也会有一些特殊情况,需要将这些步骤打乱或者某一步骤重复使用。

下面将逐一介绍这些步骤

题目定义与分析

Titanic竞赛的题目是和数据集一起给出来的,并且偶尔还会在题目的周围给一些辅助说明。Titanic的题目说明在这里.

Knowing from a training set of samples listing passengers who survived or did not survive the Titanic disaster, can our model determine based on a given test dataset not containing the survival information, if these passengers in the test dataset survived or not.

之后的那一部分主要讲述了数据处理流程中一步步需要解决的问题。小编我能力有限,之后真正理解了数据分析的精髓再补充叭~

notebook构建

这一部分主要介绍了作者的这一篇notebook结合Titanic题目真正想要达到的目的。

  • 将训练集和测试集的数据结合起来进行操作

  • 正确观察-将近30%的乘客有兄弟姐妹和/或配偶在船上。
    原谅我还没能够真正理解这句话的意思

  • 正确解释逻辑回归系数(这在文章的后面会提到)

  • 在画图的时候设置画板大小并注释图例

  • 尽快对数据的特征进行相关性分析,对特征进行相关性分析有助于对数据进行降维,减少操作数据

  • 尽量将多个数据图展现在一个面板中,增强数据的可读性

下面就到了真正操作的部分了
首先先导入python强大的外部库

# 数据分析的模块
import pandas as pd
import numpy as np
import random as rnd

# 数据可视化模块
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

# 机器学习模块
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier

导入数据

pandas是一个处理数据集十分顺手的python包,首先将官方提供的数据放到pandas的dataframe中,再统一对数据进行处理。
下载下来之后有三个文件,有个叫 gender_submission 的文件目前不用管它~
这个文件按照官方的说法,是用来提供提交数据样本的。

train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
combine = [train_df, test_df]

上述代码不要直接复制吼,这是用的相对路径读取文件袋的方式,如果不懂的话希望自己可以查一下呢~

执行完上述代码后,combine应该是list类型的

通过数据本身描述分析

依旧是用pandas对数据进行描述性分析~打开数据我们会发现,train数据每一行有12个特征,test数据每一行有11个数据。
这么多的特征,所以我们就要判断那些特征属于冗余特征,也就是对数据进行降维。官方对数据每个特征的描述在这里
接着来查看一下这些数据

print(train_df.columns.values)

输出结果如下

类别型数据特征

这类的数据特征是可以作为依据对总体数据进行分类的。并且,这类数据有助于对数据进行可视化。
这之中又可分为以下两类:

  1. 没有大小关系: Survived, Sex, and Embarked.
  2. 有大小关系:Pclass.

数字型数据特征

这类数据特征的值随着样本的不同而发生变化,又可分为以下两类:

  1. 离散的:Age, Fare
  2. 连续的:SibSp, Parch

混合型数据特征

这类数据特征的值是数字和字母类型的组合,也是需要处理的数据。其中Ticket和Cabin就是这种类型的数据

值有误的数据特征

对于大型数据集而言,这类数据特征的值很难处理,就比如姓名的值就容易出现错字。

包含空值的数据特征

train_df.info()
print('_'*40)
test_df.info()

空值特征查找
通过上述代码可以看到在test和train数据集中,均有部分值为空的数据特征。

样本中数字性数据特征的分布特点

train_df.describe()

在这里插入图片描述
通过对结果的观察,可以得到以下几条结论:

  • 样本数据有891个
  • 样本的存活率大约为38%
  • 75%以上的乘客没有和父母、孩子一起
  • 30%以上的乘客是和兄妹一起来的

非数字型数据特征描述

#注意这里是大写的英文字母O,不是0
train_df.describe(include=['O'])

活了这么久第一次用到describe中的函数,哈哈,还是自己吃的盐太少了
在这里插入图片描述
又可以得到一些有趣的信息

  • 游轮上没有重名的游客
  • 男性游客占了65%左右

根据数据分析做出一些猜测

根据之前的分析,做出下述假设,并在之后会对假设进行验证~

  • 部分特征与存活率关联性较强,应在作业初期找出这些特征,减少工作量
  • 含有空值的特征可能对是否存活有较强的影响,就比如年龄,因此,需要对空值进行一些操作
  • “Cabin”这一特征由于空值太多,可能会被舍弃
  • “Ticket”这一特征由于重复值没有什么规律,并且感觉应该和存活率没有什么关系,也应被舍弃。同样“Name”和“PassengerId”也应该被舍弃
  • 创建一个新的特征叫“Family”,将“Parch”和“SibSp”的值加起来
  • 创建将年龄、船票进行分段的特征
  • 女人、小孩可能更容易获救
  • 船舱等级越高的乘客越容易获救

数据特征独立分析

先对一些没有空值的数据特征进行分析,如:

  • Pclass
train_df[['Pclass', 'Survived']].groupby(
    ['Pclass'], as_index=False).mean().sort_values(
    by='Survived', ascending=False)

实现的功能是先只保留train_df数据集中的[‘Pclass’,‘Survived’]这两列(这么说其实不太好。应该说,这个集合中的这两个特征为了体现为什么用两个中括号),按照Pclass这一特征进行分组(as_index=False是为了不将Pclass这一特征作为索引),有一点点数据库基础的同学应该就会比较熟悉groupby的用法,后面接一个聚合函数(mean,sum,agg等),就可以得到另一个dataframe,后面的函数就是一个按照Survived属性大小的降序排列函数了。结果如下图。
在这里插入图片描述
可以看出来船舱越高级,存活率越高。

之后对剩下的特征做同样的操作,可以发现,Sex对存活率有较大的影响,而SibSpParch就不是了。因此,关于特征降维的选取就有了初步打算。

数据可视化分析

通过对数据的可视化来判断不同特征之间的关联性,还有其他特征和survived之间的关系

数字型特征

其中直方图有利于分析连续性数字特征,如Age。

g = sns.FacetGrid(train_df, col='Survived')
g.map(plt.hist, 'Age', bins=20)

若想在数据集的子集内可视化变量的分布或多个变量之间的关系时,用到的是seaborn中的FacetGrid类,下面简要介绍这个类的用法。

  • sns.FacetGrid() 画出轮廓

  • map() 填充内容
    在这里插入图片描述
    对map()中的部分参数进行说明

  • plt.hist是绘图的类型,是直方图的形式,也可以替换成plt.scatter(散点图)、sns.distplot(sns直方图)等其他图像类型。

  • Age是x轴的数据,后面也可以再跟y轴的数据。

  • bins代表从x轴的最小值到最大值有几个条。
    根据得到的直方图,可以得到以下信息。

  • 婴儿(年龄小于等于4岁的有较高的存活率)。

  • 多数游客年龄在15-35岁之间。

  • 在15-25岁的游客死亡率较高。

  • 年龄最大(80)的旅客活了下来。

可以看出来,年龄是一个十分重要的特征,所以可以的出以下结论:

  1. 将年龄中的空值补全
  2. 将年龄进行分组

数字型特征和序数型特征之间的相关性

可以在一个图中祝贺多个特征显示关联性,这可以通过数字型特征和具有数字型的分类型特征表示(Pclass)。
有两种展示形式

#形式1
grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
#形式2
grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived')
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend()
  • col代表列的分类
  • row代表行的分类
  • size和aspect共同组成图片的大小
  • alpha大小(小于1)决定条形的透明度

形式一:
形式一
形式二:
在这里插入图片描述
根据直方图可以得到以下信息:

  • Pclass3乘客最多,存活率最低
  • Age和Pclass之间有一定的关系
  • Pclass1中的乘客大多数活了下来
  • 不同的Pclass中Age和存活率的关系不一样

所以Pclass也是十分重要的一个特征

类别型特征之间的关联性

由之前的信息可以知道,类别型数据特征有四个:Pclass、Sex、Survived、Embarked。将这四个特征放在一起进行。

grid = sns.FacetGrid(train_df, col='Embarked')
grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex',palette='deep')
grid.add_legend()

这次是将Embarked作为底图的分类。图像的类型是连点图。
在这里插入图片描述
根据图像可以得到一下信息:

  • 女性存活率远高于男性。
  • 在Embarked=C中男性存活率高于女性,也不一定证明Embarked和Survived之间有直接的关系。

可以得到以下结论:

  1. 将Sex特征加到模型训练中。
  2. 将Embarked特征的空值补全,加到模型训练中。

类别型特征和数字型特征之间的关联性

将类别型特征(不具有数字值),和数字型特征相关联。Embarked (不具有数字值的类别型特征), Sex (不具有数字值的类别型特征), Fare (连续性数字型特征), with Survived (有数字值的类别型特征)。

grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'b', 1: 'r'})
grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)
grid.add_legend()
  • hue代表子图中的分类
  • sns.barplot是条形图
    在这里插入图片描述
    可以得到以下信息:
  • Fare和Survived有关,Fare越高存活率越大
  • Embarked和Survived有关

得到以下结论

1.将Fare分组作为训练模型的特征

数据处理与分析

根据前文提到的假设和结论,对数据进行处理和分析,得到最终的预测结果。

舍弃无用的特征

根据上面的初步分析,打算舍弃的数据特征有:CabinNameTicket。结合之前的非数值的类别型数据特征描述图
在这里插入图片描述
可知,舍弃的这三种特征都是类别较多的。也加快了之后数据的分析速度。 但是!由于,有的时候名字也可以体现一个人的某种特性,看下图
在这里插入图片描述
有人是Mr,有人是Miss,有人是Mrs,有人是Master,极有可能之后对名字进行拆分,就得到另一种数据特征。
之后的数据处理尽量将训练集和验证集同时处理。

train_df = train_df.drop(['Ticket', 'Cabin'], axis=1)
test_df = test_df.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_df, test_df]

创建新的数据特征

首先,就是对Name这一特征创建新的特征,来寻找与Survived之间的关联性。
因为是对字符串做处理,所以,应用正则表达式的方式。观察之前Name的内容,提取与第一个 . 匹配的单词。
正则表达式确实有一点难理解呢~不是很懂的读者可以看看菜鸟教程中关于正则表达式的部分.

for dataset in combine:
    dataset['Title'] = dataset.Name.str.extract(' ([A-Z][a-z]+)\.', expand=False)
pd.crosstab(train_df['Title'], train_df['Sex'])
  • 这里combine是一个list,有两个dataframe,一个是train_df另一个是test_df。str.extract()是Series使用正则表达式抽取匹配数据的方法。第一个参数是正则表达式,第二个参数决定不返回dataframe,而是以原本的形式存在。
  • pd.crosstab是引用交叉表。是统计分组频率的特殊透视表

在这里插入图片描述
title的值很多,需要分组,并查看分组与Survived的关系。

for dataset in combine:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',
                                                 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')

    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
    
train_df[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()

其中,MlleMiss的法语表达,MsMiss的另一种英文表达。MmeMrs是法语表达。
在这里插入图片描述
利用之前可视化的方法查AgeTitleSurvived之间的关系。结果如下。
在这里插入图片描述
可以得到如下信息:

  • 有时Title和Age的分组有关,如Master
  • Title和Survived也有关

所以:应该把Title也作为一个重要的特征加入到模型的训练中
将Title用更简单的形式表示出来。

title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
for dataset in combine:
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

最后一句是将没有上述Title值的置为0,也就是补空值,检查一下是否出现空值。
在这里插入图片描述
然后就可以丢弃不用的数据特征了。
train中的 ‘Name’‘PassengerId’
test中的 ‘Name’
由于在最后对test进行判断的时候,需要用到 ‘PassengerId’ 所以不可以省去。

train_df = train_df.drop(['Name', 'PassengerId'], axis=1)
test_df = test_df.drop(['Name'], axis=1)
combine = [train_df, test_df]

转换类别型特征

将包含字符串的特征转换为数值型,因为在之后训练模型时没有办法对字符串进行训练的。
首先是 ‘Sex’ 中的 ‘female’ 是1,‘male’ 是0。

for dataset in combine:
    dataset['Sex'] = dataset['Sex'].map( {'female': 1, 'male': 0} ).astype(int)

补全数字型特征的空值

首先对年龄这一特征进行空值的填补。
有三种办法:

  • 利用均值和标准差产生随机数
  • 利用其它的数据特征,如(Pclass、Sex),所以可用Pclass和Sex的组合来猜测Age,取对应区间的中位数。
  • 结合前两种方法

由于1和3犯法会产生随机数,可能偏离,所以,用第二种方法。
先可视化Pclass、Sex和Age的关系。

grid = sns.FacetGrid(train_df, col='Pclass', hue='Sex')
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend()

在这里插入图片描述
可以看出来,Pclas和Sex的不同取值,对Age的分布还是有一定影响的。
用一个2*3(Sex=0、1;Pclass=1、2、3)的的数组来表示每一个填补空白的值。

guess_ages = np.zeros((2,3))#创建数组
for dataset in combine:
    for i in range(0, 2):
        for j in range(0, 3):
            guess_df = dataset[(dataset['Sex'] == i) & 
                               (dataset['Pclass'] == j+1)]['Age'].dropna()

            # age_mean = guess_df.mean()
            # age_std = guess_df.std()
            # age_guess = rnd.uniform(age_mean - age_std, age_mean + age_std)

            age_guess = guess_df.median()

            # Convert random age float to nearest .5 age
            guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
            
    for i in range(0, 2):
            for j in range(0, 3):
                dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) &
                            (dataset.Pclass == j+1),'Age'] = guess_ages[i,j]
    dataset['Age'] = dataset['Age'].astype(int)

第一个for循环是为了算出对每一个分组的 预测数 也就是算出来中位数(或用均值和方差的算法)。

guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5

这句话的作用,是将之前得到的中位数进行估值类似于四舍五入的感觉,分成了三个值:0,0.5,1。
第二个for循环是对空值进行赋值。
最后一句话是将AGE转换为int类型~
小编感觉这就是一个四舍五入的操作,为什么不在之前加上0.5再int取整嘞~
之后再检查空值,就发现:
在这里插入图片描述
只有 Embarked有空值 了。
之后就是将Age分段然后确定与Survived之间的关系啦~

train_df['AgeBand'] = pd.cut(train_df['Age'], 5)
train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)

将Age分成五段,查看每一段和Survived之间的关系。在这里插入图片描述
之后将年龄段进行替换,替换成Age的顺序数,并丢掉训练集中的AgeBand:

for dataset in combine:    
    dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
    dataset.loc[ dataset['Age'] > 64, 'Age'] = 4
train_df = train_df.drop(['AgeBand'], axis=1)
combine = [train_df, test_df]

因为测试集没有生成AgeBand,所以只用丢掉训练集的AgeBand

根据已知特征创建新的数据特征

将Parch和SibSp合并成一个特征叫FamilySize。

for dataset in combine:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False)

然后观察到:
在这里插入图片描述
似乎没有什么标准,于是将有亲人与否进行了分类,创建特征IsAlone

for dataset in combine:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

train_df[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()

再观察结果:
在这里插入图片描述
这就有点意思了~于是之留下来这IsAlone这一个特征就好啦
也可以将Age和Pclass相乘,创建一个新的特征。

train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
test_df = test_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
combine = [train_df, test_df]
for dataset in combine:
    dataset['Age*Class'] = dataset.Age * dataset.Pclass
    
train_df[['Age*Class', 'Survived']].groupby(['Age*Class'],as_index=False).mean()

结果如下:
在这里插入图片描述
还有一定道理的~

类别型数据特征的空值处理

由于在上文中可以知道,训练集中,Embarked只缺少两个值,所以,用 出现最多 的Embarked值(也就是用众数)填补空值。

freq_port = train_df.Embarked.dropna().mode()[0]
#众数是S
for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].fillna(‘S’)
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

同时将Embarked的值进行数值化。

数字型数据特征空值处理

现在,经过查看,只有测试集中的Fare有空值,可以用众数、平均数、中位数代替。用的是中位数。

test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)

然后将Fare分段

train_df['FareBand'] = pd.qcut(train_df['Fare'], 4)
train_df[['FareBand', 'Survived']].groupby(['FareBand'], as_index=False).mean().sort_values(by='FareBand', ascending=True)

这个qcut和cut的区别在于:

  • qcut是根据频率分段
  • cut是根据间隔分段

具体区别可见 这篇文章.
qcut中的参数4,是不断尝试出来的,这个样子,Survived在不同的分段中,值最大限度的有差异。在这里插入图片描述
之后将Fare的分段更简单表示

for dataset in combine:
    dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
    dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
    dataset['Fare'] = dataset['Fare'].astype(int)
    
train_df = train_df.drop(['FareBand'], axis=1)
combine = [train_df, test_df]

终于数据算是处理完啦~
看一眼最后的数据是什么样子吧
在这里插入图片描述

建立模型并预测解决问题

在sklearn中有相当多的模型可以选择,但是选择什么模型是通过观察问题和数据得到的。这个问题是一个分类问题,判断测试集是否存活。并且训练集已经打上标签了。所以,可选择的模型如下:

  • Logistic Regression 逻辑回归
  • KNN or k-Nearest Neighbors
  • Support Vector Machines(SVM)支持向量机
  • Naive Bayes classifier 朴素贝叶斯分类器
  • Decision Tree 决策树
  • Random Forest 随机森林
  • Perceptron 感知器
  • Artificial neural network 人工神经网络
  • RVM or Relevance Vector Machine 相关向量机

在sklearn中所有的模型都有四个固定且常用的方法:

# 拟合模型
model.fit(X_train, y_train)
# 模型预测
model.predict(X_test)
# 获得这个模型的参数
model.get_params()
# 为模型进行打分
model.score(data_X, data_y)

emmm,小编似乎对这篇文章中模型的选择与训练部分还没有搞得很太懂,大家可以看一看KAGGLE下方的评论区,关于模型的处理方法,下一篇文章再来说叭~

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值