1、背景:
1.1 关于kaggle:
谷歌旗下的 Kaggle 是一个数据建模和数据分析竞赛平台。该平台是当下最流行的数据科研赛事平台,其组织的赛事受到全球数据科学爱好者追捧。 如果学生能够在该平台的一些比赛中获得较好的名次,不仅可以赢得大量的奖金,还可以收获 Google 、 Amazon 等知名互联网公司的面试邀请。
1.2 关于泰坦尼克灾难(Titanic: Machine Learning from Disaster)
以下是关于泰坦尼克生存预测的说明,在‘data’处可以点击下载预测数据。
1.3泰坦尼克问题的背景:
这是一个大家都非常熟悉的故事,泰坦尼克号邮轮航行途中不行撞击冰山,导致船翻了,在救援的过程中,船长的要求是女士与小孩优先上游艇,所以最终是否存活并不是随机事件,而是有一定的决定因素的。训练和测试数据是一些乘客的个人信息以及存货状况,要尝试根据它生成合适的模型并预测其他人的生存状况这是一个二分类的问题,在监督模型中,逻辑回归,支持向量机,决策树,随机森林,KNN等算法都够进行处理,本篇文章中,主要内容还是处理数据构建特征,对于模型的选择与调优将放到后续的文章中。
2、 怎么做?
手把手教程马上就来,先来两条我看到的,觉得很重要的经验:印象中Andrew Ng老师似乎在coursera上说过,应用机器学习,千万不要一上来就试图做到完美,先撸一个baseline的model出来,再进行后续的分析步骤,一步步提高,所谓后续步骤可能包括『分析model现在的状态(欠/过拟合),分析我们使用的feature的作用大小,进行feature selection,以及我们模型下的bad case和产生的原因』等等。Kaggle上的大神们,也分享过一些experience,说几条我记得的哈:对数据的认识太重要了!数据中的特殊点/离群点的分析和处理太重要了!特征工程(feature engineering)太重要了!在很多Kaggle的场景下,甚至比model本身还要重要要做模型融合(model ensemble)3、初步的数据认知
官方提供的数据文件一共有三个,一个是带标签的训练数据,一个是不带标签的预测数据,第三个是结果的提交格式。下面就将训练数据与预测数据都读进来,并合并查看。在这多说一句,将训练数据与预测数据合并进行统一的清洗,可以减少许多的重复性工作
import pandas as pdimport matplotlib.pyplot as pltimport numpy as nptrain=pd.read_excel(r'E:huawei我的作品泰坦尼数据rain.xlsx')test=pd.read_excel(r'E:huawei我的作品泰坦尼数据est.xlsx')data=pd.concat([train,test]) #将训练数据与预测数据进行合并data_1=data.copy()
如下图为数据结果:
Age,Fare,Parch,Pclass,SibSp等字段为数值型数据,Cabin,Embarked,Name,Sex,Ticket等字段为字符型数据
对数据进行初步的了解
data_describe=data_1.describe() #查看数值型数据的统计指标
从上边的数据我们可以得到什么样的结论呢
乘客的平均年龄是29岁平均票价是33.9,最高票价是512二等舱、三等舱要比一等舱的乘客多很多约有38.3%的乘客获救了以上计算都忽略掉缺失值
各字段缺失情况如下
data_null=data_1.isnull().sum() #查看缺失值
初步假设:
Cabin字段的缺失值最多,将近80%的记录都为空,假设缺失值有实际意义,在处理时将空值作为新的一类Age字段缺失值占比20%,也不能建单的通过平均值/中位数/众数来填充,填充的方法可以采用对不同乘客属性的中位数进行填充。Embarked与Fare的缺失值较少,简单的中位数/平均数填充即ok
乘客的各属性分布以及与获救结果的关联统计
分别查看性别,仓位等级,上船港口以及仓位编号与是否生存的关系
fig=plt.figure(figsize=(15,10))###Sex与Survived关系axes_1=fig.add_subplot(2,2,1)sur_sex=pd.pivot_table(train,index='Survived',columns='Sex',values='PassengerId',aggfunc='count')sur_sex.plot(kind='bar',stacked=True,ax=axes_1)plt.title('Sex-Survived')##pclass与是否存活svi_p=pd.pivot_table(train,index='Survived',columns='Pclass',values='PassengerId',aggfunc='count')axes_2=fig.add_subplot(2,2,2)svi_p.plot(kind='bar',stacked=True,ax=axes_2)plt.title('Pclass-Survived')###embarked与是否存活axes_3=fig.add_subplot(2,2,3)sur_emb=pd.pivot_table(train,index='Survived',columns='Embarked',values='PassengerId',aggfunc='count')sur_emb.plot(kind='bar',stacked=True,ax=axes_3)plt.title('Embarked-Survived')##cabin与survived关系axes_6=fig.add_subplot(2,2,4)train['Cabin'].fillna('N',inplace=True)train['Cabin_c']=train['Cabin'].map(lambda x : x[0])sur_ca=pd.pivot_table(train,index='Survived',columns='Cabin_c',values='PassengerId',aggfunc='count')sur_ca.T.plot(kind='bar',stacked=True,ax=axes_6)
结论:
图一,性别与生存的有着强关系,在生存的乘客中,女性的比例明显要高很多。图二,仓位等级与生存有着强关系,一等舱的乘客生存的概率明显要高很多。图三、登录港口与生存有着一定的关系,并不是很明显,在生存的乘客中,C港口登录的乘客要多一些。图四、A~G的仓位中,生存的概率是差不多的,而N仓的生存率明显会低很多。
年龄与船票价格的的概率密度曲线
结论:
图一、死亡的乘客中低Fare的比例明显要比生存的乘客比例高图二、死亡的乘客中20-30岁的比例要比生存的乘客高,在0-10岁的乘客明显生存概率要高一些
4、缺失值填充
除去要预测的Survived字段,一共有四个字段有缺失值,下边将按照缺失值从少到多的顺序来填充
4.1 Fare字段的缺失值填充
Fare只有一个记录的缺失值
Fare的整体分布:Fare主要分布在100以下,如果用全局平均数或中位数填充,受高于100值的影响会比较大,所以我们看一下,能不能从其他的字段中找出一点相关性
在这一条记录中,Cabin的字段是缺失的,Pclass的字段是有的,那么先用Pclass=3的Fare的中位数填充
data_fare=pd.pivot_table(data_1,index='Pclass',values='Fare',aggfunc='median')data_1['Fare'].fillna(data_fare.loc[3,'fare'],inplace=True)
4.2 填充Embarked的缺失值
在Embarked-Survived的图中可以看出,相比未生存下来的人,生存者中C港口登录的占比要高很多,同时,缺失的这两条记录都是生存者,那么久很简单了额,直接用C来填充缺失值
data_1['Embarked'].fillna('C',inplace=True)
4.3 最简单的两个缺失值填充已经完成了,下面要来填充年龄的缺失值了
由于年龄的缺失值相对较多,也不能直接使用全局均值/平均数来填充,跟Fare字段的填充逻辑一致,先在现有数据中寻找与Age相关的字段,在现有字段中与年龄最相关的就是Name中的title了,先在决定使用不同title对应的年龄的中位数进行填充
由下图的Title-Age的密度曲线可以看出,除了‘Miss’外,其他的各个title的年龄密度曲线都比较集中,将‘Miss’中Parch是否大于零划分为两类,继续观察密度曲线,如右图。现在来看Miss的年龄目睹曲线将会平滑一些。
##Age字段缺失值填充def name(x): #先对Name字段进行处理,构造解析title函数 str_1=x.split(',')[1] str_2=str_1.split('.')[0] str_3=str_2.strip() return str_3data_1['title']=data_1['Name'].map(lambda x: name(x))fig=plt.figure(figsize=(15,7))axes=fig.add_subplot(1,2,1)data_1.loc[data_1['title']=='Mr','Age'].plot(kind='kde',ax=axes)data_1.loc[data_1['title']=='Miss','Age'].plot(kind='kde',ax=axes)data_1.loc[data_1['title']=='Mrs','Age'].plot(kind='kde',ax=axes)data_1.loc[data_1['title']=='Master','Age'].plot(kind='kde',ax=axes)data_1.loc[data_1['title']=='Dr','Age'].plot(kind='kde',ax=axes)plt.legend(('Mr','Miss','Mrs','Master','Dr'),loc='best')plt.title('Title-Age')plt.xlabel('Age')axes_2=fig.add_subplot(1,2,2)data_1.loc[(data_1['title']=='Miss')&(data_1['Parch']>0),'Age'].plot(kind='kde',ax=axes_2)data_1.loc[(data_1['title']=='Miss')&(data_1['Parch']==0),'Age'].plot(kind='kde',ax=axes_2)plt.legend(('Parch>0','Parch=0'))plt.title('Miss/Parch-Age')plt.xlabel('Age')
#先输出除去'Miss'字段的各Title的年龄中位数data_age=pd.pivot_table(data_1,index='title',values='Age',aggfunc='median')data_age.drop(['Miss'],axis=0,inplace=True)title_sex=data_age.to_dict()['Age']data_1.set_index('title',inplace=True)data_1['Age'].fillna(title_sex,inplace=True)data_1.reset_index(inplace=True)##对miss中age的缺失值进行填充data_1['Parch>0']=0data_1.loc[(data_1['title']=='Miss')&(data_1['Parch']>0),'Parch>0']=1miss_age=pd.pivot_table(data_1.loc[data_1['title']=='Miss'],index='title',values='Age',columns='Parch>0',aggfunc='median')data_1.loc[(data_1['title']=='Miss')&(data_1['Parch']>0)]=9.5data_1['Age'].fillna(25.5,inplace=True)
Miss分Parch是否大于零的Age的中位数分别为25.5与9.5
4.4 现在对最后一个缺失值Cabin进行填充
Cabin的数据比较特殊,字符类型,分类也比较多,经观察,仓位的首字母是可以提取出来作为特征使用的。Cabin空值较多,用N来代替空值,空值的产生应该是这一部分人本身就没有仓位导致的。
data_1['Cabin'].fillna('N',inplace=True)data_1['cabin_1']=data_1['Cabin'].map(lambda x : x[0])cabin_s=pd.pivot_table(data_1,index='cabin_1',columns='Survived',values='PassengerId',aggfunc='count')cabin_s['存活率']=cabin_s[1.0]/(cabin_s[0.0]+cabin_s[1.0])data_1['cabin_1'].replace({'G':'A','F':'C','E':'B','D':'B','T':'N'},inplace=True)
仓位的分类有点过多,且各仓位的存活率是有相似性的,将A,G仓归为A类,C,F仓归为C类,将B,E,D仓归为B类,T仓归为N类。其中归为N类的记录有点多,看看能不能再优化一下。下面看一下各仓位类型和Fare的密度曲线。
fig=plt.figure(figsize=(8,8))axes=fig.add_subplot(1,1,1)data_1.loc[data_1['cabin_1']=='A','Fare'].plot(kind='kde',ax=axes)data_1.loc[data_1['cabin_1']=='B','Fare'].plot(kind='kde',ax=axes)data_1.loc[data_1['cabin_1']=='C','Fare'].plot(kind='kde',ax=axes)data_1.loc[data_1['cabin_1']=='N','Fare'].plot(kind='kde',ax=axes)plt.legend(('A','B','C','N'),loc='best')
通过曲线可以看出,填充的N仓是有一部分凸起的,且部分高价票的乘客也是N仓的,现在将N仓中Fare>240的改为B仓,Fare>80的改为C仓。
data_1.loc[(data_1['cabin_1']=='N')&(data_1['Fare']>240),'cabin_1']='B'data_1.loc[(data_1['cabin_1']=='N')&(data_1['Fare']>80),'cabin_1']='B'
优化后的曲线
5、构造新特征
以上已经将各变量的缺失值填充完毕,现在就来到了最考验创造力的时刻:构造新特征。在上文中,我们还有几个变量没有用到:Name,Parch,SibSp等字段。
5.1Name字段清洗
思路:现有title分类过多,对title进行分类,聚类的规则就是按照title的实际意义进行分类,分类规则如下:
title_dict={'Mlle':'Miss','Ms':'Mrs','Dr':'Officer','Dona':'Royalty', 'Lady':'Royalty','Mme':'Mrs','the Countess':'Royalty', 'Rev':'Officer','Col':'Officer','Major':'Officer','Capt':'Officer', 'Don':'Royalty','Jonkheer':'Royalty','Sir':'Royalty'}data_1['title'].replace(title_dict,inplace=True)
5.2 Parch与SibSp字段的清洗
data_1['family_size']=data_1['Parch']+data_1['SibSp']+1data_1['family_size'].value_counts()def f_size(x): if x==1: a='single' elif x<=3 and x>=2: a='small' elif x<=6: a='media' else: a='large' return adata_1['family_size_']=data_1['family_size'].map(lambda x :f_size(x))
5.3 构造‘儿童’与‘母亲’字段
将年龄<12的作为儿童,将title=Mrs,parch>1的作为母亲
data_1['child']=0data_1.loc[data_1['Age']<12,'child']=1data_1['mother']=0data_1.loc[(data_1['Parch']>1)&(data_1['title']=='Mrs'),'mother']=1
5.4 构造高票价的字段
将Fare大于高于200的构造一个新字段
data_1['high_fare']=0data_1.loc[data_1['Fare']>200,'high_fare']=1
6 特征已经构造完了,最后一步需要将one-hot编码了
finall_df=data_1[['PassengerId','Survived','Age','Fare','family_size','child', 'mother','high_fare']].copy()title_df=pd.get_dummies(data_1['title'],prefix='title')embarked_df=pd.get_dummies(data_1['Embarked'],prefix='Embarked')pclass_df=pd.get_dummies(data_1['Pclass'],prefix='pclass')sex_df=pd.get_dummies(data_1['Sex'],prefix='sex')cabin_df=pd.get_dummies(data_1['cabin_1'],prefix='cabin')family_df=pd.get_dummies(data_1['family_size_'],prefix='family')finall_df=pd.concat([finall_df,title_df,embarked_df,pclass_df,sex_df,cabin_df,family_df],axis=1)
7 数据基本已经清洗完了,还有最后一步,数值型数据的规范化
在选择分类器的时候会有一部分分类器是基于距离的,所以数值型数据需要进行标准化,一方面能够加快收敛的速度,另一方面在计算距离的避免不同量纲带来的距离不统一的问题
在此对‘Age’,‘Family_size’采用最大最小化归一,由于Fare的值分布不均匀,采用z-score规范化
finall_df['Age']=(finall_df['Age']-finall_df['Age'].min())/(finall_df['Age'].max()-finall_df['Age'].min())finall_df['family_size']=(finall_df['family_size']-finall_df['family_size'].min())/(finall_df['family_size'].max()-finall_df['family_size'].min())x_=finall_df['Fare'].max()finall_df['Fare']=finall_df['Fare'].map(lambda x: math.log(x+1,10)/math.log(x_,10))finall_df['cabin']=finall_df['cabin_n']/finall_df['cabin_n'].max()