数据分析 | 任性娜娜,在线算命

大家好,今天也是被生活安排地明明白白的一天。

来龙去脉不细说了,总而言之就是根据赌约我要完成Kaggle里一个比赛。纵然对方十分亲切地帮我挑选了一个最最基础的入门级别的比赛,可我一个数据分析小学生,忽然去搞机器学习……那好像还是有点拔苗助长哟(⊙_⊙;)

好吧,买定离手愿赌服输。那么,我就开始搜索如何从零开始入门机器学习了。
一个小时过后:我开始慌惹
两个小时过后:我是谁我在哪我要干什么???

大概流程和框架捋了一下,娜某人就这么漫不经心地去决定别人的生死了!这本生死簿,就是正义!

泰坦尼克号幸存者预测 Titanic: Machine Learning from Disaster

先研究研究到底需要干什么吧。现在我手里有两张表,一张叫train,一张叫test。第一张表(train)上记录了泰坦尼克号部分(891位)客人的姓名性别家里几口人人均几亩地等等各种信息,包括他们最后有没有生还(Survived);另一张表(test),和train一样也记录着一部分(418位)客人的各种信息,唯一的不同是Survived这一列为空值,也就是说不知道他们有没有在灾难中获救。好的!懂了!我的任务就是通过手里的两张表安排一下那418位乘客有没有获救。

891条数据有Survived值,418条数据Survived为空

表中各特征的含义如下:
PassengerId: 乘客编号
Survived: 是否生还(0 = 死亡,1 = 生存)
Pclass: 船票级别(1 = 高级,2 = 中级,3 = 低级)
Name: 姓名
Sex: 性别(male = 男性,female = 女性)
Age: 年龄
SibSp: 在 Titanic 上的兄弟姐妹以及配偶的人数
Parch: 在 Titanic 上的父母以及子女的人数
Ticket: 船票编号
Fare: 票价
Cabin: 所在的船舱
Embarked: 登船的港口 (C=Cherbourg, Q=Queenstown, S=Southampton)

接下来是《算命速成》,可能将浪费你人生中宝贵的15分钟:
0 导入基本模块以及加载数据
1 数据初探
2 数据预处理
3 特征工程
4 模型选择
5 模型优化

0 导入基本模块以及加载数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
% matplotlib inline

from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] # 负号正常显示

import warnings
warnings.filterwarnings('ignore') 

import os
os.chdir(r'..\Desktop\泰坦尼克号幸存者模拟')
data = pd.read_csv('train.csv',header = 0)
1 数据初探

数据加载好了,看看我们能从数据里发现什么小秘密吧。
这里总结了一些数据初判的方法:

1.1 查看基本特征(空值、统计描述等)

(1) df.info() #查看基本信息
(2) df.isnull().sum() #查看每列空值数量,查看非空使用notnull()
(3) df.describe() #查看每列数据均值、标准差、分位数等统计值
(4) df['Pclass'].unique() #查看某列的几种不重复的结果是什么

具体来看看:
1.2 查看基本数据分布(分布分析、对比分析等)

(1) df['Fare'].hist(bins=10) #查看某列直方图,设置分段数量用bins=10
(2) df['Fare'].plot(kind='kde',style='k--',grid=True) #查看某列密度图
(3) df['Fare'].boxplot(by='Pclass') #查看某列箱型图,可按另一个特征分组查看
(4) 有一些特征变量是一些连续的数值,这时可以按照区间查看:
例如年龄Age,是这样的一些数值:18,30,45……,单看这些年龄值不容易发现规律,这时用pd.cut()把它们划分在不同区间里更容易看出规律。

 bins = 4 #分成4组
 group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior'] #每组标签
 agecut = pd.cut(data['Age'],bins,labels=group_names,right = False) #分组区间
 agecut_count = agecut.value_counts(sort = False)#不按count数量多少排序

针对上面的步骤举几个栗子:

# (1)直方图:查看票价Fare的分布,可发现大多为低价票
data.hist(column='Fare',
          bins = 20,        # bin箱子个数
          histtype = 'bar', # histtype 风格,bar,barstacked,step,stepfilled
          align = 'mid',    # align : {‘left’, ‘mid’, ‘right’}, optional(对齐方式)
          orientation = 'vertical',# orientation 水平还是垂直{‘horizontal’, ‘vertical’}
          alpha=0.6,
          color='forestgreen',
          normed =True)    # normed 标准化

# (2)密度图:查看票价Fare的分布
data['Fare'].plot(kind='kde',style='--',grid = True)
直方图和密度图:查看Fare票价分布,发现多数为低价票
# (3)箱型图:查看按船舱等级Pclass分类的票价Fare箱型图,船票级别1=高级,2=中等,3=低等
data.boxplot(column='Fare',
             by = 'Pclass',
             sym = 'o',    # 异常点形状
             vert = True,  # 是否垂直
             whis = 1.5,   # IQR,默认1.5,也可以设置区间比如[5,95],代表强制上下边缘为数据95%和5%位置
             patch_artist = True,  # 上下四分位框内是否填充,True为填充
             meanline = False,showmeans=True,  # 是否有均值线及其形状
             showbox = True,     # 是否显示箱线
             showcaps = True,    # 是否显示边缘线
             showfliers = True,  # 是否显示异常值
             notch = False,      # 中间箱体是否缺口
             ) 
箱型图:按不同船舱等级Pclass查看票价Fare分布,船舱等级越高票价越高
# (4)用pd.cut()查看年龄Age分组占比
bins = [0, 18, 40, 60, 80]
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
agecut = pd.cut(data['Age'],bins)
agecut_count = agecut.value_counts(sort = False)
colors = ['lightcyan','paleturquoise','mediumturquoise','c']
plt.pie(agecut_count,labels = group_names,autopct='%i %%',colors=colors)
饼图:查看年龄分组占比,大部分乘客处在18~40岁这个区间
# 除此之外还可以用热图初步查看几个特征之间的相关性:
sns.heatmap(data[['Survived','SibSp','Parch','Age','Fare']].corr(),annot=True, fmt = '.2f', cmap = 'coolwarm')
热图:图中发现似乎只有Fare与生存率密切相关,但这不意味着其他因素与生存无关
1.3 查看数据分组分布(groupby、crosstab、pivot_table、sns.pairplot)

只分析单一因素是不够全面的,我们还需要查看不同因素联立时的数据分布,下面的方法都可以用来进行分组查看:

(1) groupby:分组统计,可代替下面的两种
temp = data[['Sex','Pclass','Survived']].groupby(['Pclass','Sex']).sum()
temp.plot(kind='bar')

(2)交叉表crosstab:可以按照指定的行和列统计分组频数
temp = pd.crosstab([data['Pclass'], data['Sex']], data.Survived.astype(bool))
temp.plot(kind='bar', stacked=True, color=['red','blue'], grid=False)

(3)透视表pivot_table:产生类似数据透视表的效果
temp = pd.pivot_table(data, index='Pclass', columns='Sex',values = 'Survived')
temp.plot(kind='bar',stacked=True)

(4)查看多个变量两两之间的相互关系
sns.pairplot()

快来看看具体是怎么应用的:
1.3.1 按照船上家庭人数查看生存率:groupby

data['Family_Size'] = data['Parch'] + data['SibSp'] + 1
temp = data[['Family_Size','Survived']].groupby(['Family_Size']).mean()
temp.plot(kind='bar',color='lightcoral',alpha=0.9)
家庭人数增多时生存率呈现增加趋势,但人数大于一定值(4个)后生存率会大幅减小

1.3.2 查看登船港口Embarked与生存率的关系:groupby

temp = data[['Embarked','Survived']].groupby(['Embarked']).mean()
temp.plot(kind='bar',color = 'crimson',alpha=0.7)

泰坦尼克号航行路线:Southampton--Cherbourg-Octeville--Queenstown
考虑到在Southampton港口和Cherbourg-Octeville港口登船的人将有机会在Queenstown港口之前下船,那么前面两个港口登船的人果真生存率会更大吗?


在Cherbourg-Octeville上船的人生还几率最大,Queenstown次之,Southampton最小

1.3.3 查看船舱Cabin与生存率的关系,用船舱号首字母分组:groupby

data['Cabin_Initial'] = data['Cabin'].str[0]
temp = data[['Cabin_Initial','Survived']].groupby(['Cabin_Initial']).mean()
temp.plot(kind='bar',color = 'dodgerblue',alpha=0.7)
似乎并不能看出船舱号和获救有什么直接关系,但直觉上船舱号决定了船舱位置,应该和逃生相关

1.3.4 查看按照Age分组区间幸存情况:crosstab

temp = pd.crosstab(agecut.values,data['Survived'])
temp.plot(kind='bar',color=['red','green'],alpha=0.7)
虽然18~40岁的乘客获救人数最多,但获救占比很小

1.3.5 按照船舱等级和性别查看幸存人数:crosstab

temp = pd.crosstab([data['Pclass'], data['Sex']], data['Survived'].astype(bool))
temp.plot(kind='bar', stacked=True, color=['red','green'],alpha=0.7)
基本上呈现出“Lady First”,同时船舱等级对能否获救也有显著影响

1.3.6 查看多个变量两两之间的相互关系

sns.pairplot(data[['Survived','Pclass','Age','Fare']], hue='Survived', palette = 'husl',diag_kind='kde',diag_kws=dict(shade=True),plot_kws=dict(s=10),markers=["o", "x"])
1.4 查看特征类别分布是否平衡

sns.countplot(x='Survived',data=data)

幸存和死亡人数相差不大,可以认为属于类别平衡问题。

类别不平衡(class-imbalance)是指在分类任务中,不同类别的训练样例数目相差很大的情况。(比如本例是一个二分类问题,每位乘客分为生存或者死亡两种状态,如果绝大部分乘客死亡而生存人数仅有几个的话,就叫类别不平衡。)为什么要进行这样的判断呢?因为对于分类问题的训练集,如果有999个都属于A类,只有1个属于B类,那么在预测时只需要永远将新样本预测为A类,这个分类器就能达到99.9%的精度,但这样是没有意义的。现实中,在银行信用欺诈交易识别中,属于欺诈交易的应该是很少部分,绝大部分交易是正常的,这就是一个正常的类别不平衡问题。如果出现类别不平衡的情况,已经超出我目前的知识范围了,可以参考这里


2 数据预处理

经过第一步我们假装已经了解手里的数据长什么样了,下面就需要对其中一些特征进行预处理了。在此之前需要先把train表和test表用pd.concat()拼成一张表,这样按照接下来的步骤处理完它两还是相同的数据分布。

data_01 = pd.read_csv('train.csv',header = 0)
data_02 = pd.read_csv('test.csv',header = 0)
data_12 = pd.concat([data_01,data_02]) #合并两个
data_12.reset_index(inplace=True)

下面总结了一些数据预处理的方法:

2.1 处理缺失值
缺失值情况

(1) 如果缺失值对于学习来说不是很重要,同时占比非常小:可以删掉这条数据或者填补均值or众值
(2) 如果缺失值的特征相对重要,而且不属于数值型:可以考虑重新赋值
(3) 如果缺失值占比很大而且相对重要:可以建立模型(线性回归、随机森林等)预测缺失值

2.1.1 年龄Age(177个缺失值)
由于年龄是比较重要的特征,缺失值占比也比较大,考虑将Age完整的项作为训练集,将Age缺失的项作为测试集,采用RandomForestRegressor()。因为想要利用其他特征量来预测Age,所以这里把Age的缺失值处理放到下面一步,先分析其他特征。

2.1.2 登船港口Embarked(2个缺失值)
缺失值很少,考虑用众值填补:

train_data = pd.DataFrame()

mod = data_12['Embarked'].mode()[0]
data_12['Embarked'].fillna(mod,inplace=True)
train_data['Embarked'] = data_12['Embarked']

2.1.3 船舱Cabin(1014个缺失值)
对于所在的船舱的大量缺失值,考虑按特殊情况重新赋值为"Z0"(特殊情况是指,结合票价考虑,这些缺失值出现的原因可能是因为实际中这些人没有船舱)

data_12['Cabin'].fillna('Z0',inplace=True)
train_data['Cabin'] = data_12['Cabin']

2.1.4 票价Fare(1个缺失值)
票价Fare的缺失值只有一个,考虑按他所在的船舱等级均价填补;另外发现有些人的票价为0,这些人共享同一个Ticket号码,他们应该是团体票,因此还需要将团体票按总票价平均分配给这个团体中的每个人

# 缺失票价用所在船舱等级票价均值填补
train_data['Fare'] = data_12['Fare']
train_data['Pclass'] = data_12['Pclass']
train_data['Fare'] = train_data['Fare'].fillna(train_data.groupby('Pclass').transform('mean'))

# 将团体票的总价平均分配给组里每个人
train_data['Ticket'] = data_12['Ticket']
train_data['Group_Member'] = train_data['Fare'].groupby(by=train_data['Ticket']).transform('count')
# transform(func, args, *kwargs) 方法会把func应用到所有分组,然后把结果放置到原数组的index上

train_data['Fare'] = train_data['Fare'] / train_data['Group_Member']

那么我们就把登船港口、所在船舱和票价的缺失值填充好了!Age放在下一步处理!

2.2 处理异常值

刚刚提到的Fare票价为0的值其实就算异常值,在处理上应该具体问题具体分析,看它为什么异常,异常在哪里。用第一步中绘制箱型图的方法可以查看异常值情况。箱型图这里小小的解释一下,箱型图是用作显示一组数据分散情况的统计图。

箱形图提供了一种只用5个点对数据集做简单总结的方式。
这5个点包括中点、Q1、Q3、分部状态的高位和低位
① 矩形盒两端的边线分别对应数据批的上下四分位数(Q3和Q1)。
② 矩形盒内部的线为中位线,代表了中位数(Xm)。
③ 在Q3+1.5IQR和Q1-1.5IQR处的线段为异常值截断点,称其为内限;在Q3+3IQR和Q1-3IQR处的线段称其为外限。处于内限以外位置的点表示的数据都是异常值:其中在内限与外限之间的异常值为温和的异常值(mild outliers);在外限以外的为极端的异常值(extreme outliers)。
④ 四分位距IQR=Q3-Q1。.
(以上内容整理自百度百科)

2.3 数据连续属性离散化

连续属性变换成分类属性,即连续属性离散化。第一步提到的年龄分组区间就是在做离散化。在数值的取值范围内设定若干个离散划分点,将取值范围划分为一些离散化的区间,最后用不同的符号或整数值代表每个子区间中的数据值。
下面看看票价Fare如何做这样的分组处理:

# 数据连续属性离散化:票价Fare
train_data['Fare_bin'] = pd.qcut(train_data['Fare'], 5)
#cut是根据数值本身来选择箱子均匀间隔,qcut是根据这些值的频率来选择箱子的均匀间隔
train_data['Fare_bin'].head()

将得到这样的分组效果:Categories (5, interval[float64]): [(-0.001, 7.229] < (7.229, 7.896] < (7.896, 10.5] < (10.5, 26.277] < (26.277, 128.082]]

每个人会根据票价被划分在不同组:
仅截取部分乘客
2.4 数据标准化

数据的标准化是将数据按比例缩放,使之落入一个小的特定区间。常在比较和评价的指标处理中用到,去除数据的单位限制,将其转化为无量纲的纯数值,便于不同单位或量级的指标能够进行比较和加权。
(1) MinMaxScaler(最小最大值标准化):将数据统一映射到[0,1]区间上
(2) Standardization标准化:将特征数据的分布调整成标准正太分布,也叫高斯分布,也就是使得数据的均值维0,方差为1。

下面以票价Fare为例做标准化处理:

# 数据标准化:票价Fare
from sklearn import preprocessing
train_data['Fare_scaled'] = preprocessing.StandardScaler().fit_transform(train_data['Fare'].values.reshape(-1,1))
#X.reshape(-1,1)是指我们不知道X的shape属性是多少,但是想让X变成只有一列。
#X.reshape(-1,1)由Numpy自动计算出有1309行,新的数组shape属性为(1309, 1),与Fare列数据配套。
2.5 特征类型转换

回忆一下我们的数据,有一些特征是这样不同类别的取值:male男性 / female女性,这样的变量需要转化为数值型才能被模型识别。但如果只是简单地把男性转化为0女性转化为1,就会为它们带来数值上的意义。例如用数字1-12表示1-12月,那么就潜在表示了12月和1月差得很远,其实它们离得很近。这时就需要引入下面的方法了:

(1) 引入虚拟变量dummy variable
当频繁出现的几个独立变量时,可以用pandas.get_dummies()将定性变量转换为虚拟变量。

(2) factorize因子化
当某个特征有非常多取值时,可以使用pandas.factorize()创建一些数字来表示类别变量,对每一个类别映射一个ID,这种映射最后只生成一个特征,不像dummy那样生成多个特征。

说了这么多,到底怎么完成这种炫酷的操作呢:

# 对于船舱等级Pclass,引入dummy变量
Pclass_dummies_df = pd.get_dummies(train_data['Pclass']).rename(columns=lambda x: 'Pclass_' + str(x))
train_data = pd.concat([train_data, Pclass_dummies_df], axis=1)

Pclass这一特征就转化完成了:
为船舱等级Pclass引入虚拟变量

其他几个特征也同样需要进行转化:

# 票价Fare分组
Fare_bin_dummies_df = pd.get_dummies(train_data['Fare_bin']).rename(columns=lambda x: 'Fare_' + str(x))
train_data = pd.concat([train_data, Fare_bin_dummies_df], axis=1)


# 登船港口Embarked
Embarked_dummies_df = pd.get_dummies(train_data['Embarked']).rename(columns=lambda x: 'Embarked_' + str(x))
train_data = pd.concat([train_data, Embarked_dummies_df], axis=1)


# 性别Sex
train_data['Sex'] = data_12['Sex']
Sex_dummies_df = pd.get_dummies(train_data['Sex']).rename(columns=lambda x: 'Sex_' + str(x))
train_data = pd.concat([train_data, Sex_dummies_df], axis=1)

再来看船舱编号Cabin,这一特征下存在非常多的变量,所以这次采用factorize因子化的方法:

# 对于船舱Cabin:采用factorize因子化方法:
# 首先提取首字母Cabin_Initial
train_data['Cabin_Initial'] = data_12['Cabin'].str[0]
train_data['Cabin_Initial_factorize'] = pd.factorize(train_data['Cabin_Initial'])[0]

看下前几行的效果:
factorize因子化

3 特征工程

特征工程即从各项参数中提取出可能影响到最终结果的特征,作为模型的预测依据。回顾上面一步,已经处理好了缺失值、异常值,也已经转换了一些特征的变量类型。现在再来梳理一下手里的特征:

(1)登船港口Embarked
经过Step2预处理,登船港口引入虚拟变量,转换为Embarked_C,Embarked_Q,Embarked_S;
(2)票价Fare
经过Step2预处理,将票价分为了5个等级;
(3)性别Sex
经过Step2预处理,性别Sex转换为Sex_female,Sex_male;
(4)船舱等级Pclass
经过Step2预处理,船舱等级Pclass转换为Pclass_1,Pclass_2,Pclass_3;
(5)船舱Cabin
经过Step2预处理,船舱Cabin转换为Cabin_Initial_factorize;
(6)姓名Name
前面一直没有细看这个特征,我们可以从名字中提取到称呼(例如“Mr.”,“Capt”等),或许可以发现社会地位对生存率的影响。
(7)家庭成员Parch and SibSp
在Step1中发现,家庭成员的多少对获救有一定影响,这里可以将两项合并一下,研究家庭成员数量对生存率的影响。还可以根据人数对家庭进行分类:单个人、小型家庭、中型家庭、大型家庭。
(8)年龄Age
这一步将解决step2的遗留问题,用模型预测缺失年龄。
(9)船票号码Ticket
观察Ticket的值,前缀相同的号码似乎是一起订的,他们应该也是在同一个船仓的,所以这里把船票号码前缀提取出来。(后面发现预测时这一特征并不是重要特征。)

3.1 姓名Name提取Title
train_data['Title']=data_12['Name'].apply(lambda x: x.split(',')[1].split('.')[0].strip())
train_data['Title'].unique()

['Mr', 'Mrs', 'Miss', 'Master', 'Don', 'Rev', 'Dr', 'Mme', 'Ms','Major', 'Lady', 'Sir', 'Mlle', 'Col', 'Capt', 'the Countess','Jonkheer', 'Dona']
发现title种类很多,可以将相似的头衔做合并为5类,并且引入虚拟变量。
(1) 有职务:Staff
Capt --上校
Col -- 上校
Major -- 少校
Dr -- 医生/博士
Rev.= reverend -- 基督教的牧师

(2) 没有头衔的男性:Mr
Mr.= mister -- 先生

(3) 没有头衔的已婚女性:Mrs
Mrs.= mistress -- 太太/夫人
Mme.=Madame -- 女士
Ms -- 婚姻状态不明的女性

(4) 没有头衔的未婚女性:Miss
Miss -- 未婚女性
Mlle -- 小姐

(5) 贵族:Noble
Lady -- 女士
Dona -- 夫人,女士
Master -- 主人
Don -- 先生
Sir -- 先生
Jonkheer -- 乡绅
The Countless -- 女伯爵

train_data['Title'][train_data.Title.isin(['Capt', 'Col', 'Major', 'Dr', 'Rev'])] = 'Staff'
train_data['Title'][train_data.Title.isin(['Don', 'Sir', 'the Countess', 'Dona', 'Lady','Master','Jonkheer'])] = 'Noble'
train_data['Title'][train_data.Title.isin(['Mr'])] = 'Mr'
train_data['Title'][train_data.Title.isin(['Mme', 'Ms', 'Mrs'])] = 'Mrs'
train_data['Title'][train_data.Title.isin(['Mlle', 'Miss'])] = 'Miss'

# 称谓Title:引入dummy变量
Title_dummies_df = pd.get_dummies(train_data['Title']).rename(columns=lambda x: 'Title_' + str(x))
train_data = pd.concat([train_data, Title_dummies_df], axis=1)
3.2 家庭成员Parch and SibSp

Parch和SibSp可以合并成家庭成员数量Family_Size,分类后引入虚拟变量。

train_data['Family_Size'] = data_12['Parch'] + data_12['SibSp'] + 1
def Family_Category(size):
    if size == 1:
        return 'Single'
    elif size < 3:
        return 'Small'
    elif size < 5:
        return 'Medium'
    else:
        return 'Large'
train_data['Family_Category'] = train_data['Family_Size'].map(Family_Category)
# Series的map方法可以接受一个函数或含有映射关系的字典型对象。 使用map()是一种实现元素级转换以及其他数据清理工作的便捷方式。 

# 家庭类型Family_Category:引入dummy变量
Family_Category_dummies_df = pd.get_dummies(train_data['Family_Category']).rename(columns=lambda x: 'Family_Category_' + str(x))
train_data = pd.concat([train_data, Family_Category_dummies_df], axis=1)
3.3 年龄Age

前面提到过对于年龄Age的缺失值,将年龄完整的项作为训练集,将年龄缺失的项作为测试集,采用RandomForestRegressor()预测。

from sklearn.ensemble import RandomForestRegressor

# 选择训练集数据
train_data['Age'] = data_12['Age']
age_df = train_data[['Age','Family_Size','Pclass','Sex_female','Sex_male','Fare']]
age_df_notnull = age_df[age_df['Age'].notnull()]
age_df_isnull = age_df[age_df['Age'].isnull()]
X = age_df_notnull.values[:,1:] # 除第一列Age外其他非空数据
Y = age_df_notnull.values[:,0]  # 第一列Age非空
# 随机森林回归
RFR = RandomForestRegressor(n_estimators=500, n_jobs=-1)
RFR.fit(X,Y) # 训练
predictAges = RFR.predict(age_df_isnull.values[:,1:]) # 测试
train_data.loc[train_data['Age'].isnull(),['Age']]= predictAges

# 查看预测年龄Age前后数据分布:与之前数据分布基本相同
fig,ax=plt.subplots(1,2,figsize=(12,6))
data_12['Age'].hist(bins=20,ax=ax[0],color='green')
train_data['Age'].hist(bins=20,ax=ax[1],color='yellowgreen')
预测前后年龄分布相似

差点忘了年龄也应该做标准化处理:

from sklearn import preprocessing
scaler = preprocessing.StandardScaler()
train_data['Age_scaled'] = scaler.fit_transform(train_data['Age'].values.reshape(-1,1))
3.4 船票号码Ticket

船票数字部分暂时没看出什么规律,先尝试把字母部分提取出来吧:

Ticket = []
for i in list(train_data['Ticket']):
    if not i.isdigit() :
        Ticket.append(i.replace('.','').replace('/','').strip().split(' ')[0]) 
    else:
        Ticket.append('X')
        
train_data['Ticket'] = Ticket

# 船票Ticket字母部分:引入dummy变量
Ticket_dummies_df = pd.get_dummies(train_data['Ticket']).rename(columns=lambda x: 'Ticket_' + str(x))
train_data = pd.concat([train_data, Ticket_dummies_df], axis=1)

特征工程其实是非常非常重要的一步,但能力太菜时间紧迫只能分析出这么多了……


4 模型选择

可以用于分类的模型很多很多,我也看得眼花缭乱,可以参考这里


看了Kaggle论坛里好多大神的分享,暂时选了几种python会调用的(原理目前还不理解)先看一下准确率。

Where is a beginner to start? I recommend starting with Trees, Bagging, Random Forests, and Boosting. They are basically different implementations of a decision tree, which is the easiest concept to learn and understand. (转自Kaggle论坛

计算准确率前需要用到train_test_split(),它的作用是给定数据集X和类别标签Y,将数据集按一定比例随机切分为训练集和测试集。挑选了下面四种模型进行准确率的计算:

(1) 逻辑回归 Logistic Regression
我们熟悉的线性回归模型通常可以处理因变量是连续变量的问题,如果因变量是分类变量,线性回归模型就不再适用了,可用逻辑回归模型解决。

(2) 支持向量机 SVM
SVM是Support Vector Machines支持向量机的缩写,可以用来做分类和回归。SVC是SVM的一种Type,是用来的做分类的。概念有点难理解,基本概念以及参数说明可以参考这里这里

(3) 随机森林 Random Forest
随机森林是集成学习的一个子类,它依靠决策树的投票选择来决定最后的分类结果。

随机森林的工作原理是生成多个子模型,各自独立地学习和作出预测。这些预测最后结合成单预测,因此优于任何一个单分类的做出预测。 一个形象的比喻:森林中召开会议,讨论某个动物到底是老鼠还是松鼠,每棵树都要独立地发表自己对这个问题的看法,也就是每棵树都要投票。该动物到底是老鼠还是松鼠,要依据投票情况来确定,获得票数最多的类别就是森林的分类结果。森林中的每棵树都是独立的,99.9%不相关的树做出的预测结果涵盖所有的情况,这些预测结果将会彼此抵消。少数优秀的树的预测结果将会超脱于芸芸“噪音”,做出一个好的预测。将若干个弱分类器的分类结果进行投票选择,从而组成一个强分类器,这就是随机森林bagging的思想。(随机森林通俗易懂的解释

(4) 梯度提升Gradient Boosting
Boosting是一种知错就改的思想。通过一系列的迭代来优化分类结果,每迭代一次引入一个弱分类器,来克服现在已经存在的弱分类器组合的缺点。Gradient Boosting 在迭代的时候选择梯度下降的方向来保证最后的结果最好。

损失函数用来描述模型的“靠谱”程度,假设模型没有过拟合,损失函数越大,模型的错误率越高。让损失函数持续下降,就能使得模型不断改性提升性能,其最好的方法就是使损失函数沿着梯度方向下降。(参考这里

#备份一下,以备不时之需
data_backup = train_data.copy()
# 删掉不需要的特征
train_data.drop(['Embarked', 'Cabin', 'Fare', 'Pclass', 'Ticket', 'Group_Mumber','Cabin_Initial', 
                'Fare_bin', 'Family_Size', 'Family_Category', 'Age','Sex','Title'],axis=1,inplace=True)
# 分开训练集和测试集
Titanic_train_data = train_data[:891]
Titanic_test_data = train_data[891:]
train_data_X = Titanic_train_data
train_data_Y = data_01['Survived']
test_data_X = Titanic_test_data

# 各种模型预测准确率
from sklearn.model_selection import train_test_split  
seed = 4
train_X,test_X,train_Y,test_Y=train_test_split(train_data_X,train_data_Y,test_size=0.4,random_state=seed)  

# Logistic Regression
from sklearn.linear_model import LogisticRegression  
LR=LogisticRegression(C=1.0,tol=1e-6,random_state=seed)  
LR.fit(train_X,train_Y)  
print(LR.score(test_X,test_Y))  

# SVC
from sklearn.svm import SVC  
SVC=SVC(C=2, kernel='rbf', decision_function_shape='ovo',random_state=seed)  
SVC.fit(train_X,train_Y)  
print(SVC.score(test_X,test_Y))  

# Random Forest
from sklearn.ensemble import RandomForestClassifier  
RF=RandomForestClassifier(n_estimators=500,max_depth=5,random_state=seed)  
RF.fit(train_X,train_Y)  
print(RF.score(test_X,test_Y))  

# GradientBoosting
from sklearn.ensemble import GradientBoostingClassifier  
GB=GradientBoostingClassifier(n_estimators=600,max_depth=5,random_state=seed)  
GB.fit(train_X,train_Y)  
print(GB.score(test_X,test_Y))  

Logistic Regression、SVC、Random Forest、GradientBoosting模型的得分依次为:
四种模型准确率

试着变换随机数种子,前三种模型得分依然比GradientBoosting模型高。可以考虑用前面三种任意一种来做最终预测,或者通过votingClassifer综合几种模型。

5 模型优化

虽说特征工程非常非常重要,但模型优化也是必不可少的。准备从下面两个方面入手:

(1) 筛选重要特征
(2) 参数调整

5.1 筛选重要特征

这一步目标是找到与应变量高度相关的特征变量。

# 用两个模型来筛选重要特征
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

def get_top_n_features(train_data_X, train_data_Y, top_n_features):
    seed = 4
    
    # RandomForest
    RF_estimator = RandomForestClassifier(random_state=seed)
    RF_param_grid = {'n_estimators': [500,1000], 'min_samples_split': [2, 3], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
    RF_grid = model_selection.GridSearchCV(RF_estimator, RF_param_grid, cv=10, verbose=1)
    RF_grid.fit(train_data_X, train_data_Y)
    feature_imp_sorted_RF = pd.DataFrame({'feature': list(train_data_X),
                                          'importance': RF_grid.best_estimator_.feature_importances_}).sort_values('importance', ascending=False)
    features_top_n_RF = feature_imp_sorted_RF.head(top_n_features)['feature']
    print('Sample 10 Features from RF_Classifier')
    print(str(features_top_n_RF[:10]))

    # GradientBoosting
    GB_estimator =GradientBoostingClassifier(random_state=seed)
    GB_param_grid = {'n_estimators': [500,1000], 'learning_rate': [0.001, 0.01, 0.1], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
    GB_grid = model_selection.GridSearchCV(GB_estimator, GB_param_grid, cv=10, verbose=1)
    GB_grid.fit(train_data_X, train_data_Y)
    feature_imp_sorted_GB = pd.DataFrame({'feature': list(train_data_X),
                                           'importance': GB_grid.best_estimator_.feature_importances_}).sort_values('importance', ascending=False)
    features_top_n_GB = feature_imp_sorted_GB.head(top_n_features)['feature']
    print('Sample 10 Feature from GB_Classifier:')
    print(str(features_top_n_GB[:10]))

    # concat
    features_top_n = pd.concat([features_top_n_RF, features_top_n_GB], 
                               ignore_index=True).drop_duplicates()
    features_importance = pd.concat([feature_imp_sorted_RF, feature_imp_sorted_GB],ignore_index=True)

    return features_top_n , features_importance

N = 20
feature_top_n, feature_importance = get_top_n_features(train_data_X, train_data_Y, N)
train_data_X = pd.DataFrame(train_data_X[feature_top_n])
test_data_X = pd.DataFrame(test_data_X[feature_top_n])
RandomForest
GradientBoosting

两个分类模型的重要特征不尽相同,但是可以发现票价、年龄、性别、社会地位及家庭大小是比他们在船上的位置更为重要的因素。因此可以将Ticket相关的特征删掉。

5.2 参数调整

参数调整采用交叉验证,可以用scikit-learn中的网格搜索,即GridSearchCV类。这是一种穷举的搜索方式:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果。

为什么叫网格搜索?以有两个参数的模型为例,参数a有3种可能,参数b有4种可能,把所有可能性列出来,可以表示成一个3*4的表格,其中每个cell就是一个网格,循环过程就像是在每个网格里遍历、搜索,所以叫grid search(转自这里

from sklearn import model_selection
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

seed = 4
# SVC
SVC_estimator = SVC(random_state=seed)
SVC_param_grid = {'C': [0.1,1,2,5], 'gamma': [0.2,0.1,0.04,0.01]}
SVC_grid = model_selection.GridSearchCV(SVC_estimator, SVC_param_grid, cv=10, verbose=1)
SVC_grid.fit(train_data_X, train_data_Y)
print('Best SVC Params:',str(SVC_grid.best_params_))
print('Best SVC Score:',str(SVC_grid.best_score_))

    
# RandomForest
RF_estimator = RandomForestClassifier(random_state=seed)
RF_param_grid = {'n_estimators': [500,1000], 'min_samples_split': [2, 3], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
RF_grid = model_selection.GridSearchCV(RF_estimator, RF_param_grid, cv=10, verbose=1)
RF_grid.fit(train_data_X, train_data_Y)
print('Best RF Params:',str(RF_grid.best_params_))
print('Best RF Score:',str(RF_grid.best_score_))


# Logistic Regression
LR_estimator =LogisticRegression(tol=1e-6, random_state=seed)
LR_param_grid = [{'penalty':['l1','l2'],'C': [0.01,0.1,1,5,10],'solver':['liblinear']},
                 {'penalty':['l2'],'C': [0.01,0.1,1,5,10],'solver':['lbfgs']}]
LR_grid = model_selection.GridSearchCV(LR_estimator, LR_param_grid, cv=10, verbose=1)
LR_grid.fit(train_data_X, train_data_Y)
print('Best LR Params:',str(LR_grid.best_params_))
print('Best LR Score:',str(LR_grid.best_score_))

# GradientBoosting
GB_estimator =GradientBoostingClassifier(random_state=seed)
GB_param_grid = {'n_estimators': [500,1000], 'learning_rate': [0.001, 0.01, 0.1], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
GB_grid = model_selection.GridSearchCV(GB_estimator, GB_param_grid, cv=10, verbose=1)
GB_grid.fit(train_data_X, train_data_Y)
print('Best GB Params:' + str(GB_grid.best_params_))
print('Best GB Score:' + str(GB_grid.best_score_))

可以得到几种模型最优的参数设置:
Best SVC Params: {'C': 1, 'gamma': 0.2}
Best SVC Score: 0.843995510662

Best RF Params: {'max_depth': 10, 'min_samples_split': 2, 'n_estimators': 1000, 'min_samples_leaf': 2}
Best RF Score: 0.83950617284

Best LR Params: {'C': 5, 'solver': 'liblinear', 'penalty': 'l1'}
Best LR Score: 0.829405162738

Best GB Params:{'max_depth': 10, 'n_estimators': 1000, 'min_samples_leaf': 3, 'learning_rate': 0.001}
Best GB Score:0.818181818182

最后就可以用votingClassifer建立最终预测模型了(参考了Kaggle论坛):

# 将几种模型的结果通过投票的方式进行聚合
from sklearn.ensemble import VotingClassifier
SVC_estimator = SVC(C=1, gamma = 0.2, kernel='rbf', decision_function_shape='ovo',random_state=seed, probability=True)  
RF_estimator = RandomForestClassifier(n_estimators=1000,max_depth=10, min_samples_split=2,
                                      max_features='sqrt',criterion = 'gini',
                                      min_samples_leaf=2,random_state =seed)
LR_estimator =LogisticRegression(tol=1e-6, C=5, solver='liblinear', penalty='l1', random_state=seed)
GB_estimator = GradientBoostingClassifier(n_estimators=1000, learning_rate=0.001, max_depth=10,
                                          min_samples_split=2, min_samples_leaf=3,random_state =seed)


vote_soft = VotingClassifier(estimators = [('SVC', SVC_estimator),('RF', RF_estimator),('LR', LR_estimator),('GB', GB_estimator)],
                             voting = 'soft', weights = [1.5,0.8,0.8,0.5],n_jobs =-1)
vote_soft.fit(train_data_X,train_data_Y)
predictions = vote_soft.predict(test_data_X).astype(np.int32)

最后别忘了输出结果:

# 结果输出
result = pd.DataFrame({'PassengerId':data_02['PassengerId'].as_matrix(),'Survived':predictions})
result.to_csv('submission_result_01.csv',index=False,sep=',')

准确率:0.79425

回顾这三天:

  • 参数调整过程太痛苦了,我的笔记本算的好慢...
  • 因为是带着问题学习,所以很不系统,有很多很多很多没考虑到的地方,比如过拟合欠拟合,比如提取到的特征不够好,比如最终模型预测权重不知道怎么合理设置…
  • 一直以来都很喜欢边用边学,再学以致用的过程,是痛并享受着的三天。最终并没有成为算命大师,准确率有点低~如果能让屏幕前的你对数据分析产生一点点点点兴趣的话就觉得很开心了!
  • 可能你也会对数据分析,从入门到放弃感兴趣。

参考:
[1] http://www.cnblogs.com/fantasy01/p/4581803.html?utm_source=tuicool
[2] http://www.cnblogs.com/pinard/p/6126077.htm
[3] http://mars.run/2015/11/Machine%20learning%20kaggle%20titanic-0.8/
[4] https://blog.csdn.net/u012897374/article/details/74999940
[5] https://blog.csdn.net/app_12062011/article/details/50385522
[6] https://blog.csdn.net/app_12062011/article/details/50536369
[7] lhttps://zhuanlan.zhihu.com/p/30538352
[8] https://www.kaggle.com/yassineghouzam/titanic-top-4-with-ensemble-modeling
[9] https://www.kaggle.com/helgejo/an-interactive-data-science-tutorial

(-'๏_๏'-)谢谢您阅读,请勿转载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,030评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,198评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,995评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,973评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,869评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,766评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,967评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,599评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,886评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,901评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,728评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,504评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,967评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,128评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,445评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,018评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,224评论 2 339

推荐阅读更多精彩内容

  • 探索数据集-泰坦尼克号数据 一、读取数据 import pandas as pdimport numpy as n...
    杨小彤阅读 812评论 0 1
  • 对kaggle不做过多介绍 都知道这是一个数据挖掘的圣地,泰坦尼克号事件也不多做介绍,马上进入正题 ...
    披风海胆放阅读 1,189评论 1 4
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,426评论 0 13
  • 1、加载文件,查看:(两个数据集,train作为学习集进行数据建模,通过test测试集查看建模的情况。) trai...
    12_21阅读 1,033评论 0 0
  • 最近挤出时间,用python在kaggle上试了几个project,有点体会,记录下。 Step1: Explor...
    JxKing阅读 39,574评论 8 140