Kaggle：Titanic（Logistic回帰）

## ■ はじめに
Kaggle初心者向けのコンペに取り組みましたので
簡単にまとめてみました。

【概要】
・[Titanic: Machine Learning from Disaster](https://www.kaggle.com/c/titanic)
・沈没する船「タイタニック号」の乗客情報をもとに、助かる人とそうでない人について判別する

今回はロジスティック回帰を用いて、モデルの作成をしていきます。
<br>
## 1. モジュールの用意
```python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.metrics import accuracy_score, f1_score
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.feature_selection import RFE

%matplotlib inline
```
<br>
## 2. データの準備

```python

train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

print(train.shape)
print(test.shape)

# (891, 12)
# (418, 11)
```

```python

train.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/e6d8aabd-af2b-7e37-9776-9e7b8a8db474.png)
【データ項目】
・PassengerId：乗客ID
・Survived：生存したかどうか（0：助からない、1：助かる）
・Pclass – チケットのクラス（1：上層クラス、2：中級クラス、3：下層クラス）
・Name：乗客の名前
・Sex：性別
・Age：年齢
・SibSp：船に同乗している兄弟・配偶者の数
・parch：船に同乗している親・子供の数
・ticket：チケット番号
・fare：料金
・cabin：客室番号
・Embarked：船に乗った港（C：Cherbourg、Q：Queenstown、S：Southampton）

```python

test.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/a01b11d0-cc99-55f5-7b1c-a35bea3d18a1.png)
testデータの乗客番号（PassengerId）を保存しておきます。

```python

PassengerId = test['PassengerId']
```
実際にはtrainデータのみでモデルの作成を行いますが
モデルにtestデータを投入する際、同じ特徴量が必要となります。

前処理にOne-Hot-Encodingなどを使用した場合、
trainとtestデータの特徴量数が異なってしまうため
両データを結合し、まとめて前処理を行っていきます。

まず、trainデータは項目が1つ多い（目的変数：Survived）ので分離します。

```python

y = train['Survived']
train = train[[col for col in train.columns if col != 'Survived']]

print(train.shape)
print(test.shape)

# (891, 11)
# (418, 11)
```

これでtrain データとtest データの項目（特徴量）数が同じになったので、結合します。

```python

X = pd.concat([train, test], axis=0)

print(X.shape)
X.head()

# (1309, 11)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/4fc4d2ae-ab1e-e4a7-7f78-67835c6adcd1.png)
<br>
## 3-1. 前処理（全体）

まず、欠損値がどの程度あるのかを確認します。

```python

X.isnull().sum()

'''
PassengerId       0
Pclass            0
Name              0
Sex               0
Age             263
SibSp             0
Parch             0
Ticket            0
Fare              1
Cabin          1014
Embarked          2
dtype: int64
'''
```
文字列データのままでは、モデルの作成をすることができないため
随時、数値に変換をしていきます。

性別について、「男性：0、女性：1」に変換します。

```python

def code_transform(x):
    
    if x == 'male':
        y = 0
    else:
        y = 1
        
    return y

X['Sex'] = X['Sex'].apply(lambda x: code_transform(x))
X.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/721f5e90-585f-e798-5214-5a63cee7cdb5.png)
船に乗った港について、「0：C、1：Q、2：S」に変換します。

```python

def code_transform(x):
    
    if x == 'C':
        y = 0
    elif x == 'Q':
        y = 1
    else:
        y = 2
        
    return y

X['Embarked'] = X['Embarked'].apply(lambda x: code_transform(x))
X.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/987e518e-385d-1af2-c3fb-50c34ef329ae.png)
ここで、数値だけを含む列と、文字だけを含む列について調べます。

```python

numerical_col = [col for col in X.columns if X[col].dtype != 'object']
categorical_col = [col for col in X.columns if X[col].dtype == 'object']

print(numerical_col)
print(categorical_col)

# ['PassengerId', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
# ['Name', 'Ticket', 'Cabin']
```
数値カラムと文字列カラムについて、別々の前処理をしたいので分離します。

```python
X_num = X[numerical_col]
X_cat = X[categorical_col]

print(X_num.shape)
print(X_cat.shape)

# (1309, 8)
# (1309, 3)
```
## 3-2. 前処理（数値カラム）
データの中身を確認しておきます。

```python

X_num.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/8e297fab-286a-1128-3021-f2b0f404813a.png)
欠損値をそれぞれの列の中央値で埋めます。

```python
X_num.fillna(X_num.median(), inplace=True)
```
欠損値の状態を確認します。

```python

X_num.isnull().sum()

'''
PassengerId    0
Pclass         0
Sex            0
Age            0
SibSp          0
Parch          0
Fare           0
Embarked       0
dtype: int64
'''
```
# 3-3. 前処理（文字列カラム）
データの中身を確認しておきます。

```python
X_cat.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/55b5c2f8-3183-833b-6199-4c57d29acf7f.png)
欠損値には、一貫して'missing'の文字を入れます。

```python

X_cat.fillna(value='missing', inplace=True)
```
欠損値の状態を確認します。

```python

X_cat.isnull().sum()

'''
Name      0
Ticket    0
Cabin     0
dtype: int64
'''
```
One-Hot-Encodingをして、文字列を全て数値に変換します。

```python

X_cat = pd.get_dummies(X_cat)

print(X_cat.shape)
X_cat.head()

# (1309, 2422)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/b4fabf36-e8bb-1fae-7f4d-62e4c00c0003.png)
# 3-4. 前処理（全体）

X_numとX_catは、両方とも欠損値がなくなり、数値のみのデータとなったので
結合して全体のデータに戻します。

```python

X_total = pd.concat([X_num, X_cat], axis=1)

print(X_total.shape)
X_total.head()

# (1309, 2431)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/6b9e4325-1399-afde-828b-67845c9437ce.png)
trainデータのみでモデルを作成したいですが
X_totalはtestデータも含んでしまっているため、必要な部分のみを抽出します。

```python
train_rows = train.shape[0]
X = X_total[:train_rows]

std = StandardScaler()
X = std.fit_transform(X)

print(X.shape)
print(y.shape)

# (891, 2431)
# (891,)
```
<br>
# 4. モデルの作成
trainデータに該当する特徴量と目的変数が揃ったので
さらに学習データとテストデータに分けて、モデルの作成をしていきます。

```python

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=123)

print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)

# (623, 2431)
# (623,)
# (268, 2431)
# (268,)
```
```python

logreg = LogisticRegression(class_weight='balanced')
logreg.fit(X_train, y_train)

'''
LogisticRegression(C=1.0, class_weight='balanced', dual=False,
                   fit_intercept=True, intercept_scaling=1, l1_ratio=None,
                   max_iter=100, multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
'''
```
次に予測値を求めます。

y_probaに[ : , 1]を指定することで、Class1（Survived=1）になる確率を予測します。
y_predは0.5よりも大きければ1を、小さければ0を振り分けています。

```python

y_proba = logreg.predict_proba(X_test)[: , 1]
print(y_proba[:5])

y_pred = logreg.predict(X_test)
print(y_pred[:5])

# [0.90784721 0.09948558 0.36329043 0.18493678 0.43881127]
# [1 0 0 0 0]
```
<br>
## 5. 性能評価
ROC曲線とAUCを用いて評価します。

```python

fpr, tpr, thresholds = roc_curve(y_test, y_proba)
auc_score = roc_auc_score(y_test, y_proba)
plt.plot(fpr, tpr, label='AUC = %.3f' % (auc_score))
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)

print('accuracy:',accuracy_score(y_test, y_pred))
print('f1_score:',f1_score(y_test, y_pred))

# accuracy: 0.7723880597014925
# f1_score: 0.6013071895424837
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/a102dc10-5912-6af4-eb4a-f7b9f7f8915e.png)
また、混同行列も用いて評価してみます。

```python

classes = [1, 0]
cm = confusion_matrix(y_test, y_pred, labels=classes)

cmdf = pd.DataFrame(cm, index=classes, columns=classes)

sns.heatmap(cmdf, annot=True)
print(classification_report(y_test, y_pred))
'''
              precision    recall  f1-score   support

           0       0.76      0.95      0.84       170
           1       0.84      0.47      0.60        98

    accuracy                           0.77       268
   macro avg       0.80      0.71      0.72       268
weighted avg       0.79      0.77      0.75       268

'''
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/6cab7158-b332-d55b-181d-e2eb2ed52710.png)
<br>
## 6. Submit
trainデータを用いて、モデルの作成・評価ができたので
testデータの情報を与えて、予測値を出していきます。

まず全体データ（X_total）のうち、testデータに該当する部分を抽出します。

```python
X_submit = X_total[train_rows:]
X_submit = std.fit_transform(X_submit)

print(X_train.shape)
print(X_submit.shape)

# (623, 2431)
# (418, 2431)
```

モデルを作成したX_trainと比較して、同じ特徴量数（2431）になっているので
X_submitをモデルに投入して、予測値を出します。

```python

y_proba_submit = logreg.predict_proba(X_submit)[: , 1]
print(y_proba_submit[:5])

y_pred_submit = logreg.predict(X_submit)
print(y_pred_submit[:5])

# [0.02342065 0.18232356 0.06760457 0.06219097 0.76277487]
# [0 0 0 0 1]
```
KaggleへSubmit（提出）するCSVデータを用意します。

まず、必要な情報を揃えたデータフレームを作成します。

```python

df_submit = pd.DataFrame(y_pred_submit, index=PassengerId, columns=['Survived'])
df_submit.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/d37ff457-d16d-4171-0cf1-5eb1850fbb60.png)
その後、CSVデータに変換します。

```python

df_submit.to_csv('titanic_submit.csv')
```
これで、Submitをして終了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/4f0d31a4-a22c-6252-2f0f-28b37f44a395.png)
<br>
## ■ 最後に
今回はKaggle初心者の方に向けて、記事をまとめさせていただきました。
少しでもお役に立てたようでしたら幸いです。

ご精読いただきありがとうございました。