美文网首页
Kaggle|数据科学Hello World: Titanic生

Kaggle|数据科学Hello World: Titanic生

作者: 杏仁小核桃 | 来源:发表于2020-08-31 00:27 被阅读0次

Kaggle数据科学的入门项目:Titanic

写在前面

基本步骤

  1. 数据准备和探索
  2. 数据清洗
  3. 特征选择
  4. 模型训练
  5. 模型验证
  6. 提交结果

1. 数据准备和探索

1.1 明确数据来源和要研究的问题

Titanic是世界十大灾难之一,一部经典的电影让沉船的惨烈和凄美的爱情故事都留在了大众心中。同时,灾难的伤亡数据一直成为了数据科学领域的研究热门。
这次的研究的目的是试图根据Titanic乘客的数据,如性别、年龄、舱位、客舱编号等,来预测乘客最终是获救生还,还是不幸遇难。
显然,这是一个标准的二分类问题,预测类别为:生还和遇难。
先从Titanic Data下载数据集,一共有3个文件:

文件 内容
train.csv 训练数据集,包含特征信息和存活与否的标签,用来建模
test.csv 测试数据集,只包含特征信息,用来检测模型的准确度
gender_submission.csv 提交文档模板,假设所有女乘客都生还

1.2 数据探索

先简单查看一下数据,读取训练数据后,用head()函数来查看前5行数据:

#matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

train = pd.read_csv('train.csv', index_col=0)
test = pd.read_csv('test.csv', index_col=0)

train.head()
train数据中的前5项

通过对数据的观察,可以总结出如下数据字典:

变量名 变量解释 数据解释
PassengerId 乘客编号 唯一编号
Survived 乘客是否生还 0=未生还,1=生还
Pclass 乘客所在舱位 1=一等舱,2=二等舱,3=三等舱
Name 乘客姓名
Sex 乘客性别 male,female
SibSp 乘客的兄弟姐妹和配偶数量
Parch 乘客的父母和子女数量
Ticket 船票编号
Fare 票价
Cabin 乘客所在船舱号
Embarked 乘客登船港口 C = Cherbourg, Q = Queenstown, S = Southampton

由此可见,除了确定乘客的唯一编号PassengerId外,一共有10个可以研究的特征变量。

train.info()来查看数据的基本情况,运行结果如下:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

可以看到各个数据字段的缺失情况和数据类型。
同时也不忘查看一下测试数据,test.info()运行结果如下:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  418 non-null    int64  
 1   Pclass       418 non-null    int64  
 2   Name         418 non-null    object 
 3   Sex          418 non-null    object 
 4   Age          332 non-null    float64
 5   SibSp        418 non-null    int64  
 6   Parch        418 non-null    int64  
 7   Ticket       418 non-null    object 
 8   Fare         417 non-null    float64
 9   Cabin        91 non-null     object 
 10  Embarked     418 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB

2. 数据清洗

通过初步探索发现,在AgeCabinFareEmbarked特征上都有数据缺失,我们需要处理缺失值才能进行下一步的分析。
处理缺失数据一般有两种方法:滤除缺失数据填充缺失数据。Titanic数据集特征只有10个,显然不能舍弃数据缺失的特征,所以需要填充缺失数据。
填充数据的时,一般会根据实际情况,将数据补0,或以该特征数据的均值或中位数来填充,或将缺失数据划为新的类型来处理。

2.1 补齐Embarked字段

Embarked为乘客登船港口,首先观察一下字段数据情况。

#查看Embarked字段数据
train['Embarked'].value_counts()

结果如下:

S    644
C    168
Q     77
Name: Embarked, dtype: int64

由于港口数据类型是字符型,而且数据量比较少,我们可以用众数来填补空缺数据,即港口数最多的'S'

train['Embarked'].fillna('S', inplace=True)
test['Embarked'].fillna('S', inplace=True)

2.1 补齐Age和Fare字段

Age和Fare字段简单地采用均值来填补

train['Age'].fillna(train['Age'].mean(), inplace=True)
test['Age'].fillna(test['Age'].mean(), inplace=True)

train['Fare'].fillna(train['Fare'].mean(), inplace=True)
test['Fare'].fillna(test['Fare'].mean(), inplace=True)

3. 特征选择

选取数据中有价值的特征,提取train和test中的特征向量

features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_features = train[features]
train_labels = train['Survived']

test_features = test[features]

由于数据中Embarked和Sex字段都是字符型数据,这样不利于分析,可以用sklearn中的特征提取,将符号化数据抽取成不同的特征向量:

from sklearn.feature_extraction import DictVectorizer

dvec = DictVectorizer(sparse=False)
train_features = dvec.fit_transform(train_features.to_dict(orient='record'))

print(dvec.feature_names_)

查看feature_names_可以看出,原来的EmbarkedSex两列均根据变量取值拆成了若干列:

['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Fare', 'Parch', 'Pclass', 'Sex=female', 'Sex=male', 'SibSp']

4. 模型训练

选择sklearn中的决策树模型,使用ID3算法构造决策树:

from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(criterion='entropy')
clf.fit(train_features, train_labels)

决策树根据train数据fit过,可以用来预测test数据了:

test_features = dvec.transform(test_features.to_dict(orient='record'))
pred_labels = clf.predict(test_features)

5. 模型验证

因为没有测试数据的正确结果,计算决策树在训练数据上的准确率:

acc = round(clf.score(train_features, train_labels), 6)
print(u'accuracy = %.4lf' % acc)

运行结果:

accuracy = 0.9820

6. 提交结果

最终提交的结果要符合kaggle示范文件的格式,也就是包含两列PassengerIDSurvived数据的csv文件。

passengers = test['PassengerId']
survived= pd.Series(data=pred_labels, name='Survived')
result = pd.concat([passengers, survived], axis=1)
result.to_csv('submission.csv', index=False)

保存好文件后,在kaggle比赛页面提交,提交成功后会计算出此次的分数:
最终准确率是0.746╮(╯▽╰)╭

数据分析版hello world到此结束,如何提高准确率提升名次,把文中偷懒和简化的部分做得更细致,大概就可以提升不少了吧,日后再刷~~

---------------------------------------更新的分割线----------------------------------------
很显然之前简单用决策树模型得出的准确率74.6%是远远不够的,不足的地方有:

  • 数据探索不够深入,没有挖出数据更深层的隐含信息
  • 数据清洗不到位,对数据的宏观把握不足,对数据的认识和感知还不够,所以进而数据的清洗也没有找到更合适的方法
  • 数据模型单一且简单,应该选择更合适的模型
  • 模型验证方法很显然不对,与实际准确率相去甚远

接下来就来逐个点优化吧~~~

7. 数据再探索

7.1 数据基本分布

先从宏观上了解一下数据,看看几个主要特征上的分布情况,然后再进一步分析的切入点:

fig = plt.figure(figsize=(17,10), dpi=200)

#查看获救人数和未获救人数分别有多少
plt.subplot2grid((2,3),(0,0))
train_data.Survived.value_counts().plot(kind='bar',color=['lightblue','pink'])
plt.ylabel(u'人数')
plt.title(u'获救情况(1为获救)')

# 查看各舱位乘客人数
plt.subplot2grid((2,3),(0,1))
train_data.Pclass.value_counts().plot(kind='bar',color=['lightblue','pink','palegreen'])
plt.ylabel(u'人数')
plt.title(u'乘客等级分布')

# 查看获获救和未获救乘客的年龄分布
plt.subplot2grid((2,3),(0,2))
plt.scatter(train_data.Survived, train_data.Age, color='skyblue')
plt.ylabel(u'年龄')
plt.grid(b=True, which='major', axis='y')
plt.title(u'按年龄看获救分布(1为获救)')

# 查看各舱位等级乘客的年龄分布
plt.subplot2grid((2,3),(1,0), colspan=2)
train_data.Age[train_data.Pclass==1].plot(kind='kde')
train_data.Age[train_data.Pclass==2].plot(kind='kde')
train_data.Age[train_data.Pclass==3].plot(kind='kde')
plt.xlabel(u'年龄')
plt.ylabel(u'密度')
plt.title(u'各等级的乘客年龄分布')
plt.legend((u'头等舱',u'2等舱',u'3等舱'), loc='best')

# 查看各登船港口的获救人数
plt.subplot2grid((2,3),(1,2))
train_data.Embarked.value_counts().plot(kind='bar',color=['lightblue','pink','palegreen'])
plt.title(u'各登船口岸上岸人数')
plt.ylabel(u'人数')

plt.show()
数据基本分布

图表果然清晰多了,从图中不难看出:

  • 泰坦尼克号沉船是个大灾难,获救的人只是一小部分,为逝者默哀一分钟
  • 3等舱的乘客人数远远大于其他两个船舱的乘客
  • 获救和未获救的人年龄分布都很广
  • 各舱位乘客的年龄分布,3等舱的乘客集中分布在20左右,头等舱乘客40岁左右最多,很符合社会财富分布
  • S港口的登陆乘客最多,远多于C、Q港口
    大概了解了数据分布后可以大胆假设一下:
  • 乘客的财富地位也许会影响到最终是否被获救,所以与财富地位相关的因素如:舱位等级、姓名Title等都可能是相关特征
  • 乘客的登船港口和地点相关,各地区之间居民收入和身份地位也许也不同
    接下来就进行相关性分析,探讨一下到底哪些因素会影响到是否获救。

7.2 各特征与是否获救的关联统计

7.2.1 性别与获救情况
# 按性别查看获救情况
survived_M = train_data.Survived[train_data.Sex=='male'].value_counts()
survived_F = train_data.Survived[train_data.Sex=='female'].value_counts()

df = pd.DataFrame({u'男性':survived_M, u'女性':survived_F})
df.plot(kind='bar', stacked=True, color=['lightblue','pink'])

plt.title(u'按性别查看获救情况')
plt.xlabel(u'性别')
plt.ylabel(u'人数')

plt.show()
根据性别查看获救情况

果然,lady first不仅仅是一句口号,获救的乘客中还是女性居多,牺牲的乘客中男性占了绝大多数。

7.2.2 客舱等级与获救情况
# 按客舱等级查看获救情况
Survived_0 = train_data.Pclass[train_data.Survived == 0].value_counts()
Survived_1 = train_data.Pclass[train_data.Survived == 1].value_counts()

df = pd.DataFrame({u'获救':Survived_1,u'未获救':Survived_0})
df.plot(kind='bar', stacked=True, color=['#92ff92','#ff9292'])
plt.title(u'各乘客等级的获救情况')
plt.ylabel(u'人数')
plt.xlabel(u'乘客等级')

plt.show()
按客舱等级查看获救情况

1等舱和2等舱的获救比例远远大于3等舱,乘客的钱不是白花的呀,万恶的资本主义~~

7.2.3 登船港口与获救情况
# 查看各港口的获救情况
survived_0 = train_data.Embarked[train_data.Survived == 0].value_counts()
survived_1 = train_data.Embarked[train_data.Survived == 1].value_counts()

df = pd.DataFrame({u'获救':survived_0,u'未获救':survived_1})
df.plot(kind='bar', stacked=True, color=['#92ff92','#ff9292'])

plt.title(u'各港口乘客的获救情况')
plt.ylabel(u'人数')
plt.xlabel(u'港口')

plt.show()
各港口的获救情况

S港口的乘客人数众多,获救率似乎也低一些,C港口获救率比稍微高一些,港口这个特征暂且留着吧

7.2.4 船舱号与获救情况

有个特征Cabin缺失严重,补足数据也不太方便,我们可以考虑或许有没有Cabin字段会和生还率相关呢?

# 根据有没有Cabin属性创建一个新的CabinBool属性
train_data['CabinBool'] = (train_data['Cabin'].notnull().astype('int'))

cabin_1 = train_data['CabinBool'][train_data['Survived']==1].value_counts(normalize=True)
cabin_0 = train_data['CabinBool'][train_data['Survived']==0].value_counts(normalize=True)

df = pd.DataFrame({'获救':[cabin_1[0], cabin_0[0]],'未获救':[cabin_1[1], cabin_0[1]]})
df.plot.bar(stacked=True, color=['#92ff92','#ff9292'])

plt.show()
0代表没有Cabin值,1代表有Cabin值
很显然,有Cabin记录的乘客的获救率远远高于没有Cabin记录的乘客,可以考虑将Cabin转化成二元属性用于模型训练。
还有一些属性ParchSibSp简单的查看了一下,参考价值不大,暂且不用了吧。
分析到这里就可以进行下一步的预处理了。

8. 数据预处理

为了方便起见,我们将原始的训练数据和测试数据合并后一并处理。

y_train = train_data.pop('Survived')
data_all = pd.concat((train_data, test_data), axis=0)

8.1 提取姓名Title

先查看一下Name属性的格式

print(data_all['Name'].head(20))
Name属性前20项
可以看出来,Name属性的格式是+Title+,名和姓对我们都不重要,只要把Title提取出来就行了。
title = pd.DataFrame()
title['Title'] = data_all['Name'].map(lambda name:name.split(',')[1].split('.')[0].strip())
print(title['Title'].value_counts())
Title种类
可以大致把Title分为NormalMiddleRoyal三类。
# 先将Title归类
title_dict = pd.DataFrame()
title_dict['Normal'] = ['Mr', 'Miss', 'Mrs', 'Ms', 'Mme','Mlle']
title_dict['Middle']=['Capt', 'Dr', 'Rev', 'Col', 'Master', 'Major']
title_dict['Royal']=['the Countess', 'Sir', 'Lady', 'Don','Jonkheer', 'Dona']
#构造title_map
title_map = {}
for index,row in title_dict.iteritems():
    for title_ in row:
        title_map[title_] = index
        
print(title_map)
title和等级的对应关系

捋请了Title的分类后,就可以对原始数据进行改造了:

title['Title'] = title.Title.map(title_map)
# 将title进行one-hot encoding
title = pd.get_dummies(title.Title)
#拼接到data上
data_all = pd.concat((data_all, title), axis=1)
data_all.pop('Name')
print(data_all.head())

这时数据变成了13列,丢弃了Name,将title拆为了NormalMiddleheRoyal三列。

8.2 处理Cabin属性

根据是否有Cabin将原字符型属性转为binary属性:

data_all['Cabin'] = (data_all.Cabin.notnull().astype('int'))

8.3 处理Pclass属性

根据上面的分析,Pclass和是否获救的相关性很大,Pclass也是字符型属性,所以也很适合用one-hot encoding处理:

data_all.Pclass = data_all.Pclass.astype(str)
data_all.Pclass = data_all.Pclass.map(lambda pclass:'pclass_'+pclass)
pclass = pd.get_dummies(data_all.Pclass)
data_all = pd.concat((data_all, pclass), axis=1)
data_all.pop('Pclass')

Pclass被拆成了pclass_1pclass_2pclass_33列。

8.4 处理其他属性

先填充缺失值,用均值填充Age

data_all.Age.fillna(data_all.Age.mean(),inplace=True)

填充Embarked属性,这是个字符型属性,用出现概率最高的S来填充,再用one-hot encoding处理:

data_all.Embarked.fillna('S', inplace=True)
embarked = pd.get_dummies(data_all.Embarked)
data_all = pd.concat((data_all, embarked), axis=1)
data_all.pop('Embarked')

test数据中有一个Fare缺失,也用均值来填充:

data_all.Fare.fillna(data_all.Fare.mean(),inplace=True)

Sex属性拆成二元属性:

sex = pd.get_dummies(data_all.Sex)
data_all = pd.concat((data_all, sex),axis=1)
data_all.pop('Sex')

还有一个比较迷的Ticket属性,现在特征数量已经很多了,就利用奥卡姆剃刀把它剃掉吧~~
经过这一系列的处理后,现在的数据变成了这样:

清洗后的data info
由原来的10个特征变成了17个特征,并且全都是数值型的。
最后,别忘了把训练数据和测试数据拆开:
train_d = data_all.loc[train_data.index]
test_d = data_all.loc[test_data.index]
print(train_d.shape, test_d.shape)

拆开后shape分别是(891, 16)和(418, 16),符合原始数据的大小。

9. 模型训练

这次准备使用sklearn里的逻辑回归模型,同样也是一个很适合分类问题的模型。
先用sklearn试试最简单的baseline模型效果咋样:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

lr_clf = LogisticRegression(C=1.0, penalty='none', tol=1e-6)
lr_clf.fit(train_features, y_train)

lr_pred = lr_clf.predict(test_features)

corss_score = np.mean(cross_val_score(lr_clf, train_features, y_train, cv=10))
print(u'cross score = %.4lf' % corss_score)

cross validation 的准确率是 0.7834,似乎也没比决策树优秀多少。
下面就要开始进一轮的模型优化了~~

9.1 模型系数关联分析

查看一下目前这个LR模型中各特征的关联系数:

pd.DataFrame({"columns":list(train_d.columns)[0:], "coef":list(lr_clf.coef_.T)})
关联系数

这个关联系数表示的是每个特征的在逻辑回归模型中的模型参数,逻辑回归模型会通过sigmod函数将这个参数映射到0~1之间。
coef大于0时,说明特征和结果是正相关,小于0时是负相关。
从上面的关联系数表可以看出:

  • female和是否获救正相关,male则是负相关,很符合数据给人的印象
  • Age有一点点负相关,说明年龄越小越容易获救
  • Cabin这个字段竟然很有助于获救,说明有登记船舱信息的都是比较容易获救的
  • 登船港口S、Q、C竟然呈现出截然不同的表现,很出乎意料,不知道是不是有点弄错了o(╥﹏╥)o
  • Parch的关联性竟然这么高,还有SibSp也不错,可以考虑多挖掘挖掘

9.2 模型优化

根据上表,找出几个可以尝试的模型调优的几个点:

  • 增加Child属性,年龄小于12的添加Child属性
  • Pclass似乎没有利用起来,吧Pclass和港口组合成新的特征
  • 增加一个Family属性,把Parch和SibSp还有自己加起来,看看家庭人数的影响

经过一系列处理后,目前最好成绩是0.78947
各种特征排列组合寻求最优解是一个需要耐心和细心的活儿,可能也需要一点点的灵感和新的视角
暂时告一段落啦,接下来会用模型融合来试一试~~

相关文章

网友评论

      本文标题:Kaggle|数据科学Hello World: Titanic生

      本文链接:https://www.haomeiwen.com/subject/hsvysktx.html