Kaggle – Titanic倖存預測 #1

                Titanic競賽是初入門Kaggle的Hello world,透過這個競賽讓我們能夠完整的走過Data science的基本步驟:蒐集、概觀、清理、分析、視覺化,以及後續的機器和深度學習階段。此外,此競賽中我覺得最有趣的部份在於資料分析階段,當我們去檢視這些歷史資料時,很容易浸淫於背後的歷史災難所衍生的悲劇情節,讓一幕幕極具張力的電影情節渲染成說不盡故事的生動圖表,回顧整場競賽,尤如旁觀整齣劇情。

Train dataset架構

該競賽提供的train dataset其各欄位名稱及定義如下,經由這些欄位,我們可考慮是否作為feature來納入分析。例如,PassengerId(乘客編號)欄位對於預測結果應無影響可忽略不看,survival欄位是我們要針對Test dataset預測的是否倖存答案,因此它應該是Lable。其餘欄位則需經由分析來判斷是否跟survival有直接或間接關係,再決定是否列為特徵。

以下為train dataset的各欄位資訊:

Variable

Definition

Key

PassengerId

乘客ID編號

survival

是否倖存

0 = No, 1 = Yes

pclass

船票等級

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

sex

性別

Age

年齡

sibsp

在船上同為兄弟姐妹或配偶的數目

parch

在船上同為家族的父母及小孩的數目

ticket

船票編號

fare

船票價格

cabin

船艙號碼

embarked

登船的口岸

C = Cherbourg

Q = Queenstown,

S = Southampton

補充:
Cherbourg:瑟堡,位於法國西北的一個城鎮,屬重要軍港和商港。Queenstown:目前稱為科芙,位於愛爾蘭,於1850年更名為皇后鎮(又稱昆士敦),以紀念維多利亞女王的造訪,直到1920年,愛爾蘭自由邦建立後,它被重新命名為科芙。Southampton:南安普敦,位於陽光燦爛的英國南方海岸,是個港口城市,離倫敦僅1小時車程,鐵達尼號正是從這裡出航。

針對Kaggle的Titanic倖存預測競賽,將分為下列三個階段來進行,本文所進行的是第一階段。

  • 資料分析Data analysis
  1. 資料形態、架構的掌握。
  2. 資料發現Data exploration。
  3. 資料的相關及變異。
  • 特徵工程Feature engineering

         包含Feature cleaning、imputation、selection、encoding、normalization…等。

  • 模型建立與訓練
  1. 模型選擇
  2. 訓練
  3. 評估
  4. 參數(Hyperparameter)調整
  5. 預測
  • 載入必要模組

import pandas as pd

import numpy as np

from matplotlib import pyplot as plt

import matplotlib

%matplotlib inline

matplotlib.style.use(‘ggplot’)

  • 資料概觀

Titanic倖存分析競賽提供了2個dataset,分別為Train及Test,皆為cvs格式。我們使用Pandas將它們讀入並分析。

  1. 讀入從Titanic的train及test dataset

train = pd.read_csv(‘train.csv’)

test = pd.read_csv(‘test.csv’)

  1. 看看其維度。

Train與Test dataset皆為二維陣列,Train有12個欄位891 records,Test則有11個欄位418 records。

print(train.shape)

print(test.shape)

(891, 12)

(418, 11)

  1. Survived欄位是該乘客是否倖存,這也是我們最終要預測的結果值,因此把該欄位從train dataset移出成為一個獨立的Y_label dataset。

Y_label = train.Survived

    train.drop(‘Survived’, 1, inplace=True)

  1. 將Test dataset置於train dataset之後,複製成新的dataset x_train。

將這兩個dataset放一起的原因是可一併分析處理,例如一些資料會有缺值情況需補值,以及不同資料分群時不用分開進行。不過最後在訓練學習之前,必須要記得將Test從Train dataset移出。

x_train = train.append(test)

print(x_train.shape)

 (1309, 11)

*下面的分析都是針對Train + Test dataset兩者合併的內容。

  1. 列出dataset所有的欄位名稱

print(x_train.columns.values)

         [‘PassengerId’ ‘Survived’ ‘Pclass’ ‘Name’ ‘Sex’ ‘Age’ ‘SibSp’ ‘Parch’ ‘Ticket’ ‘Fare’ ‘Cabin’ ‘Embarked’]

  1. 顯示前五筆記錄的內容,NaN為 Null,代表該筆無此資料(缺值)。

x_train.head()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp46.png                

  1. Describe()指令可概觀整個dataset以掌握資料的範圍分佈,並找出那個欄位有缺值(空值)。理論上每個欄位都應該有1309筆,但Age欄位僅1046筆,可見該欄位少了263筆資料,Fare欄位有1308筆看來少了一筆,我們稍後需要處理此問題。

x_train.describe()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp67.png

  1. Info()指令可顯示所有欄位的格式。我們從中得知float64有2個,int64有5個,object類型有5個

x_train.info()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp68.png

  •  Data exploration

針對Dataset中的欄位,視情況在文章中可能被稱為欄位或feature,但基本上都是指一樣的東西。另外,由於本競賽目的是要預測某個乘客是否倖存,因此,在思考各欄位之間的關係我是以是否倖存為中心,只要可能連帶影響到的皆視為需要的feature。

  1. 檢視非數值欄位:include=[‘O’]參數可針對Object類型欄位進行統計。執行後發現count=1309,unique=1307,Name欄位有兩筆是重複的名字。Sex欄位只有兩種Male/Female,其中以Male最多有843位。

train.describe(include=[‘O’])

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp69.png

  1. 性別與倖存的關係:
  1. 船上的乘客各年齡層的男女比例:

小於20歲的男女人數比例接近,但若超過20歲(含)的乘客,則男性比例為女性的2倍。

figure = plt.figure(figsize=(15,8))

plt.hist([train[train[‘Sex’]==’male’][‘Age’], train[train[‘Sex’]==’female’][‘Age’]], stacked=False, color = [‘g’,’r’], bins = 30,label = [‘Male’,’Female’])

plt.xlabel(‘Age’)

plt.ylabel(‘Number of Sex’)

plt.legend()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp61.png

  1. 以人數來看不同性別的倖存率:

以下pandas指令統計出乘客性別與倖存的比率,發現男性的倖存比率只有0.18,而女性高達0.74。

train[[“Sex", “Survived"]].groupby([‘Sex’], as_index=False).mean().sort_values(by=’Survived’, ascending=False)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp51.png

        以圖形化方式顯示,藍/紅分別代表男性與女性,左側bar為存活數,右側為死亡數:

survived_sex = train[train[‘Survived’]==1][‘Sex’].value_counts()

dead_sex = train[train[‘Survived’]==0][‘Sex’].value_counts()

df = pd.DataFrame([survived_sex,dead_sex])

df.index = [‘Survived’,’Dead’]

df.plot(kind=’bar’,stacked=True, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp52.png

  1. 以人數比例來看不同性別的倖存率:

若改用比例來看,則男性只有不到2成生存的機會,女性則超過8成有機會生存:

total_sex = train[‘Sex’].value_counts()

p_survived_sex = train[train[‘Survived’]==1][‘Sex’].value_counts() / total_sex

p_dead_sex = train[train[‘Survived’]==0][‘Sex’].value_counts() / total_sex

df = pd.DataFrame([p_survived_sex,p_dead_sex])

df.index = [‘Survived’,’Dead’]

df.plot(kind=’bar’,stacked=True, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp70.png

  1. 姓名與稱呼:

我們檢視一下Name欄位,會發現該欄位除了乘客姓名之外,還帶有稱呼及職稱,如下圖的紅框部份,這些也是相當有用的資訊。

我們來分析看看。首先瀏覽過整個Name欄位後,找出一些不同的稱呼及職稱,看看其人數及平均年齡:

for title in [“Mr.", “Sir.", “Dr.", “Major.", “Master."]:

    num = X_train[(X_train[‘Name’].str.contains(title))][“Name"].count()

    age = X_train[(X_train[‘Name’].str.contains(title))][“Age"].mean()

   

    print(“{} –> {} males, Age average is {}".format(title, num, age))

print(“————————————————————-“)

for title in [“Ms.", “Miss.", “Mrs.", “Lady."]:

    num = X_train[(X_train[‘Name’].str.contains(title))][“Name"].count()

    age = X_train[(X_train[‘Name’].str.contains(title))][“Age"].mean()

   

    print(“{} –> {} females, Age average is {}".format(title, num, age))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp126.png

        從上方可看出不同稱呼的人數及平均年齡,有趣的是, 有61個年齡很小的男孩都冠有Master.稱呼。接下來,我們再加入倖存率看看。

                for title in [“Mr.", “Sir.", “Dr.", “Major.", “Master."]:

    num_survived = X_train[(X_train[‘Survived’]==1) & (X_train[‘Name’].str.contains(title))][“Name"].count()

    num_died = X_train[(X_train[‘Survived’]==0) & (X_train[‘Name’].str.contains(title))][“Name"].count()

    #print(num_survived, num_died)

    print(“{} total:{} –> {} survived, {} died. {}% survived".format(title, num_survived+num_died, num_survived, num_died, (100*num_survived/(num_survived+num_died))))

print(“————————————————————-“)

for title in [“Ms.", “Miss.", “Mrs.", “Lady."]:

    num_survived = X_train[(X_train[‘Survived’]==1) & (X_train[‘Name’].str.contains(title))][“Name"].count()

    num_died = X_train[(X_train[‘Survived’]==0) & (X_train[‘Name’].str.contains(title))][“Name"].count()

   

    print(“{} total:{} –> {} survived, {} died. {}% survived".format(title, num_survived+num_died, num_survived, num_died, (100*num_survived/(num_survived+num_died))))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp127.png

帶有Master.稱呼的男性由於年紀皆相當輕,因此其倖存率較高,其次為Dr.,可能他們的社經地位較高,至於Major.,由於他們的平均年齡較高,所以應是高倖存率的主因。女性的話,已婚的Mrs.比起未婚的Miss.有較高的倖存率。

  1. 船票等級與存活的關係:

Pclass欄位指的是船票等級。以下看出Pclass的等級愈高(1>2>3)則存活機率愈大,看來愈富有的人坐上救生艇的機率更高。

  1. 從人數來看不同船票等級的存活率:明顯看出等級3的乘客最多,死亡者也大部份為等級3的乘客。

survived_pclass = train[train[‘Survived’]==1][‘Pclass’].value_counts()

dead_pclass = train[train[‘Survived’]==0][‘Pclass’].value_counts()

df = pd.DataFrame([survived_pclass,dead_pclass])

df.index = [‘Survived’,’Dead’]

df.plot(kind=’bar’,stacked=False, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp54.png

  1. 改以機率來看各Pclass的存活比率:改為比率更能正確的反映兩者的關係。等級3的倖存率僅有0.24,是等級1的三分之一,等級2的二分之一。

train[[‘Pclass’, ‘Survived’]].groupby([‘Pclass’], as_index=False).mean().sort_values(by=’Survived’, ascending=False)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp55.png

  1. 不同性別與船票等級對於存活率的影響:

我們知道船上女性的倖存率很高,男性非常低,但這現象在不同等級船票之間會有差嗎?從下方的結果,我們發現,持有P1, P2等級船票的女性有高達九成的倖存率,但持有等級最低的P3船票女性只有一半的存活率。至於男性方面,持有最高等級船票P1的存活率是另外二個等級的2倍。

train[[‘Pclass’, ‘Sex’, ‘Survived’]].groupby([‘Pclass’, ‘Sex’], as_index=False).mean().sort_values(by=’Survived’, ascending=False)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp57.png

將以上結果以圖示顯示如下,女性部份:全體女性的幸存率雖然高達八成以上,但主要集中於P1, P2等級的船票,最低級船票P3的存活率僅有5成。

total_female_p1 = train[(train[‘Pclass’]==1) & (train[‘Sex’]=="female")][‘Survived’].count()

female_p1 = train[(train[‘Pclass’]==1) & (train[‘Sex’]=="female")][‘Survived’].value_counts() / total_female_p1

total_female_p2 = train[(train[‘Pclass’]==2) & (train[‘Sex’]=="female")][‘Survived’].count()

female_p2 = train[(train[‘Pclass’]==2) & (train[‘Sex’]=="female")][‘Survived’].value_counts() / total_female_p2

total_female_p3 = train[(train[‘Pclass’]==3) & (train[‘Sex’]=="female")][‘Survived’].count()

female_p3 = train[(train[‘Pclass’]==3) & (train[‘Sex’]=="female")][‘Survived’].value_counts() / total_female_p3

df = pd.DataFrame([female_p1[[0, 1]],female_p2[[0, 1]],female_p3[[0, 1]]])

df.index = [‘Female in P1′,’Female in P2’, ‘Female in P3’]

df.plot(kind=’bar’,stacked=False, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp78.png

男性部份:迥異於女性的高倖存率偏向P1與P2,及低倖存率集中P3,男性乘客的高倖存率皆集中於P1這個等級,P2與P3則不分軒輊。

total_male_p1 = train[(train[‘Pclass’]==1) & (train[‘Sex’]=="male")][‘Survived’].count()

male_p1 = train[(train[‘Pclass’]==1) & (train[‘Sex’]=="male")][‘Survived’].value_counts() / total_male_p1

total_male_p2 = train[(train[‘Pclass’]==2) & (train[‘Sex’]=="male")][‘Survived’].count()

male_p2 = train[(train[‘Pclass’]==2) & (train[‘Sex’]=="male")][‘Survived’].value_counts() / total_male_p2

total_male_p3 = train[(train[‘Pclass’]==3) & (train[‘Sex’]=="male")][‘Survived’].count()

male_p3 = train[(train[‘Pclass’]==3) & (train[‘Sex’]=="male")][‘Survived’].value_counts() / total_male_p3

df = pd.DataFrame([male_p1[[0, 1]],male_p2[[0, 1]],male_p3[[0, 1]]])

df.index = [‘Male in P1′,’Male in P2’, ‘Male in P3’]

df.plot(kind=’bar’,stacked=False, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp79.png

  1. 不同年齡層與倖存的關係:從下圖可看出年齡愈偏向兩極(較年長或較年幼)則存活率愈高,其中尤以年齡愈小愈明顯。

figure = plt.figure(figsize=(15,8))

plt.hist([train[train[‘Survived’]==1][‘Age’], train[train[‘Survived’]==0][‘Age’]], stacked=True, color = [‘g’,’r’],

         bins = 30,label = [‘Survived’,’Dead’])

plt.xlabel(‘Age’)

plt.ylabel(‘Number of passengers’)

plt.legend()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp58.png

  1. 不同票價與存活的關係:

經由下方統計結果發現,與船票等級類似,票價愈高則存活率愈大。

figure = plt.figure(figsize=(15,8))

plt.hist([train[train[‘Survived’]==1][‘Fare’],train[train[‘Survived’]==0][‘Fare’]], stacked=True, color = [‘g’,’r’],

         bins = 30,label = [‘Survived’,’Dead’])

plt.xlabel(‘Fare’)

plt.ylabel(‘Number of passengers’)

plt.legend()

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp59.png

  1. 票價、年齡與存活三者之間的關係:

可看出票價高及年齡偏低者愈有存活機會,此外,從下方的分佈圖來看,年齡與票價兩者並沒有關聯性。

plt.figure(figsize=(15,8))

ax = plt.subplot()

ax.scatter(train[train[‘Sex’]==’female’][‘Age’],train[train[‘Sex’]==’female’][‘Fare’],c=’green’,s=40)

ax.scatter(train[train[‘Sex’]==’male’][‘Age’],train[train[‘Sex’]==’male’][‘Fare’],c=’red’,s=40)

ax.set_xlabel(‘Age’)

ax.set_ylabel(‘Fare’)

ax.legend((‘survived’,’dead’),scatterpoints=1,loc=’upper right’,fontsize=15,)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp60.png

  1. 親屬人數對於存活率的影響:

SibSp與Parch這兩個欄位指的是旁系與直系親屬人數,親屬或家人一起可以互相幫助,對於存活率有一定的影響,而且理論上,直系的Parch對於倖存率影響力會比旁系SibSp更大一些。不過現階段為了單純起見,我們先不考慮兩者的差異,而是將SibSp與Parch這兩個欄位相加作為親屬人數來與存活率比較。經由下方的統計,可發現親屬人數為3人時有最高的存活率,其次為2人、1人以及6人:

train[‘Family’] = train[‘SibSp’] + train[‘Parch’]

train[[‘Family’, ‘Survived’]].groupby([‘Family’], as_index=False).mean().sort_values(by=’Survived’, ascending=False)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp62.png

  1. 親屬人數、年齡與存活率三者的關係:

從下方的統計發現,無論在各年齡層親屬人數對於存活率皆有影響,但對於較年幼的小孩(<16歲)則影響較小,可能是年幼小孩有先上救生艇的權利,而年紀較大的成人需要親屬幫忙才比較有活命的機會。

train[‘Family’] = train[‘SibSp’] + train[‘Parch’]

plt.figure(figsize=(20,8))

ax = plt.subplot()

ax.scatter(train[train[‘Survived’]==1][‘Age’],train[train[‘Survived’]==1][‘Family’],c=’green’,s=40)

ax.scatter(train[train[‘Survived’]==0][‘Age’],train[train[‘Survived’]==0][‘Family’],c=’red’,s=40)

ax.set_xlabel(‘Age’)

ax.set_ylabel(‘Family’)

ax.legend((‘survived’,’dead’),scatterpoints=1,loc=’upper right’,fontsize=15,)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp63.png

  1. 上岸港口與存活率的關係:

上岸港口的不同會影響到最終的倖存率嗎?很有趣的,從下方的統計看出,如果您是從Cherbourg上船,那麼恭喜您的存活率有5成5,如果從Queenstown上船那存活率降到3成8,從Southampton呢?那存活率只有3成3了。

train[[“Embarked", “Survived"]].groupby([‘Embarked’], as_index=False).mean().sort_values(by=’Survived’, ascending=False)

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp64.png

total_Embarked_S = train[train[‘Embarked’]==’S’][‘Survived’].count()

total_Embarked_C = train[train[‘Embarked’]==’C’][‘Survived’].count()

total_Embarked_Q = train[train[‘Embarked’]==’Q’][‘Survived’].count()

Embarked_S = train[train[‘Embarked’]==’S’][‘Survived’].value_counts() / total_Embarked_S

Embarked_C = train[train[‘Embarked’]==’C’][‘Survived’].value_counts() / total_Embarked_C

Embarked_Q = train[train[‘Embarked’]==’Q’][‘Survived’].value_counts() / total_Embarked_Q  

             

df = pd.DataFrame([Embarked_S,Embarked_C,Embarked_Q])

df.index = [‘Southampton’,’Cherbourg’,’Queenstown’]

df.plot(kind=’bar’,stacked=False, figsize=(15,8))

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp80.png

  1. 上岸港口與船票等級的關係:

我們可從下方統計看出不同港口乘客有不同的經濟狀況。從法國Cherbourg上船的目的主要可能為出訪旅遊,因此多購買等級最高的P1船票(也使得Cherbourg上船的有較高的存活率),來自Queenstown的乘客則有極大的比例是購買最低等級的P3船票,目的可能為工作或移民。

total_Pclass_S = train[train[‘Embarked’]==’S’][‘Pclass’].count()

total_Pclass_C = train[train[‘Embarked’]==’C’][‘Pclass’].count()

total_Pclass_Q = train[train[‘Embarked’]==’Q’][‘Pclass’].count()

Embarked_S = train[train[‘Embarked’]==’S’][‘Pclass’].value_counts() / total_Pclass_S

Embarked_C = train[train[‘Embarked’]==’C’][‘Pclass’].value_counts() / total_Pclass_C

Embarked_Q = train[train[‘Embarked’]==’Q’][‘Pclass’].value_counts() / total_Pclass_Q  

             

df = pd.DataFrame([Embarked_S,Embarked_C,Embarked_Q])

df.index = [‘Southampton’,’Cherbourg’,’Queenstown’]

C:\Users\CHE7C6~1.TSE\AppData\Local\Temp\x10sctmp81.png

  1. 艙房號碼與存活的關係:

Cabin欄位存放的是艙房號碼,此欄位的有極大比例是空值,僅僅只有295筆記錄有值。

該欄位資料的格式是:字母+編號,例如C123、B85…等,不過我認為後方編號可忽略只要考慮字母的部份。此外,我先將所有的空值NaN以 “-“符號取代。

X_train[‘Cabin’].fillna(“-“, inplace=True)

cabinList = [“C", “E", “G", “D", “A", “B", “F"]

for Cabin in cabinList:

    mask =  (X_train[“Cabin"].str.contains(Cabin))

    X_train.loc[mask,"Cabin"] = Cabin

                接下來,看看這些艙房編號與船票等級的關係:

total_Cabin_p1 = X_train[X_train[‘Pclass’]==1][‘Cabin’].value_counts()

total_Cabin_p2 = X_train[X_train[‘Pclass’]==2][‘Cabin’].value_counts()

total_Cabin_p3 = X_train[X_train[‘Pclass’]==3][‘Cabin’].value_counts()

print(total_Cabin_p1)

print(total_Cabin_p2)

print(total_Cabin_p3)

                

發現遺失Cabin艙房編號的,有極大比例是屬於P3最低等級船票的乘客,其次為P2,最後是P1。此外,這295筆船艙編號的倖存統計如下:

                total_Cabin_servived = X_train[X_train[‘Survived’]==1][‘Cabin’].value_counts()

total_Cabin_died = X_train[X_train[‘Survived’]==0][‘Cabin’].value_counts()

print(total_Cabin_servived)

print(total_Cabin_died)

                

從船艙資料與票價等級以及倖存與否,我們發現有著關聯性,此部份在稍後的特徵工程中將會用到,可提供線索讓我們將缺少的船艙資料補上。

以上為針對Titanic dataset的資料分析,接下來,我們需要針對這些欄位資料進行處理及加工,以便提供給後續的ML或DL使用,這也就是所謂的Feature engineering,此部份將於下文介紹。

廣告