泰坦尼克号生存预测是Kaggle举办的一项数据挖掘比赛,目的是根据给定的乘客信息来预测该乘客最终是否可以存活下来。泰坦尼克号生存预测是Kaggle竞赛的入门案例,同时也是机器学习的经典案例,今天我们用Python3结合机器学习库sklearn进行分析。欢迎关注微信公众号《Python练手项目实战》,后台回复"资源"即可免费获得python机器学习全套课程视频资源。
导入用到的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier,VotingClassifier
from xgboost import XGBClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
加载和查看数据
在进行分析前的第一步就是要加载和查看我们的数据
trainData = pd.read_csv('train.csv')#加载训练数据
testData= pd.read_csv('test.csv')#加载测试数据
full_data = [trainData,testData]
trainData.info()#训练数据的信息
testData.info()
trainData.sample(n=10) #随机查看训练数据
testData.sample(n=10)
从输出的结果中可以看出:
1、训练集中一共有819个数据,其中Age, Cabin和Embarked列的数据有缺失值
2、测试集中一共有418个数据,其中Age, Fare, Cabin列的数据有缺失值
3、训练集中的特征一共有:PassengerId, Survived, Pclass, Name, Sex, Age, SibSp, Parch, Ticket, Fare, Cabin和Embarked; 其中Name , Sex, Ticket, Cabin和Embarked列数据为文本数据,这在后面需要进行编码的转换
4、而测试集中的数据基本和训练集中的一样,只不过少了Survived列,这也是我们要预测的那一列数据
描述性统计
一、生存率:
训练集中存活下来的人占比0.383
trainData["Survived"].value_counts(normalize=True)
0 0.616162
1 0.383838
二、分类变量和生存率
在给定的特征中,分类变量或离散型变量一共有:Pclass, Sex, SibSp, Parch, Embarked,用柱状图表示它们和生存率之间的关系
fig,axes = plt.subplots(2,3,sharey=True)
#Pclass和生存率
sns.factorplot(x='Pclass',y='Survived',data=trainData,kind='bar',ax=axes[0,0])
sns.barplot(x='Sex',y='Survived',data=trainData,ax=axes[0,1])
sns.barplot(x='SibSp',y='Survived',data=trainData,ax=axes[0,2])
sns.barplot(x='Parch',y='Survived',data=trainData,ax=axes[1,0])
sns.barplot(x='Embarked',y='Survived',data=trainData,ax=axes[1,1])
从上面的描述统计中可以看出:
1、不同阶级(Pclass)下用户之间的存活率差别比较大,而且Pclass=1的乘客的存活率最大
2、女性存活的几率远大于男性,说明性别对生存率的影响较强
3、SibSp和Parch对存活率也有一定的影响,而且他们的数值越小的话就越有可能存活下来
4、Embarked对生存率也有一定的影响,考虑保留在最终的特征中
三、连续变量和生存率
给定的特征中,连续变量有:Age和Fare, 他们和Survived的情况如下:
从上面的图中可以看出:
fig,axes = plt.subplots(2,1)
#不同Survived下年龄的分布
sns.kdeplot(trainData["Age"][(trainData["Survived"] == 0) & (trainData["Age"].notnull())], color="Red", shade = True,ax=axes[0],label='Not Survived')
sns.kdeplot(trainData["Age"][(trainData["Survived"] == 1) & (trainData["Age"].notnull())], color="Blue", shade= True,ax=axes[0],label='Survived')
#不同Survived下Fare的分布
sns.kdeplot(trainData["Fare"][(trainData["Survived"] == 0) & (trainData["Fare"].notnull())], color="Red", shade = True,ax=axes[1],label='Not Survived')
sns.kdeplot(trainData["Fare"][(trainData["Survived"] == 1) & (trainData["Fare"].notnull())], color="Blue", shade= True,ax=axes[1],label='Survived')
1、在年龄方面,大部分乘客的年龄都集中在20-40之间,而存活下来的乘客年龄有两个峰段,年龄较小(0-8)和年龄中等(20-35)
2、而Fare在存活的乘客和没有存活的乘客的分布较为相似,但没有存活的乘客的Fare更为集中一些
特征处理
一、特征处理的步骤
异常值处理(Correcting):检测数据中是否有异常值的出现,如果有的话,可以采取删除该实例或用其他数值来替换异常值。
缺失值处理(Completing):从之前的查看数据时,就发现数据中存在缺失值,对于这些缺失值,如果样本量很大的话,可以考虑删除;但是这里样本量比较小,考虑用其他的数值来代替这些缺失值。
创建特征(Creating):创建可能对预测结果有帮助的其他特征。
特征编码转化(converting):主要是将文本类型的数据、分类变量转换为可供模型计算的数值型变量。
二、异常值处理(Correcting)
对于分类变量而言,通过前面的描述统计可以看出不存在异常的地方, 因此异常值检测主要就是集中于连续型变量(Age, Fare)上,用箱线图来检测:
fig,axes = plt.subplots(1,2)
sns.boxplot(y='Age',data=trainData,ax=axes[0])
sns.boxplot(y='Fare',data=trainData,ax=axes[1])
fig.show()
可以看出:相比于年龄,Fare列的数据更集中一点;
Age列数据有几个异常值,都是年龄比较大的乘客,不过这也比较正常;
Fare中有一个极端值是大于500的,可以考虑将其替换为Fare的中位数。
三、缺失值处理(Completing)
在训练集中缺失数据的列有:Age, Cabin 和Embarked;在测试集中有:Age, Fare, Cabin和Embarked。处理的方法如下:
Age:首先要从乘客的名称中提取出称谓(title), 然后将缺失的具有某个title的乘客的年龄替换为拥有这个title的其他乘客年龄的均值
Cabin:该列数据缺失太多了,因此考虑最终将其从特征中去掉
Embarked:用该列的中位数来替换缺失值
Fare:用中位数来替换缺失值
#如果title不在出现次数较多的title中,将其替换为Rare
def fix_title(title):
if title.strip() not in ['Mr','Miss','Mrs','Master','Dr']:
return 'Rare'
else:
return title.strip()
# 用含有相同title的人的年龄的均值来填补年龄的缺失值
for dataset in full_data:
#提取名称中的title
dataset['Title'] = dataset['Name'].str.extract(', (S+). ',expand = False)
#将一些出现次数较少的title替换为Rare
dataset['Title'] = dataset['Title'].map(fix_title)
#替换年龄中的缺失值
dataset['Age']=dataset.groupby('Title')['Age'].apply(lambda x:x.fillna(x.mean()))
#用Embarked的众数来填补缺失值
dataset['Embarked'].fillna(dataset['Embarked'].mode()[0],inplace=True)
#测试数据中的Fare有缺失值,因此用中位数来替代
dataset['Fare']= dataset['Fare'].fillna(dataset['Fare'].median())
#查看训练集和测试集中的缺失值情况
trainData.isnull().sum()
testData.isnull().sum()
四、创建特征(Creating)
创建特征主要是从数据中提取出一些对预测结果有帮助的特征,我新建的特征有以下几个:
FamilySize:乘客家庭的大小,有SibSp和Parch相加,再加1得到
IsAlone:乘客是否是独自一人,当FamilySize==1时,IsAlone为True
IsMother:表明该乘客是否为母亲
Child:表示该乘客是否为孩子
def is_mother(row):
if row['Title']=='Mrs' and row['Age']>20 and row['Parch']>0:
return 1
else:
return 0
for dataset in full_data:
dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
dataset['IsAlone'] = dataset.apply(lambda x:1 if x['FamilySize']==1 else 0,axis=1)
dataset['Mother'] = dataset.apply(is_mother,axis=1)
dataset['Child'] = dataset.apply(lambda row:1 if row['Age']
五、特征编码转化(converting)
现在就剩下最后的编码转换部分了:
文本类型的列有:Sex, Embarked,Title, 对于这些列而言,需要将文本转换为对应的数字编码,便于模型的计算。
对于Age, Fare这样的数值列数据而言,他们的数据范围和其他列的数据范围相差太大,会导致模型受这两列的影响很大,因此也将其进行分组,然后转换为离散型的变量。
#将分类变量转换为数值型
sex_mapping ={'female':0,'male':1}
embarked_mapping = {'S':1,'C':2,'Q':3}
title_mapping = {'Mr':1,'Miss':2,'Mrs':3,'Master':4,'Dr':5,'Rare':6}
for dataset in full_data:
dataset['Sex'] = dataset['Sex'].map(sex_mapping)
#另外创建一个变量:Class_Sex
dataset['Class_Sex'] = dataset['Pclass'] * dataset['Sex']
dataset['Embarked'] = dataset['Embarked'].map(embarked_mapping)
dataset['Title'] = dataset['Title'].map(title_mapping)
#将Age和Fare分组并编码
def age_encode(age):
if age
elif age
elif age
elif age
elif age
def fare_encode(fare):
if fare
elif fare
elif fare
elif fare
for dataset in full_data:
dataset['Age'] = dataset.Age.map(age_encode)
dataset['Fare'] = dataset.Fare.map(fare_encode)
#最后去掉一些我们不需要的列
drop_columns=['PassengerId','Name','SibSp','Parch','Ticket','Cabin']
trainData.drop(drop_columns,axis =1,inplace=True)
testData.drop(drop_columns,axis=1,inplace=True)
训练单个模型
#准备训练的数据
X_train=trainData.iloc[:,1:]
y_train=trainData['Survived']
X_test = testData
#用于交叉验证
kf = KFold(n_splits = 10,random_state = 0)
分别使用5个基本的分类模型来拟合训练数据,并根据交叉验证的结果来选择模型的参数
#模型1:训练逻辑回归模型
C = [0.01,0.1,0.5,1,2,3,4,5,6]
accuracy = dict()
for c in C:
ls = LogisticRegression(penalty='l2',C = c, solver='lbfgs',random_state=0)
cross_scores = cross_val_score(ls,X_train,y=y_train,cv =kf)
accuracy[c] = np.mean(cross_scores)
print('best C:',sorted(accuracy.items(),key=lambda x:x[1],reverse=True)[0])
#模型2:用支持向量机来拟合模型
svc = SVC(random_state=0)
params={'kernel':['linear','rbf','sigmoid'],
'C':[1,1.2,1.4,1.5,1.8,2,2.5,3,4,10,20,50],
'gamma': [0.1,0.15,0.2,0.25,0.3,0.4]}
best_model_svc = GridSearchCV(svc,param_grid = params,refit=True,cv=kf).fit(X_train,y_train)
print('best accuracy',best_model_svc.best_score_
print('best parameters',best_model_svc.best_params_)
#模型3:用随机森林来拟合模型
params = {'n_estimators':[50,100,150,200,250],
'max_depth':[3,5,7],
'min_samples_leaf':[2,4,6]}
RF = RandomForestClassifier(random_state=0)
best_model_rf = GridSearchCV(RF,param_grid=params,refit=True,cv=kf).fit(X_train,y_train)
print('best accuracy',best_model_rf.best_score_)
print('best parameters',best_model_rf.best_params_)
#模型4:用GBDT来拟合模型
GBDT = GradientBoostingClassifier(random_state=0)
params ={'learning_rate':[0.01,0.05,0.1,0.15,0.2],
'n_estimators':[100,300,500],
'max_depth':[3,5,7]}
best_model_gbdt = GridSearchCV(gbdt,param_grid=params,refit=True).fit(X_train,y_train)
print('best accuracy',best_model_gbdt.best_score_)
print('best parameters',best_model_gbdt.best_params_)
#模型5:xgboost拟合模型
xbc = XGBClassifier(random_state = 0)
params ={'n_estimators':[50,100,300,500],
'max_depth':[2,3,5,7],
'learning_rate':[0.01, 0.03, 0.05, 0.1, 0.25]}
best_model_xbc = GridSearchCV(xbc,param_grid=params,refit=True,cv=kf).fit(X_train,y_train)
print('best accuracy',best_model_xbc.best_score_)
print('best parameters',best_model_xbc.best_params_)
模型运行的结果:
从单个模型的结果来看,随机森林交叉验证的准确率是最高的,为0.8350。分别用上面的模型来拟合训练集的数据,并对测试集进行预测,提交之后得到的准确率如下:
best C: (2, 0.82383270911360795)
best accuracy 0.83164983165
best parameters {'C': 1.2, 'gamma': 0.1, 'kernel': 'rbf'}
best accuracy 0.832772166105
best parameters {'max_depth': 5, 'min_samples_leaf': 2, 'n_estimators': 250}
best accuracy 0.830527497194
best parameters {'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 100}
best accuracy 0.83164983165
best parameters {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 300}
LogisticRegression : 0.80382
SVC : 0.78947
RandomForest : 0.79425
GradientBoostingClassifier : 0.79904
XGBClassifier : 0.79425
看起来逻辑回归模型得到的准确率是最高的。
集成学习模型
为了让预测的结果准确率更高一点,可以将上面5个表现最佳的模型结合起来,采用多数表决(majority vote)的方式来对一个实例进行预测。
多数表决的具体方法:对于测试集中的一个实例而言,如果有超过3个模型预测的结果为1,那么最终的结果就是1,否则预测的结果为0。可以借助sklearn的VotingClassifier来实现。
models = [('lr':LogisticRegression(penalty='l2',C =2, solver='lbfgs',random_state=0)),
('svc':SVC(kernel='rbf',C=1.2,gamma=0.1,random_state=0)),
('rf':RandomForestClassifier(n_estimators=250,max_depth=5,min_samples_leaf=2,random_state=0)),
('gbdt':GradientBoostingClassifier(learning_rate=0.01,n_estimators=100,max_depth=3,random_state=0)),
('xgbc':XGBClassifier(learning_rate=0.01,max_depth=2,n_estimators=300,random_state=0))
vote= VotingClassifier(models,voting='hard')
vote.fit(X_train,y_train)
#最后利用vote对测试集中的数据进行预测
y_test_pred = vote.predict(X_test)
将测试集中的PassengerId和预测的结果y_test_pred输出为csv文件并提交到Kaggle上就可以得到预测的准确率:0.79425。
综上,通过对泰坦尼克号数据集进行数据描述、特征构建和拟合模型,最终发现利用逻辑回归模型可以得到最高的准确率(0.80382)。