Titanic Disaster Prediction 泰坦尼克号生还预测

泰坦尼克号生还预测-from Kaggle

    对于熟悉kaggle的同学来说这是一个极其经典的机器学习入门项目,但是要从头至尾做好这个项目则需要一定的实践经验以及使用如何机器学习方法的基本知识,主要包括Python操作和常用的针对机器学习方法的数据预处理操作。本文在此并不讨论所用到的机器学习方法的计算原理说明,着重讨论如何按照规则化的流程使用ML方法进行数据分析处理。
    项目地址戳这里: here!
    然后今天要介绍的是一篇我个人认为很好的对Titanic这个项目做的分析,十分适合初学者。 原文

  话不多说,直入主题。

    1.数据导入

    数据包含三个数据,均已csv格式进行存储。train.csv为训练数据,包含了各种特征以及最后的target预测目标,test.csv基本和前者一样,只是少了预测特征值。然后还有一个gender_submission.csv,这是为你提交预测值做的一个示范格式,主要是将columns写对。
    下面直接贴上代码,kaggle中的数据是直接存在‘../input’路径下。
import pandas as pd
import numpy as np
train = pd.read_csv('../input/train.csv', header = 0, dtype={'Age': np.float64})
test = pd.read_csv('../input/test.csv' , header = 0, dtype={'Age': np.float64})
    read_csv中出现了header的参数设置,这个参数的作用是指代以第几行作为列名,这样可以忽略掉一些文件置顶的一些注释说明,0表示默认参数,一般是第一行,dtype参数是设置data 的 type 也就是数据类型,注意这个参数接受的数据类型是字典型,键为'Age',设置为np中的64位浮点型。

    2.特征工程 feature engineering

    开始进入重要阶段,这部分的工作主要集中于如何挑选合适的特征作为predictor,并且需要对特征数据进行合适的操作。

    首先看看数据的构成

print(train.info())

    得到以下结果:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None

    第一列Data columns 是各个列的表头名也就是特征数据,包含了乘客的ID等信息,第二、三列表示非空数据个数,最后一列是各个特征数据的数据类型。

    从表中可以看到,大部份的数据都是比较全的,并且可操作的空间相当高,因为大部分的数据都是数值型。

    2.1 Pclass(等级)

    首先查看下Pclass数据对生存率的影响,我们使用中括号索引功能将Pclass和Survived数据抽取出来组成一个DataFram数据,然后利用groupby()函数选择标签分类,最后利用pandas中自带的mean()方法求取各个Pclass的平均值,具体操作如下:

pclass_survived = train[['Pclass','Survived']]
pclass_survived.head()
Pclass	Survived
0	3	0
1	1	1
2	3	1
3	1	1
4	3	0

    pcl_survi_grouped是一种DataFrameGroupBy数据类型,里面包含了很多数学计算方法,包括mean(),std()等,可以直接调用describe()方法显示出来。

pcl_survi_grouped = pclass_survived.groupby(['Pclass'])
print(type(pcl_survi_grouped))
print(pcl_survi_grouped.describe())
<class 'pandas.core.groupby.groupby.DataFrameGroupBy'>
       Survived                                             
          count      mean       std  min  25%  50%  75%  max
Pclass                                                      
1         216.0  0.629630  0.484026  0.0  0.0  1.0  1.0  1.0
2         184.0  0.472826  0.500623  0.0  0.0  0.0  1.0  1.0
3         491.0  0.242363  0.428949  0.0  0.0  0.0  0.0  1.0

 可以看到3等乘客人数最多,生存率最低,一等乘客的生存率最高。

    2.2 性别 Sex

    性别也是影响了生存率的一个重要因素,与上面一样的方法,直接上代码:

sex_survi = train[['Sex','Survived']]
print(sex_survi.groupby(['Sex']).mean())
        Survived
Sex             
female  0.742038
male    0.188908

    可以发现女性的生存率远高于男性啊!

    2.3 家族成员影响 sibsp and parch

    由于兄弟姐妹和恋人均可以看做是家族成员,我们在这里对其进行相加处理组成新的特征FamilySize,没有家族成员和恋人的+1表示孤身一人。代码:

full_data = [train , test]
for data in full_data:
    data['FamilySize'] = data['SibSp']+data['Parch']+1
print(train.info())
print(test.info())

    可以通过info()函数获取到我们已经成功在train和test数据中插入了新的特征数据FamilySize。

    按照同样的操作查看下与生存率的影响关系。

            Survived
FamilySize          
1           0.303538
2           0.552795
3           0.578431
4           0.724138
5           0.200000
6           0.136364
7           0.333333
8           0.000000
11          0.000000

    嗯,看来还是有个人陪着获救几率大点,但是也别太多。

    然后我们看看如果孤单一人上船,获救率如何。

    先创建IsAlone列,全部设置为0,然后利用dataFrame中的loc()函数功能筛选出FamilySize为1的乘客,将其标记为1,然后统计生还率。(注意这里的loc()函数有一个非常有用的功能,第一个参数是[Index],第二个参数一般是columns名称,但是在第一个参数可以做筛选操作,看代码,非常强大的功能)

for data in full_data:
    data['IsAlone'] = 0
    data.loc[data['FamilySize']==1,['IsAlone']] = 1
print(train[['IsAlone','Survived']].groupby(['IsAlone']).mean())
    
   IsAlone  Survived
0        0  0.505650
1        1  0.303538

    可以看出其影响程度还是挺高的。

    2.4 Embarked 乘仓等级

    首先注意到这个特征是属于存在数据缺失,标准的特征数据大小是891,这个数据是889,可以代码查看下:
print(train['Embarked'].count())
embarked = train[['Embarked']]
print(embarked.describe())
print(embarked.groupby('Embarked').size())
889
       Embarked
count       889
unique        3
top           S
freq        644
Embarked
C    168
Q     77
S    644
dtype: int64

      然后我们可以发现S仓位的人数最多。因此我们对缺失部分填上S标签(可能有点草率,但是鉴于数据较少,不会产生较大影响)。

    查看其对生存率的影响。

for data in full_data:
    data['Embarked'].fillna('S')
print(train[['Survived','Embarked']].groupby('Embarked').mean())
        Survived
Embarked          
C         0.553571
Q         0.389610
S         0.336957

     2.5 Fare 票价

    票价其实也是一种反映了乘客身份的特征,数据显示没有票价缺失

fare = train.Fare
fare.hasnans
    
False

    那我们可以将票价按照区间分为四类,使用padans自带的qcut()函数

train['CategoricalFare'] = pd.qcut(train['Fare'],4)
print(train[['CategoricalFare','Survived']].groupby('CategoricalFare').mean())
 Survived
CategoricalFare          
(-0.001, 7.91]   0.197309
(7.91, 14.454]   0.303571
(14.454, 31.0]   0.454955
(31.0, 512.329]  0.581081

    2.6 Age 年龄

    在这个特征数据中存在大量缺失。因此我们对缺失数据按照(mean-std,mean+std)这个范围生成随机数,然后填入原数据的nan(not a number)部分。

    这部分代码会比较长点。

for data in full_data:
    data_avg = data['Age'].mean()
    data_std = data['Age'].mean()
    data_null_count = data['Age'].isnull().sum()
    data_null_randlist = np.random.randint(data_avg-data_std,data_avg+data_std,size=data_null_count)
    data['Age'][np.isnan(data['Age'])] = data_null_randlist
    data['Age'] = data['Age'].astype(int)
train['CategoricalAge'] = pd.cut(train['Age'],5)
print(train[['CategoricalAge','Survived']].groupby('CategoricalAge').mean())

    得到结果:

                Survived
CategoricalAge          
(-0.08, 16.0]   0.463576
(16.0, 32.0]    0.356962
(32.0, 48.0]    0.387931
(48.0, 64.0]    0.392157
(64.0, 80.0]    0.090909

    这里的年龄分类出现了负数,很神奇,不知什么原因,但是可以看出青少年获救成功率最高。

    2.7 Name 名字

    在名字这个属性中,可以发现一些有趣的事情,比如头衔,比如Miss Mr这类具有性别指代的称呼。因此我们需要将其拾取出来。

    先看看Name属性中的数据长什么样。

import re
print(train['Name'].head())
    
0                              Braund, Mr. Owen Harris
1    Cumings, Mrs. John Bradley (Florence Briggs Th...
2                               Heikkinen, Miss. Laina
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)
4                             Allen, Mr. William Henry
Name: Name, dtype: object

    我们可以注意到这样一个细节,在诸如Mr、Miss这样具有Title性质的称谓前面有一个空格并且之后有一个’.‘作为分隔符区分开,因此我们可以利用python中的re正则表达式库中的功能将其提取出来,组成一个新的Title特征。

    这一段的代码涉及到正则表达式,会稍显复杂一点,不过会跟大家详细解释清楚。

def get_title(name):
    title_search = re.search(' ([a-zA-Z]+)\.',name)
    if title_search:
        return title_search.group(1)
    return ''
for data in full_data:
    data['Title'] = data['Name'].apply(get_title)
print(pd.crosstab(train['Title'],train['Sex']))

    get_title()函数用来获取Name特征中的如Miss、Mr等头衔,如果没有则返回空字符。

    re.search()函数中的第一个参数是pattern,表示识别模式,引号中间的依次是空格,然后是大小写字母a-zA-z识别,用中括号包围,表示只要识别到大小写字母就提取,后面的+号表示执行1到n次,这一部分用圆括号围起来,表示这个+号的重复是对[]中括号内的特定操作,后面的\表示转义字符,对 . 符号执行转义操作。

    然后用aplly()函数应用到Name属性中,提取出Title,最后利用padans的交叉表做出头衔与性别的图,看看各个头衔的性别分布情况。

Sex       female  male
Title                 
Capt           0     1
Col            0     2
Countess       1     0
Don            0     1
Dr             1     6
Jonkheer       0     1
Lady           1     0
Major          0     2
Master         0    40
Miss         182     0
Mlle           2     0
Mme            1     0
Mr             0   517
Mrs          125     0
Ms             1     0
Rev            0     6
Sir            0     1
    由于某些头衔的人数较少,并且属性重合度较高,比如Miss和Mrs这两类,因此我们对其做一个合并操作,转化为Title属性。
for data in full_data:
    data['Title'] = data['Title'].replace(['Capt','Col','Countess','Don','Dr','Jonkheer','Lady','Major','Sir','Rev'],'Rare')
    data['Title'] = data['Title'].replace('Mlle','Miss')
    data['Title'] = data['Title'].replace('Ms','Miss')
    data['Title'] = data['Title'].replace('Mme','Mrs')
print(train[['Title','Survived']].groupby('Title').mean())

    一共划分出五个Title属性,统计各属性对应的生存率。

print(train[['Title','Survived']].groupby('Title').mean())
    
        Survived
Title           
Master  0.575000
Miss    0.702703
Mr      0.156673
Mrs     0.793651
Rare    0.347826

    可以看出不同的Title的人群具有较为明显区别的生存率。

3.Data Clean 数据清洗

    从原始数据集中分析了上述特征之后,我们需要对以上提到的数据进行数据编码也称数据清洗,用以最终生成能被机器学习方法所接受的输入数据的形式。

    总的来说这一步过程就是将一些字符型数据映射成数值型数据,或者是消除奇异数据、数据规则化运算的过程。

    长代码来咯!

for data in full_data:
    data['Sex'] = data['Sex'].map({'male':1,'female':0})
    title_mapping = {'Mr':1,'Miss':2,'Mrs':3,'Master':4,'Rare':5}
    data['Title'] = data['Title'].map(title_mapping)
    data['Title'] = data['Title'].fillna(0)
    
    data['Embarked'] = data['Embarked'].map({'S':0,'C':1,'Q':2})
    #mapping Fare
    data.loc[data['Fare']<=7.91,'Fare'] = 0
    data.loc[(data['Fare']>7.91) & (data['Fare']<=14.454),'Fare']  = 1
    data.loc[(data['Fare']>14.454) & (data['Fare']<=31),'Fare'] = 2
    data.loc[data['Fare']>31,'Fare'] = 3
    data['Fare']
    
    #mapping Age
    data.loc[data['Age']<=16,'Age']  = 0
    data.loc[(data['Age']>16) & (data['Age']<=32),'Age'] = 1
    data.loc[(data['Age']>32) & (data['Age']<=48),'Age'] = 2
    data.loc[(data['Age']>48) & (data['Age']<=64),'Age'] = 3
    data.loc[data['Age']>64,'Age'] = 4
    
#Feature selection
drop_elements = ['PassengerId','Name','Ticket','Cabin','SibSp','Parch','FamilySize']
train = train.drop(drop_elements,axis=1)
train = train.drop(['CategoricalAge', 'CategoricalFare'], axis = 1)
test = test.drop(drop_elements,axis=1)
print(train.head())
 Survived  Pclass  Sex  Age  Fare  Embarked  IsAlone  Title
0         0       3    1    1   0.0       0.0        0      1
1         1       1    0    2   3.0       1.0        0      3
2         1       3    0    1   1.0       0.0        1      2
3         1       1    0    2   3.0       0.0        0      3
4         0       3    1    2   1.0       0.0        1      1

    full_data包含了train和test的数据,需要同时对这两个数据进行同样的预处理操作。对于Sex,Title,Embarked这样的字符串类型数据编码成数值数据,然后对于一定范围内分布较为连续的离散数据如Age,Fare则分段标记数据,最后删除掉一些不必要的数据然后组成标准的机器学习能够接受的数据。

train = train.values
test  = test.values
    这是将该数据转化成numpy数组型数据,方便操作计算。

    4.Classifier Comparison 各种分类器对比

    机器学习的库我们主要会用到sklearn以及用来辅助可视化的工具库matplotlib和seaborn。


import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.metrics import accuracy_score, log_loss
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression

classifiers = [
    KNeighborsClassifier(3),
    SVC(probability=True),
    DecisionTreeClassifier(),
    RandomForestClassifier(),
	AdaBoostClassifier(),
    GradientBoostingClassifier(),
    GaussianNB(),
    LinearDiscriminantAnalysis(),
    QuadraticDiscriminantAnalysis(),
    LogisticRegression()]

log_cols = ["Classifier", "Accuracy"]
log 	 = pd.DataFrame(columns=log_cols)

sss = StratifiedShuffleSplit(n_splits=10, test_size=0.1, random_state=0)

X = train[0::, 1::]
y = train[0::, 0]

acc_dict = {}

for train_index, test_index in sss.split(X, y):
	X_train, X_test = X[train_index], X[test_index]
	y_train, y_test = y[train_index], y[test_index]
	
	for clf in classifiers:
		name = clf.__class__.__name__
		clf.fit(X_train, y_train)
		train_predictions = clf.predict(X_test)
		acc = accuracy_score(y_test, train_predictions)
		if name in acc_dict:
			acc_dict[name] += acc
		else:
			acc_dict[name] = acc

for clf in acc_dict:
	acc_dict[clf] = acc_dict[clf] / 10.0
	log_entry = pd.DataFrame([[clf, acc_dict[clf]]], columns=log_cols)
	log = log.append(log_entry)

plt.xlabel('Accuracy')
plt.title('Classifier Accuracy')

sns.set_color_codes("muted")
sns.barplot(x='Accuracy', y='Classifier', data=log, color="b")

    这里的代码很长,但是都是一些基础操作,只是实现了多个分类器,并且最终显示其效果。

    

    可以发现,SVC的正确率最高,当然,可能你修改不同的参数,结果可能又会不一样哦!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值