泰坦尼克号数据科学解决方案
关于著名的泰坦尼克号数据分析应该会有一系列的文章,本文是参考于 《Titanic Data Science Solutions》, 作为自己的学习,也希望能对你有帮助。
数据科学一般的工作流程
- 定义问题
- 获取训练集和测试集
- 分析、探索数据
- 建立模型和预测数据
- 数据可视化以及结论报告
- 提交答案
当然实际情况并不完全遵守这个流程的,有可能会省略一些步骤,也可能会互换顺序,希望我们也不要太墨守陈规。
定义问题
从一组样本中得知,谁在泰坦尼克号灾难中遇难或幸存下来,我们的模型可以根据一个给定的测试数据集的信息来确定测试数据集中这些乘客是否幸存下来。
审题是必须的
对于做数据分析来讲,审题是非常重要的,尽可能的去读懂数据,已经获取数据样本中的所有信息。比如我们这个命题的数据描述中有几个要点我们需要留意。
- 泰坦尼克号这次航行事故中在2224个乘客和工作人员中有1502遇难生亡。我们得出幸存率是32%。
- 造成这么重大的损失的原因是因为没有足够的救生艇。
- 虽然幸存下来有一些运气因素,但是一些人比其他人更可能生存,比如妇女,儿童和上层阶级等。
明确目标
总结了下我们有7大目标
- Classifying:我们可能想对我们的样本进行分类,我们可能还想了解不同类与解决方案目标之间的关联。
- Correlating:可以根据培训数据集中的可用特性来解决这个问题,数据集中的哪些特性对我们的解决方案会提供显著的帮助?即哪些特征与我们要解决的目标有显著相关关系。
- Converting:建模阶段时候根据选择的模型算法要求所有的特征值必须转化为数值变量。那么需要将文本分类转化为数值型。
- Completing:处理缺失值。
- Correcting:同样要去分析判断训练集中有哪些不精确的值或者说是异常值,改正他们或剔除。我们还可能彻底抛弃那些完全扭曲结果的特征。
- Creating:如果需要,我们能基于现有的特性或一组特性来创建新特性。
- Charting:根据数据的性质和目标选择正确的可视化图表。
Let’s start
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.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
combine = [train_df,test_df]
train_df.columns.values
array(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype=object)
- 首先看下哪些是类别值,那些是数值。
Categorical: Survived, Sex, and Embarked. Ordinal: Pclass.
Continous: Age, Fare. Discrete: SibSp, Parch.
train_df.head()
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
哪些是混合数据类型?
Ticket 是字母和数字混合类型. Cabin 是字母.
哪些可能包含拼写错误?
name这一列很可能有哦
train_df.tail()
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
886 | 887 | 0 | 2 | Montvila, Rev. Juozas | male | 27.0 | 0 | 0 | 211536 | 13.00 | NaN | S |
887 | 888 | 1 | 1 | Graham, Miss. Margaret Edith | female | 19.0 | 0 | 0 | 112053 | 30.00 | B42 | S |
888 | 889 | 0 | 3 | Johnston, Miss. Catherine Helen "Carrie" | female | NaN | 1 | 2 | W./C. 6607 | 23.45 | NaN | S |
889 | 890 | 1 | 1 | Behr, Mr. Karl Howell | male | 26.0 | 0 | 0 | 111369 | 30.00 | C148 | C |
890 | 891 | 0 | 3 | Dooley, Mr. Patrick | male | 32.0 | 0 | 0 | 370376 | 7.75 | NaN | Q |
哪些特征包含空白,null 值?
- 训练集:Cabin > Age > Embarked
- 测试集:Cabin > Age > Embarked
train_df.info()
print('_'*40)
test_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
________________________________________
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Name 418 non-null object
Sex 418 non-null object
Age 332 non-null float64
SibSp 418 non-null int64
Parch 418 non-null int64
Ticket 418 non-null object
Fare 417 non-null float64
Cabin 91 non-null object
Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
- 强烈建议你做一波数据探索,尽可能多的罗列一些信息出来,对于后面的方向与思路会有帮助。例如“接近30%的乘客有兄弟姐妹或者伴侣同时在这条船上”之类的。
train_df.describe(percentiles=[.1, .2, .3, .4, .5, .6, .7, .8, .9, .99])
PassengerId | Survived | Pclass | Age | SibSp | Parch | Fare | |
---|---|---|---|---|---|---|---|
count | 891.000000 | 891.000000 | 891.000000 | 714.000000 | 891.000000 | 891.000000 | 891.000000 |
mean | 446.000000 | 0.383838 | 2.308642 | 29.699118 | 0.523008 | 0.381594 | 32.204208 |
std | 257.353842 | 0.486592 | 0.836071 | 14.526497 | 1.102743 | 0.806057 | 49.693429 |
min | 1.000000 | 0.000000 | 1.000000 | 0.420000 | 0.000000 | 0.000000 | 0.000000 |
10% | 90.000000 | 0.000000 | 1.000000 | 14.000000 | 0.000000 | 0.000000 | 7.550000 |
20% | 179.000000 | 0.000000 | 1.000000 | 19.000000 | 0.000000 | 0.000000 | 7.854200 |
30% | 268.000000 | 0.000000 | 2.000000 | 22.000000 | 0.000000 | 0.000000 | 8.050000 |
40% | 357.000000 | 0.000000 | 2.000000 | 25.000000 | 0.000000 | 0.000000 | 10.500000 |
50% | 446.000000 | 0.000000 | 3.000000 | 28.000000 | 0.000000 | 0.000000 | 14.454200 |
60% | 535.000000 | 0.000000 | 3.000000 | 31.800000 | 0.000000 | 0.000000 | 21.679200 |
70% | 624.000000 | 1.000000 | 3.000000 | 36.000000 | 1.000000 | 0.000000 | 27.000000 |
80% | 713.000000 | 1.000000 | 3.000000 | 41.000000 | 1.000000 | 1.000000 | 39.687500 |
90% | 802.000000 | 1.000000 | 3.000000 | 50.000000 | 1.000000 | 2.000000 | 77.958300 |
99% | 882.100000 | 1.000000 | 3.000000 | 65.870000 | 5.000000 | 4.000000 | 249.006220 |
max | 891.000000 | 1.000000 | 3.000000 | 80.000000 | 8.000000 | 6.000000 | 512.329200 |
# 对于分类特征的描述
train_df.describe(include=['O'])
Name | Sex | Ticket | Cabin | Embarked | |
---|---|---|---|---|---|
count | 891 | 891 | 891 | 204 | 889 |
unique | 891 | 2 | 681 | 147 | 3 |
top | Williams, Mr. Charles Eugene | male | 1601 | C23 C25 C27 | S |
freq | 1 | 577 | 7 | 4 | 644 |
基于数据分析的假设
我们根据目前所做的数据分析得出了以下假设。在采取适当的行动之前,我们可能会进一步验证这些假设。
- 首先我们要了解哪些特征是与Survival有怎样的相关性。
- 猜想Age和Embarked这两个特征与目标应该有较强的相关性。
- Ticket这个特征应该被丢弃掉,因为有很高的重复率另外它与是否存亡也没有什么关系。
- Cabin也应该被丢弃,它有太多的null值了。
- PassengerId 也应该被丢弃,分析过它没有太多的意义
-
Name特征也去除。
- 创建一个叫Family的新特征,基于Parch 和SibSp。
- 基于Name特征创建一个Title的特征。
- 创建一个新的Age bands特征,将Age由连续型数据转变为有序的分类型数据。
- 最后创建一个Fare range 特征。
设计分类假设
- 妇女(Sex = female)更容易幸存。
- 小孩(Age<?)更容易幸存。
- 社会层级在(Pclass=1)更容易幸存。
分析确认
我们需要确认我们的分类假设。首先快速地通过各自的相关性分析。这个分析对于数值型、分类型、序数和离散型的数据都有意义,但是不包含空值。
- Pclass:当Placss=1时,我们得到与Survival的相关性>0.5,所以保留它在模型内。 Sex:当Sex= female时,幸存率高达74%,故保留。
train_df[['Pclass','Survived']].groupby(['Pclass'],as_index=False).mean().sort_values(by='Survived',ascending = False)
Pclass | Survived | |
---|---|---|
0 | 1 | 0.629630 |
1 | 2 | 0.472826 |
2 | 3 | 0.242363 |
train_df[['Sex','Survived']].groupby(by='Sex',as_index=False).mean().sort_values(by='Survived', ascending=False)
Sex | Survived | |
---|---|---|
0 | female | 0.742038 |
1 | male | 0.188908 |
train_df[['SibSp','Survived']].groupby('SibSp',as_index=False).mean().sort_values(by='Survived', ascending=False)
SibSp | Survived | |
---|---|---|
1 | 1 | 0.535885 |
2 | 2 | 0.464286 |
0 | 0 | 0.345395 |
3 | 3 | 0.250000 |
4 | 4 | 0.166667 |
5 | 5 | 0.000000 |
6 | 8 | 0.000000 |
train_df[['Parch','Survived']].groupby('Parch',as_index=False).mean().sort_values(by='Survived', ascending=False)
Parch | Survived | |
---|---|---|
3 | 3 | 0.600000 |
1 | 1 | 0.550847 |
2 | 2 | 0.500000 |
0 | 0 | 0.343658 |
5 | 5 | 0.200000 |
4 | 4 | 0.000000 |
6 | 6 | 0.000000 |
图表可视化
-
直方图对于连续性数据变现非常有用
-
观察结论
- Age<= 4 有很高的幸存率。
- Age=80 幸存。
- 大多数15-25岁的没有幸存。
- 15-35年龄层的人数最多。
g = sns.FacetGrid(train_df,col='Survived',hue='Survived',hue_order=[0,1])
g.map(plt.hist,'Age',bins=20)
<seaborn.axisgrid.FacetGrid at 0x2c4dfb8cf98>
g = sns.FacetGrid(train_df,row='Survived',hue='Survived',hue_order=[0,1])
g.map(plt.hist,'SibSp',bins=20)
<seaborn.axisgrid.FacetGrid at 0x2c4dfd396a0>
关联数值和序数特征
- 观察结论有:
- Pclass=3的乘客大多数没有幸存。
- Pclass=2和Pclass=3乘客中的婴儿基本幸存。
- 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)
grid.add_legend()
<seaborn.axisgrid.FacetGrid at 0x2c4dfe21ef0>
关联分类特征
- 观察结论
- 女性的幸存率高于男性
- 观察比较各个港口的男女幸存情况后,发现因为港口不同而存在差异。
- 故:
- 将性别特征加入模型。
- 将Embarked特征加入模型。
# 点图的取值默认是np.mean 即estimator=np.mean
grid = sns.FacetGrid(train_df, row='Embarked', size=2.2, aspect=1.6)
grid.map(sns.pointplot, 'Pclass', 'Survived','Sex', palette='deep',order=[1,2,3], hue_order=["female","male"])
grid.add_legend()
sns.pointplot()
<matplotlib.axes._subplots.AxesSubplot at 0x2c4e053ff98>
关联分类和数值特征
- 观察结论
- 貌似高票价的有更高的存活率
- 港口号貌似与Survival存在关联性
- 故:
- Consider banding Fare feature.
# grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'k', 1: 'w'})
grid = sns.FacetGrid(train_df, row='Embarked', col='Survived', size=2.2, aspect=1.6)
grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)
grid.add_legend()
<seaborn.axisgrid.FacetGrid at 0x2c4e04b49e8>
sns.barplot(train_df.SibSp,train_df.Sex)
<matplotlib.axes._subplots.AxesSubplot at 0x2c4e09cfd30>
处理样本数据
到目前为止我们已有许多个假设和相对的结论了,那么接下来我们就开始根据我们的结论处理我们的数据集。
- 修正和丢弃一些特征
- 丢弃Cabin和Ticket两个特征
print('Before',train_df.shape,test_df.shape)
train_df = train_df.drop(['Ticket','Cabin'],axis=1)
test_df = test_df.drop(['Ticket','Cabin'],axis=1)
combine =[train_df,test_df]
print('After',combine[0].shape,combine[1].shape)
Before (891, 12) (418, 11)
After (891, 10) (418, 9)
创建新特征
- 想要基于Name创建新的特征Title,然后测试Title与Survival的相关性,由于Name是文本类型,所以可能会需要用到正则表达式。
for dataset in combine:
dataset['Title'] = dataset.Name.str.extract('([A-Za-z]+)\.',expand=False)
pd.crosstab(train_df['Title'], train_df['Sex'])
Sex | female | male |
---|---|---|
Title | ||
Capt | 0 | 1 |
Col | 0 | 2 |
Countess | 1 | 0 |
Don | 0 | 1 |
Dr | 1 | 6 |
Jonkheer | 0 | 1 |
Lady | 1 | 0 |
Major | 0 | 2 |
Master | 0 | 40 |
Miss | 182 | 0 |
Mlle | 2 | 0 |
Mme | 1 | 0 |
Mr | 0 | 517 |
Mrs | 125 | 0 |
Ms | 1 | 0 |
Rev | 0 | 6 |
Sir | 0 | 1 |
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()
Title | Survived | |
---|---|---|
0 | Master | 0.575000 |
1 | Miss | 0.702703 |
2 | Mr | 0.156673 |
3 | Mrs | 0.793651 |
4 | Rare | 0.347826 |
我们将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)
train_df.head()
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Fare | Embarked | Title | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | 7.2500 | S | 1 |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | 71.2833 | C | 3 |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | 7.9250 | S | 2 |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 53.1000 | S | 3 |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 8.0500 | S | 1 |
train_df = train_df.drop(['Name','PassengerId'],axis=1)
test_df = test_df.drop('Name',axis=1)
combine = [train_df,test_df]
- 转换分类特征
将分类特征中的字符转换为数值替代。
for dataset in combine:
dataset['Sex'] = dataset['Sex'].map({'female':1,'male':0}).astype(int)
train_df.head()
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | Title | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 22.0 | 1 | 0 | 7.2500 | S | 1 |
1 | 1 | 1 | 1 | 38.0 | 1 | 0 | 71.2833 | C | 3 |
2 | 1 | 3 | 1 | 26.0 | 0 | 0 | 7.9250 | S | 2 |
3 | 1 | 1 | 1 | 35.0 | 1 | 0 | 53.1000 | S | 3 |
4 | 0 | 3 | 0 | 35.0 | 0 | 0 | 8.0500 | S | 1 |
完善数值连续特征
完善这一特征——处理特征中的缺失值,主要有三个方法: - 最简单的方式是直接采用用均值或者标准偏差。 - 更加精准的方法是通过其他相关的特征在同等情况下去猜缺失值,可以用均值,这个案例我们注意到的存在相关性在是Age、Gender和Pclass。所以我们应该是Gender和Pclass组合的一些均值去猜Age值。 比如Pclass=1 和 Gender=0情况下的Age中位数,Pclass=1 和 Gender=1这种情况呢?等等
我们下面就用第二种方法试试
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()
<seaborn.axisgrid.FacetGrid at 0x2c4e0a72c88>
先准备一个空的array去容纳Age基于Pclass和Gender的组合
guess_ages = np.zeros((2,3))
guess_ages
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
现在我们对性别(0或1)和Pclass(1、2、3)进行迭代,以计算6种组合的猜测值
for dataset in combine:
for i in range(0,2):
for j in range(0,3):
#获取Sex和Pclass所有情况组合下的Age的均值(不包含NA值)
guess_df = dataset[(dataset['Sex']==i) & (dataset['Pclass']==j+1)]['Age'].dropna()
#留意mean有一个skipna参数,如果设置True那么上一行代码就不需要做.dropna()处理了。两种方式效果一样
age_guess = guess_df.mean()
# 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):
# 将guess_ages中的值填入dataset对应情况下的Age列中
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()
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | Title | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 22 | 1 | 0 | 7.2500 | S | 1 |
1 | 1 | 1 | 1 | 38 | 1 | 0 | 71.2833 | C | 3 |
2 | 1 | 3 | 1 | 26 | 0 | 0 | 7.9250 | S | 2 |
3 | 1 | 1 | 1 | 35 | 1 | 0 | 53.1000 | S | 3 |
4 | 0 | 3 | 0 | 35 | 0 | 0 | 8.0500 | S | 1 |
我们创建年龄段列确定下和Survival的相关性
# 将所有年龄分为5段
train_df['AgeBand'] = pd.cut(train_df['Age'],5,precision=1)
train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)
AgeBand | Survived | |
---|---|---|
0 | (-0.08, 16] | 0.550000 |
1 | (16, 32] | 0.337374 |
2 | (32, 48] | 0.412037 |
3 | (48, 64] | 0.434783 |
4 | (64, 80] | 0.090909 |
将年龄分段列的值转换为序数值
for dataset in combine:
dataset.loc[ dataset['Age'] <= 16.3, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16.3) & (dataset['Age'] <= 32.3), 'Age'] = 1
dataset.loc[(dataset['Age'] > 32.3) & (dataset['Age'] <= 48.2), 'Age'] = 2
dataset.loc[(dataset['Age'] > 48.2) & (dataset['Age'] <= 64.08), 'Age'] = 3
dataset.loc[ dataset['Age'] > 64.08, 'Age'] = 4
dataset['Age'] = dataset['Age'].astype(int)
train_df.head()
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | Title | AgeBand | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 1 | 1 | 0 | 7.2500 | S | 1 | (16, 32] |
1 | 1 | 1 | 1 | 2 | 1 | 0 | 71.2833 | C | 3 | (32, 48] |
2 | 1 | 3 | 1 | 1 | 0 | 0 | 7.9250 | S | 2 | (16, 32] |
3 | 1 | 1 | 1 | 2 | 1 | 0 | 53.1000 | S | 3 | (32, 48] |
4 | 0 | 3 | 0 | 2 | 0 | 0 | 8.0500 | S | 1 | (32, 48] |
移除AgeBand特征
train_df = train_df.drop(['AgeBand'], axis=1)
combine = [train_df, test_df]
train_df.head()
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | Title | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 1 | 1 | 0 | 7.2500 | S | 1 |
1 | 1 | 1 | 1 | 2 | 1 | 0 | 71.2833 | C | 3 |
2 | 1 | 3 | 1 | 1 | 0 | 0 | 7.9250 | S | 2 |
3 | 1 | 1 | 1 | 2 | 1 | 0 | 53.1000 | S | 3 |
4 | 0 | 3 | 0 | 2 | 0 | 0 | 8.0500 | S | 1 |
创建新的特征
创建FamilySize
for dataset in combine:
dataset['FamilySize'] = dataset['SibSp']+ dataset['Parch']+1
train_df[['FamilySize','Survived']].groupby(['FamilySize'],as_index=False).agg('mean').sort_values(by='Survived',ascending=False)
FamilySize | Survived | |
---|---|---|
3 | 4 | 0.724138 |
2 | 3 | 0.578431 |
1 | 2 | 0.552795 |
6 | 7 | 0.333333 |
0 | 1 | 0.303538 |
4 | 5 | 0.200000 |
5 | 6 | 0.136364 |
7 | 8 | 0.000000 |
8 | 11 | 0.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()
IsAlone | Survived | |
---|---|---|
0 | 0 | 0.505650 |
1 | 1 | 0.303538 |
移除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()
Survived | Pclass | Sex | Age | Fare | Embarked | Title | IsAlone | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 1 | 7.2500 | S | 1 | 0 |
1 | 1 | 1 | 1 | 2 | 71.2833 | C | 3 | 0 |
2 | 1 | 3 | 1 | 1 | 7.9250 | S | 2 | 1 |
3 | 1 | 1 | 1 | 2 | 53.1000 | S | 3 | 0 |
4 | 0 | 3 | 0 | 2 | 8.0500 | S | 1 | 1 |
创建Age*Class特征
for dataset in combine:
dataset['Age*Class'] = dataset.Age * dataset.Pclass
train_df.loc[:9, ['Age*Class', 'Age', 'Pclass']]
Age*Class | Age | Pclass | |
---|---|---|---|
0 | 3 | 1 | 3 |
1 | 2 | 2 | 1 |
2 | 3 | 1 | 3 |
3 | 2 | 2 | 1 |
4 | 6 | 2 | 3 |
5 | 3 | 1 | 3 |
6 | 3 | 3 | 1 |
7 | 0 | 0 | 3 |
8 | 3 | 1 | 3 |
9 | 0 | 0 | 2 |
完善类别特征
用该列众数填充缺失值
# 获取这列的众数
freq_port = train_df.Embarked.dropna().mode()[0]
freq_port
'S'
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)
Embarked | Survived | |
---|---|---|
0 | C | 0.553571 |
1 | Q | 0.389610 |
2 | S | 0.339009 |
- 转换分类特征
将类别特征中的字符转换为数值替代。
for dataset in combine:
dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
train_df.head()
Survived | Pclass | Sex | Age | Fare | Embarked | Title | IsAlone | Age*Class | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 1 | 7.2500 | 0 | 1 | 0 | 3 |
1 | 1 | 1 | 1 | 2 | 71.2833 | 1 | 3 | 0 | 2 |
2 | 1 | 3 | 1 | 1 | 7.9250 | 0 | 2 | 1 | 3 |
3 | 1 | 1 | 1 | 2 | 53.1000 | 0 | 3 | 0 | 2 |
4 | 0 | 3 | 0 | 2 | 8.0500 | 0 | 1 | 1 | 6 |
快速的完善和转化Fare特征
用中位数填充NA值
test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)
test_df.head()
PassengerId | Pclass | Sex | Age | Fare | Embarked | Title | IsAlone | Age*Class | |
---|---|---|---|---|---|---|---|---|---|
0 | 892 | 3 | 0 | 2 | 7.8292 | 2 | 1 | 1 | 6 |
1 | 893 | 3 | 1 | 2 | 7.0000 | 0 | 3 | 0 | 6 |
2 | 894 | 2 | 0 | 3 | 9.6875 | 2 | 1 | 1 | 6 |
3 | 895 | 3 | 0 | 1 | 8.6625 | 0 | 1 | 1 | 3 |
4 | 896 | 3 | 1 | 1 | 12.2875 | 0 | 3 | 0 | 3 |
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)
FareBand | Survived | |
---|---|---|
0 | [0, 7.91] | 0.197309 |
1 | (7.91, 14.454] | 0.303571 |
2 | (14.454, 31] | 0.454955 |
3 | (31, 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)
Survived | Pclass | Sex | Age | Fare | Embarked | Title | IsAlone | Age*Class | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 1 | 0 | 0 | 1 | 0 | 3 |
1 | 1 | 1 | 1 | 2 | 3 | 1 | 3 | 0 | 2 |
2 | 1 | 3 | 1 | 1 | 1 | 0 | 2 | 1 | 3 |
3 | 1 | 1 | 1 | 2 | 3 | 0 | 3 | 0 | 2 |
4 | 0 | 3 | 0 | 2 | 1 | 0 | 1 | 1 | 6 |
5 | 0 | 3 | 0 | 1 | 1 | 2 | 1 | 1 | 3 |
6 | 0 | 1 | 0 | 3 | 3 | 0 | 1 | 1 | 3 |
7 | 0 | 3 | 0 | 0 | 2 | 0 | 4 | 0 | 0 |
8 | 1 | 3 | 1 | 1 | 1 | 0 | 3 | 0 | 3 |
9 | 1 | 2 | 1 | 0 | 2 | 1 | 3 | 0 | 0 |
test_df.head(10)
PassengerId | Pclass | Sex | Age | Fare | Embarked | Title | IsAlone | Age*Class | |
---|---|---|---|---|---|---|---|---|---|
0 | 892 | 3 | 0 | 2 | 0 | 2 | 1 | 1 | 6 |
1 | 893 | 3 | 1 | 2 | 0 | 0 | 3 | 0 | 6 |
2 | 894 | 2 | 0 | 3 | 1 | 2 | 1 | 1 | 6 |
3 | 895 | 3 | 0 | 1 | 1 | 0 | 1 | 1 | 3 |
4 | 896 | 3 | 1 | 1 | 1 | 0 | 3 | 0 | 3 |
5 | 897 | 3 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
6 | 898 | 3 | 1 | 1 | 0 | 2 | 2 | 1 | 3 |
7 | 899 | 2 | 0 | 1 | 2 | 0 | 1 | 0 | 2 |
8 | 900 | 3 | 1 | 1 | 0 | 1 | 3 | 1 | 3 |
9 | 901 | 3 | 0 | 1 | 2 | 0 | 1 | 0 | 3 |
模型预测
现在我们已经准备好训练模型并预测所需的解决方案,有60多种预测建模算法可供选择。我们必须了解问题的类型和解决方案的需求,将其缩小到可以评估的少数几种模型,我们的问题是分类和回归问题,我们想要确定输出(存活与否)与其他变量或特性(Gender, Age, Port…)之间的关系。 我们也正在执行一类机器学习,这个机器学习称为监督学习,因为我们正在用给定的数据集来训练我们的模型。有了这两个标准—监督学习加上分类和回归,我们可以将模型的选择范围缩小到以下:
- Logistic Regression
- KNN or k-Nearest Neighbors
- Support Vector Machines
- Naive Bayes classifier
- Decision Tree
- Random Forrest
- Perceptron
- Artificial neural network
- RVM or Relevance Vector Machine
X_train = train_df.drop('Survived',axis=1)
Y_train = train_df['Survived']
X_test = test_df.drop('PassengerId',axis=1).copy()
X_train.shape,Y_train.shape,X_test.shape
((891, 8), (891,), (418, 8))
逻辑回归是在工作流早期运行的一个有用的模型,逻辑回归测量了分类依赖变量(特征)和一个或多个自变量(特征)之间的关系,通过使用逻辑函数估计概率,这是累积的逻辑分布。
请注意基于我们的训练数据集的模型所产生的信心分数。
- Logistic Regression
logreg = LogisticRegression()
logreg.fit(X_train,Y_train)
Y_pred = logreg.predict(X_test)
# train数据中训练出来的分类器在X_test上的分数
acc_log = round(logreg.score(X_train,Y_train)*100,2)
acc_log
81.260000000000005
我们可以使用逻辑回归来验证我们的假设和决定,以创建和完成目标。这可以通过计算决策函数中特性的系数来完成。
正系数增加了响应的对数(从而增加了概率),而负系数会降低响应的对数(从而降低概率)。
- Sex是最高的正系数,意味着Sex的值增加(male: 0 to female: 1),Survived=1的可能性增加最显著。
- 相反Pclass增加,Survived=1的可能性减少最显著。
- Age*Class是第二高的负相关特征。
- Title 是第二高的正相关特征。
coeff_df =pd.DataFrame(train_df.columns.delete(0))
coeff_df.columns = ['Feature']
# logreg.coef_获取决策函数中特性系数
coeff_df["Correlation"] = pd.Series(logreg.coef_[0])
coeff_df.sort_values(by='Correlation', ascending=False)
Feature | Correlation | |
---|---|---|
1 | Sex | 2.200978 |
5 | Title | 0.414362 |
4 | Embarked | 0.281026 |
6 | IsAlone | 0.262084 |
3 | Fare | -0.022062 |
7 | Age*Class | -0.085959 |
2 | Age | -0.371647 |
0 | Pclass | -1.083515 |
- Support Vector Machines
支持向量机是具有关联的学习算法的监督学习模型,其分析用于分类和回归分析的数据。给定一组训练样本,每个标记属于两个类别中的一个或另一个。支持向量机构建一个将新的测试样本分配给一个类别或另一个类别的模型,使之成为非概率的二元线性分类器。
值得注意的是,模型生成的置信分数要比物流回归模型高。
svc = SVC()
svc.fit(X_train,Y_train)
Y_pred = svc.predict(X_test)
acc_svc = round(svc.score(X_train, Y_train) * 100, 2)
acc_svc
83.5
- K-Nearest Neighbors
在图像识别方面,k-最近邻算法是一种用于分类和回归的非参数方法。样本通过邻居的多数投票进行分类,样本被分配到最近k个近邻中最常见的类别中。
KNN confidence score is better than Logistics Regression but worse than SVM.
# 当然这里的n_neighbors=3是不断调参得到的最优。
knn = KNeighborsClassifier(n_neighbors = 3)
knn.fit(X_train,Y_train)
knn.predict(X_test)
acc_knn = round(knn.score(X_train, Y_train) * 100, 2)
acc_knn
84.060000000000002
- naive Bayes classifiers
The model generated confidence score is the lowest among the models evaluated so far.
gaussian = GaussianNB()
gaussian.fit(X_train,Y_train)
gaussian.predict(X_test)
acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)
acc_gaussian
76.879999999999995
- 感知机
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.790000000000006
- Linear SVC
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.459999999999994
- Stochastic Gradient Descent
sgd = SGDClassifier()
sgd.fit(X_train, Y_train)
Y_pred = sgd.predict(X_test)
acc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)
acc_sgd
67.0
- Decision Tree
该模型使用决策树作为预测模型,它将特征(树分支)映射到目标值(树叶子),目标变量可以采用有限的一组值的树模型称为分类树; 目标变量可以采用连续值(通常是实数)的决策树称为回归树。
The model confidence score is the highest among models evaluated so far
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.640000000000001
- Random Forest
最通俗理解,随机森林就是很多颗决策树……其他的自行查阅文档。
The model confidence score is the highest among models evaluated so far.
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
- 以上是做分类算法的初选,最终我们选择随机森林的输出 (Y_pred)。
模型评估
models = pd.DataFrame({
'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression',
'Random Forest', 'Naive Bayes', 'Perceptron',
'Stochastic Gradient Decent', 'Linear SVC',
'Decision Tree'],
'Score': [acc_svc, acc_knn, acc_log,
acc_random_forest, acc_gaussian, acc_perceptron,
acc_sgd, acc_linear_svc, acc_decision_tree]})
models.sort_values(by='Score', ascending=False)
submission = pd.DataFrame({
"PassengerId": test_df["PassengerId"],
"Survived": Y_pred
})
# submission.to_csv('../output/submission.csv', index=False)
submission
总结
本篇在后续算法处理是非常的简陋的,特别是评估仅用训练数据来选择模型,很容易出现过拟合现象。所以主要的学习精华应该是前面数据处理和特征选择的一些思路和方法。关于这个模型评估也有人认为用交叉验证的方式更加好,而且得出结论是SVM的表现最优秀,大家可以自行尝试看看。
文章也花了一些时间处理和整理希望你能有所收获,如果大家有发现比较好的案例或者学习材料也欢迎可以留言跟我分享,谢谢!