最近开始学习机器学习的相关知识,学习需要实践,于是就打算在kaggle上做个项目。第一个kaggle项目就是Titanic,kaggle最简单,也是最经典的项目。我按照kaggle提供的教学文章做了一遍,断断续续得做了两个星期,但是学到了很多知识,感觉最重要的,是熟悉了这种数据竞赛的流程。
项目地址:Titanic: Machine Learning from Disaster
参考文章:Titanic Data Science Solution
1. 问题描述
已有的数据是Titanic号的乘客资料。要求通过分析这份资料,通过机器学习的方法,预测乘客的存活情况。
前期准备
1 | # 数据整理、分析 |
2. 获得数据
对于一些操作,可以把训练集和测试集合并处理,比如把数据集的标题转化为数值。
1 | train_df = pd.read_csv('./input/train.csv') |
3. 数据分析
数据集中有哪些特征?
1 | print(train_df.columns.values) |
| 特征缩写 | 特征含义 |
|---|---|
| PassengerId | 乘客编号 |
| Survived | 是否存活(1表示存活,0表示死亡) |
| Pclass | 舱位等级(1-3表示舱位从高到低) |
| Name | 姓名 |
| Sex | 性别 |
| Age | 年龄 |
| SibSp | 一同乘船的兄弟姐妹或配偶的人数 |
| Parch | 一同乘船的父母或子女的人数 |
| Ticket | 票号 |
| Fare | 票价 |
| Cabin | 舱位号 |
| Embarked | 出发地(C指Cherbourg,Q指Queenstown,S指Southampton) |
数据类型
不同特征具有不同的数据类型。
- 分类型数据(categorical)
分类型数据一般表示事物的某种特性。题目中分类型数据为:Survived,Sex。 - 顺序型数据(ordinal)
数据是有分类性质的,但衡量性质的数字也有实际意义。一般被归为分类型数据。本题中Pclass就是一个例子,数字1~3表示一等舱,二等舱,三等舱,舱位级别从高到低。 - 数值型数据(numerical)
数值型数据一般分为离散型和连续型。其中离散型数据一般通过计数得到,本题中出现的是SibSp,Parch。连续型数据一般表示特征的测量值,不能被计数,只能通过区间去衡量,本题中有Age,Fare。 - 混合型数据(mixed)
既包含数值,又包含字符。这种数据需要校正。本题中Ticker和Cabin这种数据就是混合型。
1 | # preview the data |
1 | train_df.tail() |
哪些特征的数据可能包含错误或缺失?
在姓名这个特征中,可能包含称呼,名字,外号等,容易在录入的时候出现错误。这种数据需要校正。
在训练集中,舱位号,年龄,出发地都存在数据缺失。在测试集中,舱位号和年龄也有缺失。
1 | train_df.info() |
样本中数值型数据的分布
- 题目所给乘客总人数为2224,死亡人数为1502,幸存人数为722,存活率为32.4%。
- 样本总数为891, 存活率为38.3%。
- 大多数的乘客(>75%)没有和父母或孩子同行。
- 将近30%的乘客有兄弟姐妹或配偶也在船上。
- 不同乘客所购船票的票价差异很大,极少数人(1%)的票价高至$512。
- 年龄在65~80岁之间的乘客只占不到总数的1%。
1 | # 数值型数据 |
样本中分类型数据的分布
- 样本中每个人名字是唯一的,没有重复的。
- 样本中有577位男性,314位女性。
- 舱位号的值存在重复,这说明有一些乘客共用一个船舱。
- 出发地有三个,其中从Southampton出发的乘客最多。
- 票号大约有22%的重复。
1 | # 分类型数据 |
一些假设
- 相关性(Correlating)
我们想知道每个特征和是否存活的关系。这个工作需要在早期进行,把这些特征应用到预测模型中。
- 完整性(Completing)
- 年龄特征需要补全,因为题目告诉了它影响了乘客是否存活。
- 出发地特征也需要补全,因为它可能和存活率或其他特征有关。
- 校正(Correcting)
- 票号特征可以去掉,因为它存在很高的重复率(22%),而且它可能和存活率没有关系。
- 舱位号特征也可以去掉,因为它的数据大部分缺失,而且有很多空白值。
- 乘客编号和姓名也可以去掉,因为这类数据和存活率并没有直接联系。
- 新增特征(Creating)
- 我们可能会新增一个特征叫做家庭成员,根据SibSp和Parch来计算这个乘客有多少个家人也一同登船。
- 我们可能会根据乘客姓名,提取乘客的title,并新建特征。
- 我们可能会把年龄特征改为年龄带,从而把数值型特征转变为顺序型特征。
- 我们可能会新增一个特征,来表示船票票价的区间。
- 分类(Classifying)
- 女性乘客更可能存活。(Sex = female)
- 儿童更可能存活。(Age < ?)
- 上层阶级更可能存活。(Pclass = 1)
数据透视
为了确认我们的发现和假设,可以快速的分析一些特征和存活率的关系通过数据透视的方法(感觉这个翻译有点奇怪)。这种方法需要特征没有空值。满足条件的特征有Sex,Pclass,SibSp,Parch这四个。
- Sex
可以看到,女性乘客中,有74%的乘客最终存活。于是我们验证了classifying #1,女性乘客更容易存活。
1 | train_df[["Sex", "Survived"]].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False) |
- Pclass
可以看到,在一等舱的乘客中存活率达到了62.9%(>50%),这验证了classifying #3,上层阶级更容易存活。
1 | train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False) |
- SibSp 和 Parch
把SibSp特征和Parch特征的数据结合起来,新建一个特征表示家庭成员的情况。
1 | train_df[["SibSp", "Survived"]].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived', ascending=False) |
1 | train_df[["Parch", "Survived"]].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived', ascending=False) |
4. 数据可视化
数值型特征与存活率
分析年龄与存活率的关系。
直方图的x轴表示乘客的数量。
观察
- 婴儿(Age <= 4)有很高的存活率。
- 最老的乘客(Age = 80)活下来了。
- 很大一批年龄在15~25之间的乘客没有活下来。
- 大多数乘客的年龄在15~35之间。
决定
根据这个分析,我们应该:
- 在预测模型中考虑年龄(Age)因素。(classifying #2)
- 补全Age特征下数据的空白。(completing #1)
- 把年龄分组。(creating #3)
1 | g = sns.FacetGrid(train_df, col='Survived') |
顺序型特征与存活率
分析年龄,舱位等级和存活率的关系。
观察
- 大多数乘客在三等舱(Pclass=3),其中大部分人没能幸存。(classifying #3) (原文写的2 写错了?)
- 大多数的婴儿在二等舱和三等舱,而且大部分婴儿活了下来。(classifying #2)
- 大部分一等舱的乘客活了下来。(classifying #3)
- 三种舱位在各个年龄段的乘客中都有分布。
决定
- 应当把Pclass列入预测模型中。
1 | grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived') |
分类型特征与存活率
分析出发地,性别,舱位和存活率的关系。
观察
- 女性乘客比男性乘客有更高的存活率。classifying #1
- 只有从Cherbourg出发的乘客中,男性的存活率更高。这说明了Pclass和Embarked有联系,同样的,Pclass和Survived也有联系,但并不一定说明Embarked和Survived有直接联系。
- 对于从C和Q地出发的男乘客中,乘坐三等舱的乘客的存活率比二等舱高。completing #2
- 对于乘坐三等舱的乘客还有男乘客来说,他们的存活率在不同的出发地都不同。Ports of embarkation have varying survival rates for Pclass=3 and among male passengers.correlating #1
决定
- 应当把Sex列入预测模型中。
- 把出发地特征的数据补全。
1 | grid = sns.FacetGrid(train_df, col='Embarked') |
分类型、数值型特征与存活率
分析出发地,性别,票价和存活率的关系。
观察
- 购买更贵票价的乘客有更高的存活率。creating #4
- 出发地和存活率有关系。correlating #1,completing #2
决定
- 应当把票价区间列入预测模型。
1 | #grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'k', 1: 'b'}) |
5. 数据整理
去除无用特征
没有用处的数据的清楚能简化分析,加速模型的训练。根据correcting #1和correcting #2,Ticket和Cabin应当去除。
注意训练集和测试集都要去除特征。
1 | print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape) |
新增Title特征
通过正则表达式提取名字中的title。正则表达式(\w+\.)能匹配第一个以点为结尾的单词。
观察
当我们观察title,age和存活率的关系,有以下发现:
- 不同title间的年龄有明显差异,title很好的划分了年龄区间。
- Survival among Title Age bands varies slightly. (这句不太理解)
- 某些特定的title都活下来了(Mme, Lady, Sir),有些都没活下来(Don, Rev, Jonkheer)。
决定
- 把title特征列入预测模型。
1 | # 提取title,并加入已有的数据集中 |
把一些相似的title合并到一起,并把少见的title归到一个名为rare的title里。
1 | for dataset in combine: |
把分类型数据转换为顺序型数据。
1 | title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5} |
记得把name和PassengerID这两个特征去掉。correcting #3
1 | train_df = train_df.drop(['Name', 'PassengerId'], axis=1) |
性别特征的数值化
性别特征是分类型特征,为了在模型中能运用,需要把它转换为数值型特征。(female = 1, male = 0)
1 | for dataset in combine: |
补全年龄特征
原文给出三种方法去补全数据:
- 根据原始数据,随机产生在均值和标准差之间的数值。
- 根据相关的特征猜出缺失的年龄。比如年龄与Pclass和Sex有关,那么对于一个已知Pclass和Sex的乘客,可以猜测他/她的年龄为所有这个Pclass和Sex的乘客年龄的中位数。
- 把方法1和2结合起来。对于所有该Pclass和Sex的乘客,计算他们年龄的均值与标准差,随机产生之间的数值作为猜测的年龄。
由于方法1和方法3会引入随机误差,每次的结果都不一样,所以这里采用方法2。
1 | # 不同的Pclass和Sex一共有6种组合 |
1 | # 新建空数组,用来储存这6种组合下的中位数 |
新建特征AgeBand,用来显示不同年龄区间下的存活率。
1 | train_df['AgeBand'] = pd.cut(train_df['Age'], 5) |
1 | # 根据AgeBand,把年龄换成顺序型数据。 |
1 | # 去掉AgeBand特征 |
创建IsAlone特征
通过把Parch和SibSp特征结合起来,创建FamilySize特征,来记录与该乘客同行的所有家庭成员的数量。
1 | for dataset in combine: |
可以发现,不同FamilySize下乘客的存活率并没有明显关联。因此,尝试创建一个IsAlone特征,来区分独自乘船和有家人陪同乘船的乘客。
1 | for dataset in combine: |
可以看出,独自乘船和有家人陪同乘船的乘客,存活率有明显差异。因此,保留IsAlone特征,去除Parch、SibSp和FamilySize特征。
1 | train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1) |
新建Age*Class特征
把Age和Pclass相乘,获得Age*Class特征。(不太理解为什么要加这个特征)
1 | for dataset in combine: |
处理Embarked特征
训练集中有两组数据缺少Embarked特征,在这里简单的补全为Embarked的众数。
1 | # S |
Embarked特征为分类型特征,需要转化为数值型特征才能用到预测模型中。
1 | for dataset in combine: |
处理Fare特征
测试集中有一组数据的fare特征丢失,在这里简单的补全为fare特征的众数。
1 | test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True) |
类似于Age特征,为了方便计算,这里把Fare特征先转换为FareBand,再转换为顺序型特征。
1 | train_df['FareBand'] = pd.qcut(train_df['Fare'], 4) |
1 | for dataset in combine: |
6. 模型的建立、训练与预测
本题目是一个二元分类与回归型问题。适合该题目的预测模型有如下几种:
- Logistic Regression(逻辑回归)
- KNN or k-Nearest Neighbors(K近邻)
- Support Vector Machines(支持向量机)
- Naive Bayes classifier(朴素贝叶斯分类器)
- Decision Tree(决策树)
- Random Forrest(随机森林)
- Perceptron(感知机)
- Artificial neural network(人工神经网络)
- RVM or Relevance Vector Machine(相关向量机)
1 | X_train = train_df.drop("Survived", axis=1) |
Logistic Regression
1 | logreg = LogisticRegression() |
通过logistic Regression模型的参数,我们可以验证之前的假设。越大的参数值说明该特征对结果的影响越大。参数值的正负也表现出特征和结果的正负相关。
- Sex参数有最大的数值,说明性别对存活率的影响最大。
- 相应的,Pclass越大,存活率越低。
- Age*Class对存活率有第二大的负相关。
- Title有第二大的正相关。
1 | coeff_df = pd.DataFrame(train_df.columns.delete(0)) |
Support Vector Machines
1 | svc = SVC() |
k-Nearest Neighbors
1 | knn = KNeighborsClassifier(n_neighbors = 3) |
Naive Bayes Nlassifiers
1 | gaussian = GaussianNB() |
Perceptron
1 | perceptron = Perceptron() |
Linear SVC
1 | linear_svc = LinearSVC() |
Stochastic Gradient Descent
1 | sgd = SGDClassifier() |
Decision Tree
1 | decision_tree = DecisionTreeClassifier() |
Random Forest
1 | random_forest = RandomForestClassifier(n_estimators=100) |
Model evaluation
对各个模型进行排名。
1 | models = pd.DataFrame({ |
1 | # 储存结果 |