跟Kaggle做泰坦尼克乘客生存分析
作者:互联网
参照kaggle来学习Python数据分析的思路和方法:https://www.kaggle.com/startupsci/titanic-data-science-solutions
中间夹杂了一些微专业视频中的图表,完全跟做下来,其实对如何认识数据、清洗数据有了初步的认识。虽然看的时候感觉不难,但照着敲代码还是有很多细微的错误,主要还是因为刚接触python不太熟悉,需要熟能生巧地练习。
数据处理过程中有两点我个人觉得很重要:尽量备份原数据,每次处理后输出看一下是否得到想要的结果。
数据认识
导入需要的包
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os
import warnings
warnings.filterwarnings('ignore')
plt.rcParams["patch.force_edgecolor"] = True#边缘线
导入数据,读取head来看一下数据的格式
#读取数据
os.chdir('D:\\数据分析\\微专业\\kaggle\\titanic\\')
train_data=pd.read_csv('train.csv')
test_data=pd.read_csv('test.csv')
print(train_data.columns.values)
head=train_data.head()
观察数据的格式
categorical:某些数据可以将样本数据分类,从而选择后续合适的可视化图。可以发现Survived, Sex, Embarked、Pclass都是代表分类的变量。
Numerical:是否存在数值类的数据,如离散、连续、时间序列等。连续数据 Age, Fare. 离散数据SibSp(
Number of Siblings/Spouses Aboard,兄弟姐妹/配偶登船), Parch(Number of Parents/Children Aboard 父母/子女登船)
mixed data types:Ticke、Cabint是字母+数字的格式
train_data.info()
一共891条训练数据
Age\Cabin\Embarked数据存在数据缺失
train_d=train_data.describe()
* Passengerid作为唯一标识,共891条数据
* 因Survived仅有0标识去世,1标识存活,mean均值0.38说明38%的存活率
* Age中上到80下至婴儿0.42,均值为29.7,Age-75%说明有75%的乘客小于38岁。
* Parch %75=0 超过75%的样本没有和父母/子女登船
* SibSp %50=0 %75=1 超过%50的样本没有兄弟姐妹/配偶登船(
Nearly 30% of the passengers had siblings and/or spouse aboard. 原文可能是估计?)
* 原文这两条不知道是怎么从describe中解读的
Fares varied significantly with few passengers (<1%) paying as high as $512.
Few elderly passengers (<1%) within age range 65-80.
train_d2=train_data.describe(include='O')
describe默认是只计算数值型特征的统计量,输入参数include=['O'],describe可以计算离散型变量的统计特征,得到总数、唯一值个数、出现最多的数据和出现频数。
* 姓名是唯一变量
* 男性多于女性,男性占比577/891=65%
* Cabin房间号是重复使用的,多个人共享一个房间
* Ticket不是唯一编号,存在多人同一Ticket的情况
* Embarked登陆港口共3个,S最多
基于数据分析的假设
分析各数据和存活的关联
可能无分析意义的数据:
* Ticket数据重复率过高,不作为特征
* Cabin缺失过度,不作为特征
* Passengerid作为唯一标识没有作为分类的意义
* Name因为格式不标准可能无关不作为分析特征(看到过博客提取title如Mr,Ms作为分析)
数据处理:
* 填充Age,Embarked特征
* 基于Parch和SibSp创建新的数据Family标志船上所有家庭成员数目
* 姓名中提取title作为新的特征
* 可以将Age参数进行分类,转换为多个类别
* 创建Fare特征,可能有助于分析
假设:
* Sex中female女性可能存活率更高
* 儿童(需要设定Age的范围)可能存活率更高
* 一等舱(Pclass=1)可能存活率更高
粗略判断分类特征Pclass\Sex\SibSp and Parch和survived的关系
Pclass和sex明显和存活率相关
数据可视化
#复制了一个新的数据来用均值填充一下Age, 查看Age的分布
#真实分布应该去除空值而非填充train_data_age=train_data[train_data['Age'].notnull()]
train_data_age=train_data
train_data_age['Age'].fillna(train_data['Age'].mean()).astype(np.int)
#年龄分布
fig,axes=plt.subplots(1,2,figsize=(18,8))
train_data_age['Age'].hist(bins=80,ax=axes[0])
train_data_age.boxplot(column='Age',showfliers=False,ax=axes[1])
年龄和存活情况的关系
观察:
年龄较小存活较高
年级最大80岁存活
15-25岁中的大部分未存活
年龄分布在15-35岁乘客较多
结论:
在训练模型中考虑Age特征
完整Age特征
设置Age特征分组
pclass_survived=sns.FacetGrid(train_data,col='Survived',row='Pclass')
pclass_survived.map(plt.hist,'Age')
Age, Pclass, Survived
观测:
Age pclass和存活
Pclass=3乘客最多但幸存者并不多,Pclass和Survive相关,验证假设1
Pclass=2和Pclass=3中,年龄较小的乘客存活较多,验证假设2
各年龄乘客分布在不同的Pclass
结论:训练模型中应考虑Pclass
sns.pointplot(x='Pclass',y='Survived',data=train_data,hue='Sex',
dodge=True,join=True,markers=['o','x'],linestyle=['--','-'])#??linestyle显示不出
观察到不同Pclass中女性明显比男性存活率高,性别是分类的有效特征
grid=sns.FacetGrid(train_data,col='Embarked',size=3,aspect=1.5)
grid.map(sns.pointplot,'Pclass','Survived','Sex')
#plt.legend()
#grid.add_legend() 多图标注图例
for i in range(0,3):
grid.axes[0][i].legend()
关联特征Embarked Pclass Sex
Observations.
女性明显比男性存活率高
观察到S和Q的Embarked中,女性生存率高于男性,Embarked=C男性比女性存活率高,可能是Embarked和的关联pclass转而影响survived,而非直接关联
Embarked=C Embarked=Q中,Pclass=3的男性存活率高于Pcalss=2
Embarked不同的Pclass=3的男性存活率存在较大差异
Decisions.
增加性别特征
完善并增加embarked特征
grid=sns.FacetGrid(train_data,row='Embarked',col='Survived',aspect=1.6)
grid.map(sns.barplot,'Sex','Fare',ci=None)#barplot默认用平均值估计 ci置信区间
grid.add_legend()
correlating Embarked (Categorical non-numeric), Sex (Categorical non-numeric), Fare (Numeric continuous), with Survived (Categorical numeric)
barplot按照分类,用estimator方法(默认平均值)计算相应的值。
观察:
对比左右两列,Embarked=S/C中,存活的人船票平均值较高
Embarked=Q的票价都较低,可能关联存活率也较低
Embarked=C幸存者的票价明显高于其他。
结论:
考虑划分船票价格区间
Wrangle data(数据清洗)
去除无用信息
去除无用的ticket cabin的信息
#去除ticket cabin这两列
print("Before", train_data.shape, test_data.shape)
train_data = train_data.drop(['Ticket', 'Cabin'], axis=1)
test_data = test_data.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_data, test_data]
print("After", train_data.shape, test_data.shape, combine[0].shape, combine[1].shape)
Name中提取Title(社会地位)
对Name进行特征提取,从中提取出头衔
#从name中提取title 如Mr. Mrs.等
for dataset in combine:
dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
#crosstab交叉表 按照Title分组,统计各分组中'Sex'的频数
pd.crosstab(train_data['Title'], train_data['Sex'])
crosstab交叉表得到的结果
可以发现Master、Miss、Mr、Mrs死者较多,其他较少,因此将较少的称谓替换成Rare,将同义词进行替换如Mlle替换成Miss。
#将同义的不同写法进行替换,将较少及未知意义替换成Rare
for dataset in combine:
dataset['Title'].replace(['Capt','Col','Countess','Don','Dr','Master','Jonkheer','Lady','Major','Rev',
'Sir'],'Rare',inplace=True)
dataset['Title'].replace('Mlle', 'Miss',inplace=True)
dataset['Title'].replace('Ms', 'Miss',inplace=True)
dataset['Title'].replace('Mme', 'Mrs',inplace=True)
train_data[['Survived','Title']].groupby('Title',as_index=False).mean()
可以发现不同称谓的存活率相差较大,特别是Miss、Mrs比Mr明显要高,佐证了性别对生存率的影响。
由于文本无法用作训练特征,将文本通过map映射到数字,以数字作为训练特征
#将文字map投影到数字,便于作为特征训练 空值投影为0
title_mapping={'Mr':1,'Miss':2,'Mrs':3,'Master':4,'Rare':5}
for dataset in combine:
dataset['Title']=dataset['Title'].map(title_mapping)
dataset['Title']=dataset['Title'].fillna(0)
train_data[['Survived','Title']].groupby('Title',as_index=False).mean()
#删除id和name 如果不重新指向,就inplace=True
train_data=train_data.drop(['PassengerId','Name'],axis=1)
test_data.drop(['Name'],axis=1,inplace=True)#Id作为测试集提交需使用
Age填充(连续数字属性离散化)
方法一:在均值和标准差的范围中生成随机数(最简单)
方法二:根据关联特征填补缺失值,Age Gender Pclass三者相关,根据Pclass和Gender的分类,用均值填充
方法三:基于Pclass和Gender,用均值和标准差范围内的随机数进行填充
方法一和三使用随机数会引入随机噪声,采用方法二
grid=sns.FacetGrid(train_data,col='Sex',row='Pclass',aspect=1.5)
grid.map(plt.hist,'Age',bins=20)
grid.add_legend()
#按照Sex Pclass分组求各组中位数
guess_ages=np.zeros((2,3))#注意是两个括号
for dataset in combine:#第一个dataset是train的df,第二个是test的df
for i in range(0, 2):
for j in range(0, 3):
guess_df = dataset[(dataset['Sex'] == i) & \
(dataset['Pclass'] == j+1)]['Age'].dropna()
# age_mean = guess_df.mean()
# age_std = guess_df.std()
# age_guess = rnd.uniform(age_mean - age_std, age_mean + age_std)
#age_guess = guess_df.median()
# Convert random age float to nearest .5 age
#guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
guess_ages[i,j]=guess_df.median()
#按筛选条件填充空值
for i in range(0, 2):
for j in range(0, 3):
dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j+1),\
'Age'] = guess_ages[i,j]
dataset['Age'] = dataset['Age'].astype(int)
#利用cut连续属性离散化,增加辅助列AgeBand,将Age范围均匀分成5份
train_data['AgeBand']=pd.cut(train_data['Age'],5)
train_data[['AgeBand','Survived']].groupby('AgeBand').mean().sort_values(by='AgeBand',ascending=True)
train_data['AgeBand'].value_counts()
#按照cut的分类对Age进行离散化
for dataset in combine:
dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
dataset.loc[ dataset['Age'] > 64, 'Age']=4
#移除辅助列
train_data = train_data.drop(['AgeBand'], axis=1)
combine = [train_data, test_data]#如果没有这条语句,combine不能被更新?
train_data.head()
可以看出低龄组存活率比其他年龄大,这个地方我不太懂combine和train_data,test_data的关系,combine中的元素指向两个df的地址?所以更改combine就可以直接更新两个df?但为什么drop AgeBand以后,如果不重新赋值则combine中的traindata没有变化。【要补课!!!】
'''
Age*Class
假设越年轻生存率越高+船舱等级越高生存率越高
创造联合特征(个人认为这只能说明都高很难活下来,但年龄最低划分为0无法反映不同Pclass)
纯属按照kernel走一下流程
'''
for dataset in combine:
dataset['Age*Class']=dataset['Age']*dataset['Pclass']
sns.barplot(x='Age*Class',y='Survived',data=train_data,ci=False)
IsAlone参数:SibSp Parch FamilySize
'''
SibSp Parch
a:有无兄弟姐妹/父母子女对存活率的影响
b:亲戚多少与存活率
'''
#筛选有无的数据
sibsp_df=train_data[train_data['SibSp']!=0]
no_sibsp_df=train_data[train_data['SibSp']==0]
parch_df=train_data[train_data['Parch']!=0]
no_parch_df=train_data[train_data['Parch']==0]
plt.figure(figsize=(12,3))
plt.subplot(141)
plt.axis('equal')
plt.title('sibsp')
sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Blues')
plt.subplot(142)
plt.axis('equal')
plt.title('no_sibsp')
no_sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Blues')
plt.subplot(143)
plt.axis('equal')
plt.title('parch')
parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Reds')
plt.subplot(144)
plt.axis('equal')
plt.title('no_parch')
no_parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Reds')
#Parch和SibSp人数多少和存活率的关系
fig,ax=plt.subplots(1,2,figsize=(13,4))
train_data[['Parch','Survived']].groupby('Parch').mean().plot.bar(ax=ax[0],rot=0)
train_data[['SibSp','Survived']].groupby('SibSp').mean().plot.bar(ax=ax[1],rot=0)
#总数和存活率的关系
#利用Parch和SibSp创造新的参数FamilySize +1是因为计算本人
for dataset in combine:
dataset['FamilySize']=dataset['Parch']+dataset['SibSp']+1
train_data[['FamilySize','Survived']].groupby('FamilySize').mean().plot.bar(rot=0)
整体呈现先增后减的趋势
#根据FamilySize创建新的列——IsAlone判断是否一个人
for dataset in combine:
dataset['IsAlone']=0
dataset.loc[dataset['FamilySize']==1,'IsAlone']=1#将总数为1的定义为IsAlone=1
sns.barplot(x='IsAlone',y='Survived',data=train_data,ci=False)
#使用IsAlone代替FamilySize,删除无用的列
train_data=train_data.drop(['Parch','SibSp','FamilySize'],axis=1)
test_data=test_data.drop(['Parch','SibSp','FamilySize'],axis=1)
combine=[train_data,test_data]
IsAlone=1意味着独身一人上传,存活率明显较低。
Embarked参数
'''
Embarked 用众数mode填充
'''
print(train_data['Embarked'].unique())
print(train_data['Embarked'].value_counts())
mode_embarked=train_data['Embarked'].mode()
for dataset in combine:
#mode得到的是list,用索引取value
dataset['Embarked'].fillna(mode_embarked[0],inplace=True)
#sns.barplot(x='Embarked',y='Survived',hue='Pclass',data=train_data,ci=False)
train_data[['Embarked','Survived']].groupby('Embarked').mean().plot.bar(rot=0)
#标签特征离散化
#注意下面是错误的写法,要注意赋值啊!
#for dataset in combine:
# dataset['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
for dataset in combine:
dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
train_data.head()
猜测Embarked口岸不同,可能位置不同从而影响生存率,因此填充很重要,选择用众数进行填充。
Fare填充,连续数字属性离散化
'''
Fare用中位数填充
'''
#中位数填充
for dataset in combine:
dataset['Fare'].fillna(dataset['Fare'].median(),inplace=True)
train_data['FareBand']=pd.qcut(train_data['Fare'],4)#等频划分,尽量每个部分频数相同
train_data[['FareBand','Survived']].groupby(['FareBand'],as_index=False).mean().sort_values(by='FareBand')
#连续数字属性离散化
for dataset in combine:
dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare'] = 2
dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
dataset['Fare'] = dataset['Fare'].astype(int)
train_data = train_data.drop(['FareBand'], axis=1)
combine = [train_data, test_data]
和Age的处理相似,这里用到qcut是按照等频数划分区间(四分位数),而age的cut是按照等宽划分。
突然发现对test的划分处理是按照train的数据划分进行的,所以test中没有辅助列也不需要删除辅助列。
训练和测试
目标是一个分类和回归的问题,想要得到Survived和其他变量之间的关系。
已有数据是有标签的,因此是监督学习。
适用于:(居然每个名字都知道是什么但只会最简单的几个= =)
Logistic Regression
KNN or k-Nearest Neighbors
Support Vector Machines
Naive Bayes classifier
Decision Tree
Random Forrest
Perceptron
Artificial neural network
RVM or Relevance Vector Machine
#准备数据 将特征和标签分开
X_train=train_data.drop('Survived',axis=1)
Y_train=train_data['Survived']
X_test=test_data.drop('PassengerId',axis=1).copy()
#Logistic Regression逻辑回归
from sklearn.linear_model.logistic import LogisticRegression
logreg = LogisticRegression()
logreg=LogisticRegression()
logreg.fit(X_train,Y_train)#数据和标签
Y_pred=logreg.predict(X_test)
acc_log= round(logreg.score(X_train,Y_train)*100,2)
print('LogisticRegression',acc_log)#LogisticRegression 81.26
#Logistic Regression看各特征贡献率
coeff_df = pd.DataFrame(train_data.columns.delete(0))#delete(0)去除Survived标签
coeff_df.columns=['Feature']#自定义列名
coeff_df['Correlation']=pd.Series(logreg.coef_[0])#读取系数
coeff_df.sort_values(by='Correlation',ascending=False)
Positive coefficients increase the log-odds of the response (and thus increase the probability), and negative coefficients decrease the log-odds of the response (and thus decrease the probability).
Sex(male: 0 to female: 1)是最大的正数,Sex增加(即=1 female)最可能增加Survived=1的可能性。Title第二大的正数(这种情况是否在做离散化的时候,赋值也应该具有逻辑性?)
Pclass是最大的负数,Pclass越大,Survived=1的可能性降低。Age*Class作者结果中是第二大的负数,不知道为什么这个地方有比较大的差别。
#SVM
from sklearn import svm
svc=svm.SVC()
svc.fit(X_train,Y_train)
Y_pred=svc.predict(X_test)
acc_svc=round(svc.score(X_train,Y_train)*100,2)
acc_svc#83.5
# Linear SVC
linear_svc = svm.LinearSVC()
linear_svc.fit(X_train, Y_train)
Y_pred = linear_svc.predict(X_test)
acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)
acc_linear_svc#79.46
#KNN
from sklearn import neighbors
knn=neighbors.KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train,Y_train)
Y_pred=knn.predict(X_test)
acc_knn=round(knn.score(X_train,Y_train)*100,2)
acc_knn# 83.73
# Gaussian Naive Bayes
from sklearn.naive_bayes import GaussianNB
gaussian =GaussianNB()
gaussian.fit(X_train, Y_train)
Y_pred = gaussian.predict(X_test)
acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)
acc_gaussian#76.88
# Perceptron
from sklearn import linear_model
perceptron = linear_model.Perceptron()
perceptron.fit(X_train, Y_train)
Y_pred = perceptron.predict(X_test)
acc_perceptron = round(perceptron.score(X_train, Y_train) * 100, 2)
acc_perceptron#76.66
# Stochastic Gradient Descent
sgd = linear_model.SGDClassifier()
sgd.fit(X_train, Y_train)
Y_pred = sgd.predict(X_test)
acc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)
acc_sgd#77,33
# Decision Tree
from sklearn.tree import DecisionTreeClassifier
decision_tree = DecisionTreeClassifier()
decision_tree.fit(X_train, Y_train)
Y_pred = decision_tree.predict(X_test)
acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)
acc_decision_tree#86.64
# Random Forest
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
acc_random_forest#86.64
#对不同算法的性能进行排序
models = pd.DataFrame({
'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression',
'Random Forest', 'Naive Bayes', 'Perceptron',
'Stochastic Gradient Decent', 'Linear SVC',
'Decision Tree'],
'Score': [acc_svc, acc_knn, acc_log,
acc_random_forest, acc_gaussian, acc_perceptron,
acc_sgd, acc_linear_svc, acc_decision_tree]})
models.sort_values(by='Score', ascending=False)
#按照格式导出提交文档
submission = pd.DataFrame({
"PassengerId": test_data["PassengerId"],
"Survived": Y_pred
})
submission.to_csv('submission.csv', index=False)
violette_lx 发布了33 篇原创文章 · 获赞 1 · 访问量 643 私信 关注
标签:Embarked,乘客,Age,Kaggle,dataset,train,泰坦尼克,Survived,data 来源: https://blog.csdn.net/violette_lx/article/details/103938212