Python数据分析(九):泰坦尼克号获救分析预测

好久没更新了,由于疫情的原因,家里的工作一直比较忙,最近闲下来了,学校也还没开学,正好趁着这段时间,复习一下前段时间的知识,泰坦尼克号的案例在数据分析中是比较经典的,今天我们就借助这个案例看一下数据分析的流程是怎么样的。

一、数据集

首先我们先来看一下数据集,不要嘲笑我的中文路径(哈哈哈),当初整理文件的时候嫌麻烦,直接上中文了,不过这里还是提醒一下大家,路径尽量不要带中文,以免有些代码不支持。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')    # 样式美化
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
data = pd.read_csv('../../数据集汇总/泰坦尼克号数据集/titanic_train.csv')
data.head()

这里我们分别看一下每个列名代表的含义:

  • PassengerId: 乘客id
  • Survived:是否获救,0表示未获救,1表示获救
  • Pclass:乘客等级(1/2/3等舱位)
  • Name:乘客姓名
  • Sex:性别
  • Age:年龄
  • SlbSp:堂兄弟/妹个数
  • Parch:父母与小孩个数
  • Ticket:船票信息
  • Fare:票价
  • Cabin:客舱
  • Embarked:登船港口

数据集相对来说比较简单,想要的特征基本上都给出来,简单明了。

二、数据分析

数据集已经知道各个字段的含义,接下来我们要进行数据分析,看一下各个特征对结果的影响,统计绘图得出结论。

data.isnull().sum()

先来看一下,有没有缺失值,通过上图可以看出,Age(年龄)、Cabin(客舱)和Embarked(登船港口)这三列存在缺失值,其中Cabin(客舱)的缺失值相对较多,由于这一列本身价值不高,后期我们会舍去。

data.describe()


我们看一下具体的数据分布,通过data.describe()可以看到数据一共是891行,还可以看到一些列的最大最小值,比如年龄这一列,年龄最大的有80岁,最小的还不到一岁。

接下来,我们画图看一下,

fig,ax = plt.subplots(1,2,figsize=(18,8))
# data['Survived'].value_counts().plot.pie(explode=[0,0.1],autopct='%1.1f%%',shadow=True,ax=ax[0])
ax[0].pie(data['Survived'].value_counts(),explode=[0,0.1],autopct='%1.1f%%',shadow=True,labels=[0,1])
ax[0].set_title('Survived')
ax[0].set_ylabel('')
sns.countplot('Survived',data=data,ax=ax[1])
ax[1].set_title('Survived')
plt.show()

通过饼图可以看出来,获救的占比才38.4%,一共有891个人,获救的才300多一点,说明救援率还是比较低的。

接下来我们对各个特征进行单独分析,看一下各个特征对结果的影响情况。

  • 1、性别特征分析
data.groupby(['Sex','Survived'])['Survived'].count()

根据上面的统计分析可以看出,女性被救出来的几率非常大,我们还可以画图看一下,

fig,ax = plt.subplots(1,2,figsize=(10,8))
data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax = ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot(x='Sex',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show


通过左图可以看出,如果是女人,在所有的女人中有70%多的几率会被救,而男人只有20%不到(嗯...深刻的感受到了做男人的不易~),右图可以看出女性被救的人数比男性的两倍还要多。通过分析发现,性别是一个重要的特征。

  • 2、船舱等级特征分析
pd.crosstab(data.Pclass,data.Survived,margins=True).style.background_gradient(cmap='summer_r')

通过表格我们可以看出一等舱一共216人,有136人被救,占比高于50%;二等舱有184人,87人被救,占比在50%左右;三等舱一共491人,只有119人被救,占比最低。虽然我们讲生命不分贵贱,但是现实还是比较残酷的,有钱人被救的几率还是远高于穷人。

我们画图看一下,

fig,ax=plt.subplots(1,2,figsize=(10,8))
data['Pclass'].value_counts().plot.bar(color=['green','blue','yellow'],ax=ax[0])
ax[0].set_title('Number Of Passengers By Pclass')
sns.countplot('Pclass',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Pclass:Survived vs Dead')
plt.show()

两图对比更加明显,一等舱被救的人数高于未被救的人数,二等舱差不多,三等舱被救的人远低于未被救的人数。
我们再来看一下性别和船舱等级与被救之间的关系,

pd.crosstab([data.Sex,data.Survived],data.Pclass,margins=True).style.background_gradient(cmap='summer_r')

通过上表可以看出,在一等舱里,女性被救的比例非常高,女性一共有94人,91人被救。
我们画图看一下男性与女性在各个船舱被救的比例,

sns.factorplot('Pclass','Survived',hue='Sex',data=data)
plt.show()

通过上图可以看明显看出,三个船舱中,女性被救的比例远高于男性。(心疼自己一秒钟...)

  • 3、年龄特征分析
    先来看一下年龄的数值特征,
print('最大年龄',data['Age'].max())
print('最小年龄',data['Age'].min())
print('平均年龄',data['Age'].mean())
# 输出
最大年龄 80.0
最小年龄 0.42
平均年龄 29.69911764705882

接下来画图分析一下,船舱等级和年龄、性别和年龄之间被救的关系,

fig,ax = plt.subplots(1,2,figsize=(10,8))
sns.violinplot('Pclass','Age',hue='Survived',data=data,split=True,ax=ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot('Sex','Age',hue='Survived',data=data,split=True,ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()

通过左图,可以分析出,一等舱被救的人中大部分集中在37、8岁左右,二等舱被救的人大部分集中在30岁,三等舱集中在20 ~ 30岁左右,可以看出一等舱被救的年龄比其他两个舱的要大,有可能是一等舱本身就是比较富有的人,一般都是拼搏到了一定的年龄才会这样(瞎猜的);右图的话,可以看出男性被救的年龄在30岁左右,女性被救的20~30岁左右,差距不是很大。

  • 4、登船地点特征分析
pd.crosstab([data.Embarked,data.Pclass],[data.Sex,data.Survived],margins=True).style.background_gradient(cmap='summer_r')

通过图表可以看出,S登船地的人数最多,Q最少。

sns.factorplot('Embarked','Survived',data=data)
fig=plt.gcf()
fig.set_size_inches(5,3)

通过上图可以看出,C港的生存率最高,在0.55左右,而S港的生存率最低。(莫名的想起吃鸡~)

fig,ax=plt.subplots(2,2,figsize=(15,15))
sns.countplot('Embarked',data=data,ax=ax[0][0])
ax[0][0].set_title('No. Of Passengers Boarded')
sns.countplot('Embarked',hue='Sex',data=data,ax=ax[0][1])
ax[0][1].set_title('Male-Femal Split for Embarked')
sns.countplot('Embarked',hue='Survived',data=data,ax=ax[1][0])
ax[1][0].set_title('Embarked vs Survived')
sns.countplot('Embarked',hue='Pclass',data=data,ax=ax[1][1])
ax[1][1].set_title('Embarked vs Pclass')
plt.subplots_adjust(wspace=0.2,hspace=0.5)
plt.show()

左上,登船地点的人数统计,可以明显看出S港最多;右上,各个登船地点的男女人数统计;左下,各个登船地点获救与未获救的人数统计,结合上表,S港的获救人数最高,但是获救占比却是最低的;右下,各个登船地点的船舱等级人数统计,可以看出S港登船的人数最多,其中三等舱的人数最多,Q港几乎没有一等舱的人登船。

sns.factorplot('Pclass','Survived',hue='Sex',col='Embarked',data=data)
plt.show()

上图是各个登船地点,每个船舱等级被救的男女比例,总之完全符合女人和孩子第一的政策。

  • 5、堂兄弟/妹特征分析
pd.crosstab(data.Survived,data.SibSp,margins=True).style.background_gradient(cmap='summer_r')
plt.figure(figsize=(8,8))
sns.countplot('SibSp',hue='Survived',data=data)
plt.show()

通过上图表可以看出,堂兄弟/妹个数越多,基数越少,被救的也就越少,所以我们就简单分析一下,保留这个特征。

  • 6、父母与小孩个数特征分析
pd.crosstab(data.Survived,data.Parch,margins=True).style.background_gradient(cmap='summer_r')
plt.figure(figsize=(8,8))
sns.countplot('Parch',hue='Survived',data=data)
plt.show()

这个特征与堂兄弟/妹个数的特征相似,父母孩子个数越多,基数越少,被救的人数也越少。比如有父母或孩子4、5个的人很少,都没有被救,这个特征我们也保留。

以上就是我们对各个特征之间的理解,还有几个特征比如Ticket(船票信息)、Cabin(客舱)等我们没有进行分析,主要原因是船票信息对是否获救意义不大,而客舱Cabin缺失值较多,这些信息在接下来的数据处理中我们直接删掉了。

三、数据清洗与预处理

1、缺失值填充

首先我们先处理一下缺失值,通过上面的data.isnull().sum()我们知道有两个特征值存在缺失的情况(Cabin不考虑)。

  • Age年龄问题
    关于年龄填充,我们一般是选择年龄的平均值进行填充,但是这里我们发现,在Name这一列,名字都比较长,中间会带有Mr,Miss,Mrs等特征来标识每一个人,我们可以根据这个特点,求出每个标识中,年龄的平均值进行填充,这样填充的效果会更加实际。
# 先用正则表达式进行提取
data['initial'] = 0
for i in data:
    data['initial']=data.Name.str.extract('([A-Za-z]+)\.')
data.head()

接下来我们看一下分出来的男女人数,

pd.crosstab(data.initial,data.Sex).T.style.background_gradient(cmap='summer_r')


我们将人数比较多的提取出来,剩下的用others代替,

data['initial']=data['initial'].replace(['Capt','Col','Countess','Don','Dr','Jonkheer','Lady','Major','Master','Mlle','Mme','Ms','Rev','Sir'],'others')

接下来,我们求出每个标识的平均年龄,

data.groupby('initial')['Age'].mean()

最后我们可以填充了,

data.loc[(data.Age.isnull())&(data.initial == 'Miss'),'Age']=22
data.loc[(data.Age.isnull())&(data.initial == 'Mr'),'Age']=32
data.loc[(data.Age.isnull())&(data.initial == 'Mrs'),'Age']=36
data.loc[(data.Age.isnull())&(data.initial == 'others'),'Age']=20

我们还可以画图展示一下,

fig,ax = plt.subplots(1,2,figsize=(10,8))
data[data['Survived']==0].Age.plot.hist(ax=ax[0],bins=20,edgecolor='black',color='red')
ax[0].set_title('Survived = 0')
data[data['Survived']==1].Age.plot.hist(ax=ax[1],bins=20,edgecolor='black',color='green')
ax[1].set_title('Survived = 1')
plt.show()

通过上图可以看到,被救最多人的的年龄分布在20~40之间,超过60岁的被救的比较少了(年龄最大的人(80岁)被救了)。

  • Embarked缺失值填充
    Embarked列的缺失值填充比较简单,通过上面的特征分析我们可以发现,S港的人数最多,所以我们就用众数填充,
data['Embarked'].fillna('S',inplace=True)

填充完毕后,我们再统计一下缺失值,

data.isnull().sum()


就只剩Cabin列,接下我们进行数据预处理。

2、数据预处理

首先我们先将没用的特征进行处理,

data=data.drop(['Name','Ticket','Cabin','initial'],axis=1)
data.head()


接下来我们将性别特征和登船港口数字化,因为接下来用到的算法没法处理文字,

#将性别数字化,male=0,female=1
data.loc[data['Sex']=='male','Sex']=0
data.loc[data['Sex']=='female','Sex']=1
# 登船港口数字化 S=0,C=1,Q=2
data.loc[data['Embarked']=='S','Embarked']=0
data.loc[data['Embarked']=='C','Embarked']=1
data.loc[data['Embarked']=='Q','Embarked']=2
data.head()

四、建立模型

这里我们使用多个机器学习算法进行预测,看一下效果如何。

1、线性回归预测

将训练集划分出特征和标签

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
linear = LinearRegression()
features = ['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']
kf=KFold(n_splits=3,shuffle=False,random_state=1)
source_x = data[features]  # 训练数据集:特征
source_y = data['Survived']  #训练数据集:标签
source_x.shape
#输出
(891,7)

将数据集划分为训练集和测试集,

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=0.8)
print('原始数据集特征:',source_x.shape,'训练集特征:',train_x.shape,'测试集特征',test_x.shape)
# 输出
原始数据集特征: (891, 7) 训练集特征: (712, 7) 测试集特征 (179, 7)

进行模型训练,

linear.fit(train_x,train_y)
linear.score(test_x,test_y)

准确率太低了,不知道哪里出了问题。
接下来,我们进行交叉验证再试一下,

# K折交叉验证
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
# from sklearn.model_selection import cross_val_score

model = LinearRegression()

kf = KFold(n_splits=3,shuffle=False,random_state=1)
# scores = cross_val_score(model,source_x,source_y,cv=kf)
# scores.mean()
scores=[]
for train,test in kf.split(source_x):
    train_data = source_x.iloc[train,:]
    train_target = source_y.iloc[train]
    model.fit(train_data,train_target)
    test_data = model.predict(source_x.iloc[test,:])
    scores.append(test_data)

因为结果只有0和1,而我们预测的结果分布在[0,1]区间上,所以我们将预测的得分值超过0.5设为1,小于0.5的设为0,

import numpy as np
scores = np.concatenate(scores,axis=0)
scores[scores>.5]=1
scores[scores<.5]=0
accuracy = sum(scores==source_y)/len(source_y)
print(accuracy)

准确率提高了不少。

2、逻辑斯蒂回归预测

LR虽然是回归算法,但是也可以做分类的,这里我们也进行了交叉验证,

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(random_state=1)
scores = cross_val_score(model,source_x,source_y,cv=3)
print(scores.mean())

准确率也有所提升。

3、随机森林预测

# 随机——随机对样本进行采样
# 随机——对特征进行随机抽取
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state=1,
                              n_estimators=200,#200课树
                              min_samples_split=4,
                              min_samples_leaf=2)
kf = KFold(n_splits=3,shuffle=False,random_state=1)
scores = cross_val_score(model,source_x,source_y,cv=kf)
print(scores.mean())

随机森林还是比较强的,结果也有所提升。

4、集成算法

集成算法是将几个算法合并在一起进行预测的算法,在生产实际和各种比赛都被广泛应用;随机森林也是一种集成算法,它由多个决策树合并在一起进行决策,这里我们将随机森林与逻辑斯蒂回归算法进行合并,看一下效果如何。

features = ['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']
model = RandomForestClassifier(random_state=1,
                              n_estimators=200,#100课树
                              min_samples_split=8,
                              min_samples_leaf=4)
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np
#定义两个算法集成在一起
algorithms = [
    [GradientBoostingClassifier(random_state=1,n_estimators=200,max_depth=3),features],
    [LogisticRegression(random_state=1,solver='liblinear'),features]
]

kf=KFold(n_splits=3,shuffle=False,random_state=1)
predictions=[]
for train,test in kf.split(source_x):
    train_target = source_y.iloc[train]
    full_test_predictions = []
    for alg,predictors in algorithms:
        alg.fit(source_x.iloc[train,:],train_target)
        test_predictions = alg.predict_proba(source_x.iloc[test,:].astype(float))[:,1]
        full_test_predictions.append(test_predictions)
    test_predictions = (full_test_predictions[0]+full_test_predictions[1])/2
    test_predictions[test_predictions <=.5] =0
    test_predictions[test_predictions >.5] =1
    predictions.append(test_predictions)
    
predictions = np.concatenate(predictions,axis=0)
accuracy = sum(predictions == source_y)/len(predictions)
print(accuracy)

可以看到效果也是不错,跟随机森林的准确率差不多。

五、总结

算法预测到这里就结束了,如果小伙伴有其他的想法可以自行测试,这里其实还是有很多可以改变的地方,比如,对于Name列,一般名字比较长的属于大家族,比较富有,可以统计一下名字的长度作为一个特征;在提取Mr,Miss,Mrs等标识时也可以使用one-hot编码进行处理,这样获得特征就会增加很多;还可以将堂兄弟/妹和父母孩子个数做一个求和添加一个Family的特征...这些都是可以的,感兴趣的同学可以尝试一下。

最后我们总结一下数据挖掘的一般流程:

1、数据读取

  • 读取数据,并进行展示
  • 统计数据各项指标
  • 明确数据规模与要完成的任务

2、特征理解分析

  • 单特征分析,逐个变量分析其对结果的影响
  • 多变量统计分析,综合考虑多种情况影响
  • 统计绘图得出结论

3、数据清洗与预处理

  • 对缺失值进行填充
  • 特征标准化/归一化
  • 筛选有价值的特征
  • 分析特征之间的相关性

4、建立模型

  • 特征数据与标签准备
  • 数据集切分
  • 多种建模算法对比
  • 集成策略等方案改进

好了,今天的学习到这里就结束了~

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

推荐阅读更多精彩内容