泰坦尼克号建模分析-你能活下来吗?

你在泰坦尼克号上你能活下来吗?

泰坦尼克号的沉没是历史上最具影响力的海难之一,在1912年4月15日,泰坦尼克号的处女航中,与冰山相撞后沉没。在当时,船上没有足够的救生艇供所有人使用,导致2224名乘客和机组人员中的1502人死亡。虽然幸存有一些运气成分,但似乎有些人比其他人更有可能生存。
泰坦尼克号预测项目从kaggle建站开始,已经经历了很多大佬的分析建模,有2万多的团队参与过该项目,是你加入kaggle最好的项目之一。

先想明白我们怎么去做

参考《数据科学解决方案》一书,建模的7个流程:

  • 定义问题,确定问题知道我们要做什么
  • 获取数据,一般是指训练和测试数据。
  • 数据清洗,得到一份干净好用的数据
  • 数据分析,探索数据,为后续建模做准备。
  • 建模,预测和解决问题。
  • 可视化报告,呈现问题的解决步骤和最终解决方案。
  • 得到结果。

建模流程

确定问题

每个数据集提出的同时也会伴随着问题,kaggle会给与数据说明(https://www.kaggle.com/c/titanic/overview/description ),我们的问题很简单: “什么样的人更有可能生存呢?”
在泰坦尼克竞赛中,我们可以访问两个类似的数据集,训练集和测试集,字段包括乘客信息,例如姓名、年龄、性别、社会经济舱等。训练集为“ train.csv”,测试集为“ test.csv”。Train.csv将包含一部分乘客的详细信息(准确地说是891位乘客),并且重要的是,它会告诉我们他们是否幸存下来。“ test.csv”数据集包含类似的信息,但没有透露每位乘客的是否存活,我们的工作就是利用train.csv的数据训练出模型,去预测机上其他418名乘客(在test.csv中找到)是否幸免于难。

获取数据

kaggle 提供了很方便的下载方式,可以在数据界面中找到并下载数据:https://www.kaggle.com/c/titanic/overview/description

导包

# 导入数据分析一些常用库 
import pandas as pd
import numpy as np
import random as rnd
# pd.options.display.max_columns = None
# 可视化包
import seaborn as sns
import matplotlib.pyplot as plt
plt.style.use("seaborn")
%matplotlib inline
sns.set(font="simhei")
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置加载的字体名
plt.rcParams['axes.unicode_minus'] = False   # 解决保存图像是负号'-'显示为方块的问题 
# 机器学习包
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.neural_network import MLPClassifier
from sklearn.tree import DecisionTreeClassifier

导入数据

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

预览数据

# 看一下我们有那些字段
print(train_df.columns.values)
['PassengerId' 'Survived' 'Pclass' 'Name' 'Sex' 'Age' 'SibSp' 'Parch'
 'Ticket' 'Fare' 'Cabin' 'Embarked']
# 简单看一下我们的数据
train_df.head(3)
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
train_df.tail(3)
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
88888903Johnston, Miss. Catherine Helen "Carrie"femaleNaN12W./C. 660723.45NaNS
88989011Behr, Mr. Karl Howellmale26.00011136930.00C148C
89089103Dooley, Mr. Patrickmale32.0003703767.75NaNQ

数据清洗

字段整理

  • PassengerId 用户编号:目前作为用户唯一标识,此处无特别意义(有些编号是具有价值的,比如身份证)
  • Survived 是否幸存:目标字段,是我们要预测的数据
  • Pclass 用户阶级:分类字段,1为最高级,3为最低级
  • Name 姓名:可以看出家族关系
  • Sex 性别
  • Age 年龄
  • SibSp:泰坦尼克号上与乘客同行的兄弟姐妹(Siblings)和配偶(Spouse)数目
  • Parch:描述了泰坦尼克号上与乘客同行的家长(Parents)和孩子(Children)数目
  • Ticket 船票号
  • Fare 票价
  • Cabin 船舱号
  • Embarked 乘客上船时的港口
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2En3s3d-1614530159408)(attachment:image.png)]

识别特征类型

特征主要类型有:时间型、数值型、类别型、文本型

哪些特征是分类型特征?

类别型特征将相似属性归为一类,但是大多数模型都不能直接处理文本型数据,必须要转换为数值型才能使用。
类别型特征可以分为定类变量、定序变量、定距变量和定比变量。

  • 定类变量:如性别(男、女、其他),三种取值之间是相互独立的,彼此之间完全没有关系,这种变量称之为名义变量。
  • 定序变量:如学历(小学、初中、高中),三种取值不是完全独立的,我们可以明显看出,在性质上可以有高中>初中>小学这样 的联系,学历有高低,但是学历的取值之间却不是可以计算的,我们不能说小学 + 某个取值 = 初中。这是有序变 量。
  • 定距变量:如温度(>25摄氏度、>30摄氏度 、>35摄氏度),各个取值之间有联系,且是可以互相计算的,而且两者的差值有 意义。比如35摄氏度 - 25摄氏度 = 10摄氏度,分类之间可以通过数学计算互相转换。这是有距变量。
  • 定比变量:如质量(>10kg、>50kg、>100kg),各个取值之间有联系,不仅可以计算差值,还可以计算其商值。

定序变量:Pclass 用户阶级:分类字段,1为最高级,3为最低级

定类变量:Sex 性别,Embarked 乘客上船时的港口,Survived 是否幸存

哪些特征是数值型特征?

数值型随样本的不同而进行变化,一般分为连续型,离散型,经常使用归一化,离散化等方法进行处理

连续型:Age 年龄,fare 票价。

离散型:SibSp,Parch。

哪些特征是文本型特征?

文本型:Name,姓名中有很多符号,也存在一些错别字的问题

还有一些混合型特征

混合型数据特征:Ticket 船票号,Cabin 船舱号

  • 还有1个特征: PassengerId 用户编号(无意义,后续会删除)

数据清洗

空值,数据类型

空值:

训练数据集空值排序:Cabin > Age > Embarked。

测试数据集空值排序:Cabin > Age 。

各特征的数据类型是什么?

训练集:7个特征是整数或浮点数,字符串对象5个。

测试集:6个特征是整数或浮点数,字符串对象5个。

print(train_df.info())
print("--"*20)
print(test_df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
None
----------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  418 non-null    int64  
 1   Pclass       418 non-null    int64  
 2   Name         418 non-null    object 
 3   Sex          418 non-null    object 
 4   Age          332 non-null    float64
 5   SibSp        418 non-null    int64  
 6   Parch        418 non-null    int64  
 7   Ticket       418 non-null    object 
 8   Fare         417 non-null    float64
 9   Cabin        91 non-null     object 
 10  Embarked     418 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
None
  • 通过训练集各个特征的分布情况,我们可以得到一些很有用的信息:
    1. 训练集中有891条数据,泰塔尼克号实际有2224个客户,占旅客实际数量的40%
    2. 是否幸存是只有0或1的分类特征
    3. 约有38%的样本存活下来,但泰坦尼克号实际存活率为32%
    4. 大多数乘客(> 75%)没有和父母或孩子一起旅行
    5. 近30%的乘客有兄弟姐妹和/或配偶
    6. 票价差异很大,只有极少的乘客(<1%)支付的费用高达512美元
    7. 65-80岁年龄段的老年乘客很少(<1%)。

下面会有具体的数据展示

数值型分布

train_df.describe()
PassengerIdSurvivedPclassAgeSibSpParchFare
count891.000000891.000000891.000000714.000000891.000000891.000000891.000000
mean446.0000000.3838382.30864229.6991180.5230080.38159432.204208
std257.3538420.4865920.83607114.5264971.1027430.80605749.693429
min1.0000000.0000001.0000000.4200000.0000000.0000000.000000
25%223.5000000.0000002.00000020.1250000.0000000.0000007.910400
50%446.0000000.0000003.00000028.0000000.0000000.00000014.454200
75%668.5000001.0000003.00000038.0000001.0000000.00000031.000000
max891.0000001.0000003.00000080.0000008.0000006.000000512.329200

类别型特征的分布

 姓名在数据集中是唯一的(count = unique = 891)
 性别变量有2个值,其中男性占65%(top=男性,freq = 577 / count = 891)。
 船舱号在样本中具有多个重复项。也可以看出,有几个乘客共用一个船舱。
 港口有3个可能的值。大多数乘客使用的S端口(top= S)
 船票号具有很高的重复率(unique = 681),为22%。
# 类别型分布
train_df.describe(include=['O'])
NameSexTicketCabinEmbarked
count891891891204889
unique89126811473
topSlabenoff, Mr. Petcomale1601G6S
freq157774644

数据探索及猜想

数据分析猜想
基于到目前为止完成的数据分析,我们得出以下假设,在采取适当措施之前,我们可能会进一步验证这些假设。
1. Correlating.相关性
我们想知道每个特征与生存率的关联程度。找到他们的相关关系,尽可能在早期就去做它,并将相关性与项目后期的建模相关性进行匹配。
2. Completing.完成
我们可能要完成“年龄”特征,因为它肯定与生存相关。
我们可能要完成“港口”特征,因为它也可能与生存或其他重要功能相关。
3. Correcting.纠正
船票号特征可能会从我们的分析中删除,因为它包含很高的重复率(22%),并且船票号与生存率之间可能没有关联。
船舱号特征可能由于高度不完整或在训练和测试数据集中包含许多空值而被删除。
游客ID可能会从训练数据集中删除,因为它对生存没有帮助,没什么意义。
姓名特征是相对非标准的,可能不会直接有助于生存分析,因此可能会被放弃。
4. Creating.创建
我们可能想基于Parch和SibSp创建一个称为“家庭”的新特征,以获取船上家庭成员的总数。
我们可能要设计名称特征,以将姓提取为新特征。
我们可能要为年龄段创建新功能。这会将连续的数字特征转换为序数分类特征。
如果它有助于我们的分析,我们可能还想创建一个票价范围特征。
5. Classifying.分类
我们也可以根据前面提到的问题描述增加假设。
女性(性别=女性)更有可能存活下来。
儿童(年龄<?)存活的可能性更高。
上等乘客(Pclass = 1)更有可能幸存下来。

透视表分析

透视表分析,类似Excel中的数据透视表,可以看到数据的分布趋势

为了确认我们的一些观察和假设,我们可以通过使特征独立来快速分析特征的相关性。在此阶段,我们只能对没有任何空值的特征执行此操作。对分类型(Sex),有序型(Pclass)或离散型的(SibSp,Parch)的特征这样做也是有意义的。
 1. Pclass我们发现Pclass = 1和Survived之间存在显着相关性(> 0.5)。我们决定在模型中包括此功能。
 2. 性别,我们观察问题可以发现,即性别=女性具有很高的生存率,为74%。
 3. SibSp和Parch这些特征对于某些值零相关。最好是从这些单个特征中派生一个特征或一组特征,做一个更好的特征出来。
# 上等乘客更容易存活
train_df[['Pclass', 'Survived'
          ]].groupby(['Pclass'],
                     as_index=False).mean().sort_values(by='Survived',
                                                        ascending=False)
PclassSurvived
010.629630
120.472826
230.242363
# 女性更容易存活
train_df[['Sex', 'Survived']].groupby(['Sex'],as_index=False).mean().sort_values(by='Survived',
                                                        ascending=False)
SexSurvived
0female0.742038
1male0.188908
# 与乘客同行的兄弟姐妹和配偶数量
train_df[["SibSp", "Survived"
          ]].groupby(['SibSp'],
                     as_index=False).mean().sort_values(by='Survived',
                                                        ascending=False)
SibSpSurvived
110.535885
220.464286
000.345395
330.250000
440.166667
550.000000
680.000000
# 与乘客同行的家长和孩子的数目
train_df[["Parch", "Survived"
          ]].groupby(['Parch'],
                     as_index=False).mean().sort_values(by='Survived',
                                                        ascending=False)
ParchSurvived
330.600000
110.550847
220.500000
000.343658
550.200000
440.000000
660.000000
可视化分析

可视化分析,通过图表来形象的刻画数据,确认一些假设,为后续建模做准备

1. 关联数字特征
首先要了解数值特征与我们的求解目标之间的相关性。
一般使用直方图分析连续数字变量(例如Age),直方图可以使用自动定义的bin或等距范围的带指示样本的分布。这有助于我们回答与特定频段有关的问题(比如:婴儿的存活率更高吗?)
2. 观察可视化
婴儿(年龄<= 4)具有较高的存活率。
年龄最大的乘客(年龄= 80)幸存下来。
15至25岁的大批人无法生存。
大多数乘客年龄在15-35岁之间。
3. 决定
通过简单的可视化分析证实了我们的假设,为后面的建模做铺垫。
在模型训练中要考虑年龄。
补充年龄特征(空值)。
分组年龄。
g = sns.FacetGrid(train_df, col='Survived')
g.map(plt.hist, 'Age', bins=20)

在这里插入图片描述

sns.set()
sns.catplot(data=train_df, kind="swarm",  x="Sex",y="Age", hue="Survived")

在这里插入图片描述

# grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived')
grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend();

在这里插入图片描述

# grid = sns.FacetGrid(train_df, col='Embarked')
grid = sns.FacetGrid(train_df, row='Embarked', size=3.2, aspect=1.6)
grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette='deep')
grid.add_legend()

在这里插入图片描述

make数据

数据分析猜想

基于到目前为止完成的数据分析,我们已经得到了许多有用的信息,现在我们要利用之前得到的信息对我们的数据,进行选择,纠正,创造,选择我们需要的数据,化繁为简,以点透面。

删除特征

通过删除特性,我们可以处理更少的数据,加速数据处理,简化分析。
基于前面我们的分析和假设:

  • 删除Cabin(船舱号),它的缺失率很高(只有204条数据),空值太多且对于是否生存来说可能没啥效果
  • 删除Ticket(船票号),因为它包含很高的重复率(22%),并且船票号与生存率之间可能没有关联
    通常我们可以同时对训练和测试数据集删除无意义的字段。
print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)

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

"After", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape
Before (891, 12) (418, 11) (891, 12) (418, 11)

('After', (891, 10), (418, 9), (891, 10), (418, 9))

从现有特征中提取新特征,创造一个合适新特征可以让模型更加准确。

在去掉名字和乘客id特征之前,我们想分析一下是否可以通过名字这个特征来提取到一些有用的信息,仔细去看姓名这个特征会发现在姓前面是有记载着一些头衔的,Miss,Mr,Sir等等,这些会不会和生存有关系呢,提取出这些称呼看一下:

观察数据
某些头衔大多活了下来(夫人、女士、先生)
我们决定保留新的title特征用于模型训练。

#使用正则表达式提取标题
for dataset in combine:
    dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)

pd.crosstab(train_df['Title'], train_df['Sex'])
Sexfemalemale
Title
Capt01
Col02
Countess10
Don01
Dr16
Jonkheer01
Lady10
Major02
Master040
Miss1820
Mlle20
Mme10
Mr0517
Mrs1250
Ms10
Rev06
Sir01
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()
TitleSurvived
0Master0.575000
1Miss0.702703
2Mr0.156673
3Mrs0.793651
4Rare0.347826
dataset
PassengerIdPclassNameSexAgeSibSpParchFareEmbarkedTitle
08923Kelly, Mr. Jamesmale34.5007.8292QMr
18933Wilkes, Mrs. James (Ellen Needs)female47.0107.0000SMrs
28942Myles, Mr. Thomas Francismale62.0009.6875QMr
38953Wirz, Mr. Albertmale27.0008.6625SMr
48963Hirvonen, Mrs. Alexander (Helga E Lindqvist)female22.01112.2875SMrs
.................................
41313053Spector, Mr. WoolfmaleNaN008.0500SMr
41413061Oliva y Ocana, Dona. Ferminafemale39.000108.9000CRare
41513073Saether, Mr. Simon Sivertsenmale38.5007.2500SMr
41613083Ware, Mr. FrederickmaleNaN008.0500SMr
41713093Peter, Master. Michael JmaleNaN1122.3583CMaster

418 rows × 10 columns

#将分类特征转换成数值型
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)

train_df.head()
PassengerIdSurvivedPclassNameSexAgeSibSpParchFareEmbarkedTitle
0103Braund, Mr. Owen Harrismale22.0107.2500S1
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.01071.2833C3
2313Heikkinen, Miss. Lainafemale26.0007.9250S2
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01053.1000S3
4503Allen, Mr. William Henrymale35.0008.0500S1
#现在把姓名和PassengerId删除
print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)

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

"After", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape
Before (891, 11) (418, 10) (891, 11) (418, 10)

('After', (891, 9), (418, 8), (891, 9), (418, 8))

转换分类特性

大部分的模型都不能直接处理文本数据,需要将分类型数据转换成数值型,目前我们还有Sex性别变量需要进行转换

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

train_df.head()
SurvivedPclassSexAgeSibSpParchFareEmbarkedTitle
003022.0107.2500S1
111138.01071.2833C3
213126.0007.9250S2
311135.01053.1000S3
403035.0008.0500S1

现在我们已经得到了一份看起来很不错的数据,有我们需要的字段,但一些字段仍有很多的空值,会影响模型的准确度,接下来我们要使用一些方法进行空值填充,缺失值填充有许多方法,一般的有,特殊值填充,均值/众数/中位数填充,人工补充(利用数据间的联系进行补充或者重新采集等),机器学习方法填充等(回归,最大似然估计、聚类等)。值得一说的是实际情况如果对模型的精度的要求不高或者特征的作用有限的情况下,考虑到运行效率的问题可以删除一些缺失度较高的特征。

补充连续数值型特征

现在,我们应该开始评估和填充缺失值或空值,我们对于目前模型来说可以考虑三种方法来完成数值连续特征。

  • 一个简单的方法是在均值和标准偏差之间生成随机数。
  • 更准确的猜测缺失值的方法是使用其他相关特征。在我们的案例中,我们注意到年龄、性别和Pclass之间的相关性。我们可以使用Pclass和性别特征组合的年龄中位数猜测年龄值。因此,Pclass=1, Gender=0, Pclass=1, Gender=1,依此类推…
  • 结合方法1和2。因此,不要根据中位数猜测年龄值,而是根据Pclass和性别组合,在平均值和标准偏差之间使用随机数字。方法1和3将在我们的模型中引入随机噪声。多次执行的结果可能不同。我们更喜欢方法二。
# grid = sns.FacetGrid(train_df, col='Pclass', hue='Gender')
grid = sns.FacetGrid(train_df, row='Pclass', col='Sex', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend()

在这里插入图片描述

#现在开始用性别和Pclass 预测年龄,用数组储存
guess_ages = np.zeros((2,3))
guess_ages
array([[0., 0., 0.],
       [0., 0., 0.]])
#遍历 Sex和Pclass 进行预测
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()
            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)

train_df.head()
SurvivedPclassSexAgeSibSpParchFareEmbarkedTitle
003022107.2500S1
1111381071.2833C3
213126007.9250S2
3111351053.1000S3
403035008.0500S1
#切分年龄,将年龄分段看看和存活之间的关系
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)
AgeBandSurvived
0(-0.08, 16.0]0.550000
1(16.0, 32.0]0.337374
2(32.0, 48.0]0.412037
3(48.0, 64.0]0.434783
4(64.0, 80.0]0.090909
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']
train_df.head()
SurvivedPclassSexAgeSibSpParchFareEmbarkedTitleAgeBand
00301107.2500S1(16.0, 32.0]
111121071.2833C3(32.0, 48.0]
21311007.9250S2(16.0, 32.0]
311121053.1000S3(32.0, 48.0]
40302008.0500S1(32.0, 48.0]
#这样年龄段就做好了
train_df = train_df.drop(['AgeBand'], axis=1)
combine = [train_df, test_df]
train_df.head()
SurvivedPclassSexAgeSibSpParchFareEmbarkedTitle
00301107.2500S1
111121071.2833C3
21311007.9250S2
311121053.1000S3
40302008.0500S1

继续精简数据,利用SibSp和Parch创建一个新特征

#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)
FamilySizeSurvived
340.724138
230.578431
120.552795
670.333333
010.303538
450.200000
560.136364
780.000000
8110.000000
#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()
IsAloneSurvived
000.505650
110.303538
#使用IsAlone 代替'Parch', 'SibSp', 'FamilySize'
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]

train_df.head()
SurvivedPclassSexAgeFareEmbarkedTitleIsAlone
003017.2500S10
1111271.2833C30
213117.9250S21
3111253.1000S30
403028.0500S11

同样的 年龄和Pclass 我门也可以放到一起,做一个人工特征

for dataset in combine:
    dataset['Age*Class'] = dataset.Age * dataset.Pclass

train_df.loc[:, ['Age*Class', 'Age', 'Pclass']].head(10)
Age*ClassAgePclass
0313
1221
2313
3221
4623
5313
6331
7003
8313
9002

现在让我们把分类特征也补充完整

#使用港口的众数填充缺失值
freq_port = train_df.Embarked.dropna().mode()[0]
for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].fillna(freq_port)

train_df[['Embarked', 'Survived'
          ]].groupby(['Embarked'],
                     as_index=False).mean().sort_values(by='Survived', ascending=False)
EmbarkedSurvived
0C0.553571
1Q0.389610
2S0.339009
#转换成数值型                                                     
for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

train_df.head()
SurvivedPclassSexAgeFareEmbarkedTitleIsAloneAge*Class
003017.25000103
1111271.28331302
213117.92500213
3111253.10000302
403028.05000116

快速完成和转换的数字功能

现在,我们可以使用mode来完成测试数据集中单个缺失值的Fare特征,以获得该特征最频繁出现的值。我们只需要一行代码就可以完成。请注意,我们并没有创建一个中间的新特性,也没有做任何进一步的相关性分析来猜测丢失的特性,因为我们只替换了一个值。完成目标实现了模型算法对非空值操作的预期要求。我们可能还想把票价四舍五入到两个小数,因为它代表货币。

test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)
test_df.head()
PclassSexAgeFareEmbarkedTitleIsAloneAge*Class
03027.82922116
13127.00000306
22039.68752116
33018.66250113
431112.28750303
#票价分组
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)
FareBandSurvived
0(-0.001, 7.91]0.197309
1(7.91, 14.454]0.303571
2(14.454, 31.0]0.454955
3(31.0, 512.329]0.581081
#将票价特征转换为基于票价带的序数值
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]
 
train_df.head(10)
SurvivedPclassSexAgeFareEmbarkedTitleIsAloneAge*Class
0030100103
1111231302
2131110213
3111230302
4030210116
5030112113
6010330113
7030020400
8131110303
9121021300
test_df.head(10)
PclassSexAgeFareEmbarkedTitleIsAloneAge*Class
030202116
131200306
220312116
330110113
431110303
530010110
631102213
720120102
831101313
930120103

到此,我们完成了一份数据的制作,为接下来的建模做好了数据准备

建模、预测和解决问题

现在我们已经准备好训练模型并预测所需的解决方案了。
有60多种预测建模算法可供选择。我们必须了解问题的类型和解决方案的需求,将范围缩小到我们可以评估的少数几个模型。我们的问题是一个分类和回归的类问题,我们想要确定输出(是否存活)与其他变量或特征(性别、年龄、港口等)之间的关系。当我们用给定的数据集训练我们的模型时,我们也在进行一类被称为监督学习的机器学习。有了这两个标准-监督学习加上分类和回归,我们可以缩小我们的模型选择,包括:

  • 逻辑回归
  • SVM 支持向量机
  • K近邻
  • 朴素贝叶斯分类器
  • 决策树
  • 随机森林
  • 感知机
  • 人工神经网络
X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test  = test_df.copy()
X_train.shape, Y_train.shape, X_test.shape
((891, 8), (891,), (418, 8))
#看下我们的数据
X_train
PclassSexAgeFareEmbarkedTitleIsAloneAge*Class
030100103
111231302
231110213
311230302
430210116
...........................
88620110512
88711120211
88831120203
88910121111
89030102113

891 rows × 8 columns

逻辑回归

逻辑回归是一个非常经典的算法,虽然被称为回归,但其实际上是一个分类模型,并常用于二分类。

优点:就是简单、可并行化、可解释强。

缺点:对于多重共线性很敏感,如果出现高相关的特征,需要使用因子分析、聚类分析等方法进行拆解降维。

log = LogisticRegression()
log.fit(X_train,Y_train)
Y_pre = log.predict(X_test)
acc_log = round(log.score(X_train, Y_train) * 100, 2)
acc_log
80.36

我们可以使用逻辑回归来验证我们之前创建的一些特征,这可以通过计算决策函数中特征的系数来实现。正系数增加响应的对数概率(从而增加概率),负系数降低响应的对数概率(从而降低概率)。

  • 性别是最高的正相关系数,随着性别值的增加(男性:O到女性:1),存活的概率增加最多;
  • 随着Pclass(1为最高级,3为最低级)的增加,存活的概率降低最大;
  • 因此年龄*等级是一个很好的人工特征来建模,因为它与存活的负相关系数是第二高;
  • 头衔是第二高的正相关系数。
pd.DataFrame(log.coef_)
01234567
0-0.75072.2016190.287011-0.0866550.2614730.3978880.126553-0.311069
coeff_df = pd.DataFrame(train_df.columns.delete(0))
coeff_df.columns = ['Feature']
coeff_df["Correlation"] = pd.Series(log.coef_[0])

coeff_df.sort_values(by='Correlation', ascending=False)
FeatureCorrelation
1Sex2.201619
5Title0.397888
2Age0.287011
4Embarked0.261473
6IsAlone0.126553
3Fare-0.086655
7Age*Class-0.311069
0Pclass-0.750700

SVM支持向量机

已监督学习方式对数据进行二元分类的广义线性分类器,简单来说就是进行一个二分类,求解最优的那个分类面,然后用这个最优解进行分类

优点:鲁棒性强,对样本要求不高。

缺点:SVM的超参数需要通过交叉验证得到,非常耗费时间,而且SVM的核函数必须是正定的,计算量大,多分类很难使用。

#此处效果不是很好
svm = SVC()
svm.fit(X_train,Y_train)
Y_pred = svm.predict(X_test)
acc_svm = round(svm.score(X_train, Y_train) * 100, 2)
acc_svm
78.23
#线性SVM
linear_svc = LinearSVC()
linear_svc.fit(X_train, Y_train)
Y_pred = linear_svc.predict(X_test)
acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)
acc_linear_svc
79.12

KNN最近邻

knn是测量不同特征值之间的距离来进行分类,类似于近朱者赤近墨者黑,往往通过轮廓系数、交叉检验来找最优解

优点:简单易懂。

缺点:计算量大,对于不平衡样本处理起来较困难。

# 一般使用knn 是需要将数据进行标准化,消除量纲的影响(SVM等 需要计算距离的模型一般都要进行去量纲的操作)
Knn = KNeighborsClassifier()
Knn.fit(X_train,Y_train)
Y_pred = Knn.predict(X_test)
acc_Knn = round(Knn.score(X_train, Y_train) * 100, 2)
acc_Knn
83.95

朴素贝叶斯分类器

贝叶斯模型非常特殊,是一个概率模型,通过事件属性相关事件发生的概率(先验概率)去推测该事件发生的概率。

优点:对缺失数据不太敏感,算法也比较简单,常用于文本分类、邮件分类等。

缺点:贝叶斯是一个理论上的模型,主要是因为贝叶斯首先是假设各特征之间是相互独立,通常很难保证,还有先验概率通常是假设出来,并不一定准确。

值得一提的是贝叶斯网络,也称信念网络目前是最火热的模型之一,毕竟“信贝爷, 得永生”

# Gaussian Naive Bayes
bys = GaussianNB()
bys.fit(X_train, Y_train)
Y_pred = bys.predict(X_test)
acc_gaussian = round(bys.score(X_train, Y_train) * 100, 2)
acc_gaussian
72.28

决策树

决策树(分类树)是一种十分常用的分类方法,使用信息熵增益、信息熵增益率、Gini系数等进行剪枝寻求最优解

优点:可解释性强、对样本要求较低。

缺点:容易过拟合,寻求最优解往往会形成一个NP难(能在多项式时间内验证得出一个正确解)问题。

decision_tree = DecisionTreeClassifier()
decision_tree.fit(X_train, Y_train)
Y_pred = decision_tree.predict(X_test)
acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)
acc_decision_tree
86.76

Random forest 随机森林

随机森林是一个包含多个决策树的分类器, 并且其输出的类别是由个别树输出的类别的众数而定

优点:鲁棒性好,既可以分类又可以回归,准确度高。

缺点:黑盒模型、计算量大。

random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
acc_random_forest
86.76

感知机

找一个超平面来分类

优点:简单。

缺点:通常只能用来二分类问题,对于非线性问题效果差。

perceptron = Perceptron()
perceptron.fit(X_train, Y_train)
Y_pred = perceptron.predict(X_test)
acc_perceptron = round(perceptron.score(X_train, Y_train) * 100, 2)
acc_perceptron
78.34

人工神经网络

人工神经网络就是模拟人思维的第二种方式。这是一个非线性动力学系统,其特色在于信息的分布式存储和并行协同处理。

优点:准。

缺点:黑盒模型、计算要求高,有时候可能比挖比特币更复杂,有时间跑神经网络我为什么不用来挖比特币呢。

ann = MLPClassifier()
ann.fit(X_train, Y_train)
Y_pred = lf.predict(X_test)
acc_ann = round(ann.score(X_train, Y_train) * 100, 2)
acc_ann
83.5

模型对比

对所有模型进行对比,选出最好的模型

# Random Forest和Decision Tree 评分相同 但是随机森林没有过拟合的问题,所以选择随机森林
models = pd.DataFrame({
    'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression', 
              'Random Forest', 'Naive Bayes', 'Perceptron', 
              'Artificial Neural Networks', 'Linear SVC', 
              'Decision Tree'],
        'Score': [acc_svm, acc_Knn, acc_log, 
              acc_random_forest, acc_gaussian, acc_perceptron, 
              acc_ann, acc_linear_svc, acc_decision_tree]})
models.sort_values(by='Score', ascending=False)
ModelScore
3Random Forest86.76
8Decision Tree86.76
1KNN83.95
6Artificial Neural Networks83.50
2Logistic Regression80.36
7Linear SVC79.12
5Perceptron78.34
0Support Vector Machines78.23
4Naive Bayes72.28
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
X_test['Survived']=Y_pred

如果你懂了 你应该知道你能不能活下来(老凡尔赛了)

X_test[X_test['Survived']==1]
PclassSexAgeFareEmbarkedTitleIsAloneAge*ClassSurvived
4311103031
6311022131
8311013131
11102201121
12111303011
..............................
410311022131
411112323021
412311002131
414112315121
417301214031

152 rows × 9 columns

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 护眼 设计师:闪电赇 返回首页