Kaggle泰坦尼克數據科學解決方案


原文地址如下:

https://www.kaggle.com/startupsci/titanic-data-science-solutions

----------------------------------------------------------------

泰坦尼克數據科學解決方案:

 

1. 工作流程步驟:

在 Data Science Solutions book 這本書里,描述了在解決一個競賽問題時所需要做的具體工作流程:

 

  1. 問題的定義
  2. 獲取訓練數據以及測試數據
  3. 加工、准備以及清洗數據
  4. 分析、識別數據的模式,並對數據做可視化
  5. 建模、預測,並解決問題
  6. 對結果做可視化,生成報告,並且展示問題的解決步驟和最終的解決方案
  7. 提交結果

以上的工作流程僅僅描述了一般的問題解決步驟,然而依然會有一些特殊的案例並不嚴格遵循以上流程:

  1. 我們可能會結合多個步驟。例如通過將數據可視化后直接進行分析
  2. 將某個步驟提前執行。例如在做數據加工前即開始對數據進行分析
  3. 在工作流程中多次進行某個步驟。例如在整個流程中會多次對數據進行可視化
  4. 完全棄用一個步驟。例如在非競賽場合可能並不需要做提交結果的步驟

 

2. 問題的定義:

一般的競賽網站(如 Kaggle)會在提供訓練數據以及測試數據的同時,給出問題的定義。對與此次的“泰坦尼克號幸存者”問題的定義如下:

“在訓練數據中,提供了在泰坦尼克號上乘客的具體數據以及他們是否在那次災難中存活的信息。參賽者能否通過已有的訓練數據訓練出一個模型,此模型需要根據輸入的測試數據里乘客信息,來預測此乘客是否能在災難中存活”

 

我們可能也想要通過問題描述獲取更多有關此問題的信息。在此問題的描述中,比較有意義的描述如下:

  1. 泰坦尼克號在1912年4月15日與冰山碰撞后沉沒。在一共2224名乘客與船員里,有1502人不幸逝世。這個信息即表明了此次事件中生還率為32%。
  2. 一個使得在這次災難中有如此之大死亡率的原因是:在船上沒有足夠的救生船提供給乘客以及船員
  3. 盡管在此次災難中生還存在運氣的成分,但是仍舊會有些群體的生還率高於其他人,如女人、小孩,以及上等倉的人

 

3. 工作流程里的目標:

數據科學解決方案的流程主要有7個目標:

  1. 分類:我們可能想對樣本分類,也希望根據需要解決的目標去理解不同類別之間的隱藏關系
  2. 相互關系:我們可以根據訓練集里可用的特征來解決一個問題。那到底在數據集里的哪些特征會對解決問題起着至關重要的作用呢?從統計學上來說,是否在某個特征與問題的解之間存在某種聯系?如果這個特征的值改變后,相應問題的解是否也會改變呢?反過來的情況是否也是如此呢?這個可以通過對數據集里的數值型以及離散型的特征做測試來得到。我們可能也希望得到特征之間的關系,而不是直接得到特征與問題解之間的關系。找到一些特定屬性之間的關聯性可能會在創建、補全以及修正特征上起到一定作用
  3. 轉換:在建模的階段,我們需要准備數據。根據模型、算法的選擇,我們可能需要將所有的特征轉化為同等的數值型特征。例如將文本型數據轉換為數值型數據
  4. 補全數據:在數據准備的階段中,我們可能仍然需要預測某些特征下丟失的數據值。更重要的是,模型可能在無丟失數據時表現的更好
  5. 修正數據:我們可能仍然需要分析給定的訓練數據集里某些特征下的錯誤數據以及可能是錯誤的數據,並且嘗試去修正這些數據或者除去這些包含錯誤數據的樣本。一個可行的方案是在我們樣本里或特征里檢測所有異常值。如果某個特征對我們的問題分析毫無幫助,或者會對結果產生影響,那我們可能還需要完全丟棄這個特征。
  6. 創造數據:我們是否能夠根據已存在的某個或某幾個特征創建一個新的特征呢?並讓新特征遵從“相互關系”、“轉換”以及“數據完整(補全數據)”的目標
  7. 制圖:如何根據原數據集以及要解決問題,對數據做合適的可視化圖 

 

4. 會用到的庫:

以下是在接下來的實驗里會用到的一些庫:

# data analysis and wrangling
import pandas as pd
import numpy as np
import random as rnd

# visualization
import seaborn as sns
import matplotlib.pyplot as plt

# machine learning
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier

 

5. 獲取數據:

我們可以用python 的 Pandas 來幫助我們處理數據。首先可以將訓練數據以及測試數據讀入到Pandas 的 DataFrames 里。我們也會將這兩個數據集結合起來,用於在兩個數據集上同時做一些特定的操作。

 

# set pandas
pd.set_option('display.width', 1000)

# use pandas to manage data
train_df = pd.read_csv('data/train.csv')
test_df = pd.read_csv('data/test.csv')
combine = [train_df, test_df]

 

6. 通過描述數據來分析:

Pandas 也可以幫助我們描述數據集。我們可以通過以下問答的方式來查看數據集:

 

1. 在數據集中有哪些可用的特征?

首先需要注意的是,數據集里特征的描述已經在問題描述里給出了,此次數據集里的特征描述如下:

https://www.kaggle.com/c/titanic/data

 

------------------------------------------------------------------------------------------------------

主要內容為:

Data Dictionary

Variable

Definition

Key

survival

Survival

0 = No, 1 = Yes

pclass

Ticket class

1 = 1st, 2 = 2nd, 3 = 3rd

sex

Sex

 

Age

Age in years

 

sibsp

# of siblings / spouses aboard the Titanic

 

parch

# of parents / children aboard the Titanic

 

ticket

Ticket number

 

fare

Passenger fare

 

cabin

Cabin number

 

embarked

Port of Embarkation

C = Cherbourg, Q = Queenstown, S = Southampton

Variable Notes

pclass: A proxy for socio-economic status (SES)
1st = Upper
2nd = Middle
3rd = Lower

age: Age is fractional if less than 1. If the age is estimated, is it in the form of xx.5

sibsp: The dataset defines family relations in this way...
Sibling = brother, sister, stepbrother, stepsister
Spouse = husband, wife (mistresses and fiancés were ignored)

parch: The dataset defines family relations in this way...
Parent = mother, father
Child = daughter, son, stepdaughter, stepson
Some children travelled only with a nanny, therefore parch=0 for them.

 

------------------------------------------------------------------------------------------------------

 

在 Pandas里:

>>> print(train_df.columns.values)

['PassengerId' 'Survived' 'Pclass' 'Name' 'Sex' 'Age' 'SibSp' 'Parch'

 'Ticket' 'Fare' 'Cabin' 'Embarked']

 

2. 哪些特征是離散型的?

這些離散型的數值可以將樣本分類為一系列相似的樣本。在離散型特征里,它們的數值是基於名詞的?還是基於有序的?又或是基於比率的?還是基於間隔類的?除此之外,這個可以幫助我們為數據選擇合適的圖形做可視化。

 

在這個問題中,離散型的變量有:Survived,Sex 和 Embarked。基於序列的有:Pclass

 

3. 哪些特征是數值型?

哪些特征是數值型的?這些數據的值隨着樣本的不同而不同。在數值型特征里,它們的值是離散的還是連續的?又或者是基於時間序列?除此之外,這個可以幫助我們為數據選擇合適的圖形做可視化。

 

在這個問題中,連續型的數值特征有:Age,Fare。離散型數值有:SibSp,Parch

 

>>> train_df.head()

 

4. 哪些特征是混合型數據?

數值型、字母數值型數據在同一特征下面。這些有可能是我們需要修正的目標數據。

 

在這個問題中,Ticket是混合了數值型以及字母數值型的數據類型,Cabin是字母數值型數據

 

5. 哪些特征可能包含錯誤數據或打字錯誤?

在大型數據集里要發現這些可能比較困難,然而通過觀察小型的數據集里少量的樣本,可能也可以完全告訴我們哪些特征需要修正。

 

在這個問題中,Name的特征可能包含錯誤或者打字錯誤,因為會有好幾種方法來描述名字

 

>>> train_df.tail()

 

 

6. 哪些特征包含空格,null或者空值

這些空格,null值或者空值很可能需要修正。

 

在這個問題中:

  1. 這些特征包含null值的數量大小為:Cabin > Age > Embarked
  2. 在訓練集里有不完整數據的數量的大小為:Cabin > Age 

 

7.每個特征下的數據類型是什么?

這個可以在我們做數據轉換時起到較大的幫助。

 

在這個問題中:

  1. 7個特征是int型或float 型。在測試數據集里有6個
  2. 有5個特征是string(object)類型

 

>>> train_df.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

 

>>> test_df.info()

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 418 entries, 0 to 417

Data columns (total 11 columns):

PassengerId    418 non-null int64

Pclass         418 non-null int64

Name           418 non-null object

Sex            418 non-null object

Age            332 non-null float64

SibSp          418 non-null int64

Parch          418 non-null int64

Ticket         418 non-null object

Fare           417 non-null float64

Cabin          91 non-null object

Embarked       418 non-null object

dtypes: float64(2), int64(4), object(5)

memory usage: 36.0+ KB

 

8. 在樣本里,數值型特征的數值分布是什么樣的?

這個可以幫助我們初步了解:訓練數據集如何體現了實際問題。

 

在這個問題中:

  1. 一共有891個樣本
  2. Survived的標簽是通過0或1來區分
  3. 大概38%的樣本是survived
  4. 大多數乘客(>75%)沒有與父母或是孩子一起旅行
  5. 大約30%的乘客有親屬和/或配偶一起登船
  6. 票價的差別非常大,少量的乘客(<1%)付了高達$512的費用
  7. 很少的乘客(<1%)年紀在64-80之間

 

我們可以通過以下方式獲取上述信息:

>>> train_df.describe()

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare

count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000

mean    446.000000    0.383838    2.308642   29.699118    0.523008    0.381594   32.204208

std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429

min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000

25%     223.500000    0.000000    2.000000   20.125000    0.000000    0.000000    7.910400

50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200

75%     668.500000    1.000000    3.000000   38.000000    1.000000    0.000000   31.000000

max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

 

# 通過使用 percentiles=[.61, .62] 來查看數據集可以了解到生存率為 38%

>>> train_df.describe(percentiles=[.61, .62])

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare

count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000

mean    446.000000    0.383838    2.308642   29.699118    0.523008    0.381594   32.204208

std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429

min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000

50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200

61%     543.900000    0.000000    3.000000   32.000000    0.000000    0.000000   23.225000

62%     552.800000    1.000000    3.000000   32.000000    0.000000    0.000000   24.150000

max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

 

# 通過使用 percentiles=[.75, .8] 來查看Parch的分布

>>> train_df.describe(percentiles=[.75, .8])

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare

count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000

mean    446.000000    0.383838    2.308642   29.699118    0.523008    0.381594   32.204208

std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429

min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000

50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200

75%     668.500000    1.000000    3.000000   38.000000    1.000000    0.000000   31.000000

80%     713.000000    1.000000    3.000000   41.000000    1.000000    1.000000   39.687500

max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

 

# 通過使用 percentile=[.68, .69] 來查看SibSp的分布

>>> train_df.describe(percentiles=[.68, .69])

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare

count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000

mean    446.000000    0.383838    2.308642   29.699118    0.523008    0.381594   32.204208

std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429

min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000

50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200

68%     606.200000    1.000000    3.000000   35.000000    0.000000    0.000000   26.307500

69%     615.100000    1.000000    3.000000   35.000000    1.000000    0.000000   26.550000

max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

 

# 通過使用 percentile=[.1, .2, .3, .4, .5, .6, .7, .8, .9, .99] 來查看Age和Fare的分布

>>> train_df.describe(percentiles=[.1, .2, .3, .4, .5, .6, .7, .8, .9, .99])

       PassengerId    Survived      Pclass         Age       SibSp       Parch        Fare

count   891.000000  891.000000  891.000000  714.000000  891.000000  891.000000  891.000000

mean    446.000000    0.383838    2.308642   29.699118    0.523008    0.381594   32.204208

std     257.353842    0.486592    0.836071   14.526497    1.102743    0.806057   49.693429

min       1.000000    0.000000    1.000000    0.420000    0.000000    0.000000    0.000000

10%      90.000000    0.000000    1.000000   14.000000    0.000000    0.000000    7.550000

20%     179.000000    0.000000    1.000000   19.000000    0.000000    0.000000    7.854200

30%     268.000000    0.000000    2.000000   22.000000    0.000000    0.000000    8.050000

40%     357.000000    0.000000    2.000000   25.000000    0.000000    0.000000   10.500000

50%     446.000000    0.000000    3.000000   28.000000    0.000000    0.000000   14.454200

60%     535.000000    0.000000    3.000000   31.800000    0.000000    0.000000   21.679200

70%     624.000000    1.000000    3.000000   36.000000    1.000000    0.000000   27.000000

80%     713.000000    1.000000    3.000000   41.000000    1.000000    1.000000   39.687500

90%     802.000000    1.000000    3.000000   50.000000    1.000000    2.000000   77.958300

99%     882.100000    1.000000    3.000000   65.870000    5.000000    4.000000  249.006220

max     891.000000    1.000000    3.000000   80.000000    8.000000    6.000000  512.329200

 

8. 在樣本里,離散型數據的分布是什么?

在這個問題中:

  1. 各個乘客的Name 屬性完全是唯一的(count=unique=891
  2. Sex特征里65%為男性(top=male,fre=577/count=891
  3. Cabin的count與unique並不相等,即說明有些乘客會共享一個cabin
  4. Embarked一共有種取值,其中從S港口登船的人最多
  5. Ticket的特征下,有22%左右的重復值(unique=681

 

可以通過以下方法獲得以上信息:

>>> train_df.describe(include=['O'])

                              Name   Sex    Ticket Cabin Embarked

count                          891   891       891   204      889

unique                         891     2       681   147        3

top     Andrew, Mr. Edgardo Samuel  male  CA. 2343    G6        S

freq                             1   577         7     4      644

 

7. 基於以上數據分析后的假設

根據以上的數據分析步驟后,我們可以暫時得出以下假設。當然,我們也可以在之后驗證這些假設。

 

相互關系:

我們想知道每個特征與Survival的相關性如何。我們希望能夠今早的做這一步,並且將這些相關性特征匹配到建模后的相關性特征上。

 

補全數據:

  1. 我們可能會去補全Age特征下的數據,因為它一定是與存活率是相關的
  2. 我們可能會去補全Embarked特征下的數據,因為它可能與存活率或者其他重要的特征之間存在相關性

 

修正數據:

  1. Ticket特征可能需要從我們的分析中丟棄,因為它的數值重復率高達22%,並且Ticket與survival之間很可能並沒有聯系
  2. Cabin特征可能也需要丟棄,因為它的數值非常不完整,並且在訓練集以及測試集里均包含較多的null值
  3. PassengerId特征可能也需要被丟棄,因為它對survival沒任何作用
  4. Name特征相對來說不是特別規范,並且很有可能與survival之間沒有直接聯系,所以可能也應該被丟棄

 

創造數據:

  1. 我們可以根據Parch和SibSp的特征來創建一個新的Family特征,以此得到每個乘客有多少家庭成員登了船
  2. 我們可以對Name特征做進一步加工,提取出名字里的Title作為一個新的特征
  3. 我們可以為Age特征創建一個新的特征,將它原本的連續型數值特征轉換為有序的離散型特征
  4. 我們也可以創建一個票價(Fare)范圍的特征,如果它對我們的分析有幫助的話

 

分類:

根據之前的問題描述或者已有的數據,我們也可以提出以下假設:

  1. 女人(Sex=female)更有可能存活
  2. 孩子(Age<?)也更有可能存活
  3. 上等倉的乘客(Pclass=1)有更大的存活率

 

8. 基於pivoting features的分析

為了驗證之前的觀察與假設,我們可以通過pivoting feature的方法簡單的分析一下特征之間的相關性。

這種方法僅僅對那些沒有特別多空值的屬性有效,並且僅僅對那些分類型的(Sex)、有序型的(Pclass)以及離散型(SibSp,Parch)的特征才有意義。

 

1. Pclass:我們觀察到Pclass=1與Survived的相關性較大(>0.5),所以可以考慮將此特征放入到之后的模型里

2. Sex:我們可以確認Sex=female有着高達74%的生存率

3. SibSp 和 Parch:這些特征下有些值與survived有相關性,但是有些又毫無相關性。所以我們可能需要基於這些單獨的特征或一系列特征創建一個新特征,以做進一步分析

 

以上結論可以通過下面的操作獲取:

>>> train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

   Pclass  Survived

0       1  0.629630

1       2  0.472826

2       3  0.242363

 

 

>>> train_df[['Sex', 'Survived']].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

      Sex  Survived

0  female  0.742038

1    male  0.188908

 

>>> train_df[['SibSp', 'Survived']].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

   SibSp  Survived

1      1  0.535885

2      2  0.464286

0      0  0.345395

3      3  0.250000

4      4  0.166667

5      5  0.000000

6      8  0.000000

 

>>> train_df[['Parch', 'Survived']].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

   Parch  Survived

3      3  0.600000

1      1  0.550847

2      2  0.500000

0      0  0.343658

5      5  0.200000

4      4  0.000000

6      6  0.000000

 

9.通過將數據可視化進行分析

現在我們可以通過將數據可視化對數據做進一步分析,並繼續驗證我們之前的假設是否正確

 

數值型特征與Survived之間的聯系:

柱狀圖在用於分析連續型的數值特征時非常有用,如特征Age,它的柱狀圖數值范圍(不同的年齡范圍)可以幫助我們識別一些有用的模式。

通過使用默認或自定的數值范圍(年齡范圍),柱狀圖可以幫助我們描繪出樣本所遵循的分布。

它可以幫助我們發現是否某些特定的年齡范圍(如嬰兒)有更高的存活率。

 

我們可以通過以下代碼來畫出Age的柱狀圖:

>>> g = sns.FacetGrid(train_df, col='Survived')

>>> g.map(plt.hist, 'Age', bins=20)

>>> plt.show()

 

 

 

觀察:

  1. 嬰兒(Age<=4)有較高的生存率
  2. 老人(Age=80)全部生還
  3. 大量的15-25年紀的乘客沒有生還
  4. 乘客主要在15-35的年紀范圍內

 

結論:

以上簡單的分析驗證了我們之前的假設:

  1. 我們需要將Age考慮到訓練模型里
  2. 為Age特征補全null
  3. 我們應該band不同的年齡層

 

數值型與序列型特征之間的聯系:

我們可以將多個特征組合,然后通過一個簡單的圖來識別它們之間的關系。這種方法可以應用在數值型以及分類型(Pclass)的特征里,因為它們的值都是數值型。

 

我們可以通過以下代碼來畫出Pclass的柱狀圖:

>>> grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6)

>>> grid.map(plt.hist, 'Age', alpha=.5, bins=20)

>>> grid.add_legend()

>>> plt.show()

 

 

 

觀察:

  1. Pclass=3 有着最多的乘客,但是他們大多數卻沒有存活。這也驗證了我們之前在“分類”里的假設 #2
  2. Pclass=2和Pclass=3中,大多數嬰兒活了下來,進一步驗證了我們之前在“分類”里的假設 #2
  3. 大多數Pclass=1的乘客存活,驗證我們之前在“分類”里的假設 #3
  4. Pclass根據Age的分布而改變

 

結論:

  1. 考慮將Pclass特征加入模型訓練

 

離散型特征與Survived之間的聯系:

現在我們可以查看離散型特征與survived之間的關系

 

我們可以通過以下方式將數據可視化:

>>> grid = sns.FacetGrid(train_df, row='Embarked', size=2.2, aspect=1.6)

>>> grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette='deep')

>>> grid.add_legend()

>>> plt.show()

 

 

 

觀察:

  1. 女性乘客相對於男性乘客有着更高的存活率
  2. 唯一在Embarked=C中是例外,其中男性的生存率高於女性。從這點來看,Pclass和Embarked之間可能有聯系
  3. Embarked和Survived之間可能並沒有直接的聯系。 
  4. Males had better survival rate in Pclass=3 when compared with Pclass=2 for C and Q ports
  5. 對於Pclass=3以及男性乘客來說,Embarked的港口不同會導致存活率的不同

 

結論:

  1. Sex特征加入訓練模型
  2. 補全Embarked特征下的數據並將此特征加入訓練模型

 

離散型特征與數值型特征之間的聯系:

我們可能也想找出離散型與數值型特征之間的關系。

我們可以考慮查看Embarked(離散非數值型),Sex(離散非數值型),Fare(連續數值型)與Survived(離散數值型)之間的關系

 

我們可以通過下方式將數據可視化:

>>> grid = sns.FacetGrid(train_df, row='Embarked', col='Survived', size=2.2, aspect=1.6)

>>> grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)

>>> grid.add_legend()

>>> plt.show()

 

 

 

觀察:

  1. 1. 付了高票價的乘客有着更高的生存率,驗證了我們之前的假設
  2. 2. Embarked與生存率相關,驗證了我們之前所做的假設 

結論:

  1. 1. 考慮將Fare特征做不同的區間

 

10.加工數據

我們根據數據集以及題目的要求已經收集了一些假設與結論。到現在為止,我們暫時還沒有對任何特征或數據進行處理。

接下來我們會根據之前做的假設與結論,以“修正數據”、“創造數據”以及“補全數據”為目標,對數據進行處理。

 

通過丟棄特征來修正數據:

這個步驟比較好的一個開始。通過丟棄某些特征,可以讓我們處理更少的數據點,並讓分析更簡單。

 

根據我們之前的假設和結論,我們希望丟棄Cabin和Ticket這兩個特征。

 

在這里需要注意的是,為了保持數據的一致,我們需要同時將訓練集與測試集里的這兩個特征均丟棄。

 

具體步驟如下:

>>> print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)

Before (891, 12) (418, 11) (891, 12) (418, 11)

 

>>> train_df = train_df.drop(['Ticket', 'Cabin'], axis=1)

>>> test_df = test_df.drop(['Ticket', 'Cabin'], axis=1)

>>> combine = [train_df, test_df]

>>> print('After', train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)

After (891, 10) (418, 9) (891, 10) (418, 9)

 

通過已有的特征創建新特征:

我們在丟棄Name與PassengerId這兩個特征之前,希望從Name特征里提取出Titles的特征,並測試Titles與survival之間的關系。

 

在下面的代碼中,我們通過正則提取了Title特征,正則表達式為(\w+\.),它會在Name特征里匹配第一個以“.”號為結束的單詞。

同時,指定expand=False的參數會返回一個DataFrame。

 

>>> for dataset in combine:

        dataset['Title'] = dataset.Name.str.extract('([A-Za-z]+)\.', expand=False)

>>> pd.crosstab(train_df['Title'], train_df['Sex'])

 

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

 

我們可以使用高一些更常見的名字或“Rare”來代替一些Title,如:

for dataset in combine:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess', 'Capt',
                                                 'Col', 'Don', 'Dr', 'Major',
                                                 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')

 

>>> train_df[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()

 

    Title  Survived

0  Master  0.575000

1    Miss  0.702703

2      Mr  0.156673

3     Mrs  0.793651

4    Rare  0.347826

 

進一步的,我們可以將這些離散型的Title轉換為有序的數值型:

# convert categorical titles to ordinal
title_mapping = {"Mr":1, "Miss":2, "Mrs":3, "Master":4, "Rare":5}
for dataset in combine:
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

 

>>> train_df.head()

 

 

 

現在我們可以放心的從訓練集與測試集里丟棄Name特征。同時,我們也不再需要訓練集里的PassengerId特征:

>>> train_df = train_df.drop(['Name', 'PassengerId'], axis=1)

>>> test_df = test_df.drop(['Name'], axis=1)

>>> combine = [train_df, test_df]

>>> train_df.shape, test_df.shape

((891, 9), (418, 9))

 

>>> train_df.head()

   Survived  Pclass     Sex   Age  SibSp  Parch     Fare Embarked  Title

0         0       3    male  22.0      1      0   7.2500        S      1

1         1       1  female  38.0      1      0  71.2833        C      3

2         1       3  female  26.0      0      0   7.9250        S      2

3         1       1  female  35.0      1      0  53.1000        S      3

4         0       3    male  35.0      0      0   8.0500        S      1

 

新的發現:

當我們畫出Title,Age和Survived的圖后,我們有了以下新的發現:

  1. Most titles band Age groups accurately. For example: Master title has Age mean of 5 years
  2. Survival among Title Age bands varies slightly
  3. 某些特定的title如Mme,Lady,Sir的乘客存活率較高,但某些title如Don,Rev,Jonkheer的乘客存活率不高

 

結論:

  1. 我們決定保留這個新的Title特征並加入到訓練模型

 

轉換一個離散型的特征

現在我們可以將一些包含字符串數據的特征轉換為數值型特征,因為在很多建模算法里,輸入的參數要求為數值型。

這個步驟可以讓我們達到補全數據的目標。

 

我們可以從轉換Sex特征開始,將female轉換為1,male轉換為0。我們可以將新的特征命名為Gender:

for dataset in combine:
    dataset['Sex'] = dataset['Sex'].map({'female':1, 'male':0}).astype(int)

 

>>> train_df.head()

   Survived  Pclass  Sex   Age  SibSp  Parch     Fare Embarked  Title

0         0       3    0  22.0      1      0   7.2500        S      1

1         1       1    1  38.0      1      0  71.2833        C      3

2         1       3    1  26.0      0      0   7.9250        S      2

3         1       1    1  35.0      1      0  53.1000        S      3

4         0       3    0  35.0      0      0   8.0500        S      1

 

補全連續數值型特征

現在我們可以開始為那些含null值或者丟失值的特征補全數據。我們首先會為Age特征補全數據。

 

現在我們總結一下三種補全連續數值型特征數據的方法:

 

1. 一個簡單的方法是產生一個隨機數,這個隨機數的范圍在這個特征的平均值以及標准差之間

2. 更精准的一個做法是使用與它相關的特征來做一個猜測。在這個案例中,我們發現Age,Gender和Pclass之間有關聯。

所以我們會使用一系列Pclass和Gender特征組合后的中值,作為猜測的Age值。

所以我們會有一系列的猜測值如:當Pclass=1且Gender=0時,當Pclass=1且Gender=1時,等等

3. 第三種方法是結合以上兩種方法。我們可以根據一系列Pclass與Gender的組合,並使用第一種方法里提到的隨機數來猜測缺失的Age值

 

方法1與方法3會在模型里引入隨機噪音,多次的結果可能會有所不同。所以我們在這更傾向於使用方法2:

 

>>> grid = sns.FacetGrid(train_df, row='Pclass', col='Sex', size=2.2, aspect=1.6)

>>> grid.map(plt.hist, 'Age', alpha=.5, bins=20)

>>> grid.add_legend()

>>> plt.show()

 

 

 

 

我們先准備一個空的數組來存儲猜測的年齡,因為是Pclass與Gender的組合,所以數組大小為2x3:

>>> guess_ages = np.zeros((2, 3))

 

然后我們可以對Sex(0或1)和Pclass(1,2,3)進行迭代,並計算出在6中組合下所得到的猜測(Age)值:

 

for dataset in combine:
    for i in range(0, 2):
        for j in range(0, 3):
            guess_df = dataset[(dataset['Sex'] == i) & (dataset['Pclass'] == j+1)]['Age'].dropna()

            age_guess = guess_df.median()

            # Convert random age float to nearest .5 age
           
guess_ages[i, j] = int(age_guess / 0.5 + 0.5) * 0.5

    for i in range(0, 2):
            for j in range(0, 3):
                dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j+1),
                             'Age'] = guess_ages[i,j]

    dataset['Age'] = dataset['Age'].astype(int)

 

>>> train_df.head()

   Survived  Pclass  Sex  Age  SibSp  Parch     Fare Embarked  Title

0         0       3    0   22      1      0   7.2500        S      1

1         1       1    1   38      1      0  71.2833        C      3

2         1       3    1   26      0      0   7.9250        S      2

3         1       1    1   35      1      0  53.1000        S      3

4         0       3    0   35      0      0   8.0500        S      1

 

現在我們對Age分段,並查看每段與Survived之間的相關性:

>>> train_df['AgeBand'] = pd.cut(train_df['Age'], 5)

>>> train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)

 

         AgeBand  Survived

0  (-0.08, 16.0]  0.550000

1   (16.0, 32.0]  0.337374

2   (32.0, 48.0]  0.412037

3   (48.0, 64.0]  0.434783

4   (64.0, 80.0)  0.090909

 

然后我們根據上面的分段,使用有序的數值來替換Age里的值:

for dataset in combine:
    dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
    dataset.loc[ dataset['Age'] > 64, 'Age']

 

>>> train_df.head()

   Survived  Pclass  Sex  Age  SibSp  Parch     Fare Embarked  Title       AgeBand

0         0       3    0    1      1      0   7.2500        S      1  (16.0, 32.0]

1         1       1    1    2      1      0  71.2833        C      3  (32.0, 48.0]

2         1       3    1    1      0      0   7.9250        S      2  (16.0, 32.0]

3         1       1    1    2      1      0  53.1000        S      3  (32.0, 48.0)

4         0       3    0    2      0      0   8.0500        S      1  (32.0, 48.0)

 

接着我們可以丟棄AgeBand特征:

>>> train_df = train_df.drop(['AgeBand'], axis=1)

>>> combine = [train_df, test_df]

>>> train_df.head()

 

   Survived  Pclass  Sex  Age  SibSp  Parch     Fare Embarked  Title

0         0       3    0    1      1      0   7.2500        S      1

1         1       1    1    2      1      0  71.2833        C      3

2         1       3    1    1      0      0   7.9250        S      2

3         1       1    1    2      1      0  53.1000        S      3

4         0       3    0    2      0      0   8.0500        S      1

 

通過已有的特征組合出新特征

現在我們可以通過組合Parch和SibSp特征,創建一個新的FamilySize特征。這個步驟可以讓我們從數據集里丟棄Parch與SibSp特征。

 

for dataset in combine:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

 

>>> train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

  FamilySize  Survived

3           4  0.724138

2           3  0.578431

1           2  0.552795

6           7  0.333333

0           1  0.303538

4           5  0.200000

5           6  0.136364

7           8  0.000000

8          11  0.000000

 

接着我們可以創建另一個名為IsAlone的特征:

for dataset in combine:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

 

>>> train_df[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()

 

   IsAlone  Survived

0        0  0.505650

1        1  0.303538

 

 

基於上面的數據表現,我們現在可以丟棄Parch、SibSp以及FamilySize的特征,保留IsAlone的特征:

>>> train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)

>>> test_df = test_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)

>>> combine = [train_df, test_df]

>>> train_df.head()

 

   Survived  Pclass  Sex  Age     Fare Embarked  Title  IsAlone

0         0       3    0    1   7.2500        S      1        0

1         1       1    1    2  71.2833        C      3        0

2         1       3    1    1   7.9250        S      2        1

3         1       1    1    2  53.1000        S      3        0

4         0       3    0    2   8.0500        S      1        1

 

我們還可以通過結合Pclass 和 Age來創建一個新的特征:

for dataset in combine:
    dataset['Age*Class'] = dataset.Age * dataset.Pclass

 

>>> train_df.loc[:, ['Age*Class', 'Age', 'Pclass']].head(10)

   Age*Class  Age  Pclass

0          3    1       3

1          2    2       1

2          3    1       3

3          2    2       1

4          6    2       3

5          3    1       3

6          3    3       1

7          0    0       3

8          3    1       3

9          0    0       2

 

補全一個離散型的特征

Embarked特征主要有三個值,分別為S,Q,C,對應了三個登船港口。在訓練集里,這個有2個缺失值,我們會使用頻率最高的值來填充這個缺失值。

 

>>> freq_port = train_df.Embarked.dropna().mode()[0]

>>> freq_port

'S'

 

for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].fillna(freq_port)

 

>>> train_df[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean().sort_values(by='Survived', ascending=False)

 

  Embarked  Survived

0        C  0.553571

1        Q  0.389610

2        S  0.339009

 

將離散型特征轉換為數值型

我們現在可以將離散型的Embarked特征轉換為數值型特征

 

for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)

 

>>> train_df.head()

   Survived  Pclass  Sex  Age     Fare  Embarked  Title  IsAlone  Age*Class

0         0       3    0    1   7.2500         0      1        0          3

1         1       1    1    2  71.2833         1      3        0          2

2         1       3    1    1   7.9250         0      2        1          3

3         1       1    1    2  53.1000         0      3        0          2

4         0       3    0    2   8.0500         0      1        1          6

 

補全數值型特征

現在我們可以開始為測試集里的Fare特征補全數據。在補全時,我們可以使用最頻繁出現的數據用於補全缺失值。

(我們也可以將Fare的數值做四舍五入,將它精確到2位)

 

>>> test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)

>>> test_df.head()

   PassengerId  Pclass  Sex  Age     Fare  Embarked  Title  IsAlone  Age*Class

0          892       3    0    2   7.8292         2      1        1          6

1          893       3    1    2   7.0000         0      3        0          6

2          894       2    0    3   9.6875         2      1        1          6

3          895       3    0    1   8.6625         0      1        1          3

4          896       3    1    1  12.2875         0      3        0          3

 

接下來我們將Fare分段:

>>> train_df['FareBand'] = pd.qcut(train_df['Fare'], 4)

>>> train_df[['FareBand', 'Survived']].groupby(['FareBand'], as_index=False).mean().sort_values(by='FareBand', ascending=True)

          FareBand  Survived

0   (-0.001, 7.91]  0.197309

1   (7.91, 14.454]  0.303571

2   (14.454, 31.0]  0.454955

3  (31.0, 512.329)  0.581081

 

根據分段后的特征FareBand,將Fare轉換為有序的數值型特征:

for dataset in combine:
    dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare'] = 2
    dataset.loc[dataset['Fare'] > 31, 'Fare'] = 3
    dataset['Fare'] = dataset['Fare'].astype(int)

 

>>> train_df = train_df.drop(['FareBand'], axis=1)

>>> combine = [train_df, test_df]

>>> train_df.head(10)

 

   Survived  Pclass  Sex  Age  Fare  Embarked  Title  IsAlone  Age*Class

0         0       3    0    1     0         0      1        0          3

1         1       1    1    2     3         1      3        0          2

2         1       3    1    1     1         0      2        1          3

3         1       1    1    2     3         0      3        0          2

4         0       3    0    2     1         0      1        1          6

5         0       3    0    1     1         2      1        1          3

6         0       1    0    3     3         0      1        1          3

7         0       3    0    0     2         0      4        0          0

8         1       3    1    1     1         0      3        0          3

9         1       2    1    0     2         1      3        0          0

 

 

11.建模,預測,並解決問題

現在我們已經做好了訓練模型的准備,在模型訓練完后,我們即可將其應用到解決問題中。對於預測的問題,我們至少有60多種算法可供選擇。

所以我們必須理解問題的類型和解決方案的需求,這樣才能縮小模型的選擇范圍。現在這個問題是一個分類與回歸的問題,

我們希望找出輸出(即Survived)與其他特征(即Gender,Age,Port等)之間的關系。因為給定了訓練集,所以這在機器學習里是一個有監督學習。

所以現在對算法的需求是:有監督學習加上分類與回歸。根據這個條件,我們有以下模型可供選擇:

  1. Logistic Regression
  2. kNN 
  3. SVM
  4. Naïve Bayes classifier
  5. Decision Tree
  6. Random Forrest
  7. Perceptron
  8. Artificial neural network
  9. RVM or Relevance Vector Machine

 

現在我們將訓練集與測試集再做一下區分:

>>> X_train = train_df.drop('Survived', axis=1)

>>> Y_train = train_df['Survived']

>>> X_test = test_df.drop('PassengerId', axis=1).copy()

>>> X_train, Y_train, X_test

((891, 8), (891,), (418, 8))

 

Logistic Regression 是一個非常有用的模型,可以在工作流程里優先使用。它通過使用估計概率的方法衡量了離散型特征與其他特征之間的關系,是一個漸增型的邏輯分布。

 

>>> logreg = LogisticRegression()

>>> logreg.fit(X_train, Y_train)

>>> Y_pred = logreg.predict(X_test)

>>> acc_log = round(logreg.score(X_train, Y_train) * 100, 2)

>>> acc_log

80.359999999999999

 

我們可以用Logistic Regression來驗證我們之間做的假設與結論。這個可以通過計算特征值的系數來達成。正系數可以提升對數幾率(所以增長了概率),負系數會降低對數幾率(因此降低了概率):

 

>>> coeff_df = pd.DataFrame(train_df.columns.delete(0))

>>> coeff_df.columns = ['Feature']

>>> coeff_df["Correlation"] = pd.Series(logreg.coef_[0])

>>> coeff_df.sort_values(by='Correlation', ascending=False)

     Feature  Correlation

1        Sex     2.201527

5      Title     0.398234

2        Age     0.287162

4   Embarked     0.261762

6    IsAlone     0.129140

3       Fare    -0.085150

7  Age*Class    -0.311202

0     Pclass    -0.749007

 

從上面的結果我們可以看出:

  1. Sex是有最高正系數的特征。這個表面當Sex 的值增加時(從male:0到female:1),Survived=1的概率增加最多
  2. 相反的,當Pclass增加時,Survived=1的概率減少最多
  3. 從結果來看,我們創建的新特征Age*Class非常有用,因為它與Survived的負相關性是第二高的
  4. Title是第二高的正系數特征

 

下一步我們使用SVM來分析數據並做分類與回歸分析。

>>> svc = SVC()

>>> svc.fit(X_train, Y_train)

>>> Y_pred = svc.predict(X_test)

>>> acc_svc = round(svc.score(X_train, Y_train) * 100, 2)

>>> acc_svc

83.840000000000003

 

可以看到使用SVM后的正確率得到了提升。

 

在模式識別中,KNN算法是一種非參數的方法,用於做分類與回歸。使用KNN來分析此問題的話:

>>> knn = KNeighborsClassifier(n_neighbors = 3)

>>> knn.fit(X_train, Y_train)

>>> Y_pred = knn.predict(X_test)

>>> acc_knn = round(knn.score(X_train, Y_train) * 100, 2)

84.739999999999995

 

可以看到使用KNN的正確率比Logistic Regression更高,但是比SVM更低

 

下面我們試試朴素貝葉斯:

>>> gaussian = GaussianNB()

>>> gaussian.fit(X_train, Y_train)

>>> Y_pred = gaussian.predict(X_test)

>>> acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)

72.280000000000001

 

看來在這個問題中使用朴素貝葉斯不是一個很好的選擇,從當前來看,它的正確率是最低的。

 

接下來我們試試 perceptron(感知機)算法,它可以用於二分類問題:

>>> perceptron = Perceptron()

>>> perceptron.fit(X_train, Y_train)

>>> Y_pred = perceptron.predict(X_test)

>>> acc_perceptron = round(perceptron.score(X_train, Y_train) * 100, 2)

78.0

 

可以看到perceptron的正確率也不高

 

接下來試試Linear SVC:

>>> linear_svc = LinearSVC()

>>> linear_svc.fit(X_train, Y_train)

>>> Y_pred = linear_svc.predict(X_test)

>>> acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)

79.010000000000005

 

與隨機梯度下降分類器:

>>> sgd = SGDClassifier()

>>> sgd.fit(X_train, Y_train)

>>> Y_pred = sgd.predict(X_test)

>>> acc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)

78.230000000000004

 

這幾個算法計算到的正確率都不夠理想。

 

接下來我們看看很常見的決策樹算法:

>>> decision_tree = DecisionTreeClassifier()

>>> decision_tree.fit(X_train, Y_train)

>>> Y_pred = decision_tree.predict(X_test)

>>> acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)

86.760000000000005

 

可以看到,使用決策樹的算法使得正確率達到了一個更高的值。在目前為止,它的正確率是最高的。

 

然后我們看看隨機森林,隨機森林通過組合多個決策樹算法來完成:

>>> random_forest = RandomForestClassifier(n_estimators=100)

>>> random_forest.fit(X_train, Y_train)

>>> Y_pred = random_forest.predict(X_test)

>>> acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)

86.760000000000005

 

通過比較模型的正確率,我們決定使用最高正確率的模型,即隨機森林的輸出作為結果提交。

 

12.模型評價

現在我們可以對以上的模型的正確率進行排名,並從中選擇一個正確率最高的模型:

 

models = pd.DataFrame({
    'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression',
              'Random Forest', 'Naive Bayes', 'Perceptron',
              'Stochastic Gradient Decent', 'Linear SVC',
              'Decision Tree'],
    'Score': [acc_svc, acc_knn, acc_log,
              acc_random_forest, acc_gaussian, acc_perceptron,
              acc_sgd, acc_linear_svc, acc_decision_tree]})

 

>>> models.sort_values(by='Score', ascending=False)

                        Model  Score

3               Random Forest  86.76

8               Decision Tree  86.76

1                         KNN  84.74

0     Support Vector Machines  83.84

2         Logistic Regression  80.36

7                  Linear SVC  79.01

6  Stochastic Gradient Decent  78.23

5                  Perceptron  78.00

4                 Naive Bayes  72.28

 

其中決策樹與隨機森林的正確率最高,但是我們在這里會選擇隨機森林算法,因為它相對於決策樹來說,彌補了決策樹有可能過擬合的問題。

 

最后我們做提交:

>>> submission = pd.DataFrame({"PassengerId": test_df["PassengerId"], "Survived": Y_pred})

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM