#はじめに
初めて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(チケットクラス)ごとに運賃を散布図にしてみます。
以下のようになりました。
横軸が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」を除外して再度散布図を作成すると以下になります。
少し見やすくなりました。pclass「1」の小さい点も気になります。
上の表をみると、pclass「1」でFare「5」のデータがあります。
これも異常値の可能性があるため、除外しましょう。
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 は以前もグラフ化していましたが、再度グラフ化してみましょう。
相関係数では有意な差はありませんでしたが、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
相関係数は 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文字を同じグループとみなしてグループ化してみると以下になりました。
それぞれ、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 次回のリンク追加