見よう見まねで機械学習してみた(Kaggleタイタニック)

# はじめに
[ハンズラボエンジニアブログ](https://www.hands-lab.com/tech/entry/3991.html)では、とりあえずポケモンのデータを使用し、みずタイプであるか判定してみました。
今回はKaggleの[タイタニック](https://www.kaggle.com/c/titanic)が界隈でどうやらチュートリアルっぽいのでやってみました。

# 環境
* OS: macOS High Sierra 10.13.5
* Python: 3.6.5
* scikit-learn : 0.19.1
* jupyter notebook
* やる気 : 30分くらいの集中力
* 習熟度 : PyQの機械学習初級修了

# データをダウンロード
<img width="961" alt="スクリーンショット_2018-06-27_15_41_02.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/59fa06b7-ce2d-9876-6002-c857f515ba2a.png">
test.csvとtrain.csvをダウンロードしましょう。


# データをみる
pandasで読み取り

```python
import pandas as pd
import numpy as np

df_train = pd.read_csv('train.csv')
df_test = pd.read_csv('test.csv')
```

データをとりあえずみる

```python
df_train.head()
```
<img width="979" alt="スクリーンショット 2018-06-29 11.34.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/7de8bb98-354f-848d-0014-6b37d7bcbb88.png">

```python
df_test.head()
```
<img width="829" alt="スクリーンショット 2018-06-29 11.36.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/8c1e9394-6739-81d8-79ca-ea7fab5a0578.png">
トレーニングデータで学習して、テストデータにはSurvivedがないので0か１を立てて行くんだなっと思いました。

# 説明変数Xの選択
目的変数yはSurvivedなので、説明変数Xのデータの選択をしていきます。(英語だったので訳します)

| カラム名 |  説明 |
| ----------- | ------------ | ------------ |
| Pclass | チケットクラス *1 |
| Sex | 性別 |
| Age | 年齢 |
| SibSp | 同乗している兄弟or配偶者の数 |
| Parch | 同乗している親or子供の数 |
| Fare | 料金 |
| Embarked | 乗船した場所(C, Q, S) *2 |

*1 1:金持ち, 2:一般, 3:労働者
*2 C = Cherbourg, Q = Queenstown, S = Southampton

とりあえずこんな感じかなってことです。

# 文字を数値に
数値データを前提としているのでカテゴリデータを数値データに変換します。
ここでは```Sex```と```Embarked```を変換します

変換する関数を定義

```python
def num_sex(sex) :
    if sex == 'male' :
        return 0
    else:
        return 1

def num_embarked(embarked):
    if embarked == 'S' :
        return 0
    elif embarked == 'C' :
        return 1
    else:
        return 2
```

pandasの```apply```メソッドを利用すると選択した列の要素全てに対して関数を実行してくれます。

```python
# トレーニングデータ
df_train['Sex'] = df_train['Sex'].apply(num_sex)
df_train['Embarked'] = df_train['Embarked'].apply(num_embarked)

# テストデータ
df_test['Sex'] = df_test['Sex'].apply(num_sex)
df_test['Embarked'] = df_test['Embarked'].apply(num_embarked)
```

変換されているか確認

```python
df_train.head()
```
<img width="976" alt="スクリーンショット 2018-06-29 12.26.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/aa040cef-c5f8-46da-dbd3-76a42b41ea2a.png">



# 欠損値の処理
まずはトレーニングデータから

```python
df_train.info()
```
<img width="385" alt="スクリーンショット 2018-06-29 12.05.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/06723780-4c07-20fc-bca9-378b4d83ddc7.png">

説明変数Xとして選択したカラムの欠損値を含むものはは```Age```と```Embarked```ですね。
とりあえず全て平均値を入れました。ここは何を入れるか意見が分かれますが。

```python
df_train["Age"] = df_train["Age"].fillna(df_train["Age"].mean())
df_train["Embarked"] = df_train["Embarked"].fillna(df_test["Embarked"].mean())
```

次はテストデータ

```python
df_test.info()
```
<img width="307" alt="スクリーンショット 2018-06-29 13.10.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/8470a655-5132-61db-b408-7e7f6b3cb3e4.png">
同じく欠損値はトレーニングデータ同様に```Age```と```Embarked```ですが```Fare```に欠損値が一つあるのでこれも平均で埋めましょう。

```python
df_test["Age"] = df_test["Age"].fillna(df_train["Age"].mean())
df_test["Embarked"] = df_test["Embarked"].fillna(df_test["Embarked"].mean())
df_test["Fare"] = df_test["Fare"].fillna(df_test["Fare"].mean())
```

# 交差検定
交差検定を行い分類器を選定していきます。
その前に説明変数X、目的変数yにデータを入れます

```python
X = df_train[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']].values
y = df_train['Survived'].values
```
今回はロジスティック回帰、決定木、ランダムフォレスト、SVMの精度を比較していきます。

```python
import numpy as np
from sklearn.model_selection import cross_val_score
```


ロジスティック回帰

```python
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(C=10, random_state=0)
scores = cross_val_score(lr, X, y, cv=10)

print("正答率", np.mean(scores), "標準偏差 +/-", np.std(scores))
```

```
正答率 0.7946649075019862 標準偏差 +/- 0.02025457869034716
```

決定木

```python
from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier( random_state=0)
scores = cross_val_score(tree, X, y, cv=10)

print("正答率", np.mean(scores), "標準偏差 +/-", np.std(scores))
```

```
正答率 0.7790588468959255 標準偏差 +/- 0.04370515581736431
```

ランダムフォレスト

```python
from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier(random_state=0)
scores = cross_val_score(clf, X, y, cv=10)

print("正答率", np.mean(scores), "標準偏差 +/-", np.std(scores))
```

```
正答率 0.8070377936670072 標準偏差 +/- 0.04489231076517005
```

SVM

```python
from sklearn.svm import SVC

svm = SVC(C=1, gamma=0.1, random_state=0)
scores = cross_val_score(svm, X, y, cv=10)

print("正答率", np.mean(scores), "標準偏差 +/-", np.std(scores))
```

```
正答率 0.7130039155600953 標準偏差 +/- 0.058569173987512234
```

ランダムフォレストが良さそうですね。

# トレーニングデータでテスト
ぶっつけ本番で```train.csv```のデータ全てを使って学習を行ってもいいのですがデータを分割してテストをしていきます。

```python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

clf.fit(X_train, y_train)
clf.score(X_test, y_test)
```
```
0.8097014925373134
```
スコアは0.8097014925373134でした。 個人的にはまぁまぁ

# Kaggleにアップデーするデータを作成
トレーニングデータ全てを使って学習させていきます。
提出するcsvは

```
PassengerId,Survived
892,0
893,0
894,0
895,1
・・・
・・・
・・・
```
こんな感じでアップデートします。

```python
clf_result = RandomForestClassifier()
clf_result.fit(X, y)

test_y = df_test[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']].values

output_prediction = clf_result.predict(test_y)

# KaggleにアップデートするCSVファイルを作成
PassengerId = np.array(df_test["PassengerId"]).astype(int)
result = pd.DataFrame(output_prediction, PassengerId, columns = ["Survived"])
result.to_csv("result.csv", index_label = ["PassengerId"])
```

result.csvが同ディレクトリ内に生成されるのでアップロードしましょう。
<img width="957" alt="スクリーンショット 2018-06-29 13.58.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/8c2bd577-7aec-2365-0ce9-c5cbcf357d3a.png">

# 注目の判定結果は
<img width="997" alt="スクリーンショット 2018-06-29 14.00.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/166626/4cec4575-6872-0666-6237-561124bce0c5.png">

scoreは0.76076でした。0.8の壁は厚そうです。

# 改善案
改善案としては```SibSp```+```Parch```+1 = `isAlone`という特徴量を作成しようかなと思いました。
一人だけで乗船している人なら生存率が上がるのではないかと思いました。