更新日:2020.01.13  作成日:2020.01.13

タイタニック号の生存率予測② -データの可視化と前処理-

はじめに

 前回の記事では、Kaggleプラットフォームの仕組みを学ぶということで、ひとまず解析できる形にデータの前処理をし、891人分のtrainデータ全てを用いてGradientBoostingRegressorでモデルを作成し、testデータに適合した。その際に実際に処理した内容は以下の記事である。

今回の目的

 前回はスコア0.72727だったため、今回はスコア0.8以上を目指す。本記事はデータの前処理と可視化した方法と結果を記す。

タイタニックの生存予測 - データの可視化と前処理 -

Load libraries

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

import os
import re
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import japanize_matplotlib
from IPython.core.display import display

os.chdir('/Users/yoshino/dev/kaggle/01_titanic/data')

#Load in datasets
df_train = pd.read_csv("train.csv") 
df_test = pd.read_csv("test.csv") 

Survived の処理

 死亡者と生存者数の分布を確認する。

sns.countplot(x='Survived',data=df_train)
plt.title('死亡者と生存者数の分布')
plt.xticks([0,1],['死亡者','生存者'])
plt.show()

1

 死亡者と生存者の割合を以下に示す。 結果より、おそらくtestデータの生存者の割合が大体3~4割ぐらいでなければ 作成したモデルが怪しいと考えられる。

▶︎
display(df_train['Survived'].value_counts()/len(df_train['Survived'])) 
<div class = code_re>
0    0.616162
1    0.383838
Name: Survived, dtype: float64
</div>

Sex の処理

 男女別の死亡者と生存者数の分布を以下に示す。男性より女性が優先的に助けられたと推測できる。

#男女別の生存者数
sns.countplot(x='Sex',hue='Survived',data=df_train)
plt.title('男女別の死亡者と生存者数の分布')
plt.legend(['死亡','生存'])
plt.show()

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)

2

Pclass の処理

sns.countplot(x='Pclass',hue='Survived',data=df_train)
plt.title('チケットクラス別の死亡者と生存者数の分布')
plt.legend(['死亡','生存'])
plt.show()

3

Age の処理

#年齢の分布
sns.distplot(df_train['Age'].dropna(),kde=False,bins=30,label='全体')
sns.distplot(df_train[df_train['Survived']==0].Age.dropna(),kde=False,bins=30,label='死亡')
sns.distplot(df_train[df_train['Survived']==1].Age.dropna(),kde=False,bins=30,label='生存')
plt.title('年代別死亡者と生存者数の分布')
plt.legend()
plt.show()

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

4

それぞれの年代ごとにカテゴリー分けをし、傾向を確認する。
コードがイマイチだが、とりあえず以下。

#カテゴリー分け
df_train['CG_Age']=0
df_train.loc[df_train['Age']>=10,'CG_Age'] = 1
df_train.loc[df_train['Age']>=20,'CG_Age'] = 2
df_train.loc[df_train['Age']>=30,'CG_Age'] = 3
df_train.loc[df_train['Age']>=40,'CG_Age'] = 4
df_train.loc[df_train['Age']>=50,'CG_Age'] = 5
df_train.loc[df_train['Age']>=60,'CG_Age'] = 6
df_train.loc[df_train['Age']>=70,'CG_Age'] = 7
df_train.loc[df_train['Age']>=80,'CG_Age'] = 8

df_test['CG_Age']=0
df_test.loc[df_test['Age']>=10,'CG_Age'] = 1
df_test.loc[df_test['Age']>=20,'CG_Age'] = 2
df_test.loc[df_test['Age']>=30,'CG_Age'] = 3
df_test.loc[df_test['Age']>=40,'CG_Age'] = 4
df_test.loc[df_test['Age']>=50,'CG_Age'] = 5
df_test.loc[df_test['Age']>=60,'CG_Age'] = 6
df_test.loc[df_test['Age']>=70,'CG_Age'] = 7
df_test.loc[df_test['Age']>=80,'CG_Age'] = 8

sns.countplot(x='CG_Age',hue='Survived',data=df_train)
plt.title('年代別死亡者と生存者数の分布')
plt.legend(['死亡','生存'])
plt.show()

5

Sibsp の処理

 Sibspは兄弟/配偶者の数を示す。

df_train['CG_Sibsp'] = [i if i <= 1 else 2 for i in df_train['SibSp']]
df_test['CG_Sibsp'] = [i if i <= 1 else 2 for i in df_test['SibSp']]

sns.countplot(x='CG_Sibsp',hue='Survived',data=df_train)
plt.title('同乗している兄弟・配偶者数の分布')
plt.legend(['死亡','生存'])
plt.xticks([0,1,2],['0人','1人','2人以上'])
plt.show()

6

Parch の処理

 Parchは親/子供の数を示す。

df_train['CG_Parch'] = [i if i <= 1 else 2 for i in df_train['Parch']]
df_test['CG_Parch'] = [i if i <= 1 else 2 for i in df_test['Parch']]

sns.countplot(x='CG_Parch',hue='Survived',data=df_train)
plt.title('同乗している両親・子供数の分布')
plt.legend(['死亡','生存'])
plt.xticks([0,1,2],['0人','1人','2人以上'])
plt.show()

7

FamilySize の追加

 FamilySizeは今回追加したラベルである。SibSp(兄弟/配偶者)+Parch(親/子供)+自分で示す。

df_train['FamilySize'] = df_train['SibSp'] + df_train['Parch'] + 1
df_test['FamilySize']  = df_test['SibSp']  + df_test['Parch']  + 1

sns.countplot(x='FamilySize',hue='Survived',data=df_train)
plt.title('同乗している家族数の分布')
plt.legend(['死亡','生存'])
plt.show()

8

上記の図より、1人・5人以上の乗船で生存者より死亡者数が多いことが分かる。
また、5人以上は数が少ないため、1人・2~4人・5人以上でカテゴリ分けをする。

df_train['CG_FamilySize']=0
df_train.loc[df_train['FamilySize']>=2,'CG_FamilySize'] = 1
df_train.loc[df_train['FamilySize']>=5,'CG_FamilySize'] = 2

df_test['CG_FamilySize']=0
df_test.loc[df_test['FamilySize']>=2,'CG_FamilySize'] = 1
df_test.loc[df_test['FamilySize']>=5,'CG_FamilySize'] = 2

sns.countplot(x='CG_FamilySize',hue='Survived',data=df_train)
plt.title('同乗している家族数の分布')
plt.legend(['死亡','生存'])
plt.xticks([0,1,2],['1人','2~4人','5人以上'])
plt.show()

8

正規化を実施し比較した。

▶︎ 
display(pd.crosstab(df_train['CG_FamilySize'],df_train['Survived'],normalize='index'))
<div class = code_re>
Survived	0	1
CG_FamilySize		
0	0.696462	0.303538
1	0.421233	0.578767
2	0.838710	0.161290
</div>

Fare の処理

 Fareは運賃を示す。80以下の数と比較し80以降の数が圧倒的に多く、運賃の差が大きい。そのため80以下と80以上で分ける。

sns.distplot(df_train['Fare'].dropna(),kde=False,hist=True)
plt.title('運賃分布')
plt.show()

df_train['CG_Fare'] = [0 if i <= 80 else 1 for i in df_train['Fare']]
df_test['CG_Fare'] = [0 if i <= 80 else 1 for i in df_test['Fare']]

sns.countplot(x='CG_Fare',hue='Survived',data=df_train)
plt.title('運賃分布')
plt.legend(['死亡','生存'])
plt.show()

9 9

上記の図で生存者・死亡者で差があることが分かる。80以下と80以上で人数が違うため、正規化を実施し比較する。

▶︎ 
display(pd.crosstab(df_train['CG_Fare'],df_train['Survived'],normalize='index'))
<div class = code_re>
Survived	0	1
CG_Fare		
0	0.651163	0.348837
1	0.229730	0.770270
</div>

Embarked の処理

sns.countplot(x='Embarked',hue='Survived',data=df_train)
plt.title('Embarked分布')
plt.legend(['死亡','生存'])
plt.show()

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)

10

Ticket の処理

 前回の処理と同じ。。「A/5 21171」、「STON/O2. 3101282」のように文字と数字が連なっているデータと「113803」のように数字のみのデータとが存在している。文字は処理ができないため、今回は「A/5 21171」から「21171」のように 、文字と数字の間の空白を検知し後ろの数字列を抜き出す処理を実施する。一部「LINE」の文字のみで数字が無い行があるので、その行は全体の平均値を割当てる。  ※10分割して傾向を確認してみたが正直よく分からない。性別や年代と組み合わせて傾向をみた方が良い。(今回はみてないない。)

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)
            #print(None)
        else:
            #print(s)
            Ticket_txt.append(float(s[:]))
    else:
        #print(s[fdr+1:])
        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)

# カテゴリー分けで傾向を見る
df_train['CG_Ticket']=pd.qcut(df_train['Ticket'],10,labels=False)
df_test['CG_Ticket']=pd.qcut(df_test['Ticket'],10,labels=False)

sns.countplot(x='CG_Ticket',hue='Survived',data=df_train)
plt.title('CG_Ticket分布')
plt.legend(['死亡','生存'])
plt.show()

11

Name の処理

▶︎ 
#敬称の抽出(大文字+小文字+. 例:Mr.)
df_train.Name.str.extract('([A-Za-z]+)\.',expand=False).value_counts()
<div class = code_re>
Mr          517
Miss        182
Mrs         125
Master       40
Dr            7
Rev           6
Col           2
Major         2
Mlle          2
Capt          1
Mme           1
Ms            1
Lady          1
Don           1
Sir           1
Countess      1
Jonkheer      1
Name: Name, dtype: int64
</div>
def title_to_num(title):
    if title == 'Master':
        return 1
    elif title == 'Miss':
        return 2
    elif title == 'Mr':
        return 3
    elif title == 'Mrs':
        return 4
    else:
        return 5

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

df_train['Title_num'] = [title_to_num(i) for i in df_train['Title']]
df_test['Title_num'] = [title_to_num(i) for i in df_test['Title']]
sns.countplot(x='Title_num',hue='Survived',data=df_train)
plt.title('Title_num分布')
plt.legend(['死亡','生存'])
plt.show()

12

次回

 処理したデータからモデルを作成し、結果を出す。

参考

カレーちゃんさんの「kaggleのチュートリアル」を拝読しました。読みやすくて勉強になりました。