Titanic号からの生存率を当てるKaggleコンペを元にデータ分析の流れをまとめてみた

データ分析の流れは定石が決まっているので、一度まとめておけば今後も使えるだろうと思い自分用にまとめてみました。

※この記事はAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています

## 今回使用した事例

今回使ったデータ分析の事例は、良く色んなところで引用されている、KaggleのTitanic号からの生存確率を当てるコンペです。

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

この事例は、データ分析で最も一般的な形である表形式であり、予測に関係のない特徴量やデータの欠損なども含まれているのでデータの前処理等も学べ、データ分析の事例としてはもってこいです。
ウェブ作成でいうところのHello world、電子工作でいうところのLチカのようなものですね。

## データ分析の流れ

データ分析の流れは概ね下記のようになっています。
Kaggleグランドマスターの人曰く、4の特徴量作成が最も重要で、全体の50〜80%の時間をそこに費やすとのことです。
逆に、モデルやパラメータはある程度定石があり、人によって差が出ることは少ないそうです。

1. データの用意
2. データの確認
3. データの前処理
4. 特徴量の決定
5. 検証データの作成
6. モデル選定
7. モデルの学習
8. 予測結果の評価

順番に説明していきます。

## 1.データの用意

まずはデータがないと始まりません。
もちろんデータはどこかから降ってくるわけではなく、自分の手で入手してこなくてはいけません。

通常は、公的機関や企業が公開しているデータやAPIを使ったり、自分でスクレイピング等してデータを入手します。

ただ、Kaggleの場合は既に用意してくれているのでここの工程は不要です。
下記コマンドを打つだけでデータを読んでくれます。
```python
train_df = pd.read_csv('/kaggle/input/titanic/train.csv')
```

スクレイピングを行う場合は、スクレイピング禁止のサイトから取得しないように注意したり、データの商用利用を禁止してたりする場合もあるので、利用規約をきちんと読んでから行うようにしましょう。
あと、サーバへの負荷も考慮して各ページへのアクセス頻度も注意が必要です（1ページ/秒というのが良く使われる頻度ですね）

## 2.データの確認

データを用意できたら、まず最初に肝心なのは「データを良く見る事」です。
データを良く見て、その中のどのあたりに特徴がありそうか、どこをフォーカスしたら精度の良いモデルができそうか、等を考えます。

ここではpandasやmatplotlib、seaborn等のPythonライブラリを駆使してデータを可視化します。
可視化は色んな方法があるので、センスが問われているところですね。

データの確認方法に正解はないですが、ここでの目標は、4の特徴量の決定に向けて、データの中にどういう特徴や偏りがあるのかがイメージできるようになるまでありとあらゆる角度から確認・分析をするのが理想です。

下記はその一例です。

```python
#データの先頭の5つを表示
display(train_df.head())

#各列の平均値などを一括表示
display(train_df.describe())

#データの偏りの確認(seabornライブラリを使用)
#生存者と死亡者の数をグラフで確認
import seaborn as sns
sns.countplot(x='Survived', data=train_df) 
```

その他にも、下記のような方法が考えられると思います。

* 散布図にして外れ値を確認する
* 年齢層ごとに並べて年齢と生存率の相関を確認する
* 訓練データとテストデータに偏りがないかを確認する
* 各列同士の相関性をヒートマップで確認する

特に、各列同士の相関性の確認は非常に重要で、特徴量を作成するときに参考になります。

今回の例では、性別や運賃が生存率に大きく相関があることが分かりました。
逆に年齢はほとんど相関がなかったりと意外な結果が得られる事も多いので、先入観に捉われずにニュートラルな目線で確認することが重要です。

ここまでやったら、一旦得られた知見をまとめておきましょう。

なお、今回は表形式でデータが与えられていますが、これが画像や音声になってくると確認方法はまた変わってきます。

## 3.データの前処理

データの確認・分析が終わったら特徴量を作成するのですが、その前に、必要に応じてデータの前処理を行います。
データの前処理には以下にような種類があります。

### 欠損値の処理

現実世界においてデータが完全に揃っていることは少なく、大抵の場合はデータの抜け漏れ、いわゆる欠損値が存在します。
モデルに依っては欠損値のままだとモデルが作れなかったり、高い精度が出せないことがあるので、何かしらの値を入れてやる必要があります。

下記はその例です。欠損値処理と言っても実にいろいろな方法があるのが分かります。

* 平均値を入れる
* 中央値を入れる
* ゼロを入れる
* 最頻値を入れる（文字列等の場合）
* カテゴリ化する
* そもそも使用しない

恐らく多くの場合は、数値であれば平均値を入れる、文字列等であれば最頻値を入れるというのがスタンダードだと思います。

```python
#年齢の欠損値に平均値を入れる
all_df['Age'] =  all_df['Age'].fillna(all_df['Age'].mean())
#チケットナンバーの欠損値に最頻値を入れる
train_df['Ticket'] =  train_df['Ticket'].fillna(train_df['Ticket'].mode())
```

ただ、場合に依ってはゼロを入れたり、欠損値有無でカテゴリ化して扱う、というテクニックも存在します。

Titanic号の事例を使って、どこに欠損値があるか調べてみます。

```python
train_df.isnull().sum()

#出力結果
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
```

このように、年齢とCabinに欠損値が多く存在することが分かりました。
これの本当の理由は分かりませんが、例えば年齢であれば上流階級の人へは年齢確認ができなかった、等の理由が可能性としては考えられると思います。
その場合は、年齢の欠損値あり＝上流階級、欠損値なし＝一般階級というカテゴリ分けができ、予測精度向上に寄与する可能性があります。

この辺りは技術的というより前知識やセンスになってきますね。

また、欠損値が多い場合は使用しないという選択肢もあります。
Cabinは全データ数891に対して欠損値が687もあるので、データとして使わない方が無難だと思います。

```python
train_df.shape

#出力結果
(891, 12)
```

### 外れ値の処理

散布図等を用いると、明らかに母集団の中から外れているデータ（＝外れ値）を確認することができます。
外れ値がある場合は、以下のような手段があると思います。

* 外れ値を削除する
* カテゴリカル変数に変換する
* あえてそのまま使用する

外れ値をどう扱うかは状況によるので正解はない世界になってきます。

外れ値を見るとつい削除してしまいたくなってしまいますが、その外れ値も何かしらの意味を持っている場合があるので闇雲に削除するのは精度に影響する場合があります。

カテゴリカル変数については後述します。

この辺りは、モデルの作成・評価を繰り返して精度が高くなる方法をカット＆トライで見つけていきます。



## 4.特徴量作成

ここが最も精度の良いモデル作りのキモになるところです。

別の言い方をすると、精度に一番寄与するのがこの作業だと言われています。

正直、モデルに関しては種類やパラメータはたくさんありますが、そこまで劇的に変わるわけではないことや、AutoMLと言われる自動機械学習ライブラリの発達によりパラメータ選定やチューニングは自動でやってくれるので、そこまで人に依って差が生まれるところではないです。

精度の高い特徴量を得るために、例えば下記のような手法が取られます。

### カテゴリカル変数への変換

場合に依っては、データをあるカテゴリごとに区切る（データ分析の世界ではカテゴリカル変数に変換すると言います）場合があります。

例えば年齢を10代、20代、、等の年代ごとのカテゴリにしたり、都道府県を東北地方、中部地方という地方ごとにカテゴリにしたり、など色々なパターンがあります。

データが細かいと精度は出やすいですが、モデルの学習に時間がかかったり、過学習になってしまう恐れがあります。
そのため、ある程度のカテゴリで大きい枠で括ってあげることで、これらを緩和する目的でカテゴリ化します。

カテゴリカル変数への変換方法はいくつもあり、一例を挙げておきます。

* One hot encoding
* Label encoding

前者は、例えば5種類なら5つの列を作り、該当するものは1,それ以外は全て0にする方法です。
後者は、例えば5種類のカテゴリがあれば1〜5の数値を割り当てる方法です。

ロジスティク回帰等のモデルは数値の大きさに意味を持つため、Label encodingのように数値の大小に意味を持たない方法とは相性が悪く、一般的にはOne hot encodingが使われます。

このように、モデルごとの相性によってどの方法を取るかを選びます。

```python
#One hot encodingの例
#年齢を8つのカテゴリカル変数に変換
train_df['AgeBand'] = pd.qcut(train_df['Age'], 8)
#それぞれのカテゴリカル変数にOne hot encodingを実施
train_df = pd.get_dummies(train_df, columns=['AgeBand'])
```

### 新たな特徴量の作成

場合によっては、自分の手で特徴量を新しく作るということも行います。

例えば今回のTitanic号の事例でいくと、同情している兄弟の数や配偶者の数も表に記載されています。

これをそのまま使っても良いですが、例えばそれらを足し合わせ、「家族全員の数」として新たな列を追加すると別の特徴や相関が見えてくる可能性もあります。

現実的にも、家族の数が多い人を優先して逃す等の措置が取られた可能性は否定できないので、こういった別の観点から新たな特徴量を模索します。


### 学習に使用しないカラムの削除

相関係数が低かったものや、その他モデルの学習に悪影響を及ぼす可能性のあるもの、全く寄与しないもの等は特徴量から削除します。

こういったデータは削除した方が計算リソースが少なく済み、また精度アップも期待できます。

ただし、人の目では寄与しないと思ったものでも、実際には何かしらの影響を与えている場合もあるので削除するデータについては注意が必要です。


## 5.検証データの作成

特徴量の作成が終わったら、モデルの作成に行きたいところですが、その前の事前準備として検証データの作成を行います。

データセットは下図の通り学習データとテストデータの2つに分かれ、テストデータを予測するために学習データを用いてモデルを学習させます。

この時、テストデータが未知のものだと学習したモデルの良し悪しが判断できません（もちろん、学習データに対しての精度を上げる事はできますが、それはあくまで学習データだけに適合した過学習をしている可能性も否定できません）。

そのため、その対策の一つとして下記図のように、学習データを更に学習データと検証データの2つに分け、学習データで学習させて検証データで評価を行う、ホールドアウト法という手法を取る事があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3522867/2d9a8e04-f788-cb50-38ea-2ced6d294fcd.png)

https://di-acc2.com/analytics/ai/6498/


ただし、少し考えればすぐ分かるように、この方法だと学習に使うデータの量が減ってしまうため、モデルの精度を悪化させてしまう可能性があります。

また、データの偏り等による過学習のリスクも否定できません。

そのため、一般的にはk分割交差検証という、下図のようにデータを区切ってそれぞれで学習・評価を行い、最終的にそれぞれの平均値を取ることでモデルの性能を判断する、という手法が取られています。

これを行うと、もちろん計算リソースはその分増えますが、安定した性能を発揮することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3522867/780617cc-a712-6093-5884-c17f507f752b.png)

https://di-acc2.com/analytics/ai/6498/

下記はk分割交差検証の使い方の例です。

```python
#scikit-learnのk分割ライブラリの呼び出し
from sklearn.model_selection import KFold
#分割数を10に指定
cv = KFold(n_splits=10, random_state=0, shuffle=True)
#10回下記のサイクルを回す
for i ,(trn_index, val_index) in enumerate(cv.split(train, target)):
    #データの分割する処理を書く
    XXXX
    #モデルを学習する処理を書く
    XXXX
    #モデルを評価する処理を書く
    XXXX

#10回の評価結果を平均化して表示する処理を書く
XXXX
```

## 6.モデル選定

モデルにはたくさんの種類があり、適材適所のためどんなシーンにも万能なモデルというのは存在しません。
いくつかのモデルの中から、データの中身や最終的な評価指標、更には自分の経験や勘を頼りにモデルを選びます。

モデルの代表的な例を以下に挙げておきます。

* ロジスティック回帰
* サポートベクトルマシン（SVM）
* k近接法
* ランダムフォレスト
* マルチパーセプトロン
* GBDT

一つ一つの説明は割愛しますが、最近ではGBDTが比較的バランスが良く、まずはこちらで試してみて必要に応じて他のモデルを試してみる、、という流れが良く取られているようです。

Kagglerの中でバイブルとなっている下記の本で、GBDTが激推しされたこともその要因？なんでしょうか。

https://amzn.to/3r0VS9G

また、最近ではAutoML（自動機械学習）という技術が進み、いろんなモデルやハイパーパラメータを試し、その中から自動的に最も評価の高かったモデルを選んでくれるようなライブラリも増えてきています。

そのため、わざわざ手動で一つずつ試す、、なんていう機会は少なくなってきていると思います。

ちなみにAutoMLを使えるサービスやライブラリの有名どころでは下記があります。

* Cloud AutoML (Google)
* Automated ML (Microsoft)
* AutoAI (IBM)
* PyCaret (オープンソースライブラリ）

## 7.モデルの学習

モデルの選定まで完了したら、いよいよ学習データを利用してモデルの学習を行います。

学習というと大変な作業に聞こえますが、実際には最近は便利なライブラリが豊富に揃っているので少ないコーディングで簡単に実装できます。

以下はロジスティック回帰のコーディングの一例です。

```python
#関連ライブラリの読み出し
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

#ロジスティック回帰をモデルに選ぶ
model = LogisticRegression() 
#モデルの学習
model.fit(X_train, y_train)
#予測
y_pred = model.predict(X_train)
#精度を表示
print(accuracy_score(y_train, y_pred))
```

## 8.予測結果の評価

上記のコーディングで精度まで表示させていますが、予測結果を評価する指標は精度だけではありません。

精度というのはあくまで正解したか／不正解だったかという単純な結果を表しているだけで、他にも評価指標はたくさんあり、またコンペごとに要求されている評価指標は異なっています。

下記はその評価指標の例です。

### 回帰
* RMSE：Root Mean Squared Error
* RMSLE：Root Mean Squared Logarithmic Error
* MAE：Mean Absolute Error

### 二値分類

* 混同行列
* 正答率（精度）と誤答率
* 適合率と再現率
* F1スコアとFβスコア
* logloss
* AUC

### 多クラス分類

* Multi class accuracy
* Multi class logloss

これ以外にも本当にたくさんあります。

## まとめ

ここまでで一連のデータ分析の流れは説明できたと思います。

あとは、これを精度や評価指標の向上のために3〜8のステップを繰り返して徐々にブラッシュアップしていく、、というような工程になると思います。

今回紹介したTitanic号のような表形式の問題は、Kaggleではもうあまり姿を見かけなくなってしまいましたが、データ分析の基本的な流れを網羅しているので、まずはこの表形式を完璧にこなせるようになることがデータサイエンスや機械学習に関わる人たちの最初の目標になると思っています。