【2022.12.28】kaggle上泰坦尼克号的实操(上)

前言

经过一系列的学习,现在想入门kaggle上面的实操,多为模仿

参考链接:机器学习入门:Kaggle -titanic(泰坦尼克)生存预测

# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session



import warnings 
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 忽略部分版本警告
warnings.filterwarnings('ignore')
#设置sns样式
sns.set(style='white',context='notebook',palette='muted')
#导入数据
train=pd.read_csv('/kaggle/input/titanic/train.csv')
test=pd.read_csv('/kaggle/input/titanic/test.csv')

使用info可以了解到如下信息

train.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  # 乘客ID
 1   Survived     891 non-null    int64  # 是否存活
 2   Pclass       891 non-null    int64  # 客舱等级(1/2/3等舱位)
 3   Name         891 non-null    object # 乘客姓名
 4   Sex          891 non-null    object # 性别
 5   Age          714 non-null    float64 # 年龄
 6   SibSp        891 non-null    int64  # 兄弟姐妹数/配偶数
 7   Parch        891 non-null    int64  # 父母数/子女数
 8   Ticket       891 non-null    object # 船票编号
 9   Fare         891 non-null    float64 # 船票价格
 10  Cabin        204 non-null    object # 客舱号
 11  Embarked     889 non-null    object # 登船港口
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

其中可以看到,有一些年龄和客舱号是空缺的(不足891)

而在test.info()之中,可以看到只有11列,去掉了存活

seaborn画图

# 计算不同类型embarked的乘客,其生存率为多少
print('Embarked为"S"的乘客,其生存率为%.2f' % full['Survived'][full['Embarked'] == 'S'].value_counts(normalize=True)[1])
print('Embarked为"C"的乘客,其生存率为%.2f' % full['Survived'][full['Embarked'] == 'C'].value_counts(normalize=True)[1])
print('Embarked为"Q"的乘客,其生存率为%.2f' % full['Survived'][full['Embarked'] == 'Q'].value_counts(normalize=True)[1])

###
Embarked为"S"的乘客,其生存率为0.34
Embarked为"C"的乘客,其生存率为0.55
Embarked为"Q"的乘客,其生存率为0.39
###

sns.catplot分类型数据作坐标轴画图

sns.catplot('Pclass', col='Embarked', data=train, kind='count', height=3)

这句话的意思是 根据Pclass和Embarked分类表格,并统计Pclass的数据

image-20221228144145701

S是英国,C是法国,Q是新西兰

法国登船乘客生存率较高原因可能与其头等舱乘客比例较高有关。

sns.barplot柱状图

# Sex与Survived:女性的生存率远高于男性
sns.barplot(data=train, x='Sex', y='Survived')

image-20221228145004731

其中每个柱条的黑色的线条为误差线
误差线源于统计学,表示数据误差(或不确定性)范围,以更准确的方式呈现数据。误差线可以用标准差(standard deviation,SD)、标准误(standard error,SE)和置信区间表示,使用时可选用任意一种表示方法并作相应说明即可。当误差线比较“长”时,一般要么是数据离散程度大,要么是数据样本少。

sns.FacetGrid

ageFacet = sns.FacetGrid(train, hue='Survived', aspect=3)    # 创建坐标轴
ageFacet.map(sns.kdeplot, 'Age', shade=True)                 # 作图,选择图形类型
ageFacet.set(xlim=(0, train['Age'].max()))                   # 其他信息:坐标轴范围、标签等
ageFacet.add_legend()

Age与Survived:当乘客年龄段在0-10岁期间时生存率会较高。

image-20221228145614717

数据预处理

先对表格的缺失值进行处理

字符类处理

其中有两个人没有进站数据,经过绘图我们可以知道大部分人都是S站上船的

因此我们直接填入S

image-20221228151250504

因为cabin的数据量太少,因此将空缺的地方直接填入Unknown

full['Cabin']=full['Cabin'].fillna('Unknown')
full['Embarked']=full['Embarked'].fillna('S')

数值类处理

有一个人没有票价,因此填入平均值

full[full['Fare'].isnull()]
# 查询可知
#PassengerId	Survived	Pclass	Name	Sex	Age	SibSp	Parch	Ticket	Fare	Cabin	Embarked
#1043	1044	NaN	3	Storey, Mr. Thomas	male	60.5	0	0	3701	NaN	Unknown	S
# 利用3等舱,登船港口为英国,舱位未知旅客的平均票价来填充缺失值。
full['Fare']=full['Fare'].fillna( full[(full['Pclass']==3)&(full['Embarked']=='S')&(full['Cabin']=='Unknown')]['Fare'].mean())

而age没有填入数值,但是这个数据很大概率影响到存活率,这个放到下面的特征工程讲

特征工程

身份头衔特征

合并一些有相同特征的标签,“暴力更改”部分标签,在泰坦尼克的数据中,姓名头衔可以很好反应这一点。比如有的人是Dr,Mrs等

#构造新特征Title
full['Title']=full['Name'].map(lambda x:x.split(',')[1].split('.')[0].strip())
#查看title数据分布
full['Title'].value_counts()

新建新的特征Title,分割出逗号和句号之间的身份

image-20221228155923469

其中有的是头衔,有的并不是头衔,Capt这类就是船长啥的,因此我们要将一些特征合并在一起,可以采用上面的方法对数据进行修改,也可以采用词典的方法对结果进行整合

TitleDict={}
TitleDict['Mr']='Mr'
TitleDict['Mlle']='Miss'
TitleDict['Miss']='Miss'
TitleDict['Master']='Master'
TitleDict['Jonkheer']='Master'
TitleDict['Mme']='Mrs'
TitleDict['Ms']='Mrs'
TitleDict['Mrs']='Mrs'
TitleDict['Don']='Royalty'
TitleDict['Sir']='Royalty'
TitleDict['the Countess']='Royalty'
TitleDict['Dona']='Royalty'
TitleDict['Lady']='Royalty'
TitleDict['Capt']='Officer'
TitleDict['Col']='Officer'
TitleDict['Major']='Officer'
TitleDict['Dr']='Officer'
TitleDict['Rev']='Officer'
 
full['Title']=full['Title'].map(TitleDict)

再次使用value_counts()时,可以得到以下整合后的效果

image-20221228160434663

观察结果

# 观察新特征与标签之间的关系
sns.barplot(data=full,x='Title',y='Survived')
# 头衔为'Mr'及'Officer'的乘客,生存率明显较低。

image-20221228162534846

贵族和女士的生存率较高

家庭成员特征

拖家带口和独自一人的存活率可能会有差异,可以通过数据表体现出来

full['familyNum']=full['Parch']+full['SibSp']+1
# 查看familyNum与Survived
sns.barplot(data=full,x='familyNum',y='Survived')

image-20221228161606779

可以看出8,11人的家庭没有存活,4人口的存活率最高

可以将家庭分为小中大三类

# 按照家庭成员人数多少,将家庭规模分为“小、中、大”三类:
def familysize(familyNum):
    if familyNum==1:
        return 0
    elif (familyNum>=2)&(familyNum<=4):
        return 1
    else:
        return 2
 
full['familySize']=full['familyNum'].map(familysize)
full['familySize'].value_counts()
'''
0    790
1    437
2     82
Name: familySize, dtype: int64
'''
# 查看familySize与Survived
sns.barplot(data=full,x='familySize',y='Survived')
# 当家庭规模适中时,乘客的生存率更高。

image-20221228162036180

船舱类型特征

# Cabin字段的首字母代表客舱的类型
# 也反映不同乘客群体的特点,可能也与乘客的生存率相关。
#提取Cabin字段首字母
full['Deck']=full['Cabin'].map(lambda x:x[0])
#查看不同Deck类型乘客的生存率
sns.barplot(data=full,x='Deck',y='Survived')
# 当乘客的客舱类型为B/D/E时,生存率较高;当客舱类型为U/T时,生存率较低。
# Unknown的生存率较低

image-20221228163103493

共票号乘客特征

# 同一票号的乘客数量可能不同,可能也与乘客生存率有关系。
# 提取各票号的乘客数量
TickCountDict={}
TickCountDict=full['Ticket'].value_counts()
TickCountDict.head()
#将同票号乘客数量数据并入数据集中
full['TickCot']=full['Ticket'].map(TickCountDict)
full['TickCot'].value_counts()
# 查看TickCot与Survived之间关系
sns.barplot(data=full,x='TickCot',y='Survived')
# 当TickCot大小适中时,乘客生存率较高。

image-20221228165826445

#按照TickCot大小,将TickGroup分为三类。
def TickCountGroup(num):
    if (num==1):
        return 0
    elif (num>=2)&(num<=4):
        return 1
    else :
        return 2
#得到各位乘客TickGroup的类别
full['TickGroup']=full['TickCot'].map(TickCountGroup)
#查看TickGroup与Survived之间关系
sns.barplot(data=full,x='TickGroup',y='Survived')

image-20221228170835721

数值分析方法

One-hot编码

get_dummies方法可以用one-hot编码,将原来的值加在原来的分类之后,比如以下的demo

import pandas as pd
 
df = pd.DataFrame([
    ['green', 'A'],
    ['red', 'B'],
    ['blue', 'A']])
 
df.columns = ['color', 'class']
print(df)

'''
   color class
0  green     A
1    red     B
2   blue     A
'''

df=pd.get_dummies(df,columns=["color"])
print(df)
# 将color的值加在color后面,形成一项特征
'''
  class  color_blue  color_green  color_red
0     A           0            1          0
1     B           0            0          1
2     A           1            0          0
'''

image-20221229084006906

pandas.get_dummies(data, prefix=None, prefix_sep='_', dummy_na=False, columns=None, sparse=False, drop_first=False)

参数说明

data : array-like, Series, or DataFrame
输入的数据
prefix : string, list of strings, or dict of strings, default None
get_dummies转换后,列名的前缀
columns : list-like, default None
指定需要实现类别转换的列名
dummy_na : bool, default False
增加一列表示空缺值,如果False就忽略空缺值
drop_first : bool, default False
获得k中的k-1个类别值,去除第一个

DataFrame

DataFrame这里参考了Pandas DataFrame入门教程(图解版) (biancheng.net)

corr()与热力图

这里参照了【Python-数据分析】相关关系(矩阵)的3种展示技巧:corr()-热力图-条形图 - 知乎 (zhihu.com)

预测数据

进行以上数据处理和特征工程后,数据变得很完整,就可以着手预测年龄了

相关系数

先用热力图看看,什么数据与年龄相关性较大

只能与数据进行比对,不能与字符串等比对,因此

plt.figure(figsize=(8,8))
sns.heatmap(full[['Age','Parch','Pclass','SibSp','Title','familyNum','TickCot','Fare']].corr(),cmap='BrBG',annot=True,
           linewidths=.5)
# 选用TickCot是不分类的原数据,而不是分类后的TickGroup,同理familyNum与familySize

可以得到如下热力图

image-20221229110107260

我们看age那一行/列,可以发现fare与age的相关性较大,但是在原博客之中没对fare进行相关系数处理,因此在这里先略过

以上热力图也可以通过下方代码进行筛选数据

#筛选数据集
AgePre=full[['Age','Parch','Pclass','SibSp','Title','familyNum','TickCot']]
#进行one-hot编码
AgePre=pd.get_dummies(AgePre)
ParAge=pd.get_dummies(AgePre['Parch'],prefix='Parch')
SibAge=pd.get_dummies(AgePre['SibSp'],prefix='SibSp')
PclAge=pd.get_dummies(AgePre['Pclass'],prefix='Pclass')
#查看变量间相关性
AgeCorrDf=pd.DataFrame()
AgeCorrDf=AgePre.corr()
AgeCorrDf['Age'].sort_values()

'''
Pclass          -0.408106
Title_Master    -0.385380
Title_Miss      -0.282977
SibSp           -0.243699
familyNum       -0.240229
TickCot         -0.185284
Parch           -0.150917
Title_Royalty    0.057337
Title_Officer    0.166771
Title_Mr         0.183965
Title_Mrs        0.215091
Age              1.000000
Name: Age, dtype: float64
'''
#拼接数据
AgePre=pd.concat([AgePre,ParAge,SibAge,PclAge],axis=1)
AgePre.head()

ParAge、SibAge、PclAge都是用onehot编码得到的,由此我们可以得到这么一个很长的column

image-20221229133652262

生成数据

#拆分实验集和预测集
AgeKnown=AgePre[AgePre['Age'].notnull()]
AgeUnKnown=AgePre[AgePre['Age'].isnull()]

#生成实验数据的特征和标签
AgeKnown_X=AgeKnown.drop(['Age'],axis=1)
AgeKnown_y=AgeKnown['Age']
#生成预测数据的特征
AgeUnKnown_X=AgeUnKnown.drop(['Age'],axis=1)

#利用随机森林构建模型
rfr=RandomForestRegressor(random_state=None,n_estimators=500,n_jobs=-1)
rfr.fit(AgeKnown_X,AgeKnown_y)

其中RandomForestRegressor的函数在sklearn.ensemble.RandomForestRegressor-scikit-learn中文社区

可以找到该部分的代码

fit部分的代码意义为fit(X, y[, sample_weight]) 从训练集(X, y)构建一个树的森林。

验证一下

full.info()
'''
Age一共有1046个notnull
5   Age          1046 non-null   float64
'''
AgeKnown.info()
'''
可以得到所有的行数都是1046
AgeKnown_X则是除了Age那一列以外的AgeKnown所有数据
AgeKnown_y是对应的只有Age那一列的数据
0       22.0
1       38.0
        ... 
1305    39.0
1306    38.5
Name: Age, Length: 1046, dtype: float64
'''

score的方法为score(X, y[, sample_weight]) 返回预测的决定系数R^2

利用模型进行预测并填入源数据集

#模型得分
rfr.score(AgeKnown_X,AgeKnown_y)
#预测年龄
AgeUnKnown_y=rfr.predict(AgeUnKnown_X)
#填充预测数据
full.loc[full['Age'].isnull(),['Age']]=AgeUnKnown_y
full.info()  #此时已无缺失值

同组识别

说实话并没看懂这部分为什么要这么做,那我先把源码照搬过来吧

原因

虽然通过分析数据已有特征与标签的关系可以构建有效的预测模型,但是部分具有明显共同特征的用户可能与整体模型逻辑并不一致。如果将这部分具有同组效应的用户识别出来并对其数据加以修正,就可以有效提高模型的准确率。在Titancic案例中,我们主要探究相同姓氏的乘客是否存在明显的同组效应。提取两部分数据,分别查看其“姓氏”是否存在同组效应。因为性别和年龄与乘客生存率关系最为密切,因此用这两个特征作为分类条件

12岁以上男性:找出男性中同姓氏均获救的部分

#提取乘客的姓氏及相应的乘客数
full['Surname']=full['Name'].map(lambda x:x.split(',')[0].strip())
SurNameDict={}
SurNameDict=full['Surname'].value_counts()
full['SurnameNum']=full['Surname'].map(SurNameDict)

#将数据分为两组
MaleDf=full[(full['Sex']=='male')&(full['Age']>12)&(full['familyNum']>=2)]
FemChildDf=full[((full['Sex']=='female')|(full['Age']<=12))&(full['familyNum']>=2)]
MSurNamDf=MaleDf['Survived'].groupby(MaleDf['Surname']).mean()
MSurNamDf.head()
MSurNamDf.value_counts()
'''
0.0    89
1.0    19
0.5     3
Name: Survived, dtype: int64
'''

大多数同姓氏的男性存在“同生共死”的特点,因此利用该同组效应,对生存率为1的姓氏里的男性数据进行修正,提升其预测为“可以幸存”的概率。

MSurNamDict={}
MSurNamDict=MSurNamDf[MSurNamDf.values==1].index
MSurNamDict
'''
Index(['Beane', 'Beckwith', 'Bishop', 'Cardeza', 'Chambers', 'Dick',
       'Duff Gordon', 'Frauenthal', 'Frolicher-Stehli', 'Goldenberg',
       'Greenfield', 'Harder', 'Hoyt', 'Kimball', 'Lindqvist', 'McCoy',
       'Nakid', 'Persson', 'Taylor'],
      dtype='object', name='Surname')
'''

女性以及年龄在12岁以下儿童:找出女性及儿童中同姓氏均遇难的部分。

#分析女性及儿童同组效应
FCSurNamDf=FemChildDf['Survived'].groupby(FemChildDf['Surname']).mean()
FCSurNamDf.head()
FCSurNamDf.value_counts()
'''
1.000000    115
0.000000     27
0.750000      2
0.333333      1
0.142857      1
Name: Survived, dtype: int64
'''

与男性组特征相似,女性及儿童也存在明显的“同生共死”的特点,因此利用同组效应,对生存率为0的姓氏里的女性及儿童数据进行修正,提升其预测为“并未幸存”的概率。

#获得生存率为0的姓氏
FCSurNamDict={}
FCSurNamDict=FCSurNamDf[FCSurNamDf.values==0].index
FCSurNamDict

然后对其进行修改

#对数据集中这些姓氏的男性数据进行修正:1、性别改为女;2、年龄改为5。
full.loc[(full['Survived'].isnull())&(full['Surname'].isin(MSurNamDict))&(full['Sex']=='male'),'Age']=5
full.loc[(full['Survived'].isnull())&(full['Surname'].isin(MSurNamDict))&(full['Sex']=='male'),'Sex']='female'

#对数据集中这些姓氏的女性及儿童的数据进行修正:1、性别改为男;2、年龄改为60。
full.loc[(full['Survived'].isnull())&(full['Surname'].isin(FCSurNamDict))&((full['Sex']=='female')|(full['Age']<=12)),'Age']=60
full.loc[(full['Survived'].isnull())&(full['Surname'].isin(FCSurNamDict))&((full['Sex']=='female')|(full['Age']<=12)),'Sex']='male'

筛选子集

在对数据进行处理的时候,比如采用one-hot编码的时候,难免会导致维度上升,为了提升数据的有效性,那么就必须对数据进行降维处理

通过找出与乘客生存率“Survived”相关性更高的特征,剔除重复的且相关性较低的特征,从而实现数据降维。

# 先删除没有用的数据,大多是字符串类型的
fullSel=full.drop(['Cabin','Name','Ticket','PassengerId','Surname','SurnameNum'],axis=1)
#查看各特征与标签的相关性
corrDf=pd.DataFrame()
corrDf=fullSel.corr()
corrDf['Survived'].sort_values(ascending=True)

'''
Pclass       -0.338481
Age          -0.059547
SibSp        -0.035322
familyNum     0.016639
TickCot       0.064962
Parch         0.081629
familySize    0.108631
TickGroup     0.151702
Fare          0.257307
Survived      1.000000
Name: Survived, dtype: float64
'''

检查一下数据

image-20221229153720629

生成热力图

# 通过热力图,查看Survived与其他特征间相关性大小。
# 热力图,查看Survived与其他特征间相关性大小
plt.figure(figsize=(8,8))
sns.heatmap(fullSel[['Survived','Age','Embarked','Fare','Parch','Pclass',
                    'Sex','SibSp','Title','familyNum','familySize','Deck',
                     'TickCot','TickGroup']].corr(),cmap='BrBG',annot=True,
           linewidths=.5)
# plt.xticks(rotation=45)

因为TickGroup我与博主的的分类不一致,所以会有那么一点不同

image-20221229153854313

生成one-hot编码

相当于将字符串转化为数值

# 先人工初步筛除与标签预测明显不相关或相关度很低的特征
# 再查看剩余特征与标签之间的相关性大小做进一步降维。
fullSel=fullSel.drop(['familyNum','SibSp','TickCot','Parch'],axis=1)
#one-hot编码
fullSel=pd.get_dummies(fullSel)
PclassDf=pd.get_dummies(full['Pclass'],prefix='Pclass')
TickGroupDf=pd.get_dummies(full['TickGroup'],prefix='TickGroup')
familySizeDf=pd.get_dummies(full['familySize'],prefix='familySize')

fullSel=pd.concat([fullSel,PclassDf,TickGroupDf,familySizeDf],axis=1)

使用info检查
image-20221229154308225

参考链接

超详细Seaborn绘图 ——(一)barplot

get_dummies函数介绍_mengke_yu的博客

Pandas DataFrame入门教程(图解版) (biancheng.net)

【Python-数据分析】相关关系(矩阵)的3种展示技巧:corr()-热力图-条形图 - 知乎 (zhihu.com)

posted @ 2022-12-29 15:46  Mokou  阅读(250)  评论(0编辑  收藏  举报