泰坦尼克号生还预测-from Kaggle
话不多说,直入主题。
1.数据导入
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})
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 乘仓等级
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的正确率最高,当然,可能你修改不同的参数,结果可能又会不一样哦!