10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KaggleのTitanicで生データを確認する(kaggle⑥)

Last updated at Posted at 2020-01-29

#はじめに
初めてKaggle(カグル)のコンペに参加してみたお話です。
前回の「KaggleのTitanicでscikit-learnの全モデルを試す」では、scikit-learnのモデルに対して「交差検証」を行うことでスコアを少し上げることができました。
今回は、本来なら最初にやるべきだった「生データを確認する」ことをやってみたいと思います。

#目次
1.生データを確認する意義
2.結果
3.生データを確認する
4.学習する
5.全コード
6.まとめ

履歴

#1.生データを確認する意義

会社を変える分析の力という本を読みました。
本の内容の1つに、「データを分析する前に生データを確認しましょう」と書かれています。
生データを見ないと異常値も発見できない。
データ分析を始める前に、まずは生データをビジュアル化して異常値がないか視覚的に見る。
そういった習慣を身につけてくださいとのことです。
生データを確認し、異常値の確認やデータの利用方法など再度確認してみましょう。

#2.結果

結果から言うと、入力データを精査することで、さらにスコアが少しあがり「0.80382」になりました。
上位9%(2020/1/7現在)という結果です。
提出までの流れを見ていきたいと思います。

#3.生データを確認する

いくつかの生データを確認してみましょう。

Fare(運賃)

pclass(チケットクラス)ごとに運賃を散布図にしてみます。
以下のようになりました。

20200109_01.png

横軸がpclassです。
「1」の運賃が高い傾向にあります。チケットクラスとしては、1 > 2 > 3 の順でグレードが良くなるようです。
散布図から運賃「0」がそれぞれのpclassにあることが分かります。
生データを見てみましょう。運賃(Fare)の昇順でソートします。

PassengerId Survived Pclass Sex Age SibSp Parch Ticket Fare Cabin Embarked
180 0 3 male 36 0 0 LINE 0 S
264 0 1 male 40 0 0 112059 0 B94 S
272 1 3 male 25 0 0 LINE 0 S
278 0 2 male 0 0 239853 0 S
303 0 3 male 19 0 0 LINE 0 S
414 0 2 male 0 0 239853 0 S
467 0 2 male 0 0 239853 0 S
482 0 2 male 0 0 239854 0 S
598 0 3 male 49 0 0 LINE 0 S
634 0 1 male 0 0 112052 0 S
675 0 2 male 0 0 239856 0 S
733 0 2 male 0 0 239855 0 S
807 0 1 male 39 0 0 112050 0 A36 S
816 0 1 male 0 0 112058 0 B102 S
823 0 1 male 38 0 0 19972 0 S
379 0 3 male 20 0 0 2648 4.0125 C
873 0 1 male 33 0 0 695 5 B51 B53 B55 S
327 0 3 male 61 0 0 345364 6.2375 S
844 0 3 male 34.5 0 0 2683 6.4375 C

Fareの昇順にするとFare「0」でPClassが1、2、3が存在しています。
Fare「0」は無料ではなく「運賃不明」の意味が近そうです。
Fare「0」は学習データから除外しましょう。
Fare「0」を除外して再度散布図を作成すると以下になります。

20200109_02.png

少し見やすくなりました。pclass「1」の小さい点も気になります。
上の表をみると、pclass「1」でFare「5」のデータがあります。
これも異常値の可能性があるため、除外しましょう。

20200109_03.png

pclassごとに運賃の幅がある程度定まった散布図になりました。

Ticket(チケット番号)

チケット番号は名義尺度です。
チケット番号の昇順に並べてみます。

PassengerId Survived Pclass Sex Age SibSp Parch Ticket Fare Cabin Embarked
258 1 1 female 30 0 0 110152 86.5 B77 S
505 1 1 female 16 0 0 110152 86.5 B79 S
760 1 1 female 33 0 0 110152 86.5 B77 S
263 0 1 male 52 1 1 110413 79.65 E67 S
559 1 1 female 39 1 1 110413 79.65 E67 S
586 1 1 female 18 0 2 110413 79.65 E68 S
111 0 1 male 47 0 0 110465 52 C110 S
476 0 1 male 0 0 110465 52 A14 S
431 1 1 male 28 0 0 110564 26.55 C52 S
367 1 1 female 60 1 0 110813 75.25 D37 C

チケット番号を見ると、数字のみであったり、文字と数字の組み合わせだったり、規則性は読み取れません。
同じチケット番号の人がいることも分かります。
氏名を見ると同じチケット番号の人は、同じ姓であることが多いです。家族だと思われます。
また、同じチケット番号の人の Survived と見比べてみると、同じチケット番号だと Survived が同じ傾向にあります。
チケット番号でラベル付けする方針で検討します。
以下のイメージです。

PassengerId Survived Ticket Ticket(ラベル)
505 1 110152 チケットA
258 1 110152 チケットA
760 1 110152 チケットA
586 1 110413 チケットB
559 1 110413 チケットB
263 0 110413 チケットB
111 0 110465 チケットC
476 0 110465 チケットC
431 1 110564 NaN
367 1 110813 NaN

同じチケット番号をグループ化したいため、重複がないチケットは「NaN」にします。
チケットA、Bをそのまま数字化してもよいのですが、ラベル付けであることを明記するため、One-Hot エンコーディングを行います。
以下のようなイメージです。
ソースコードは後で記載しますが、 pandas.get_dummies で One-Hot エンコーディングできます。

PassengerId Survived チケットA チケットB チケットC
505 1 1 0 0
258 1 1 0 0
760 1 1 0 0
586 1 0 1 0
559 1 0 1 0
263 0 0 1 0
111 0 0 0 1
476 0 0 0 1
431 1 0 0 0
367 1 0 0 0

sibsp(兄弟/配偶者の数) / parch(親/子供の数)

sibsp と parch は以前もグラフ化していましたが、再度グラフ化してみましょう。
20200119_02.png
20200119_01.png
相関係数では有意な差はありませんでしたが、sibsp、parchともに、グラフをみると以下が分かります。
・sibsp、parchが 0 の場合、Survived は 0 の方が多い(倍くらい)
・sibsp、parchが 1 または 2の場合、Survived の 0 と 1 は同じくらい
・sibsp、parchが 3以上は母数が少ない

前回は、相関係数が小さいため学習データから除外しましたが、0、1、2 はラベルデータとして利用できそうです。
sibsp と parch が3以下のデータのみ抽出して相関係数を確認すると以下になりました。

# SibSpが3未満の場合のクラメール連関係数を確認する
df_SibSp = df[df['SibSp'] < 3]
cramersV(df_SibSp['Survived'], df_SibSp['SibSp'])

# SurvivedとSibSp(3未満)のクロス集計表を表示する
cross_sibsp = pandas.crosstab(df_SibSp['Survived'], df_SibSp['SibSp'])
cross_sibsp

cross_sibsp.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()
0.16260950922794606

20200120_01.png

相関係数は 0.16 で「弱い相関あり」になりました。
省略しますが、Parch も同様の結果になります。
そこで、Ticketと同様、SibSp と Parch も One-Hot エンコーディングしてみましょう。
以下のようなイメージです。

PassengerId Survived SibSp_1 SibSp_2 SibSp_3 SibSp_4 SibSp_5 SibSp_8
505 1 0 0 0 0 0 0
258 1 0 0 0 0 0 0
760 1 0 0 0 0 0 0
586 1 0 0 0 0 0 0
559 1 1 0 0 0 0 0
263 0 1 0 0 0 0 0
111 0 0 0 0 0 0 0
476 0 0 0 0 0 0 0
431 1 0 0 0 0 0 0
367 1 1 0 0 0 0 0

Cabin(客室番号)

Cabinを確認してみましょう。
検証データ(train.csv)約900件中、Cabin200件程度です。
Cabinは名義尺度です。
先頭1文字を同じグループとみなしてグループ化してみると以下になりました。

20200120_03.png

それぞれ、Survived「1」が多い結果になっています。
先頭1文字のラベルデータは有用そうです。
Cabinも 先頭1文字を One-Hot エンコーディングしてみましょう。
以下のようなイメージです。

PassengerId Survived Cabin_A Cabin_B Cabin_C Cabin_D Cabin_E Cabin_F Cabin_G Cabin_T
505 1 0 1 0 0 0 0 0 0
258 1 0 1 0 0 0 0 0 0
760 1 0 1 0 0 0 0 0 0
586 1 0 0 0 0 1 0 0 0
559 1 0 0 0 0 1 0 0 0
263 0 0 0 0 0 1 0 0 0
111 0 0 0 1 0 0 0 0 0
476 0 1 0 0 0 0 0 0 0
431 1 0 0 1 0 0 0 0 0
367 1 0 0 0 1 0 0 0 0

#4.学習する

ここまでの状況をもとに学習してみましょう。
入力データは以下です。

No 項目名 項目名(日本語) 変換方式
1 Pclass チケットクラス 標準化
2 Sex 性別 数値化
3 SibSp 兄弟/配偶者 one-hotエンコーディング
4 Parch 親/子供 one-hotエンコーディング
5 Ticket チケット番号 one-hotエンコーディング
6 Fare 運賃 標準化
7 Cabin 客室番号 先頭1文字をone-hotエンコーディング

kaggle⑤の全モデルを試す、さらに、kaggle④のグリッドサーチでモデルとパラメータを試したところ、以下になりました。

GradientBoostingClassifier(criterion='friedman_mse', init=None,
                           learning_rate=0.1, loss='exponential', max_depth=6,
                           max_features=None, max_leaf_nodes=None,
                           min_impurity_decrease=0.0, min_impurity_split=None,
                           min_samples_leaf=1, min_samples_split=2,
                           min_weight_fraction_leaf=0.0, n_estimators=100,
                           n_iter_no_change=None, presort='auto',
                           random_state=1, subsample=1.0, tol=0.0001,
                           validation_fraction=0.1, verbose=0,
                           warm_start=False)

#5.全コード
全コードは以下です。
ただし、実際に学習させると「Cabin」を含めた場合にスコアが上がらなかったため、最終的にCabinは除外しました。

import numpy
import pandas 
import matplotlib.pyplot as plt

######################################
# クラメールの連関係数
# Cramer's coefficient of association
# 0.5  >= : 非常に強い相関(Very strong correlation) 
# 0.25 >= : 強い相関(strong correlation) 
# 0.1  >= : やや弱い相関(Slightly weak correlation) 
# 0.1 < :  相関なし(No correlation) 
######################################
def cramersV(x, y):
    """
    Calc Cramer's V.

    Parameters
    ----------
    x : {numpy.ndarray, pandas.Series}
    y : {numpy.ndarray, pandas.Series}
    """
    table = numpy.array(pandas.crosstab(x, y)).astype(numpy.float32)
    n = table.sum()
    colsum = table.sum(axis=0)
    rowsum = table.sum(axis=1)
    expect = numpy.outer(rowsum, colsum) / n
    chisq = numpy.sum((table - expect) ** 2 / expect)
    return numpy.sqrt(chisq / (n * (numpy.min(table.shape) - 1)))

######################################
# 相関比
# Correlation ratio
# 0.5  >= : 非常に強い相関(Very strong correlation) 
# 0.25 >= : 強い相関(strong correlation) 
# 0.1  >= : やや弱い相関(Slightly weak correlation) 
# 0.1 < :  相関なし(No correlation) 
######################################
def CorrelationV(x, y):
    """
    Calc Correlation ratio 

    Parameters
    ----------
    x : nominal scale {numpy.ndarray, pandas.Series}
    y : ratio   scale {numpy.ndarray, pandas.Series}
    """
    variation = ((y - y.mean()) ** 2).sum()
    inter_class = sum([((y[x == i] - y[x == i].mean()) ** 2).sum() for i in numpy.unique(x)])
    correlation_ratio = inter_class / variation
    return 1 - correlation_ratio

# train.csvを読み込む
# Load train.csv
df = pandas.read_csv('/kaggle/input/titanic/train.csv')

# test.csvを読み込む
# Load test.csv
df_test = pandas.read_csv('/kaggle/input/titanic/test.csv')

# 'PassengerId'を抽出する(結果と結合するため)
# Extract 'PassengerId'(To combine with the result)
df_test_index = df_test[['PassengerId']]

df_all = pandas.concat([df, df_test], sort=False)

##############################
# データ前処理
# 必要な項目を抽出する
# Data preprocessing 
# Extract necessary items
##############################
df = df[['Survived', 'Pclass', 'Sex', 'SibSp', 'Parch', 'Ticket', 'Fare']]
df_test = df_test[['Pclass', 'Sex', 'SibSp', 'Parch', 'Ticket', 'Fare']]

##############################
# Fare と pclassの散布図をプロットする
# Draw scatter plot of Fare and pclass
##############################
plt.scatter(df['Pclass'], df['Fare'])
plt.xticks(numpy.linspace(1, 3, 3))
plt.ylim(0, 300)
plt.show()

##############################
# Fare 0 を除外する
# Exclude Fare 0
##############################
df = df[df['Fare'] != 0].reset_index(drop=True)

##############################
# Fare と pclassの散布図をプロットする
# Draw scatter plot of Fare and pclass
##############################
plt.scatter(df['Pclass'], df['Fare'])
plt.xticks(numpy.linspace(1, 3, 3))
#plt.xlim(1, 3)
plt.ylim(0, 300)
plt.show()

##############################
# Fare 0 を除外する
# Exclude Fare 0
##############################
df = df[df['Fare'] != 5].reset_index(drop=True)

##############################
# Fare と pclassの散布図をプロットする
# Draw scatter plot of Fare and pclass
##############################
plt.scatter(df['Pclass'], df['Fare'])
plt.xticks(numpy.linspace(1, 3, 3))
plt.ylim(0, 300)
plt.show()

##############################
# SurvivedとAgeのクロス集計表を表示する
# Display Survived and Age crosstabulation table
##############################
cross_age = pandas.crosstab(df_all['Survived'], round(df_all['Age'],-1))
cross_age

cross_age.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()

##############################
# SurvivedとSibSpのクロス集計表を表示する
# Display Survived and SibSp crosstabulation table
##############################
cross_sibsp = pandas.crosstab(df['Survived'], df['SibSp'])
cross_sibsp

cross_sibsp.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()

# SibSpが3未満の場合のクラメール連関係数を確認する
# Check Cramer's coefficient of association when SibSp is less than 3
df_SibSp = df[df['SibSp'] < 3]
cramersV(df_SibSp['Survived'], df_SibSp['SibSp'])

##############################
# SurvivedとSibSp(3未満)のクロス集計表を表示する
# Display a crosstabulation of Survived and SibSp (less than 3)
##############################
cross_sibsp = pandas.crosstab(df_SibSp['Survived'], df_SibSp['SibSp'])
cross_sibsp

cross_sibsp.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()

##############################
# SurvivedとParchのクロス集計表を表示する
# Display Survived and Parch crosstabulation table
##############################
cross_parch = pandas.crosstab(df['Survived'], df['Parch'])
cross_parch

cross_parch.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()

# Parchが3未満の場合のクラメール連関係数を確認する
# Check Cramer's coefficient of association when Parch is less than 3
df_Parch = df[df['Parch'] < 3]
cramersV(df_Parch['Survived'], df_Parch['Parch'])

##############################
# SurvivedとParch(3未満)のクロス集計表を表示する
# Display a crosstabulation of Survived and Parch (less than 3)
##############################
cross_parch = pandas.crosstab(df_Parch['Survived'], df_Parch['Parch'])
cross_parch
cross_parch = pandas.crosstab(df_Parch['Survived'], df_Parch['Parch'])
cross_parch

cross_parch.T.plot(kind='bar', stacked=False, width=0.8)
plt.show()

from sklearn.preprocessing import LabelEncoder
##############################
# データ前処理
# ラベル(名称)を数値化する
# Data preprocessing
# Digitize labels
##############################
##############################
# Sex
##############################
encoder_sex = LabelEncoder()
df['Sex'] = encoder_sex.fit_transform(df['Sex'].values)
df_test['Sex'] = encoder_sex.transform(df_test['Sex'].values)

##############################
# データ前処理
# One-Hot エンコーディング
# Data preprocessing
# One-Hot Encoding
##############################
##############################
# SibSp
##############################
SibSp_values = df_all['SibSp'].value_counts()
SibSp_values = pandas.Series(SibSp_values.index, name='SibSp')
categories = set(SibSp_values.tolist())
df['SibSp'] = pandas.Categorical(df['SibSp'], categories=categories)
df_test['SibSp'] = pandas.Categorical(df_test['SibSp'], categories=categories)

df = pandas.get_dummies(df, columns=['SibSp'])
df_test = pandas.get_dummies(df_test, columns=['SibSp'])

##############################
# Parch
##############################
Parch_values = df_all['Parch'].value_counts()
Parch_values = pandas.Series(Parch_values.index, name='Parch')
categories = set(Parch_values.tolist())
df['Parch'] = pandas.Categorical(df['Parch'], categories=categories)
df_test['Parch'] = pandas.Categorical(df_test['Parch'], categories=categories)

df = pandas.get_dummies(df, columns=['Parch'])
df_test = pandas.get_dummies(df_test, columns=['Parch'])

##############################
# Ticket
##############################
ticket_values = df_all['Ticket'].value_counts()
ticket_values = ticket_values[ticket_values > 1]
ticket_values = pandas.Series(ticket_values.index, name='Ticket')
categories = set(ticket_values.tolist())
df['Ticket'] = pandas.Categorical(df['Ticket'], categories=categories)
df_test['Ticket'] = pandas.Categorical(df_test['Ticket'], categories=categories)

df = pandas.get_dummies(df, columns=['Ticket'])
df_test = pandas.get_dummies(df_test, columns=['Ticket'])

##############################
# データ前処理
# 数値を標準化する
# Data preprocessing
# Standardize numbers
##############################
from sklearn.preprocessing import StandardScaler

# 標準化
# Standardize numbers
standard = StandardScaler()
df_std = pandas.DataFrame(standard.fit_transform(df[['Pclass', 'Fare']]), columns=['Pclass', 'Fare'])
df['Pclass'] = df_std['Pclass']
df['Fare'] = df_std['Fare']

df_test_std = pandas.DataFrame(standard.transform(df_test[['Pclass', 'Fare']]), columns=['Pclass', 'Fare'])
df_test['Pclass'] = df_test_std['Pclass']
df_test['Fare'] = df_test_std['Fare']

##############################
# データ前処理
# 欠損値を処理する
# Data preprocessing
# Fill or remove missing values
##############################
df_test = df_test.fillna({'Fare':0})

# トレーニングデータを準備する
# Prepare training data
x_train = df.drop(columns='Survived').values
y_train = df[['Survived']].values
# y_train の次元を削除
# Delete y_train dimension
y_train = numpy.ravel(y_train)

##############################
# モデルを構築する
# Build the model
# GradientBoostingClassifier
##############################
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier(random_state=1,loss='exponential', learning_rate=0.1, max_depth=6)

import os
if(os.path.exists('./result.csv')):
    os.remove('./result.csv')

##############################
# 学習
# Trainig
##############################
model.fit(x_train, y_train)

##############################
# 結果を予想する
# Predict results
##############################
x_test = df_test.values
y_test = model.predict(x_test)

# PassengerId のDataFrameと結果を結合する
# Combine the data frame of PassengerId and the result
df_output = pandas.concat([df_test_index, pandas.DataFrame(y_test, columns=['Survived'])], axis=1)

# result.csvをカレントディレクトリに書き込む
# Write result.csv to the current directory
df_output.to_csv('result.csv', index=False)

これを提出するとスコアが「0.80382」になりました。

#6.まとめ

スコアが0.8を超え、上位10%以内に入ることができました。
最終的に利用した入力データは以下です。

No 項目名 項目名(日本語) 変換方式
1 Pclass チケットクラス 標準化
2 Sex 性別 数値化
3 SibSp 兄弟/配偶者 one-hotエンコーディング
4 Parch 親/子供 one-hotエンコーディング
5 Ticket チケット番号 one-hotエンコーディング
6 Fare 運賃 標準化

今回までは、scikit-learn で学習を進めていました。
機械学習は他のフレームワークもあるので、別のフレームワークも利用してみましょう。
次回はkerasを使って学習してみたいと思います。

#履歴
2020/01/29 初版公開
2020/02/03 誤字修正
2020/02/15 次回のリンク追加

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?