タイタニックに学ぶ探索的データ分析

機械学習で予測精度に大きな差をつけるために、探索的データ分析（Exploratory Data Analysis, EDA）が重要だと言われています。データの特性や構造を把握し、異常値や欠損値を特定し、データの分布や相関関係を理解する手助けになります。また、重要な特徴量を選択・生成することができるようになります。

具体的にどうやって探索的データ分析を行えばよいのか知りたくて、Kaggleのタイタニックをサンプルに試してみました。

## データを観察する

データを読み込んで、ydata-profilingによる分析を行います。視覚的にデータ分析できるのでけっこう楽しいです。

```python
import pandas as pd
import ydata_profiling as yf
df = pd.read_csv('../input/titanic/train.csv')
yf.ProfileReport(df)
```

では１つ１つ見ていきましょう。まずは概要です。
12の特徴量があり、891行のデータがあることがわかります。そのうち欠損値は866です。12の特徴量のうち、量的変数が5、質的変数が4、テキストが3です。

![overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/ba7e43af-6a1c-fb94-6808-550e086fb90d.png)


アラートタブを見ると、何かしらのデータ加工をしたほうが良さそうな特徴量がわかります。参考になりますね。

![alert.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/e15a4517-3ea5-864b-fa49-94bf3cb77104.png)


次にそれぞれの特徴量を観察してみましょう。
まず、PassengerId（乗客ID）（量的変数）は、完全に一意です。これはIDなので当然ですね。


![passsengerid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/519ab859-7f61-afe9-9aeb-3b0024aba624.png)

Servived（生存したか）（質的変数）は目的変数です。欠損値はありません。

![survive.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/4f7bfcac-df23-206b-bd77-94c339804e10.png)

Pclass（チケットクラス）（質的変数）は1,2,3の数値で構成される特徴量です。欠損値はありません。

![スクリーンショット 2024-07-31 21.07.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/db941608-4422-c02d-ee0a-7351aa9a2629.png)

Name（氏名）（テキスト）は完全に一意な特徴量です。ですが、分析結果を見るとmrやmrsといったよく表れる単語があることがわかります。


![スクリーンショット 2024-07-31 21.07.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/711d55c6-f492-e121-4844-3afa7e4f8421.png)

Sex（性別）（質的変数）はmale/femaleの2種類で構成されています。欠損値はありません。


![スクリーンショット 2024-07-31 21.07.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/4a728d27-3c82-8540-c399-5c1d42c31d33.png)

Age（年齢）（量的変数）は0.42から80までの数値で構成されています。19%が欠損値です。

![スクリーンショット 2024-07-31 21.07.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/8f36ff03-822f-8ca5-7025-ad61be49a4e3.png)

SibSb（同乗した兄弟・配偶者の数）（量的変数）は0~8の数値です。欠損値はありませんが、68%がゼロです。

![スクリーンショット 2024-07-31 21.07.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/fe7bdd63-b64d-c6f3-bd6e-c806440cdd9c.png)

Parch（同乗した両親・子どもの数）（量的変数）は0~6の数値です。こちらも欠損値はありませんが、76%がゼロです。

![スクリーンショット 2024-07-31 21.07.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/a7c3e393-d4a8-3419-5880-59136c8dc8a3.png)

Ticket（チケット番号）（テキスト）。とりあえず「番号」と書きましたが文字列です。76%が一意です。

![スクリーンショット 2024-07-31 21.07.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/bf75297a-be10-12cd-c21d-f3da3ce20f9e.png)

Fare（運賃）（量的変数）　は0~512.3の数値です。


![スクリーンショット 2024-07-31 21.08.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/6fc5411c-1d91-6f0c-5b2f-c877e698a9aa.png)

Cabin（旅客番号）（テキスト）は欠損率が77%もあります。

![スクリーンショット 2024-07-31 21.08.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/8306df45-930e-d4cb-9293-257941ed8ebe.png)

Embarked（出港地）（質的変数）は乗船した港を表す記号で、C：シェルブール、Q：クイーンズタウン、S：サザンプトンとなります。

![スクリーンショット 2024-07-31 21.08.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/cc2e5bfb-1231-a6c5-5356-8b06c46fdf72.png)


### データ観察のまとめ

各特徴量をまとめるとこうなります。

| variables                       | types    | 特徴                                               | 
| ------------------------------- | -------- | -------------------------------------------------- | 
| PassengerId（乗客ID）           | 量的変数 | 完全に一意                                         | 
| Servives（生存）                | 質的変数 | 目的変数                                           | 
| Pclass（チケットクラス）        | 質的変数 | 1,2,3の数値                                        | 
| Name（氏名）                    | テキスト | 完全に一意だが、mr、mrsなどの重複あり              | 
| Sex（性別）                     | 質的変数 | male/female                                        | 
| Age（年齢）                     | 量的変数 | 19%が欠損                                          | 
| SibSp（同乗した兄弟・配偶者数） | 量的変数 | 68%がゼロ                                          | 
| Parch（同乗した両親・子供数）   | 量的変数 | 76%がゼロ                                          | 
| Ticket（チケット番号）          | テキスト | 76%が一意                                          | 
| Fare（運賃）                    | 量的変数 | 0から512                                           | 
| Cabin（旅客番号）               | テキスト | 77%が欠損                                          | 
|  Embarked（出港地）             | 質的変数 | C:シェルブール、Q:クイーンズタウン、S:サザンプトン | 

## データを可視化する

例として、年齢を生存別のヒストグラムで表示してみます。もし、両者が同じ傾向でしたら、年齢は生存率に影響がないかもしれません。年齢と生存率の相関関係を直感的に
把握するために、グラフは適しています。

```python
import seaborn as sns
import matplotlib.pyplot as plt

g = sns.FacetGrid(df, col='Survived')
g.map(plt.hist, 'Age', bins=20)
```

乳幼児は生存率が高そうです。また、20歳前後の人達は生存率が低そうに見えます。

![スクリーンショット 2024-08-01 20.56.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/a2ace8e3-f170-0218-826f-df682a553904.png)


## データを加工する

予測の精度を上げたり、計算負荷を軽減するために、データを加工します。

#### 不要な特徴量を削除する

重複値が多い特徴量としてTicketを、欠損値が多い特徴量としてCabinを削除します。

```python
df = df.drop(['Ticket','Cabin'], axis=1)
df = df.drop(['Ticket','Cabin'], axis=1)
```

#### 欠損値を補完する

Emberkedの欠損値を最頻値で補完します。

```python
freq_embarked = df.Embarked.dropna().mode()[0]
df['Embarked'] = df['Embarked'].fillna(freq_embarked)
```

Ageの欠損値を平均値で補完します。

```python
mid_age = df.Age.dropna().median()
df['Age'] = df['Age'].fillna(mid_age)
```

#### 新しい特徴量を作る

NameはPasssengerIdと同じく完全に一意ですが、部分的に重複度が高い点に注目します。どうやら「Mr.」などの敬称(Title)が生存率に影響ありそうです。そこでTitleを特徴量として抽出します。類似の敬称をまとめ、頻度が低いものは「Rare」としてひとまとめにします。抽出が終わったら、Nameは削除します。

```python
df['Title'] = df.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
df['Title'] = df['Title'].replace('Mlle', 'Miss')
df['Title'] = df['Title'].replace('Ms', 'Miss')
df['Title'] = df['Title'].replace('Mme', 'Mrs')
​
df['Title'] = df['Title'].replace(['Lady', 'Countess','Capt', 'Col',\
    'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
​
df = df.drop(['Name'], axis=1)
```

#### 特徴量を組み合わせる

SibSpは同乗した兄弟・配偶者の数で、Parchは同乗した両親と子どもの数です。これらの特徴量を同乗した家族(FamilySize)にまとめます。さらに単独の搭乗者であることを示すIsAloneという特徴量を新たに作ります。まとめ終わったら、SibSpとParchは削除します。

```python
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
​
df['IsAlone'] = 0
df.loc[df['FamilySize']==1, 'IsAlone'] = 1
​
df = df.drop(['SibSp','Parch'], axis=1)
```

#### 質的変数を量的変数に変換する

アルゴリズムで解析しやすいよう、文字列が設定されている質的変数の特徴量を数値に置き換えます。Sex, Emberked, Titleが対象です。また、Titleの欠損値は0で補完します。

```python
df['Sex'] = df['Sex'].map( {'female': 1, 'male': 0} ).astype(int)

df['Embarked'] = df['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
df['Title'] = df['Title'].map(title_mapping)
df['Title'] = df['Title'].fillna(0)
```

## おわりに

これでだいぶ解析しやすいデータになったと思います。 他にもその特徴量の性質に合わせてデータとまとめたり、結合したり、やり方は様々あるようです。

これを基本として、今後も様々なデータ探索を参考にしたいと思います。

今回の記事を作成するために使用したkaggle notebookはこちらです。
https://www.kaggle.com/code/kysd2023/exploratorydataanalysislearningfromtitanic

## 参考文献

* [Titanic Data Science Solutions](https://www.kaggle.com/code/startupsci/titanic-data-science-solutions)
* [PythonではじめるKaggleスタートブック](https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5Data-Science%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-Python%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8BKaggle%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF-KS%E6%83%85%E5%A0%B1%E7%A7%91%E5%AD%A6%E5%B0%82%E9%96%80%E6%9B%B8-%E7%A5%A5%E5%A4%AA%E9%83%8E/dp/4065190061)