はじめに
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())
# モデルオブジェクトを生成
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プラットフォームの仕組みは学べたが、もう少し分析手法を学び精度をあげたい。。
参考
データ処理の参考にしたサイト。