WEB系の技術を詳しく解説

【機械学習入門】kaggleのタイタニック号生存者予測を、より深い分析を使ってスコアを上げる

以前、【機械学習入門】kaggleでタイタニック号の生存者予測という記事を書いて、機械学習を簡単に体験したのですが、今回は前回も扱った「タイタニック号の生存者予測」についてスコアアップを目指し、データの選定やアルゴリズム等を洗練していこうと思います。

データの確認

もう一度トレーニングデータの項目をおさらいしておきましょう。

PassengerId – 乗客識別連番ID

Survived – 生存フラグ(0=死亡、1=生存)

Pclass – チケットクラス(1=上層クラス、2=一般クラス、3=下層クラス)

Name – 乗客の名前

Sex – 性別(male=男性、female=女性)

Age – 年齢

SibSp – タイタニックに同乗している兄弟/配偶者の数

Parch – タイタニックに同乗している親/子供の数

Ticket – チケット番号

Fare – 料金(おそらく単位は$)

Cabin – 客室番号

Embarked – 出港地(タイタニックへ乗った港。C = Cherbourg、Q = Queenstown、S = Southamptonらしい。)

前回の記事では「Age」「Embarked」についてのみ欠損値の補完をしてみましたが、今回はデータの欠損具合をみても「Cabin(客室番号)」についても無視できないと思うのでそれについても考えていきます。以下、データの欠損具合をもう一度載せておきます。

             欠損数          %
PassengerId    0   0.000000
Survived       0   0.000000
Pclass         0   0.000000
Name           0   0.000000
Sex            0   0.000000
Age          177  19.865320
SibSp          0   0.000000
Parch          0   0.000000
Ticket         0   0.000000
Fare           0   0.000000
Cabin        687  77.104377
Embarked       2   0.224467
             欠損数          %
PassengerId    0   0.000000
Pclass         0   0.000000
Name           0   0.000000
Sex            0   0.000000
Age           86  20.574163
SibSp          0   0.000000
Parch          0   0.000000
Ticket         0   0.000000
Fare           1   0.239234
Cabin        327  78.229665
Embarked       0   0.000000

トレーニングデータ、テストデータ共に決して無視できない「Age」「Cabin」そして欠損数の少ない「Embarked」「Fare」についても追って見て行くことにしましょう。

タイタニック号についての簡単な予備知識

タイタニック号に関しての簡単な予備知識も持っておくと良いと思い、調べてみました。

概要

タイタニック号沈没事故(タイタニックごうちんぼつじこ)とは、1912年4月14日の夜から4月15日の朝にかけて、イギリス・サウサンプトンからアメリカ合衆国・ニューヨーク行きの処女航海中の4日目に、北大西洋で起きた。当時世界最大の客船であったタイタニックは、1912年4月14日の23時40分(事故現場時間)に氷山に衝突した時には2,224人を乗せていた。事故が起きてから2時間40分後の翌4月15日の2時20分に沈没し、1,513人が亡くなった。これは1912年当時、海難事故の最大死者数であった。

Wikipediaより引用

重要そうな項目としては

・23:40に事故が発生(当時睡眠していた人の割合が多そう)、外は暗闇(だったと思われる)。

・2224人が乗船し、2時間20分の間に1513人が亡くなった

このくらいでしょうか。

航路

航路はこのようになります。

データの可視化

機械学習における欠損値の補完は非常に重要なフェーズです(よね?笑)。ここでどのように欠損値を補完するかによってスコアも大幅に変化します。欠損値以外のデータも他のデータに影響を与える可能性もあることから、なるべく多くのデータを可視化して分析します。データを瞬時に把握するのにグラフ等で可視化するのは非常に有効な方法です。

Ageについて

では年齢について考えていきましょう。前回は単純に中央値として補完しましたが、これが正解とは限りません。

ダウンロードしたCSVデータからExelに落とし込んでデータを分析します。

それぞれのデータの代表値を簡単にまとめてみます。

平均値 29.6991176
最頻値 28
中央値 28

平均値と中央値、最頻値ともにそれほど差が無いので中央値で欠損値を補完するのは良い選択であると思われますが、別の観点から視覚化(グラフ化)していきましょう。

10歳間隔で設定した棒グラフ

10歳間隔で、そのレンジに入る層とその人数の関係を可視化したグラフです。この間隔をもう少し狭くしてみます。

最も多い階級は21~25歳の範囲です。ここで、欠損値を28-29で補完して良いのか?という疑問が生まれます。かと言って21-25の範囲で欠損値を補完して良いのか?と言う部分も疑問です。

男女比

男女比構成はこのようになります。男性の方が多いです。

出港地の割合

C = Cherbourg、Q = Queenstown、S = Southamptonです。Southamptonから乗った人の割合が最も多いことが分かります。

階級の割合

過半数が労働階級です。平民階級と上流階級の人数にそれほど差異はありませんが、若干上流階級の方が多い比率になっています。

客室について

客室については欠損値が多く、部屋番号はそれぞれ異なる事から、データを可視化することは非常に困難です。しかしながらその背景を調べることは重要でしょう。

こちらのフランス語のサイトに、詳細な船内の見取り図が添付されています。

ここから推測できることを幾つか挙げておきます。

・デッキに救命ボートが18個設置されている。(船の定員は65名とされている)

・A,B,Cとアルファベットが振られているが、これは船の階層を表している。Aが船の上の階で、Gが船の下の階である。

・A階層は上流階級フロアであり、部屋と遊歩道がある

・B階層は上流階級の部屋と、平民階級が使える遊歩道がある

・B階層の資料から、客室番号奇数が船の右側面にあり、客室番号偶数が船の左側面である

・船の先頭が客室番号が若く、船尾にいくほど客室番号が大きくなる

・A-D階層までは上流階級の部屋でほとんどを占める

・E-G階層は中流階級と労働階級の部屋でほとんどを占める

・F, Gの階層はエレベータが設置されていない

タイタニック号が氷山に衝突したのは船の右側なので、船の右側から下の層または船尾へと水が入り込みます。よって下の階層の部屋番号が奇数の人が生存に不利であることが予想されます。

各データにおける生存率の洗い出し

それぞれのデータについて可視化してきましたが、これらがどのように生存率に関わってくるのかが重要です。

性別における生存率

性別における生存率を調査します。基本的に女性優先で救助されたはずなので、女性の生存率の方が高いはずです。確認してみましょう。

 #全男性のデータフレーム
def man_count(df):
  return df[(df['Sex'] == 'male')]

#全女性のデータフレーム
def woman_count(df):
  return df[(df['Sex'] == 'female')]

#男性の生存者データフレーム
def man_survived_table(df):
  return df[((df['Sex'] == 'male') & (df['Survived'] == 1))]

#女性の生存者データフレーム
def woman_survived_table(df):
  return df[((df['Sex'] == 'female') & (df['Survived'] == 1))]

# 全体の生存者データフレーム
def passenger_survived_table(df):
  return df[(df['Survived'] == 1)]

#男性の生存率
def man_survived_rate(df):
  print("全男性:" + str(len(man_count(df))) + "人")
  print("男性生存数:"+ str(len(man_survived_table(df))) + "人")
  return str((100 * len(man_survived_table(df)) / len(man_count(df)))) + "%"

#女性の生存率
def woman_survived_rate(df):
  print("全女性:" + str(len(woman_count(df))) + "人")
  print("女性生存数:"+ str(len(woman_survived_table(df))) + "人")
  return str((100 * len(woman_survived_table(df)) / len(woman_count(df)))) + "%"

#全体の生存率
def survived_rate(df):
  print("全乗客:" + str(len(df)) + "人")
  print("生存数:"+ str(len(passenger_survived_table(df))) + "人")
  return str(100 * len(passenger_survived_table(df)) / len(df)) + "%"

少し冗長ですが関数を組んでみました。print() でそれぞれの値を確認します。

print(man_survived_rate(train))
print(woman_survived_rate(train))
print(survived_rate(train))
# 男性の生存率
全男性:577人
男性生存数:109人
18.890814558058924%
#女性の生存率
全女性:314人
女性生存数:233人
74.20382165605096%
# 全体の生存率
全乗客:891人
生存数:342人
38.38383838383838%

このように女性の生存率の方が高い事が確認できました。

階級における生存率

上流階級ほど優先的に救助されるので、上流階級の方が助かる確率が高いことが予想されます。確認してみましょう。

# 階級ごとの乗船者データフレーム
def class_passengers(df, num):
  return df[(df['Pclass'] == num)]

# 階級ごとの生存者データフレーム/class_survived(データフレーム, クラス):
def class_survived(df, num):
  return df[((df['Pclass'] == num) & (df['Survived'] == 1))]

# 階級ごとの生存者率
def class_survived_rate(df, num):
  print("クラス" + str(num) + "の全乗客:" + str(len(class_passengers(df, num))) + "人")
  print("生存数:"+ str(len(class_survived(df, num))) + "人")
  return str(100 * len(class_survived(df, num)) / len(class_passengers(df, num))) + "%"

同様に出力を見てみましょう。

print(class_survived_rate(train, 1))
print(class_survived_rate(train, 2))
print(class_survived_rate(train, 3))
クラス1の全乗客:216人
生存数:136人
62.96296296296296%

クラス2の全乗客:184人
生存数:87人
47.28260869565217%

クラス3の全乗客:491人
生存数:119人
24.236252545824847%

上流階級になるほど、生存率が高くなることが確認できました。

年齢における生存率

子供の方が優先的に救助されるので、成人していない年齢の人程助かる確率が高いと予想されます。

# 年齢におけるデータフレーム/age_passengers(データフレーム, 最小年齢, 最大年齢):
def age_passengers(df, min, max):
  data = df[(df['Age'] >= min) & (df['Age'] <= max)]
  return data[['Age', 'Name', 'Survived']]

# 年齢における生存データフレーム
def age_passengers_survived(df, min, max):
  data = df[(df['Age'] >= min) & (df['Age'] <= max) & (df['Survived'] == 1)]
  return data[['Age', 'Name', 'Survived']]

# 年齢における生存率
def age_passengers_survived_rate(df, min, max):
  print(str(min) + "歳 ~ " + str(max) + "歳における乗船者数:" + str(len(age_passengers(df, min, max))))
  print(str(min) + "歳 ~ " + str(max) + "歳における生存者数:" + str(len(age_passengers_survived(df, min, max))))
  return str(min) + "歳 ~ " + str(max) + "歳における生存率:" + str(100 * len(age_passengers_survived(df, min, max)) / len(age_passengers(df, min, max))) + "%"

今回の関数は最小値と最大値を任意に設定できる様に関数を実装しました。では詳細なデータを見ていきましょう。

率のみを簡易に確認するために、一部関数をコメントアウトしておきます。

# 年齢における生存率
def age_passengers_survived_rate(df, min, max):
  # print(str(min) + "歳 ~ " + str(max) + "歳における乗船者数:" + str(len(age_passengers(df, min, max))))
  # print(str(min) + "歳 ~ " + str(max) + "歳における生存者数:" + str(len(age_passengers_survived(df, min, max))))
  return str(min) + "歳 ~ " + str(max) + "歳における生存率:" + str(100 * len(age_passengers_survived(df, min, max)) / len(age_passengers(df, min, max))) + "%"
print(age_passengers_survived_rate(train, 0, 10))
print(age_passengers_survived_rate(train,  11, 20))
print(age_passengers_survived_rate(train, 21, 30))
print(age_passengers_survived_rate(train, 31, 40))
print(age_passengers_survived_rate(train, 41, 50))
print(age_passengers_survived_rate(train, 51, 60))
print(age_passengers_survived_rate(train, 61, 70))
print(age_passengers_survived_rate(train, 71, 80))

結果は以下の様になります。

0歳 ~ 10歳における生存率:59.375%
11歳 ~ 20歳における生存率:38.26086956521739%
21歳 ~ 30歳における生存率:36.68122270742358%
31歳 ~ 40歳における生存率:45.09803921568628%
41歳 ~ 50歳における生存率:39.285714285714285%
51歳 ~ 60歳における生存率:40.476190476190474%
61歳 ~ 70歳における生存率:23.529411764705884%
71歳 ~ 80歳における生存率:25.0%

0 ~ 10歳における生存率がほぼ60%と、他の階級に比べて生存率が高い事が確認できました。

0歳 ~ 5歳における生存率:70.45454545454545%

特に乳幼児の生存率がかなり高いです。

その他の項目について

名前の名称によって家族を推測したりする方法もあるみたいですが、今回は割愛します。

スポンサードリンク

欠損値の対応

さて、色々と視覚化、数値化できた所で本題に入ります。欠損値の処理についてです。欠損値があることで

・作業効率が低下する

・データ分析作業やデータ加工作業が複雑になる

・結果にバイアスが生じる

事を理解しておく必要があります。

欠損メカニズムの理解

欠損値の対応をしていく前に、欠損メカニズムの理解が必要です。

欠損値には3つのタイプがあります。

①データが完全にランダムに欠損する

②データが測定されている値に依存して欠損する

③データが欠損データに依存して欠損する

①のことをMCAR(Missing Completely At Random)、②のことをMAR(Missing At Random)、③のことをMNAR(Missing Not At Random)と呼びます。

MCARの場合

MCARは完全に独立してランダムに欠損しているので、どのような処理を施しても母集団への推定に全く影響しません。よってよく使われるのは「ペアワイズ削除」や「リストワイズ削除」です。

しかしながら欠損が多い時にこの手法を使うのは勿体無いと言われます。本来であればデータを測定しているので、削除すると単純に精度が落ちてしまうからです。

では欠損値に何を代入すれば良いのでしょうか。平均値などの単一の値を代入した場合、推定値にバイアスが掛かります。ここは様々な要素を考慮しつつ、代入する値は要検討です。

MARの場合

MARの場合、リストワイズ削除でも推定値にバイアスが生じます。というのも一部欠損した人の、欠損していないデータ(観測データ)と関連があるため、そのデータを削除してしまうと特定の傾向を持つデータがなくなってしまうからです。つまり、推定に必要なデータを無駄に削除してしまうので精度が落ちてしまいます。

この対処法としては

・観測データ全てを用いた推定方法で欠損値を埋める

・多重代入による推定を行う

として解決します。基本的には「最尤法」と呼ばれる推定方法を使うと精度の良い推定結果が得られる様です。

MNARの場合

MNARの場合、有効な対処法はありません。(ガーン)

欠損値の補完

これらを踏まえて、(やっと)データの処理をしていきます。

必要なモジュールのインポート

まず始めに必要なモジュールのインポートを済ませておきます。

import pandas as pd
import pylab as plt
import numpy as np
from random import random
from sklearn import tree
import pydotplus as pdp
from IPython.display import Image
from graphviz import Digraph
from sklearn.externals.six import StringIO
import matplotlib as mpl
%matplotlib inline

データの読み込み

必要なデータセットを読み込んでおきます(test.csvとtrain.csvをファイルと同じ階層に置いておきます)。

train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")

Age

Ageの欠損値を穴埋めします。何で穴埋めするかが非常に重要です。前回は「全体の乗船客の年齢の観測値の平均」すなわち「全体からの平均」で穴埋めしましたが今回はより細かく分析していきます。

英語圏では、敬称によってある程度年齢が推測できるという事が直感的に分かります。というのも英語圏の

・Mr.

・Ms.

・Mrs.

・Master.

・Miss.

といった代表的な敬称は、「性別・年齢・既婚か未婚か」で呼称が分割されているからです。この情報を元に、それぞれの敬称のデータの年齢の平均値を算出し、欠損値を補完します。

# Ageの欠損値をそれぞれの敬称の平均値で埋める
train.loc[(train['Name'].str.contains('Mr\.')) & (train['Age'].isnull()), 'Age'] = train[train['Name'].str.contains('Mr\.')].Age.mean()
train.loc[(train['Name'].str.contains('Mrs\.')) & (train['Age'].isnull()), 'Age'] = train[train['Name'].str.contains('Mrs\.')].Age.mean()
train.loc[(train['Name'].str.contains('Miss\.')) & (train['Age'].isnull()), 'Age'] = train[train['Name'].str.contains('Miss\.')].Age.mean()
train.loc[(train['Name'].str.contains('Master\.')) & (train['Age'].isnull()), 'Age'] = train[train['Name'].str.contains('Master\.')].Age.mean()
train.loc[(train['Name'].str.contains('Dr\.')) & (train['Age'].isnull()), 'Age'] = train[train['Name'].str.contains('Dr\.')].Age.mean()

Cabin

Cabinの欠損値を補完します。データの欠損率が高いのでカラム自体を削除して機械学習には考慮しないという選択肢もあったのですが、今回は「救命ボートと部屋の物理的な距離」が生存率に大きく影響を与えると見て考慮することにします。

タイタニック号は上から順にA,B,C,…とデッキ番号が付いています。

Cabinのアルファベットは恐らくデッキ階層を示しているので、トレーニングデータから上層階級の人はタイタニック号の上部、下層階級の人はタイタニック号の底部に集まっていることが分かります。

上の表は観測値から算出したデータとなっています。Pclass2,3を合体させているのは、①Pclass2,3でデッキ階層が殆ど被っているという理由から、そして②サンプル数を多くしてデータの信頼性を上げる為です。

欠損値を埋める方法として、Pclassにおけるデッキ階層の割合を用いて計算する手法を採用します。この割合でデッキ階層のアルファベットをランダムに返す関数を作成します。

def cabin_select():
    if 0.335 > random():
        return 'C'
    elif 0.267 > random():
        return 'B'
    elif 0.153 > random():
        return 'D'
    elif 0.142 > random():
        return 'E'
    else:
        return 'A'

def cabin_select_low():
    if 0.464 > random():
        return 'F'
    elif 0.25 > random():
        return 'E'
    elif 14.3 > random():
        return 'D'
    else:
        return 'G'

これを用いて欠損値を補完します。

#部屋番号を整形し、ランダムに補完
cabin = train['Cabin'].replace('[0-9]+', '', regex=True).replace(r'\s+', r'', regex=True).replace(r'([\w])[\w]+', r'\1', regex=True)
train['Cabin_replaced'] = cabin
train.loc[(train['Pclass'] == 1) & (train['Cabin_replaced'].isnull()), 'Cabin_replaced'] = cabin_select()
train.loc[((train['Pclass'] == 2) | (train['Pclass'] == 3)) & (train['Cabin_replaced'].isnull()), 'Cabin_replaced'] = cabin_select_low()

さらに、機械学習で扱いやすいようにこのデータを数値に変換します。

#部屋番号を数値に変換
train.loc[(train['Cabin_replaced'] == 'A'), 'Cabin_replaced'] = 0
train.loc[(train['Cabin_replaced'] == 'B'), 'Cabin_replaced'] = 1
train.loc[(train['Cabin_replaced'] == 'C'), 'Cabin_replaced'] = 2
train.loc[(train['Cabin_replaced'] == 'D'), 'Cabin_replaced'] = 3
train.loc[(train['Cabin_replaced'] == 'E'), 'Cabin_replaced'] = 4
train.loc[(train['Cabin_replaced'] == 'F'), 'Cabin_replaced'] = 5
train.loc[(train['Cabin_replaced'] == 'G'), 'Cabin_replaced'] = 6
train.loc[(train['Cabin_replaced'] == 'T'), 'Cabin_replaced'] = 7

ちなみにこのコードでは「Cabin_replaced」というカラムを新しく作成して、そこにデータを格納しています。

その他のデータについて

欠損値以外にもその他のカラムについて注目し、データを整形していきます。この工程もスコアに響いてきます。

性別の数値変換

タイタニック号が沈没した当時は、女性が優先されて救出されたという背景があります。

上のグラフを見ると確かに、男性は死亡率の方が高く、女性は生存率の方が高いです。

性別では機械学習しやすいように、文字列データを数値データに変換します。

#性別の数値変換
train.loc[(train['Sex'] == 'male'), 'Sex'] = 0
train.loc[(train['Sex'] == 'female'), 'Sex'] = 1

maleが0、femaleが1としています。

身内の数によるデータ分割

SibSp, Parchの合計数を代入した「Family」という新しいカラムを作成し、データを分析してみます。

ご覧の通り、1人の時(x軸が0の時)に死亡率が比較的高く、また5人(x軸が4の時)以上になっても死亡率が比較的高い事がこのグラフから分かります。

また、1人の時のデータ数のみ圧倒的にサンプル数が多いので、[0-1), [1-4), [4-]の3グループ(生存比率が類似しているグループ)に分けてデータを整形していきます。

#Familyカラムを追加
train.loc[(train['SibSp'] == 0) & (train['Parch'] == 0), 'Family'] = 0
train.loc[(train['SibSp'] > 0) | (train['Parch'] > 0), 'Family'] = train[['Parch', 'SibSp']].sum(axis=1)
#3グループに分割
train.loc[(train['Family'] == 0), 'Family'] = 0
train.loc[(train['Family'] >= 1) & (train['Family'] < 4), 'Family'] = 1
train.loc[(train['Family'] >= 4), 'Family'] = 2

運賃によるデータ分割

低賃金の乗客が殆どであり、これもFamily同様に類似した生存比率に応じて3グループに分割していきます。

#Fareの分類(10より小さければ0, [10-50)は1, [50-]は2とする)
train.loc[(train['Fare'] < 10), 'Fare_div'] = 0
train.loc[(train['Fare'] == 10), 'Fare_div'] = 1
train.loc[((train['Fare'] > 10) & (train['Fare'] < 50)), 'Fare_div'] = 1
train.loc[(train['Fare'] == 50), 'Fare_div'] = 2
train.loc[(train['Fare'] > 50), 'Fare_div'] = 2

必要ないカラムを削除する

さて、ここまでできたら必要ないカラムを削除します。

train = train.drop(['Name','Cabin', 'Ticket', 'SibSp', 'Parch', 'Embarked', 'Fare'], axis=1)

この時点で(トレーニング)データセットはこのようになっているはずです。

ここまでできたら、トレーニングデータについてのデータ整形はおしまいです。

テストデータに対して同様の処理を行う

上と同様、テストデータにも同じ処理を施します。

#テストデータに関して同様の作業
test.loc[(test['Name'].str.contains('Mr\.')) & (test['Age'].isnull()), 'Age'] = test[test['Name'].str.contains('Mr\.')].Age.mean()
test.loc[(test['Name'].str.contains('Mrs\.')) & (test['Age'].isnull()), 'Age'] = test[test['Name'].str.contains('Mrs\.')].Age.mean()
test.loc[(test['Name'].str.contains('Miss\.')) & (test['Age'].isnull()), 'Age'] = test[test['Name'].str.contains('Miss\.')].Age.mean()
test.loc[(test['Name'].str.contains('Master\.')) & (test['Age'].isnull()), 'Age'] = test[test['Name'].str.contains('Master\.')].Age.mean()
# Msについて⇨全年齢の平均値を代入
test.loc[(test['Name'].str.contains('Ms\.')) & (test['Age'].isnull()), 'Age'] = test.Age.mean()
#性別
test.loc[(test['Sex'] == 'male'), 'Sex'] = 0
test.loc[(test['Sex'] == 'female'), 'Sex'] = 1
#部屋番号を整形し、ランダムに補完
cabin = test['Cabin'].replace('[0-9]+', '', regex=True).replace(r'\s+', r'', regex=True).replace(r'([\w])[\w]+', r'\1', regex=True)
test['Cabin_replaced'] = cabin
test.loc[(test['Pclass'] == 1) & (test['Cabin_replaced'].isnull()), 'Cabin_replaced'] = cabin_select()
test.loc[((test['Pclass'] == 2) | (test['Pclass'] == 3)) & (test['Cabin_replaced'].isnull()), 'Cabin_replaced'] = cabin_select_low()
#部屋番号を数値に変換
test.loc[(test['Cabin_replaced'] == 'A'), 'Cabin_replaced'] = 0
test.loc[(test['Cabin_replaced'] == 'B'), 'Cabin_replaced'] = 1
test.loc[(test['Cabin_replaced'] == 'C'), 'Cabin_replaced'] = 2
test.loc[(test['Cabin_replaced'] == 'D'), 'Cabin_replaced'] = 3
test.loc[(test['Cabin_replaced'] == 'E'), 'Cabin_replaced'] = 4
test.loc[(test['Cabin_replaced'] == 'F'), 'Cabin_replaced'] = 5
test.loc[(test['Cabin_replaced'] == 'G'), 'Cabin_replaced'] = 6
test.loc[(test['Cabin_replaced'] == 'T'), 'Cabin_replaced'] = 7
#Familyカラムを追加
test.loc[(test['SibSp'] == 0) & (test['Parch'] == 0), 'Family'] = 0
test.loc[(test['SibSp'] > 0) | (test['Parch'] > 0), 'Family'] = test[['Parch', 'SibSp']].sum(axis=1)
#3グループに分割
test.loc[(test['Family'] == 0), 'Family'] = 0
test.loc[(test['Family'] >= 1) & (test['Family'] < 4), 'Family'] = 1
test.loc[(test['Family'] >= 4), 'Family'] = 2
#Fareには同様に中央値で欠損データを補完
test.iat[152, 8]= test.Fare.median()
# test['Fare'].fillna(test['Fare'].median())
#Fareの分類(10より小さければ0, [10-50)は1, [50-]は2とする)
test.loc[(test['Fare'] < 10), 'Fare_div'] = 0
test.loc[(test['Fare'] == 10), 'Fare_div'] = 1
test.loc[((test['Fare'] > 10) & (test['Fare'] < 50)), 'Fare_div'] = 1
test.loc[(test['Fare'] == 50), 'Fare_div'] = 2
test.loc[(test['Fare'] > 50), 'Fare_div'] = 2
#必要ないカラムを削除
test = test.drop(['Name','Cabin', 'Ticket', 'SibSp', 'Parch', 'Embarked', 'Fare'], axis=1)

機械学習させる

終盤です。今回も前回同様決定木で予測モデルを作成します。

このアルゴリズムが最適かどうかと言われればそうでは無いですが、決定木はアルゴリズムが分かりやすく、視覚化した時に何のデータが影響を与えているのかが分かり、その後のデータのチューニングがしやすいので決定木を選択しました。

まずは目的変数と説明変数をそれぞれ定義します

# 目的変数と説明変数を決定して取得
target = train['Survived'].values
explain = train[['Pclass', 'Sex', 'Age', 'Fare_div', 'Cabin_replaced', 'Family']].values

次に、決定木を作成し学習させます。

# 決定木の作成
d_tree = tree.DecisionTreeClassifier(max_depth=4)
#fit()で学習させる。第一引数に説明変数、第二引数に目的変数
d_tree = d_tree.fit(explain, target)

これを用いてテストデータに予測モデルを適応させます。

# #testデータから説明変数を抽出
test_explain = test[['Pclass', 'Sex', 'Age', 'Fare_div', 'Cabin_replaced', 'Family']].values
#predict()メソッドで予測する
prediction = d_tree.predict(test_explain)

print(prediction) で以下のデータが確認できます。

[0 1 0 0 1 0 0 1 1 1 0 0 1 1 1 1 0 0 1 0 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1
 1 0 1 1 0 0 1 1 0 0 0 1 1 1 0 1 1 0 0 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 0 0 0
 1 0 0 1 0 1 1 1 1 0 0 1 1 1 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 0 1 0 0 0 0 0 0
 1 1 1 1 1 0 1 0 1 1 1 1 0 0 1 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 1 0 0
 0 1 1 0 0 1 0 0 1 1 0 1 1 1 1 0 0 1 1 0 1 1 0 0 1 0 0 1 1 1 1 1 0 1 1 0 1
 0 1 1 0 0 1 0 1 0 1 0 1 1 0 0 0 1 1 1 0 0 0 0 1 0 0 0 1 1 0 0 1 1 1 0 1 0
 1 0 1 1 0 1 0 0 1 1 1 0 1 0 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 0 0 0 0 0 1
 0 1 0 1 1 0 0 0 0 1 0 0 0 1 1 0 1 0 1 1 0 1 1 1 1 1 0 0 1 0 0 0 0 0 1 0 0
 1 1 0 0 0 0 1 0 1 1 1 1 1 1 0 0 0 0 1 1 1 0 0 1 0 0 0 0 1 0 1 0 1 0 1 0 0
 1 0 0 0 1 0 0 0 0 0 1 1 1 0 0 0 1 1 0 1 1 1 1 1 0 0 1 0 1 1 0 1 0 1 0 1 0
 1 1 1 0 1 1 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 1 0 0 1 0 1 0 0 1 1 1 0 1 0 1
 1 1 1 1 1 0 0 1 0 0 1]

この出力をkaggle用に変換させます。

PassengerId = np.array(test["PassengerId"]).astype(int)
result = pd.DataFrame(prediction, PassengerId, columns = ["Survived"])

csvファイルとして出力します

result.to_csv("output.csv",index_label = ["PassengerId"])

kaggleでスコアを確認する

先ほど出力した「output.csv」をkaggleに提出し、スコアリングしてみます!

ドキドキ…

おお!前回より割とスコアが上がりました(0.79425)!!しかし80%は超えず…

まとめ

今回はデータからわかる事以外の要素も考慮して色々データを弄ってみました。結果的にスコアが上がったのですが、複雑な処理をした割には80%は超えずという結果になってしまいました。

もっとシンプルな方法でスコアを上げる方法もあると思いますので、各々今回の例を参考にしつつ、独自のデータ選定や整形、欠損値の補完を行い、適した予測モデルで機械学習を行ってみてください。

タイタニック号の問題は機械学習初学者にとって非常に勉強になるので、チャレンジした事無い人は是非やってみては如何でしょうか?

ちなみに決定木は以下のようになりました。笑

スポンサードリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です