更新日:2020.01.04  作成日:2020.01.04

タイタニック号の生存率予測

はじめに

 Kaggleとは、企業から提示された課題とそれに必要なデータセットを元に、世界中のデータサイエンティストが最適なモデルを競い合う、世界最大規模のプラットフォームである。
 今回はチュートリアルの1つであるタイタニック号の生存率予測を通して、 Kaggleプラットフォームの仕組みや分析手法を学ぶ。

ー Your Home for Data Science   by kaggle

タイタニックの生存予測:スコア「0.72727」

 891人分のデータ(train.csv)を用いて、418人(test.csv)の生存予測を行う。具体的には、testデータ各行の「Survival」0 or 1 を予測する。 今回はスコア「0.72727」だった手法を記録する。

データセットの確認

 まず必要なライブラリをimportし、pandasを用いtrain/testデータを読み込む。環境はVScodeを用い、jupyterNotebookを使用する。

import os
import re
import pandas as pd
import numpy as np
from sklearn.metrics import r2_score
from sklearn.ensemble import GradientBoostingRegressor
from IPython.core.display import display

#csvファイルの読み込み
os.chdir('/Users/yoshino/dev/kaggle/01_titanic/data') #自身のデータの保存場所
#データの読み込み
df_train = pd.read_csv("train.csv") 
#データの読み込み
df_test = pd.read_csv("test.csv") 

 .columns.valuesでデータのラベル(項目)を確認する。trainデータは12項目あることが分かる。 また、testデータは11項目あり、「Survival」以外の項目はtrainデータと同じ構成である。各項目の意味は以下の表に記載する。
※「PassengerId」はただのIDなので意味はない。

▶︎ 
df_train.columns.values #列ラベル確認 
<div class = code_re>
array(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype=object)
</div>

▶︎ 
df_test.columns.values #列ラベル確認
<div class = code_re>
array(['PassengerId', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype=object)
</div>
ラベル 定義 キー
survival 生存 0 = No, 1 = Yes
pclass チケットクラス 1 = 1st, 2 = 2nd, 3 = 3rd
sex 性別 male, female
Age 年齢
sibsp 兄弟/配偶者の数
parch 親/子供の数
ticket チケット番号
fare 旅費運賃
cabin キャビン番号
embarked 乗船港 C = Cherbourg, Q = Queenstown, S = Southampton

 trainデータを確認した。まずパッと見だと「Ticket」は文字と数字が混ざっているため、加工させる必要がありそうだ。また、人の名前での学習に意味があるのか不明だが、「Name」を学習に使うのであれば何かしらの数字変換を施す必要があるだろう。testデータも同様である。
▶︎
display(df_train.head(3))  ※例:3行目まで
<div class = code_re>
 PassengerId  Survived  Pclass                                               Name     Sex   Age  SibSp   Parch            Ticket     Fare Cabin Embarked
0          1         0       3                            Braund, Mr. Owen Harris    male  22.0      1       0         A/5 21171   7.2500   NaN        S
1          2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1       0          PC 17599  71.2833   C85        C
2          3         1       3                             Heikkinen, Miss. Laina  female  26.0      0       0  STON/O2. 3101282   7.9250   NaN        S
</div>

▶︎
display(df_test.head(3)) ※例:3行目まで
<div class = code_re>
 PassengerId  Pclass                               Name     Sex   Age  SibSp  Parch  Ticket    Fare  Cabin  Embarked
0        892       3                   Kelly, Mr. James    male  34.5      0      0  330911  7.8292    NaN         Q
1        893       3   Wilkes, Mrs. James (Ellen Needs)  female  47.0      1      0  363272  7.0000    NaN         S
2        894       2          Myles, Mr. Thomas Francis    male  62.0      0      0  240276  9.6875    NaN         Q
</div>

 次に各項目のtrainデータの型と欠損値を調べた。.info()を用いることで「Age」、「Cabin」、「Embarked」は欠損値が含まれていることが分かる。また、object型は数字変換を施す必要がある。  同様にtestデータ欠損値は「Age」、「Face」、「Cabin」が含まれていることが分かる。

▶︎
df_train.info()
<div class = code_re>
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
</div>

▶︎
df_test.info()
<div class = code_re>
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
</div>

(補足).isnull().sum()を用いて、簡単に欠損値の数を調べた。「Cabin」は8割ほどが欠損値であるため、予測データに使用するのは難しいだろう。

▶︎
df_train.isnull().sum()
<div class = code_re>
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
</div>
▶︎
df_test.isnull().sum()
<div class = code_re>
PassengerId      0
Pclass           0
Name             0
Sex              0
Age             86
SibSp            0
Parch            0
Ticket           0
Fare             1
Cabin          327
Embarked         0
dtype: int64
</div>

データ処理(前処理)

 確認したデータを分析できるように以下の方法で処理を施す。
  ① Sex : male = 0, female = 1
  ② Embarked : S = 0, C = 1, Q = 2, 欠損値は平均値を割当てる
  ③ Age : 欠損値は平均値を割当てる
  ④ Ticket : 数字を抽出(前の文字列を削除)、欠損値は平均値を割当てる
  ⑤ Fare : 欠損値は平均値を割当てる

①「Sex」のデータ処理

 Sexの行は欠損値は無い。またデータはobject型でmale / femaleの2つのみのため、それぞれに0と1を割当てる。

# Sex : male = 0, female = 1
df_train.loc[df_train['Sex'] == 'male','Sex']   = int(0)
df_train.loc[df_train['Sex'] == 'female','Sex'] = int(1)

df_test.loc[df_test['Sex'] == 'male','Sex']   = int(0)
df_test.loc[df_test['Sex'] == 'female','Sex'] = int(1)

②「Embarked」のデータ処理

 値はS = 0, C = 1, Q = 2を割当てる。2行の欠損値には平均値を与える。

# Embarked : S = 0, C = 1, Q = 2
df_train.loc[df_train['Embarked'] == 'S','Embarked'] = int(0)
df_train.loc[df_train['Embarked'] == 'C','Embarked'] = int(1)
df_train.loc[df_train['Embarked'] == 'Q','Embarked'] = int(2)
# 欠損値は平均値を割当てる
df_train['Embarked'].fillna(df_train['Embarked'].mean(),inplace=True)

#-------------------------------------------------------------
df_test.loc[df_test['Embarked'] == 'S','Embarked'] = int(0)
df_test.loc[df_test['Embarked'] == 'C','Embarked'] = int(1)
df_test.loc[df_test['Embarked'] == 'Q','Embarked'] = int(2)

df_test['Embarked'].fillna(df_test['Embarked'].mean(),inplace=True)

③ 「Age」のデータ処理

 欠損値には平均値を与える。

# 欠損値は平均値を割当てる
df_train['Age'].fillna(df_train['Age'].mean(),inplace=True)

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

④ 「Ticket」のデータ処理

 いくつかデータを確認したところ、「A/5 21171」、「STON/O2. 3101282」のように文字と数字が連なっているデータと「113803」のように数字のみのデータとが存在している。文字は処理ができないため、今回は「A/5 21171」から「21171」のように 、文字と数字の間の空白を検知し後ろの数字列を抜き出す処理を実施する。一部「LINE」の文字のみで数字が無い行があるので、その行は全体の平均値を割当てる。
 ※上書きせずに列を新しく作り追加させた方が良さそうだと感じるし、もう少し良いコードの書き方があると思うが、ひとまずこれで。。

Ticket_txt=[]
#空白行以降の数字の抽出
for s in pd.Series(df_train['Ticket'].values.flatten()): #Pandas.Seriesへ
    fdr= s.rfind(' ') #左から空白の場所を探す。
    if fdr == -1:
        if s == 'LINE':
            Ticket_txt.append(None)
        else:
            Ticket_txt.append(float(s[:]))
    else:
        Ticket_txt.append(float(s[fdr+1:]))
#上書き
df_train['Ticket'] = pd.Series(Ticket_txt)

#欠損値は平均値を割当てる
df_train['Ticket'].fillna(df_train['Ticket'].mean(),inplace=True)

#--------------------------------------------------------------------
Ticket_txt=[]
#空白行以降の数字の抽出
for s in pd.Series(df_test['Ticket'].values.flatten()): #Pandas.Seriesへ
    fdr= s.rfind(' ') #左から空白の場所を探す。
    if fdr == -1:
        if s == 'LINE':
            Ticket_txt.append(None)
        else:
            Ticket_txt.append(float(s[:]))
    else:
        Ticket_txt.append(float(s[fdr+1:]))
#上書き
df_test['Ticket'] = pd.Series(Ticket_txt)

# 欠損値は平均値を割当てる
df_test['Ticket'].fillna(df_test['Ticket'].mean(),inplace=True)

⑤ 「Fare」のデータ処理

欠損値には平均値を与える。※trainデータには欠損値無し。

# 欠損値は平均値を割当てる
df_test['Fare'].fillna(df_test['Fare'].mean(),inplace=True)

モデル作成

 モデルを作成するために「Pclass、Sex、Age、SibSp、Parch、Ticket、Fare、Embarked」を使用する。 また、各項目の値に差があるため、正規化を実施する。

#説明変数
x = df_train.loc[:,['Pclass','Sex','Age','SibSp','Parch','Ticket','Fare','Embarked']] 

#正解値
y = df_train.loc[:,["Survived"]] 

#正規化 --- x Normalization
for title in x :
    x[title] = (x[title] - x[title].values.min()) / (x[title].values.max() - x[title].values.min())
 学習モデルを作るアルゴリズムはひとまずserkit-learnのモジュール「勾配ブースティングレグレッサー」を使用する。パラメータも下記の通り。

# モデルオブジェクトを生成
clf = GradientBoostingRegressor(n_estimators=100,max_depth=20,random_state=0)
clf.fit(x,y.values.ravel())
y_pred = clf.predict(x)

#モデル評価:決定係数
r2 = r2_score(y, y_pred)

 モデルの評価は0.997を示した。

▶︎
print('決定係数: %.3f' % r2)
<div class = code_re>
決定係数: 0.997
</div>

予測

 上記で作成したモデルを用い、testデータから「Survival」0 or 1 を予測する。説明変数はモデルと同様の項目を用いる。

x_test = df_test.loc[:,['Pclass','Sex','Age','SibSp','Parch','Ticket','Fare','Embarked']] #説明変数

#正規化
for title in x_test :
    x_test[title] = (x_test[title] - x_test[title].values.min()) / (x_test[title].values.max() - x_test[title].values.min())

 モデルに適応し、結果を出力する。

y_test_pred = clf.predict(x_test)

with open("result.csv","w") as f:
    for ele in y_test_pred:
        #f.write(format(ele, '.1f') + '\n')
        if ele > 0.8:
            f.write('1' + '\n')
        else:
            f.write('0' + '\n') 

結果

 予測値をKaggleに提出し、予測結果の正答率を確認した結果、scoreは「0.72727」であった。 Kaggleプラットフォームの仕組みは学べたが、もう少し分析手法を学び精度をあげたい。。

参考

データ処理の参考にしたサイト。