自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

(その4-1) タイタニックの乗客の生存有無を決定木分析で予測してみた

Data Analytics
Data Analytics

とうとうモデリング作業になります。

ここまでが長かったですね。

タイタニックの乗客の生存有無を予測するモデルを作成しようと思います。

1:生存か0:非生存かを予測するいわゆる分類問題(Classification Problem)になります。

最初なので分類問題の基本的な手法である決定木(Decision tree)モデルから試そうと思います。

作成したモデルで生存 or 非生存を予測し、Kaggleにアップロードして精度を確認していくというプロセスを繰り返していこうと考えています。

スポンサーリンク

評価指標

Metric
Your score is the percentage of passengers you correctly predict. This is known as accuracy.
引用: https://www.kaggle.com/competitions/titanic/overview/evaluation

Titanicのデータセットでは、生存有無を正確に予測できた乗客の割合(Accuracy)を評価指標とするようです。

Accuracyは TP+TN / TP+TN+FP+FN で計算できるようです。
言い換えると、(0/1に関わらず)正解を予測できた数 / 全ての数 という意味になります。

スポンサーリンク

決定木分析

分析用データの準備

事前に欠損値処理や特徴量エンジニアリングを実施してデータをエクスポートしています。

本記事と同じ結果にするためには事前に下記記事を確認してデータを用意してください。

タイタニックのモデリング用データの作成まとめ
(その3-5) タイタニックのデータセットの変数選択にてモデリング用のデータを作成し、エクスポートするコードを記載していましたが分かりずらかったので簡略しまとめました。上から順に流していけばtitanic_train.csvとtitanic...

学習データと評価データの読み込み

import pandas as pd
import numpy as np
# タイタニックデータセットの学習用データと評価用データの読み込み
df_train = pd.read_csv("/Users/hinomaruc/Desktop/notebooks/titanic/titanic_train.csv")
df_eval = pd.read_csv("/Users/hinomaruc/Desktop/notebooks/titanic/titanic_eval.csv")

概要確認

# 概要確認
df_train.info()
Out[0]

    RangeIndex: 891 entries, 0 to 890
    Data columns (total 22 columns):
     #   Column         Non-Null Count  Dtype  
    ---  ------         --------------  -----  
     0   PassengerId    891 non-null    int64  
     1   Survived       891 non-null    int64  
     2   Pclass         891 non-null    int64  
     3   Name           891 non-null    object
     4   Sex            891 non-null    object
     5   Age            891 non-null    float64
     6   SibSp          891 non-null    int64  
     7   Parch          891 non-null    int64  
     8   Ticket         891 non-null    object
     9   Fare           891 non-null    float64
     10  Cabin          204 non-null    object
     11  Embarked       891 non-null    object
     12  FamilyCnt      891 non-null    int64  
     13  SameTicketCnt  891 non-null    int64  
     14  Pclass_str_1   891 non-null    float64
     15  Pclass_str_2   891 non-null    float64
     16  Pclass_str_3   891 non-null    float64
     17  Sex_female     891 non-null    float64
     18  Sex_male       891 non-null    float64
     19  Embarked_C     891 non-null    float64
     20  Embarked_Q     891 non-null    float64
     21  Embarked_S     891 non-null    float64
    dtypes: float64(10), int64(7), object(5)
    memory usage: 153.3+ KB
# 概要確認
df_eval.info()
Out[0]

    RangeIndex: 418 entries, 0 to 417
    Data columns (total 21 columns):
     #   Column         Non-Null Count  Dtype  
    ---  ------         --------------  -----  
     0   PassengerId    418 non-null    int64  
     1   Pclass         418 non-null    int64  
     2   Name           418 non-null    object
     3   Sex            418 non-null    object
     4   Age            418 non-null    float64
     5   SibSp          418 non-null    int64  
     6   Parch          418 non-null    int64  
     7   Ticket         418 non-null    object
     8   Fare           418 non-null    float64
     9   Cabin          91 non-null     object
     10  Embarked       418 non-null    object
     11  Pclass_str_1   418 non-null    float64
     12  Pclass_str_2   418 non-null    float64
     13  Pclass_str_3   418 non-null    float64
     14  Sex_female     418 non-null    float64
     15  Sex_male       418 non-null    float64
     16  Embarked_C     418 non-null    float64
     17  Embarked_Q     418 non-null    float64
     18  Embarked_S     418 non-null    float64
     19  FamilyCnt      418 non-null    int64  
     20  SameTicketCnt  418 non-null    int64  
    dtypes: float64(10), int64(6), object(5)
    memory usage: 68.7+ KB

# 描画設定
import seaborn as sns
from matplotlib import ticker
import matplotlib.pyplot as plt
sns.set_style("whitegrid")
from matplotlib import rcParams
rcParams['font.family'] = 'Hiragino Sans' # Macの場合
#rcParams['font.family'] = 'Meiryo' # Windowsの場合
#rcParams['font.family'] = 'VL PGothic' # Linuxの場合
rcParams['xtick.labelsize'] = 12       # x軸のラベルのフォントサイズ
rcParams['ytick.labelsize'] = 12       # y軸のラベルのフォントサイズ
rcParams['axes.labelsize'] = 18        # ラベルのフォントとサイズ
rcParams['figure.figsize'] = 18,8      # 画像サイズの変更(inch)

モデル作成

# 決定木のモデル作成
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import plot_tree
clf = DecisionTreeClassifier(random_state=0)
# 学習データを訓練データとテストデータに分割する。
from sklearn.model_selection import train_test_split
x_train, x_test = train_test_split(df_train, test_size=0.20,random_state=100)

# 説明変数
FEATURE_COLS=[
   'Age'
 , 'Fare'
 , 'SameTicketCnt'
 , 'Pclass_str_1'
 , 'Pclass_str_3'
 , 'Sex_female'
 , 'Embarked_Q'
 , 'Embarked_S'
]

X_train = x_train[FEATURE_COLS] # 説明変数 (train)
Y_train = x_train["Survived"] # 目的変数 (train)
X_test = x_test[FEATURE_COLS] # 説明変数 (test)
Y_test = x_test["Survived"] # 目的変数 (test)
# 学習データから分割した訓練データをfitする
model = clf.fit(X_train,Y_train)

モデル結果確認

# 決定木を描画する
plot_tree(   model
           , feature_names=FEATURE_COLS
           , class_names=["非生存","生存"]
           , filled=True, rounded=True
         )
Out[0]

[Text(0.5735625617588933, 0.9761904761904762, 'Sex_female <= 0.5\ngini = 0.469\nsamples = 712\nvalue = [445, 267]\nclass = 非生存'),
Text(0.32795516304347827, 0.9285714285714286, 'Age <= 6.5\ngini = 0.303\nsamples = 467\nvalue = [380, 87]\nclass = 非生存'),
Text(0.16946640316205533, 0.8809523809523809, 'SameTicketCnt <= 4.5\ngini = 0.42\nsamples = 20\nvalue = [6, 14]\nclass = 生存'),
・・・省略・・・
Text(0.9841897233201581, 0.7857142857142857, 'SameTicketCnt <= 4.0\ngini = 0.5\nsamples = 2\nvalue = [1, 1]\nclass = 非生存'),
Text(0.9762845849802372, 0.7380952380952381, 'gini = 0.0\nsamples = 1\nvalue = [0, 1]\nclass = 生存'),
Text(0.9920948616600791, 0.7380952380952381, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]\nclass = 非生存')]

png

作成したモデルを描画してくれます。

ツリーが深すぎて見えないですね 笑

# 学習データを分割してできた訓練データへの当てはまりを確認
# Return the mean accuracy on the given test data and labels.
model.score(X_train,Y_train)
Out[0]
0.9859550561797753

訓練データへの当てはまりが良すぎるのでoverfittingの可能性がありそうです。
テストデータへの当てはまりも確認します。

# 学習データを分割してできたテストデータへの当てはまりを確認
# Return the mean accuracy on the given test data and labels.
model.score(X_test,Y_test)
Out[0]
0.7541899441340782

訓練データの精度とテストデータの精度の乖離が大きいのでオーバーフィットしていそうです。
最初なんでこんなものでしょう 汗

オーバーフィットしているとモデルの汎用性がなくなり、未知のデータへの当てはまりが悪くなる可能性があります。

# 変数重要度
feature_importances = pd.DataFrame()
# 変数名を格納
feature_importances["feature"] = model.feature_names_in_
# 重要度を格納
feature_importances["feature_importances"] = model.feature_importances_
# 重要度の降順で表示
feature_importances.sort_values(by="feature_importances", ascending=False)
Out[0]

feature feature_importances
5 Sex_female 0.299011
0 Age 0.262170
1 Fare 0.237589
4 Pclass_str_3 0.081600
2 SameTicketCnt 0.061893
3 Pclass_str_1 0.028035
7 Embarked_S 0.022451
6 Embarked_Q 0.007252

女性であること、年齢、運賃の重要度が高いようです。

# モデルのパラメータを表示
model.get_params()
Out[0]
    {'ccp_alpha': 0.0,
     'class_weight': None,
     'criterion': 'gini',
     'max_depth': None,
     'max_features': None,
     'max_leaf_nodes': None,
     'min_impurity_decrease': 0.0,
     'min_samples_leaf': 1,
     'min_samples_split': 2,
     'min_weight_fraction_leaf': 0.0,
     'random_state': 0,
     'splitter': 'best'}

デフォルト値を使いました。
random_stateだけ0に固定しています。(たしかもう一度モデリングしても同じ結果になるようなseed的なオプションだったと思います。)

クロスバリデーションでの精度を確認
# https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html
# StratifiedKFold with KFold = 10
from sklearn.model_selection import cross_val_score

X = df_train[FEATURE_COLS] # 説明変数 (train)
Y = df_train["Survived"] # 目的変数 (train)

np.mean(cross_val_score(clf, X, Y, cv=10,verbose=3))
Out[0]
    [CV] END ................................ score: (test=0.733) total time=   0.0s
    [CV] END ................................ score: (test=0.820) total time=   0.0s
    [CV] END ................................ score: (test=0.697) total time=   0.0s
    [CV] END ................................ score: (test=0.831) total time=   0.0s
    [CV] END ................................ score: (test=0.787) total time=   0.0s
    [CV] END ................................ score: (test=0.787) total time=   0.0s
    [CV] END ................................ score: (test=0.775) total time=   0.0s
    [CV] END ................................ score: (test=0.742) total time=   0.0s
    [CV] END ................................ score: (test=0.809) total time=   0.0s
    [CV] END ................................ score: (test=0.775) total time=   0.0s

    [Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
    [Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.0s remaining:    0.0s
    [Parallel(n_jobs=1)]: Done   2 out of   2 | elapsed:    0.0s remaining:    0.0s
    [Parallel(n_jobs=1)]: Done  10 out of  10 | elapsed:    0.1s finished

    0.7755805243445695

だいたい、77.5% Accuracyになるようです。

Kaggleにアップするためのデータ作成とアップロード

# Surviviedを予測し、Kaggleにアップロードするためのcsvの作成
df_eval["Survived_predicted"] = clf.predict(df_eval[FEATURE_COLS])
df_eval[["PassengerId","Survived_predicted"]].to_csv("titanic_submission.csv",index=False)
# Kaggleに作成した予測ファイルをアップロード
!/Users/hinomaruc/Desktop/notebooks/my-venv/bin/kaggle competitions submit -c titanic -f titanic_submission.csv -m "model #001. my first submission using decision tree"
Out[0]
    100%|████████████████████████████████████████| 2.21k/2.21k [00:05<00:00, 396B/s]
    Successfully submitted to Titanic - Machine Learning from Disaster

check https://www.kaggle.com/competitions/titanic/submit

Kaggleのデータ提出画面を確認すると結果が確認できます。

場合によってエラーになる場合があります。
エラー構文もわかりやすいと思うので解決していきます。

カラム名はSurvivedにしないといけないエラー

png

データのレコード数が違う場合のエラー

png

# Survived_predictedだとエラーになるので、Survivedに直したデータを作成
df_eval["Survived"] = clf.predict(df_eval[FEATURE_COLS])
df_eval[["PassengerId","Survived"]].to_csv("titanic_submission.csv",index=False)

Kaggleの結果

# エラーを解決して再度アップロード
!/Users/hinomaruc/Desktop/notebooks/my-venv/bin/kaggle competitions submit -c titanic -f titanic_submission.csv -m "model #001. my first submission using decision tree"
    100%|████████████████████████████████████████| 2.77k/2.77k [00:05<00:00, 549B/s]
    Successfully submitted to Titanic - Machine Learning from Disaster
データに問題なければAccuracyが表示されます。

png

スコアは70%のようです。まずまずのスタートですね。

スポンサーリンク

まとめ

決定木の結果は70%でした。
こちらの結果をベースに精度改善をしていきたいと思います。

タイタニックのコンペをみると1位は精度100%のようなので、そこまでいけるのか。。

タイトルとURLをコピーしました