【项目分析】kaggle入门:泰坦尼克号生存预测分析

【目录】

kaggle介绍

项目练手:泰坦尼克号

收获分享

 

一、kaggle介绍

 

kaggle是一个数据科学竞赛平台,创立于2010年,主要为开发商和数据科学家提供举办机器学习竞赛、托管数据库、编写和分享代码的平台。kaggle官方表示,该社区目前在全世界范围内已有超过80万注册用户。

 

以上是官方介绍,我们换个角度来理解kaggle:

kaggle是为了解决什么问题而被创造出来的?

对企业来说,

在做数据分析的时候,同一个项目往往用多种模型解决,最直接的思路就是研究者一个个去试,直到找到表现最好的模型停止。这样会带来另一个问题:时间成本和人力成本大大增加。

所以研究者往往会在试到性能优于一定阈值的时候,就停下来,选择当前最优模型。

对于数据量小的项目时,不同模型间的复杂度和时间可以忽略不记。

但对于数据量大的项目时,不同复杂度的模型的效率就可以是天差地别了。

而对于瞬息万变的市场来说,时间就是金钱,谁有时间等你模型跑完了再变化。

所以,如果我们团队现在遇到一个问题,我把它放到网上,被很多人看到了:有围观的路人、有想练手的小白、有下凡的大神等,其中有10个人给出了这个问题的解决方法和答案,这样我就可以在最短的时间尝试最多的解决方法,然后用这10个答案中最好的3个进行进一步地优化,大大提升了效率。

这也是为什么有很多企业喜欢在kaggle上发布数据和问题,并提供丰厚的奖金来吸引能人义士。

 

 

对个人来说,

每个人都有同样的机会去接触、并尝试解决同一个项目,取最优解。这就创造了一种全新的劳动市场——不以学历和工作经验作为唯一的人才评判标准,而是着眼于个人技能,为顶尖人才和公司之间搭建了一座桥梁。

 

今天看到的arXiv将 170万篇arXiv论文集成为一个格式机器可读的数据库,并将该数据库托管到了Kaggle上供用户免费使用。数据库包括论文标题、作者、类别、摘要、全文pdf等论文相关信息。kaggle的用户可以直接利用该平台上丰富的数据探索工具,轻松地与他人共享相关论文文本和输出。

二、项目练手

1、问题提出

2、收集数据

3、数据预处理(缺失值处理、归一化、z_score

4、不同变量跟生存情况的关系分析

 

5、建立模型并预测

 

 

1、科学问题

 

1912年泰坦尼克号邮轮在其处女启航时触礁冰山而沉没,2224名船员和乘客中有1502人遇难。

 

什么样的人在泰坦尼克号中更容易存活?

2、收集数据

2.1 收集数据

数据下载:https://www.kaggle.com/c/titanic

 

网站一共提供了三个excel文件:

gender_submission.csv文件中包括passengerID和survived数据,是test数据集的真实标签。

test.csv和train.csv文件中包括:

passengerId:样本编号

survived:存活情况(标签)

Pclass:乘客乘坐舱位情况(1/2/3等)

Name:姓名

sex:性别

age:年龄

SibSp:堂兄弟/妹个数/ 不同代直系亲属人数

Parch:父母与小孩个数/ 同代直系亲属人数

ticket:船票信息

fare:票价

cabin:客舱

embarked:登船港口(出发地点:S=英国南安普顿Southampton;途径地点1:C=法国 瑟堡市Cherbourg;途径地点2:Q=爱尔兰 昆士敦Queenstown)

共11个特征,1个标签,数据量小,可能存在过拟合现象。

 

 

 2.2 导入并合并数据集

import os
os.chdir("C:\\Users\\liuji\\Desktop\\titanic")
import numpy as np
import pandas as pd

#导入数据
train = pd.read_csv("./train.csv")
test  = pd.read_csv("./test.csv")
#数据行列数
print("train",train.shape,"test:",test.shape)

#合并数据集,方便同时对两个数据集进行清洗
full = train.append( test , ignore_index = True )
print ('合并后的数据集:',full.shape)
output: 

train (891, 12) test: (418, 11)

合并后的数据集: (1309, 12)

 2.3 查看数据

full.head()       #查看前5行的数据
full.describe()  #查看数据类型的描述统计信息,对字符串类型(如名字)数据不显示
full.info()         #查看每一行的数据类型和数据总数

 

注:没有字符串类型数据

 

 

 由上述结果可以看出,共有1309行数据:891(train)+481(test)

其中:

数据类型列:

年龄(Age)、船舱号(Cabin)里面有缺失数据:
1)年龄(Age)里面数据总数是1046条,缺失了1309-1046=263,缺失率263/1309=20%
2)船票价格(Fare)里面数据总数是1308条,缺失了1条数据
字符串列:
1)登船港口(Embarked)里面数据总数是1307,只缺失了2条数据,缺失比较少
2)船舱号(Cabin)里面数据总数是295,缺失了1309-295=1014,缺失率=1014/1309=77.5%,缺失比较大

 

 

 接下来的任务就是对缺失数据进行处理。

3、数据预处理

3.1 缺失值处理

  • 数值型数据
    • 缺失少:用均值填补缺失值:age, fare
    • 缺失多:删除、模型预测
  • 分类型数据
    • 缺失少:用最常见的类别(众数)填补缺失值,embarked
    • 缺失多:将缺失值填补为NA,表示缺失,cabin
  • 对数值型和分类数据都可以做的处理
    • 删除
      • 直接删除此特征(缺损数据太多的情况,防止引入噪声)
      • 直接删除缺损数据的样本(只用于训练数据集,且样本量较大,缺损数据样本较少的情况)
    • 模型预测缺失值,如KNN、随机森林(实现复杂,但准确率会较高,对一些重要的特征可以考虑该方法

数据处理可以根据后续的分析反馈进行调整。

age、fare用均值填补缺失值(简单)

import matplotlib.pyplot as plt

plt.subplot(2,2,1)
full['Age'].hist(bins=10, grid=False, density=True, figsize=(10,6))
#plt.title('AGE before fillna')

plt.subplot(222)
full['Fare'].hist(bins=10, grid=False, density=True, figsize=(10,6))
#plt.title('Fare before fillna')

#年龄(Age)
full['Age']=full['Age'].fillna( full['Age'].mean() )
#船票价格(Fare)
full['Fare'] = full['Fare'].fillna( full['Fare'].mean() )


plt.subplot(223)
full['Age'].hist(bins=10, grid=False, density=True, figsize=(10,6))
#plt.title("AGE after fillna")

plt.subplot(224)
full['Fare'].hist(bins=10, grid=False, density=True, figsize=(10,6))
#plt.title("Fare after fillna")

plt.show()

 

 在年龄的缺失值处理过程中,20~30年龄段的数据分布占比较填充前有明显变化,25 ~ 30岁的人数占比增多,其余年龄段的分布与之前基本相同。

在船票的缺失值处理结果比较,其分布与之前基本相同。

 

 

embark登船港口用众数填补:

full['Embarked'].value_counts()

output: 

 从结果来看,S类别最常见。所以我们将缺失值填充为最频繁出现的值

#填补前
plt.subplot(1,2,1)
full['Embarked'].hist(bins=10, grid=False, density=True)
#填补后
full['Embarked'] = full['Embarked'].fillna( 'S' )
plt.subplot(1,2,2)
full['Embarked'].hist(bins=10, grid=False, density=True,color="orange")

填补前后数据分布没有太大的变化

 

cabin船舱将缺失值分为NA类别

#船舱号(Cabin):查看里面数据长啥样
full['Cabin'].head()
full['Cabin'].value_counts()

 

full['Cabin'] = full['Cabin'].fillna('U')
full['Cabin'].head()   #检查
full['Cabin'].value_counts()

 

检查缺失值的处理情况

full.info()

 

3.2 其它数据处理

  异常值处理:z_socre转换

  scaling

  正则化处理

   因为时间因素就不展开了,而且在没有进行进一步的数据处理的情况下,模型效果的score>0.80,算是可以接受的结果。

4、特征提取

4.1 数据分类

将数据分为三类:

1.数值类型:
乘客编号(PassengerId),年龄(Age),船票价格(Fare),同代直系亲属人数(SibSp),不同代直系亲属人数(Parch)
2.时间序列:无
3.分类数据:
1)有直接类别的——用数值代替类别,并进行One-hot编码
乘客性别(Sex):男性male,女性female
登船港口(Embarked):出发地点S=英国南安普顿Southampton,途径地点1:C=法国 瑟堡市Cherbourg,出发地点2:Q=爱尔兰 昆士敦Queenstown
客舱等级(Pclass):1=1等舱,2=2等舱,3=3等舱
2)字符串类型:可能从这里面提取出特征来,也归到分类数据中
乘客姓名(Name)
客舱号(Cabin)
船票编号(Ticket)

性别

从图中可发现,女性的存活率远高于男性。

将性别映射为数值,male对应数值1,female对应数值0。

import seaborn as sns
sns.countplot(x='Sex',hue='Survived',data=full)
sex_mapDict
={'male':1, 'female':0} #map函数:对Series每个数据应用自定义的函数计算 full['Sex']=full['Sex'].map(sex_mapDict)

 

 

 

 embarked(登船港口

登船港口(Embarked)的值是:
出发地点:S=英国南安普顿Southampton
途径地点1:C=法国 瑟堡市Cherbourg
途径地点2:Q=爱尔兰 昆士敦Queenstown

从图上看,从法国和爱尔兰上船的死亡率比较低,不知为何。

full['Embarked'].head()
sns.countplot(x='Embarked',hue='Survived',data=full)

 

 

 

使用get_dummies进行one-hot编码,产生虚拟变量(dummy variables),列名前缀是Embarked

并将变量添加进数据集full

embarkedDf = pd.DataFrame()

embarkedDf = pd.get_dummies( full['Embarked'] , prefix='Embarked' )
embarkedDf.head()

full = pd.concat([full,embarkedDf],axis=1)
full.drop('Embarked',axis=1,inplace=True)
full.head()

船舱等级

客舱等级(Pclass):
1=1等舱,2=2等舱,3=3等舱

从图中看出,船舱等级越高,生存率越高

(我也想成为有钱人

sns.countplot(x='Pclass',hue='Survived',data=full)

 

 

使用get_dummies进行one-hot编码,列名前缀是Pclass,并将其添加进数据集full

pclassDf = pd.DataFrame()

pclassDf = pd.get_dummies( full['Pclass'] , prefix='Pclass' )
pclassDf.head()

full = pd.concat([full,pclassDf],axis=1)
full.drop('Pclass',axis=1,inplace=True)  #删除原来的信息

乘客姓名

(一开始我不知道怎么处理,打算直接删除,后来看了大神博客分享,发现

(乘客头衔中每个名字都包含了具体的称谓或是头衔(Mr. Miss. Mrs. 等),可以将这部信息提出出来作为特征

定义以下几种头衔类别:

Officer政府官员
Royalty王室(皇室)
Mr已婚男士
Mrs已婚妇女
Miss年轻未婚女子
Master有技能的人/教师

从称谓和存活率的分布图可以看出,女性的存活率会高于男性,而这个特征可能与sex有共线性。

#获取称谓的函数
def getTitle(name):
    str1=name.split( ',' )[1] #返回Mr. Owen Harris
    str2=str1.split( '.' )[0]#返回Mr
    
    str3=str2.strip() #strip() 方法用于移除字符串头尾指定的字符(默认为空格),注意这里有空格要去除!不然会影响后续处理!
    return str3

titleDf = pd.DataFrame()
titleDf['Title'] = full['Name'].map(getTitle)#map函数:对Series每个数据应用自定义的函数计算

#姓名中头衔字符串与定义头衔类别的映射关系
title_mapDict = {
                    "Capt":       "Officer",
                    "Col":        "Officer",
                    "Major":      "Officer",
                    "Jonkheer":   "Royalty",
                    "Don":        "Royalty",
                    "Sir" :       "Royalty",
                    "Dr":         "Officer",
                    "Rev":        "Officer",
                    "the Countess":"Royalty",
                    "Dona":       "Royalty",
                    "Mme":        "Mrs",
                    "Mlle":       "Miss",
                    "Ms":         "Mrs",
                    "Mr" :        "Mr",
                    "Mrs" :       "Mrs",
                    "Miss" :      "Miss",
                    "Master" :    "Master",
                    "Lady" :      "Royalty"
                    }

titleDf['Title'] = titleDf['Title'].map(title_mapDict)

full = pd.concat([full,titleDf],axis=1)
sns.countplot(x='Title',hue='Survived',data=full)
full.drop('Title',axis=1,inplace=True)

#使用get_dummies进行one-hot编码
titleDf = pd.get_dummies(titleDf['Title'])
full = pd.concat([full,titleDf],axis=1)
full.drop('Name',axis=1,inplace=True)  #删掉姓名这一列

 

cabin(客舱号 

 

客舱号每个数据的首字母代表不同级别的客舱,如

C85 代表在C等级的客舱

所以需要先将客舱级别整理出来

再进行one-hot编码

好像除了Unknown类别,其它已知的类别客舱存活率都能过半,但数据缺失太多,可靠性低。

full[ 'Cabin' ] = full[ 'Cabin' ].map( lambda c : c[0] )

cabinDf = pd.DataFrame()
cabinDf = pd.get_dummies( full['Cabin'] , prefix = 'Cabin' )
full = pd.concat([full,cabinDf],axis=1)

sns.countplot(x='Cabin',hue='Survived',data=full)
full.drop('Cabin',axis=1,inplace=True)

 

 SibSp和Parch(家族人数

这两个特征的分布规律相似,

可以看出,有兄弟姐妹或父母孩子的比单独旅行的幸存率的确要高,所以以后遇到危险情况,
我上有八十老母,下有三岁小儿嗷嗷待哺,请大慈大悲放我一条生路吧,

这套贯口还是有用的哈哈哈

 

将这两个特征合并整理为 家庭人数,并整理为三类

家庭人数=同代直系亲属数(Parch)+不同代直系亲属数(SibSp)+乘客自己
(因为乘客自己也是家庭成员的一个,所以记得要加1!!!

家庭类别:
小家庭Family_Single:家庭人数=1
中等家庭Family_Small: 2<=家庭人数<=4
大家庭Family_Large: 家庭人数>=5

从图中可以看出,家庭人数在2-4的范围的人,生存率还是较高的,而独自一个人的生存率较低。(这就是生无可恋吗

familyDf = pd.DataFrame()
familyDf[ 'FamilySize' ] = full[ 'Parch' ] + full[ 'SibSp' ] + 1

#if 条件为真的时候返回if前面内容,否则返回0
familyDf[ 'Family_Single' ] = familyDf[ 'FamilySize' ].map( lambda s : 1 if s == 1 else 0 )
familyDf[ 'Family_Small' ]  = familyDf[ 'FamilySize' ].map( lambda s : 1 if 2 <= s <= 4 else 0 )
familyDf[ 'Family_Large' ]  = familyDf[ 'FamilySize' ].map( lambda s : 1 if 5 <= s else 0 )

full = pd.concat([full,familyDf],axis=1)
sns.countplot(x='FamilySize', hue="Survived", data=full)

 

ticket (船票

ticket的类别较多,发现有些人的票号是一样的,可能对应的是朋友、家人一起出行的情况(可能会与家庭人数有一点共线性),所以根据是否与他人票号重复为条件,将ticket的数据分为两组:

 

 

def getNum(num):
    str4 = num.split()[-1]
    return str4

NumDf = pd.DataFrame()
NumDf['Ticket_num'] = full['Ticket'].map(getNum)
NumDf.info()
NumDf.head() #检查

NumDf['Ticket_repeat'] = NumDf['Ticket_num'].duplicated() #根据是否重复返回True或False

num_mapDict={True:1,
            False:0}
NumDf['Ticket_repeat']=NumDf['Ticket_repeat'].map(num_mapDict)
NumDf.head()

full = pd.concat([full,NumDf],axis=1)
full.drop('Ticket',axis=1,inplace=True)

sns.countplot(x='Ticket_repeat', hue="Survived", data=full)

 

  票号有重复的人的生存率较高呀。(好累<(_ _)>

age

将年龄分为三组,old、adult、young

ageDf = pd.DataFrame()
ageDf['Age_old'] = full['Age'].map(lambda s: 1 if s>39 else 0)
ageDf['Age_adult'] = full['Age'].map(lambda s: 1 if s<=39 and s>=21 else 0)
ageDf['Age_young'] = full['Age'].map(lambda s: 1 if s<21 else 0)

full = pd.concat([full,ageDf],axis=1)
plt.subplot(2,2,1)
sns.countplot(x='Age_old', hue="Survived", data=full)
plt.subplot(2,2,2)
sns.countplot(x='Age_adult', hue="Survived", data=full)
plt.subplot(2,2,3)
sns.countplot(x='Age_young', hue="Survived", data=full)

 

 年轻人的牺牲率比较高啊,不容易啊,年轻人~

Fare

 将票价分为三组,low、middle、high

fareDf = pd.DataFrame()
fareDf['Fare_high'] = full['Fare'].map(lambda s: 1 if s>31 else 0)
fareDf['Fare_middle'] = full['Fare'].map(lambda s: 1 if s<=31 and s>=7 else 0)
fareDf['Fare_low'] = full['Fare'].map(lambda s: 1 if s<7 else 0)

full = pd.concat([full,fareDf],axis=1)
plt.subplot(2,2,1)
sns.countplot(x='Fare_high', hue="Survived", data=full)
plt.subplot(2,2,2)
sns.countplot(x='Fare_middle', hue="Survived", data=full)
plt.subplot(2,2,3)
sns.countplot(x='Fare_low', hue="Survived", data=full)

 

 票价高的survived多。

 

 4.2 特征选择

用相关系数法计算各个特征的相关系数

#相关性矩阵
corrDf = full.corr() 
sns.heatmap(corrDf)
corrDf

 

 

 查看各个特征与survived的相关系数,并进行排序

corrDf['Survived'].sort_values(ascending =False)

 

 

 

根据各个特征与生成情况(Survived)的相关系数大小,我们选择了这几个特征作为模型的输入:

头衔(前面所在的数据集titleDf)、客舱等级(pclassDf)、家庭大小(familyDf)、船票价格(Fare)、船舱号(cabinDf)、登船港口(embarkedDf)、性别(Sex)

(我也尝试加过full['Age']、full['Ticket_repeat']这两个特征,相当于用上所有给的信息,但score分数却下降了,

#特征选择
full_X = pd.concat( [titleDf,#头衔
                     pclassDf,#客舱等级
                     familyDf,#家庭大小
                     full['Fare'],#船票价格
                     cabinDf,#船舱号
                     embarkedDf,#登船港口
                     full['Sex']
                    ] , axis=1 )
full_X.head()

 

 

5、构建模型

5.1 建立训练数据集和测试数据集

(吃完饭的我又充满能量了!!

从train中拆除20%的数据作为验证集,80%的数据作为测试集,test是用训练好的模型直接预测,作为最终的结果。

sourceRow=891  #train数据集
source_X = full_X.loc[0:sourceRow-1,:]            #原始数据集:特征
source_y = full.loc[0:sourceRow-1,'Survived']   #原始数据集:标签
pred_X = full_X.loc[sourceRow:,:]                    #预测数据集:特征

print('train数据集行数:',source_X.shape[0])
print('test数据集行数:',pred_X.shape[0])
#from sklearn.cross_validation import train_test_split 
from sklearn.model_selection import train_test_split 
#建立模型用的训练数据集和测试数据集
train_X, test_X, train_y, test_y = train_test_split(source_X ,
                                                    source_y,
                                                    train_size=.8)

#输出数据集大小
print ('原始数据集特征:',source_X.shape, 
       '训练数据集特征:',train_X.shape ,
      '测试数据集特征:',test_X.shape)

print ('原始数据集标签:',source_y.shape, 
       '训练数据集标签:',train_y.shape ,
      '测试数据集标签:',test_y.shape)

原始数据集特征: (891, 27) 训练数据集特征: (712, 27) 测试数据集特征: (179, 27)
原始数据集标签: (891,) 训练数据集标签: (712,) 测试数据集标签: (179,)

5.2 选择机器学习算法

#第1步:导入算法
from sklearn.linear_model import LogisticRegression
#第2步:创建模型:逻辑回归(logisic regression)
model = LogisticRegression()

#随机森林Random Forests Model
#from sklearn.ensemble import RandomForestClassifier
#model = RandomForestClassifier(n_estimators=100)

#支持向量机Support Vector Machines
#from sklearn.svm import SVC, LinearSVC
#model = SVC()

#Gradient Boosting Classifier
#from sklearn.ensemble import GradientBoostingClassifier
#model = GradientBoostingClassifier()

 

5.3 训练模型

#第3步:训练模型
model.fit( train_X , train_y )

 

6、评估模型

 

# 分类问题,score得到的是模型的正确率
model.score(test_X , test_y )
 
Out[148]:
0.8547486033519553

 

7、数据预测

对test数据集的数据进行预测

注意,这里生成的预测值是浮点数(0.0,1.0

但Kaggle要求提交的是整数型(0,1

所以记得对数据类型进行转换!!!!

pred_Y = model.predict(pred_X)
pred_Y=pred_Y.astype(int)

passenger_id = full.loc[sourceRow:,'PassengerId']  #乘客id
predDf = pd.DataFrame(                           #数据框:乘客id,预测生存情况的值
    { 'PassengerId': passenger_id , 
     'Survived': pred_Y } )
predDf.shape
predDf.head()

predDf.to_csv( 'titanic_pred.csv' , index = False )

三、感受

不足:

训练模型如果用随机森林等其它模型,需要进行调参等一系列分析,还有待学习。

收获:

感受了项目处理的整体流程。

数据预处理是最最重要的,一定要认真做,不然越到后面越难做,还得重新来一遍。

数据信息提取和特征选择很考验对信息的敏感性,像这次从姓名中提取身份信息就很好,还是需要多看项目、多实践。

虽然和我课题没关系,但整体思路还是很相像的。

思考:如果是我来设计数据收集,我还会使用什么指标?

性格:冷静/冲动   研究表明,70%的受伤者是由于海上事故发生时惊慌失措导致的,15%的乘客举止反常,而只有15%的乘客在事发时保持冷静和警觉。

 

posted @ 2020-09-01 15:13  桃花换小鱼干好不好  阅读(1062)  评论(0)    收藏  举报