上学期尝试着去做的一个python关于数据处理的项目,主要过程包括数据预处理、特征提取、特征选择、模型构建与求解,每一部分都有详细的分析和总结。由于时间原因,没有考虑文章的排版以及可能忽略了一些细节,欢迎大家一起学习交流~
泰坦尼克号生存率预测
通过数据堆叠、数据清洗、特征提取、特征选择、构建模型等方法,实现对泰坦尼克号生存人数的预测。
1、提出问题
已给的数据包含训练数据和测试数据,其中训练数据维度为 891 12, 测试数据集维度为 418 11,训练集比测试集多了一列特征 Survived。我们要解决的问题就是根据所给的数据选择合适的模型,来预测测试数据集的 Survived 特征的那一列。
2、理解数据
1)采集数据
从Kaggle泰坦尼克号项目页面下载数据:https://www.kaggle.com/c/titanic
2)导入数据
首先我们将训练数据和测试数据进行纵向堆叠,方便数据处理。
#合并数据集,方便同时对两个数据集进行清洗
full
= train
.append
( test
, ignore_index
= True )#使用append
print ('合并后的数据集:',full
.shape
)进行纵向堆叠

3)查看数据集信息
在这用 describe 函数进行了描述,发现没有异常值,但是存在缺失值。我们发现数据总共有 1309 行。
其中数据类型列:年龄(Age)、船舱号(Cabin)里面有缺失数据:
1)年龄(Age)里面数据总数是 1046 条,缺失了 1309-1046=263
2)船票价格(Fare)里面数据总数是 1308 条,缺失了 1 条数据。
字符串列:
1)登船港口(Embarked)里面数据总数是 1307,只缺失了 2 条数据,缺失比较少
2)船舱号(Cabin)里面数据总数是 295,缺失了 1309-295=1014,缺失比较大。
这为我们下一步数据清洗指明了方向,只有知道哪些数据缺失数据,我们才能有针对性的处理。
print(full
.isnull
().sum())

3、数据清洗
1)数据预处理(fullna 函数)
缺失值处理一般采用以下方式:
如果是数值类型,用平均值取代;
如果是分类数据,用最常见的类别取代;
使用模型预测缺失值,例如:K-NN
在这采用的是以下方式进行处理:
数值类型:对于年龄和船票价格,采用的是平均数来填充缺失值。
print ('合并后的数据集:',full
.shape
)
print('处理前:')
print(full
.isnull
().sum())
#年龄(Age)
full
['Age']=full
['Age'].fillna
( full
['Age'].mean
() )
#船票价格(Fare)
full
['Fare'] = full
['Fare'].fillna
( full
['Fare'].mean
() )
print('处理后:')
print(full
.isnull
().sum())

字符串类型:
对于登船港口(Embarked),分别计算出各个类别的数量,采用最常见的类别进行填充。

对于船舱号(Cabin),由于缺失的数据太多,将缺失的数据用’U’代替,表示未知。

4、特征提取
数据分类
数据分类的过程比较麻烦,对于有直接类别的数据还有字符串类型的数据进行了不同方式的处理。
1)有直接类别的
乘客性别(Sex):男性 male,女性female

2)有直接类别的字符串类型
登船港口(Embarked):
出发地点S=英国南安普顿Southampton
途径地点 1:C=法国 瑟堡市Cherbourg,
出发地点 2:Q=爱尔兰 昆士敦 Queenstown
在这使用get_dummies进行one-hot编码,产生虚拟变量(dummy variables),列名前缀是Embarked。
embarkedDf
= pd
.DataFrame
()
embarkedDf
= pd
.get_dummies
( full
['Embarked'] , prefix
='Embarked' )

对客舱等级(Pclass)进行类似于登船港口(Embarked)相同的处理方式。
pclassDf
= pd
.DataFrame
()
#使用get_dummies进行one-hot编码,列名前缀是Pclass
pclassDf
= pd
.get_dummies
( full
['Pclass'] , prefix
='Pclass' )
full
= pd
.concat
([full
,pclassDf
],axis
=1)
#删掉客舱等级(Pclass)这一列
full
.drop
('Pclass',axis
=1,inplace
=True)

做到这一步肯定有同学存在疑问:one-hot是什么呢?
下面我们就逐步解释一下:
one-hot的基本思想:将离散型特征的每一种取值都看成一种状态,若你的这一特征中有N个不相同的取值,那么我们就可以将该特征抽象成N种不同的状态,one-hot编码保证了每一个取值只会使得一种状态处于“激活态”,也就是说这N种状态中只有一个状态位值为1,其他状态位都是0。
那么为什么使用one-hot编码?
我们之所以不使用标签编码,因为标签编码的问题是它假定类别值越高,该类别更好。显然在实际应用和生活中,这肯定不是一个好的方案,是我们所不能接受的。
我们使用one hot编码器对类别进行“二进制化”操作,然后将其作为模型训练的特征,原因正在于此。当然,如果我们在设计网络的时候考虑到这点,对标签编码的类别值进行特别处理,那就没问题。不过,在大多数情况下,使用one hot编码是一个更简单直接的方案。
另外,如果原本的标签编码是有序的,那one hot编码就不合适了——会丢失顺序信息。
举个例子:
如果把客舱等级分为1,2,3,我们要预测的是泰坦尼克号的生存率,你知道每个人的生存率跟客舱等级具体有什么样的关系吗?
也就是说我们并不知道客舱等级是1,2,3哪个好,我们没办法对其量化,1,2,3只是一个标签,并不能对其进行分类,但是我们可以通过one-hot编码来量化这个特征是0还是1.也就是说可以找到这个特征存在于不存在与生存和死亡之间的关系。
3)没有直接类别的字符串类型:对这部分数据的处理比较麻烦,可能从这部分数据里面提取出特征来,并对数据进行分类。
分别包括对乘客姓名(Name) 客舱号(Cabin) 船票编号(Ticket)的处理:
对于乘客名字(Name)的处理:
注意到在乘客名字(Name)中,有一个非常显著的特点:
乘客头衔每个名字当中都包含了具体的称谓或者说是头衔,将这部分信息提取出来后可以作为非常有用一个新变量,可以帮助我们进行预测,因为在那个年代的西方国家是很注重称谓的。
于是我们可以从姓名中获取头衔并进行 one-hot 编码。
def getTitle(name
):
str1
=name
.split
( ',' )[1] #Mr. Owen Harris
str2
=str1
.split
( '.' )[0]#Mr
#strip() 方法用于移除字符串头尾指定的字符(默认为空格)
str3
=str2
.strip
()
return str3
titleDf
= pd
.DataFrame
()
#map函数:对Series每个数据应用自定义的函数计算
titleDf
['Title'] = full
['Name'].map(getTitle
)

根据常识,外国人很讲究的,称呼不同对应于不同的社会地位:
定义以下几种头衔类别:
Officer政府官员
Royalty王室(皇室)
Mr已婚男士
Mrs已婚妇女
Miss年轻未婚女子
Master有技能的人/教师
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"
}
#map函数:对Series每个数据应用自定义的函数计算
titleDf
['Title'] = titleDf
['Title'].map(title_mapDict
)
#使用get_dummies进行one-hot编码
titleDf
= pd
.get_dummies
(titleDf
['Title'])

对于客舱号(Cabin)的处理(与Name 类似):从客舱号中提取客舱类别并进行 one-hot 编码,在这里就不赘述了,后面会给出详细的代码。

对于同代直系亲属数(Parch)和不同代直系亲属数(SibSp)的处理:
家庭人数=同代直系亲属数(Parch)+不同代直系亲属数(SibSp)+乘客自己
(因为乘客自己也是家庭成员的一个,所以这里加1)
familyDf[ ‘FamilySize’ ] = full[ ‘Parch’ ] + full[ ‘SibSp’ ] + 1
同时我们要根据家庭人数建立家庭类别:
家庭人数=同代直系亲属数(Parch)+不同代直系亲属数(SibSp)+乘客自己
家庭类别:小家庭Family_Single:家庭人数=1;
中等家庭Family_Small: 2<=家庭人数<=4
大家庭Family_Large: 家庭人数>=5
我们为什么要这么处理呢?
因为我们并不知道最后具体到每个人的生存概率和家庭成员人数之间到底有着怎样的关系,所以我们要将其量化,变成方便我们预测的形式。
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 )

5、特征选择
(1)相关系数法:计算各个特征的相关系数

(2)提取特征:对矩阵中 Survived 那一列输出:

存在问题:在进行特征选择时面临了问题,cabinDf(船舱号)和embarked(登船港口)这两个特征与特征Survived的相关性的没超过0.2,但是也不是没有相关性,所以在此尝试了使用与不使用这两种特征的两种情况,分别进行了实验,结果表明两种特征都要结果会好一点。
full_X
= pd
.concat
( [titleDf
,#头衔
pclassDf
,#客舱等级
familyDf
,#家庭大小
full
['Fare'],#船票价格
full
['Sex'],#性别
cabinDf
,#船舱号
embarkedDf
,#登船港口
] , axis
=1 )
#只留cabinDf船舱号
# full_X = pd.concat( [titleDf,#头衔
#
pclassDf,#客舱等级
#
familyDf,#家庭大小
#
full['Fare'],#船票价格
#
full['Sex']#性别
#
cabinDf,#船舱号
#
] , axis=1 )
# #只留embarkedDf,#登船港口
# full_X = pd.concat( [titleDf,#头衔
#
pclassDf,#客舱等级
#
familyDf,#家庭大小
#
full['Fare'],#船票价格
#
full['Sex']#性别
#
embarkedDf,#登船港口
#
] , axis=1 )
# #两个都不要
# full_X = pd.concat( [titleDf,#头衔
#
pclassDf,#客舱等级
#
familyDf,#家庭大小
#
full['Fare'],#船票价格
#
full['Sex']#性别
#
] , axis=1 )
6、构建模型
(1)建立训练数据集和测试数据集,从原始数据集(名名为 source,即前 891 行数据)中拆分出训练数据集和测试数据集。

(2)选择机器学习算法
在这里尝试了不同的机器学习算法,并对每个算法进行了评估。
from sklearn
.model_selection
import train_test_split
for i
in range(0,4):
train_X
, test_X
, train_y
, test_y
= train_test_split
(source_X
,
source_y
,
train_size
=size
[i
],
random_state
=5)
#逻辑回归
from sklearn
.linear_model
import LogisticRegression
model
= LogisticRegression
()
model
.fit
( train_X
, train_y
)
scorelist
[0].append
(model
.score
(test_X
, test_y
))
#随机森林Random Forests Model
from sklearn
.ensemble
import RandomForestClassifier
model
= RandomForestClassifier
(n_estimators
=100)
model
.fit
( train_X
, train_y
)
scorelist
[1].append
(model
.score
(test_X
, test_y
))
#支持向量机Support Vector Machines
from sklearn
.svm
import SVC
model
= SVC
()
model
.fit
( train_X
, train_y
)
scorelist
[2].append
(model
.score
(test_X
, test_y
))
#梯度提升决策分类Gradient Boosting Classifier
from sklearn
.ensemble
import GradientBoostingClassifier
model
= GradientBoostingClassifier
()
model
.fit
( train_X
, train_y
)
scorelist
[3].append
(model
.score
(test_X
, test_y
))
#KNN最邻近算法 K-nearest neighbors
from sklearn
.neighbors
import KNeighborsClassifier
model
= KNeighborsClassifier
(n_neighbors
= 3)
model
.fit
( train_X
, train_y
)
scorelist
[4].append
(model
.score
(test_X
, test_y
))
#朴素贝叶斯分类 Gaussian Naive Bayes
from sklearn
.naive_bayes
import GaussianNB
model
= GaussianNB
()
model
.fit
( train_X
, train_y
)
scorelist
[5].append
(model
.score
(test_X
, test_y
))
# 分类问题,score得到的是模型的正确率
#print(model.score(test_X , test_y ))
(3)训练、评估模型

对比不同模型的准确率,我们发现逻辑回归模型的表现在这个数据集上的表现要好一点。
7、方案实施
使用预测数据集到底预测结果,并保存到 csv 文件中:

总结:
选择了一个比较基础的kaggle项目,目的是熟悉kaggle竞赛流程,深入分析理解项目中的每一行个过程,这个项目对数据的特征处理那一步 进行了比较多的处理,从中学习到了很多。
后续:
经老师和同学们的反馈,还能将工作进行进一步的细化,以提高预测的准确率,比如可以在对年龄这一列进行处理的过程中,可以根据年龄的大小将年龄进行分段处理,感觉年龄的大小与最终生存率的大小有一定的关系。
但是由于时间有限(我太懒)并没有去尝试,有缘再见吧。。。
需要机器学习代码指导的可私信我。
参考内容:https://www.zhihu.com/lives/938779590051811328