Kaggleのタイタニック号のチュートリアルを試す

KaggleのTitanicのチュートリアルをやってみました。
Pandasやscikit-learnの使い方を覚えるためにJupyter NoteBook上でのコードをここに記録しておきます。

Kaggleは実際のデータをもとに機械学習をしてそのスコアを競うサイトです。

コンペティションに参加しなくてもチュートリアルがいくつかあり、機械学習に使える実際のデータがそろっているので、機械学習を勉強するにはとても便利です。

Kaggleの使い方やタイタニック号のチュートリアルについては
[【Kaggle初心者入門編】タイタニック号で生き残るのは誰？](https://www.codexa.net/kaggle-titanic-beginner/)
を参考にしました。

私のコードは、上記記事を参考にしつつ、自分でわかりやすいように変えています。

Kaggleにユーザ登録すると
[Titanic: Machine Learning from Disaster | Kaggle](https://www.kaggle.com/c/titanic)
からデータをダウンロードできるようになります。

アルゴリズムは決定木です。

これ以降は、
[Jupyter NotebookをDockerを使って簡単にインストールし起動（nbextensions、Scalaにも対応） - Qiita](https://qiita.com/suzuki-navi/items/37615024d4748aac6688)
に従って準備したJupyter Notebookの環境で試しています。

このJupyterの環境でブラウザで8888番ポートにアクセスして、Jupyter Notebookを使うことができます。右上のボタンのNew > Python3をたどると新しいノートを開けます。

# データ準備

```Python
import numpy as np
import pandas as pd
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
```

`train`、`test`はDataFrameのオブジェクトになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/847c53dc-4b36-ba8f-a7a8-de28fb7c9b2d.png)

データの前処理を`train`、`test`とで共通して行うためにいったんデータを連結します。あとで再度分割できるようにフラグを入れておきます。また`Survived`という列は`train`にしか含まれていませんので、これはいったん削除します。

```Python
train["is_train"] = 1
test["is_train"] = 0
data = pd.concat([train.drop(columns=["Survived"]), test])
```

`train.drop(columns=["Survived"])`は列を削除した新しいDataFrameオブジェクトを返します。新しいオブジェクトを返すので、元の`train`には列は残ったままです。なお、`train.drop(["Survived"], axis=1)`と書いても同じです。`axis=1`は行ではなく列を削除というフラグです。が、前者のほうがわかりやすいです。

`pd.concat`は複数のDataFrameを連結した新しいDataFrameを返します。ちなみに`pd.concat`に`axis=1`を渡すと横方向に連結します。

連結した結果、こんなデータになります。1309行のデータです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/94ca665a-374d-81c6-0bb6-303e4995f4e7.png)

## 欠損データに対応

欠損データの状況を確認します。上のスクショで`NaN`と表示されている箇所がもとのCSVファイルでは空になっていた箇所でデータが欠損しています。

```Python
data.isnull().sum()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/ec3a8036-e854-de92-9b9f-922da3bd682a.png)

以下のコードでどんなデータがあるかや、中央値を確認できます。

```Python
data["Age"].unique()
data["Age"].median()
```

`Age`の項目の欠損箇所には中央値を入れることにします。

```Python
data["Age"] = data["Age"].fillna(data["Age"].median())
```

`Embarked`にはどんなデータがあるかを確認します。データごとにそのレコード数を確認できます。

```Python
data["Embarked"].value_counts()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/73dace09-7d12-596c-f44f-3a70356e062c.png)

`Embarked`は多くを`S`が占めているようですので、欠損箇所には`S`を入れることにします。

```Python
data["Embarked"] = data["Embarked"].fillna("S")
```

```Python
data.isnull().sum()
```

## 文字列データを数値に変換

次に、文字列になっている`Sex`と`Embarked`の項目を数値に変換します。

`Embarked`にはS, C, Qの3パターンしかありませんでしたので、こんな風に変換したいと思います。これをワンホットエンコーディング(one-hot encoding)といいます。

| Embarked |
| ---- |
| S |
| S |
| C |
| Q |

↓

| Embarked_C | Embarked_Q | Embarked_S |
| ---- | ---- | ---- |
| 0 | 0 | 1 |
| 0 | 0 | 1 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |


one-hot encodingは`pd.get_dummies`という関数でできます。`pd.get_dummies(data["Embarked"], prefix="Embarked")]`というコードは`Embarked_S`, `Embarked_C`, `Embarked_Q`という3列のDataFrameを生成します。3列のうちのどれかが1になっていて、それ以外が0になっています。これをもとのdataと横に連結して、もともとの`Embarked`列を削除します。

```Python
data = pd.concat([data, pd.get_dummies(data["Embarked"], prefix="Embarked")], axis=1).drop(columns=["Embarked"])
```

`Sex`は`male`と`female`しかありませんでしたので、2列にする必要はなく、0, 1の1列で足ります。

| Sex |
| ---- |
| male |
| female |
| female |

↓

| Sex |
| ---- |
| 1 |
| 0 |
| 0 |

`pd.get_dummies`に`drop_first=True`というオプションを付けると1列目を削除して、結果1列のみにしてくれるようです。

```Python
data["Sex"] = pd.get_dummies(data["Sex"], drop_first=True)
```

`pd.get_dummies`で生成した2列のうち1列目がたまたまfemaleだったからか、maleのほうが1でfemaleが0になりました。どっちになってもいいと思います。

結果こういうデータになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/96c2efdb-f7ce-b087-ba6e-1df8dd5f83ec.png)

前処理をした`data`を再び訓練データと検証データに分割し、さらに今回使用する列のみに絞ります。

```Python
feature_columns =["Pclass", "Sex", "Age", "Embarked_C", "Embarked_Q", "Embarked_S"]
feature_train = data[data["is_train"] == 1].drop(columns=["is_train"])[feature_columns]
feature_test = data[data["is_train"] == 0].drop(columns=["is_train"])[feature_columns]
```

目的変数は`data`には含まれてませんでしたので、最初の`train`から抽出します。

```Python
target_train = train["Survived"]
```

# 学習

モデルを学習します。

```Python
from sklearn import tree
model = tree.DecisionTreeClassifier()
model.fit(feature_train, target_train)
```

参考
[sklearn.tree.DecisionTreeClassifier — scikit-learn 0.21.3 documentation](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html)

訓練データで正解率を見てみます。

```Python
from sklearn import metrics
pred_train = model.predict(feature_train)
metrics.accuracy_score(target_train, pred_train)
```

`0.9001122334455668`と出ました。9割が正解のようです。

# 評価

Kaggleに推論結果を登録して評価してみます。

まず`my_prediction.csv`に検証データでの推測結果を保存します。

```Python
pred_test = model.predict(feature_test)
my_prediction = pd.DataFrame(pred_test, test["PassengerId"], columns=["Survived"])
my_prediction.to_csv("my_prediction.csv", index_label=["PassengerId"])
```

CSVファイルの1行目はヘッダ行で、`PassengerId,Survived`になっています。

このファイルをKaggleのサイトにアップすると、スコアを出してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/cc6d6506-6209-f575-ef60-3ebc2bca491a.png)

`0.74641`でした。