2.1 数据准备

数据集是公开的,读者可以在前言中提供的下载链接找到。数据集文件包括train.csv、test.csv、gender_submission.csv,还有一个是titanic_dataset.csv(前3个数据集文件的合并)。为了方便数据清洗、缺失值处理和分割,我们就使用titanic_dataset.csv。本章主要使用pandas和sklearn作为数据预处理和训练的库,在数据可视化分析查看时使用的是seaborn和matplotlib库。

2.1.1 环境准备

◇ numpy=1.14.5。

◇ pandas=0.22.0。

◇ sklearn=0.19.2。

◇ seaborn=0.7.1。

◇ matplotlib=2.1.2。

◇ keras=2.1.6。

2.1.2 预处理数据

加载titanic_dataset.csv数据集,这是泰坦尼克号公开数据集的完整数据,我们通过pandas的read_csv()方法获取到了完整的csv数据,并且返回Pandas的DataFrame对象。因为我们的目标是预测该乘客的生还情况,所以Survived字段(列)就是y,即预测目标,而X就是特征值,根据这些特征值来预测y。drop()方法表示丢弃指定的字段,保留剩下的字段。

  import pandas as pd
  features=pd.read_csv('titanic_dataset.csv')
  y_train=features['Survived']
  X_train=features.drop('Survived',axis=1)

查看前5行特征数据,输出如图2.1所示。输出显示的列表中,不包含Survived字段,因为我们通过调用drop()函数将它移除了。

  X_train.head()

图2.1 前5行特征数据

查看X_train和y_train数据的大小。

  print("X_train.shape={},y_train.shape={}".format(X_train.shape,y_train.shape))

输出如下,变量features是有12列的,但是Survived字段分配给了y_train,所以X_train就剩下11列了。

  X_train.shape=(1309,11),y_train.shape=(1309,)

通过调用info()函数查看X_train的基本信息,输出如图2.2所示。

  X_train.info()

图2.2 X_train的基本信息

我们从该输出信息可以查看到,Age、Cabin、Embarked和Fare字段都有缺失值。接下来,将对这些缺失值进行处理,如果不处理将导致无法训练。字段说明如下。

◇ Age:乘客年龄。

◇ Cabin:乘客的客舱号。

◇ Embarked:乘客的登船港(S表示Southampton(英国南安普敦),C表示Cherbourg-Octeville(法国瑟堡-奥克特维尔),Q表示Queenstown(爱尔兰昆士敦))。

◇ Fare:乘客支付的票价。

◇ Name:乘客姓名。

◇ Parch:一起上船的父母和子女人数。

◇ PassengerId:乘客登记的ID。

◇ Pclass:社会阶层(1表示上层,2表示中层,3表示底层)。

◇ Sex:乘客性别。

◇ SibSp:一起上船的兄弟姐妹和配偶人数。

◇ Ticket:乘客的船票号。

◇ Survived:生还情况(1表示生还,0表示未生还)。

2.1.3 缺失值处理

我们先查看X_train的缺失值合计数量,代码如下,输出如图2.3所示。

  X_train.isnull().sum()

图2.3 查看X_train的缺失值数量合计

Age字段有263个有缺失值。我们导入Seaborn数据可视化模块,通过以下代码来查看Age字段的分布图,输出如图2.4所示。

  import seaborn as sns
  sns.distplot(X_train['Age'].dropna(),hist=True,kde=True)

图2.4 Age字段的分布图

使用中值来替换缺失值,这样能保证分布图的基本呈现是呈正态分布(Normal Distribution)的。调用replace()方法的第一个参数表示要替换的值,np.nan表示NaN数据,np.nanmedian()方法表示将NaN的值都替换成中值,inplace=True表示在原有的对象上修改且不会产生副本(原有对象的意思是X_train是一个类型为Pandas下的Series的变量,通过指定inplace参数的值为True后,直接在X_train对象上修改要替换的值)。代码如下。

  X_train['Age'].replace(np.nan,np.nanmedian(X_train['Age']),inplace=True)

我们再来看Age字段的分布图,代码如下,输出结果如图2.5所示。

  sns.distplot(X_train['Age'],hist=True,kde=True)

图2.5 Age字段里无NaN数据的分布图

Cabin字段有1014个缺失值,因为缺失值太多,而且该字段不会影响预测结果,所以删掉就好。

  X_train.drop("Cabin",axis=1,inplace=True)

Embarked字段有2个缺失值。我们先来看Embarked字段的直方图情况,代码如下,输出结果如图2.6所示。

  sns.countplot(x='Embarked',data=X_train)

图2.6 乘客的登船港直方图

大部分乘客的登船港都是英国南安普敦,因此假设缺失值所代表的两位乘客也是从英国南安普敦登船较为合适。我们可以通过调用replace()函数来替换缺失值,代码如下。

  X_train['Embarked'].replace(np.nan,'S',inplace=True)

Fare字段有1个缺失值。我们先查询是缺了哪位乘客的票价数据。np.isnan()方法是从指定的数组中查询有NaN值的行,然后返回有带NaN值的True或False的数组,再通过X_train的切片索引获取值,代码如下。在输出中我们可以看到只有一行数据,如图2.7所示。

  X_train[np.isnan(X_train["Fare"])]

图2.7 Fare字段有缺失值的乘客数据

我们可以看到该乘客是从英国南安普敦登船,社会阶层是3。那我们可以查询Embarked等于S、Pclass等于3的所有乘客支付的票价,并且以类型为DataFrame的对象返回。使用query()方法来查询,代码如下。

  pclass3_fares=X_train.query('Pclass==3&Embarked=="S"')['Fare']

然后将变量pclass3_fares中的NaN值修改为0,代码如下。

  pclass3_fares=pclass3_fares.replace(np.nan,0)

再对pclass3_fares取中值,代码如下。

  median_fare=np.median(pclass3_fares)

最后将该中值变量median_fare更新到X_train中缺失值的位置。通过loc来指定条件以进行赋值,该缺失值对应的乘客的PassengerId等于1044,所以他在Fare字段处的值就更新为刚刚计算出的中值了,代码如下。

  X_train.loc[X_train['PassengerId']==1044,'Fare']=median_fare

对于Sex字段,我们也需进行处理,虽然它没有缺失值,但是处理后可以更好地拟合数据。我们将男性(male)赋值为1,女性(female)赋值为0,代码如下。

  X_train['Sex'].replace(['male','female'],[1,0],inplace=True)

最后,我们来看X_train的缺失值情况,代码如下,输出如图2.8所示。

  X_train.isnull().sum()

图2.8 处理后的X_train缺失值数量合计

2.1.4 数据清洗与分割

数据清洗与分割需用到sklearn的train_test_split()方法。在分割数据前,我们先对X_train的特征数据进行独热编码(one-hot encoding)。而pandas下的get_dummies()函数就是将类别变量转换成虚拟变量(也称为哑变量);简单地说,就是将类别的变量值转换成0和1,并且最终形成一个矩形表。在该矩形表的同一个类别下,自身的类别用1表示,其他的类别用0表示。

  X_train=pd.get_dummies(X_train)

然后再次输出X_train和y_train的shape,代码如下。

  print("X_train.shape={},y_train.shape={}".format(X_train.shape,y_train.shape))

输出如下。

  X_train.shape=(1309,2246),y_train.shape=(1309,)

然后清洗与分割数据。test_size=0.2表示给测试数据集分配0.2的数据,也就是20%的数据用于测试,80%的数据用于训练。random_state表示给随机数生成器使用的种子数。

Shuffle=True表示清洗数据。

  from sklearn.model_selection import train_test_split
  train_X,test_X,train_y,test_y=train_test_split(X_train,y_train,test_size=0.2,rando m_state=42,shuffle=True)

通过train_test_split()方法,我们就得到train_X和train_y的训练集,test_X和test_y的测试集。我们再看训练集和测试集各自分配了多少数据,代码如下。

  print("train_X.shape={},train_y.shape={}".format(train_X.shape,train_y.shape))
  print("test_X.shape={},test_y.shape={}".format(test_X.shape,test_y.shape))

输出如下。

  train_X.shape=(1047,2246),train_y.shape=(1047,)
  test_X.shape=(262,2246),test_y.shape=(262,)