【入門者向け】機械学習前にデータ探索・可視化でデータを理解(pandasとmatplotlib)

`Pandas`をたまに使っていたのですが、毎回ググって調べるのが苦痛になってきたため記事にまとめました。Kaggleを本格的にやろうとしていることがきっかけです。
結構色々書いたのですが、グラフ出力はパターンが多すぎるので良いと思ったものがあれば適宜追加予定です。

# 環境
[記事「UbuntuにpyenvとvenvでPython開発環境構築」](https://qiita.com/FukuharaYohei/items/2820f7bda6678ce26ea7)に書いた方法で環境構築をしています。[記事「Windows PCで機械学習環境を作る方法まとめ」](https://qiita.com/FukuharaYohei/items/8fb34107dda208f79bca)に書いている別方法でもいいと思います(少し古いので間違っている可能性などあり)。

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu20.04.01 LTS|仮想で動かしています|
|pyenv|1.2.24|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.9.2|pyenv上でpython3.9.2を使っています<br>パッケージはvenvを使って管理しています|

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。

|種類|バージョン|
|:--|:--|
|pandas|1.2.4|
|matplotlib|3.4.1|
|jupyter lab|3.0.14|

# 前提
データは[Kaggle Titanicのtrain.csv](https://www.kaggle.com/c/titanic/data?select=train.csv)を使っています。
各ライブラリのエイリアスは以下のように定義しています。

```python
import pandas as pd 
import matplotlib.pyplot as plt
```

# ファイル読込
`read_csv`を使います。特筆することもないですが、いろいろオプションがありますので[公式API](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html)を参照ください。`read_table`もありますが、デフォルトの区切り文字が違うだけのようです([記事「pandasでcsv/tsvファイル読み込み（read_csv, read_table）」](https://note.nkmk.me/python-pandas-read-csv-tsv/)参照)。

```python
train_data = pd.read_csv("/kaggle/input/titanic/train.csv")
```

`read_table`と`read_csv`は過去に言語処理100本ノックでも多用しました。

- [12本目: 1列目をcol1.txtに，2列目をcol2.txtに保存](https://qiita.com/FukuharaYohei/items/87f0413b87c6109e8ca4#121%E5%88%97%E7%9B%AE%E3%82%92col1txt%E3%81%AB2%E5%88%97%E7%9B%AE%E3%82%92col2txt%E3%81%AB%E4%BF%9D%E5%AD%98): `header=None, usecols=[0, 1]`を使って、ヘッダ行無視と行指定
- [31本目: 動詞](https://qiita.com/FukuharaYohei/items/ab58859e861afda1a301):
    -  `sep` : 区切り文字指定
    -  `names`: index名指定
    -  `skiprows`: スキップ行指定 
    -  `skipfooter`: スキップフッタ行指定
    -   `engine`: エンジン指定(確か`skipfooter`使うと`enginge`をpythonにする必要あり)

## ファイル読込時にやること
### 概要出力: `info`
ファイル読込時に`info`関数で概要を出力します。型がわかったり、欠損値がわかったりと便利です。

```python
train_data.info()
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 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          714 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     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
```

### 統計情報出力: `describe`
`describe`関数で数値系項目は統計情報を出力できます。

```python
train_data.describe()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/488305fd-d280-4b3c-7199-2bca33234e9f.png)

# データ加工
## コピー: `copy`
元データを保存しておきたい場合は、コピーして新しいDataFrameに対して加工します。

```python
train_new = train_data.copy()
```

## 値のカウント: `value_counts`
`value_counts`を使って値の件数カウントをします。デフォルトでソートONなので、OFFにするオプションをよく使います。

```python
print(train_data.Pclass.value_counts())
print('----')
print(train_data.Pclass.value_counts(sort=False))
```

```
3    491
1    216
2    184
Name: Pclass, dtype: int64
----
1    216
2    184
3    491
Name: Pclass, dtype: int64
```

## 文字列検索・抽出系
文字列検索・抽出はよくやると思うので、簡単に書いておきます。詳しくは、以下の2つの記事で書きました。

- [DataFrameレシピ: 行列指定して出力](https://qiita.com/FukuharaYohei/items/5b739a5ceb43d25aa2cd)
- [DataFrameレシピ: データ抽出条件](https://qiita.com/FukuharaYohei/items/c3cc966b11f5fc3ed0bf)

### 列に対して文字列部分一致検索
`str.containts`を使って列に対して文字列部分一致検索をします。今回は使っていませんが、正規表現も使えます。
※[記事「ゼロから覚えるPython正規表現の基本とTips」](https://qiita.com/FukuharaYohei/items/459f27f0d7bbba551af7)に正規表現について以前まとめました。

```python
# 文字列の部分一致検索
print(train_data[train_data.Name.str.contains('Mr.', regex=False)].Name)
```

```
0             Braund, Mr. Owen Harris
4            Allen, Mr. William Henry
5                    Moran, Mr. James
6             McCarthy, Mr. Timothy J
12     Saundercock, Mr. William Henry
                    ...              
881                Markun, Mr. Johann
883     Banfield, Mr. Frederick James
884            Sutehall, Mr. Henry Jr
889             Behr, Mr. Karl Howell
890               Dooley, Mr. Patrick
Name: Name, Length: 517, dtype: object
```

### 列に対して文字列部分一致検索での件数出力
リストに検索語句を個々に入れて、各検索語句でヒットした件数を出力します。

```python
# 文字列部分一致で件数カウント
titles = ["Mr.", "Miss.", "Mrs.", "Master.", "Dr.", "Rev.", "Col.", "Ms.", 
          "Mlle.", "Mme.", "Capt.", "Countess.", "Major.", "Jonkheer.", "Don.", 
         "Dona.", "Sir.", "Lady."]
total = 0
for i, title in enumerate(titles):
    print(i, title, ": ", train_data.Name.str.contains(title, regex=False).sum())
    total += train_data.Name.str.contains(title, regex=False).sum()
    
print("Total: ", total)
```

```:出力結果
0 Mr. :  517
1 Miss. :  182
2 Mrs. :  125
3 Master. :  40
4 Dr. :  7
5 Rev. :  6
6 Col. :  2
7 Ms. :  1
8 Mlle. :  2
9 Mme. :  1
10 Capt. :  1
11 Countess. :  1
12 Major. :  2
13 Jonkheer. :  1
14 Don. :  1
15 Dona. :  0
16 Sir. :  1
17 Lady. :  1
Total:  891
```

### 列に対して文字列部分一致検索した結果を新しい列に追加
文字列検索した結果、合致した文字列を`Title`という列に追加しています。

```python
# 'Name'に'Titleリストの文字を含んでいた場合は列'Title'に設定
for title in titles:
    train_data.loc[train_data.Name.str.contains(title, regex=False), 'Title'] = title
    
print(train_data.loc[:,['Name', 'Title']])
```

```:出力結果
                                                  Name  Title
0                              Braund, Mr. Owen Harris    Mr.
1    Cumings, Mrs. John Bradley (Florence Briggs Th...   Mrs.
2                               Heikkinen, Miss. Laina  Miss.
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)   Mrs.
4                             Allen, Mr. William Henry    Mr.
..                                                 ...    ...
886                              Montvila, Rev. Juozas   Rev.
887                       Graham, Miss. Margaret Edith  Miss.
888           Johnston, Miss. Catherine Helen "Carrie"  Miss.
889                              Behr, Mr. Karl Howell    Mr.
890                                Dooley, Mr. Patrick    Mr.
```

# 可視化
## 相関行列
`corr`関数を使って相関行列を出力します。無駄な行を省くために列名を指定しています。留意点です。

- データ型がobject(文字列)は除外され、数値(int, float)型およびbool型(Trueが1、Falseが0)の相関係数が算出されます。
- 相関係数算出方法のデフォルトは**ピアソンの積率相関係数**
- 欠損値NaNは計算から除外
- `matplotlib`でグラフ化しているわけではない
- `style`と`background_gradient`を使ってヒートマップっぽく表示することで見やすい

※参考記事[「【1行で】Pandasだけで相関行列をヒートマップっぽく可視化する」](https://qiita.com/hrappuccino/items/b0b6bc6aad22b94081d9)

```python
train_data.loc[:, 
["Survived", "Pclass", "Age", "SibSp", "Parch", "Fare"]].corr().style.background_gradient(axis=None)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/5dbec4cd-65f5-5002-cb71-4da87ae6b7a4.png)

## グラフ
少量データはExcelでグラフ作ったりしたほうが早いのですが、慣れのためにライブラリ`matplotlib`でグラフ出力やってみます。
**DataFrameの列を簡単に出力するだけなら１行で出来ます**。この辺はPandasの素晴らしい点ですね。

```
train_data.Survived.value_counts(sort=False).plot.bar()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/e90eef89-c646-9b1b-b9ef-d7a24c31e522.png)

[欠損値をグラフがどのように解釈するか](https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html#plotting-with-missing-data)は**注意が必要です**。

### matplotlibの基本
簡易的な出力なら上記のコードだけでいいのですが、グリッドやラベル、グラフを並べるなど、いろいろしたくなります。そのときに基本を知っておく必要があります。
今まで、適当にググりながらグラフ表示してきた人は、[記事「早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話」](https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9)を読みましょう。**２種類の流儀が世の中に混同**されていますが、そこを整理してくれます。
あと、有名な日本語使用に関する豆腐問題は[「豆腐(グラフ文字化け対応)対応」](https://qiita.com/FukuharaYohei/items/dc355bcc038c2cee4de8#%E8%B1%86%E8%85%90%E3%82%B0%E3%83%A9%E3%83%95%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91%E5%AF%BE%E5%BF%9C%E5%AF%BE%E5%BF%9C)を参照ください。
細かい調整をしようと思ったら[記事「matplotlibのめっちゃまとめ」](https://qiita.com/nkay/items/d1eb91e33b9d6469ef51)が参考になります。

#### グラフの並列表示
ダッシュボードのようにグラフを並べて表示したくなることがあります。その場合は`plt.subplots`時に行数(nrows)・列数(ncols)と全体のサイズを指定します。ここで取得した`axes`で個々のグラフに関する調整をします。
まずは、おまじないのように以下のコマンドを実行。

```python
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(12, 3))
```

すると2行・3列の空のグラフが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/f9469672-af08-27cb-997b-a3bc8e155bd0.png)

ここで、エリア全体を司る`fig`を使えば、例えばグラフエリア間の余白長さを調整できます。私の乏しい経験ではあまり使いません。例えば、余白を調整してみます(わかりやすいようにあえて大きくしています)。

```python
fig.subplots_adjust(wspace=3, hspace=3)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/ab7c367a-5ced-4680-2795-16c043e6aa1d.png)

axesは配列です。個々のグラフを司ります。例えばX軸ラベルを`set_xticklabels`で変えてみます(グラフ左下)。`print(axes.shape)`の出力は`(2, 3)`です(`plt.subplots`で指定した行列数)。

```python
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(12, 3))
print(axes.shape)
axes[1, 0].set_xticklabels(labels=['aaa', 'bbb'], rotation=30, horizontalalignment="center")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/70b9384b-8aa8-d876-7b63-91ac5482c09b.png)

より柔軟な配置をする場合は以下のページが参考になります。

https://www.delftstack.com/ja/howto/matplotlib/how-to-make-different-subplot-sizes-in-matplotlib/

https://www.delftstack.com/ja/howto/matplotlib/add-subplot-to-a-figure-matplotlib/

### 基本: 円グラフ
割合を見たい場合には円グラフですね。`autopct="%1.1f%%`とすることでデータラベルを%で小数点第１位まで出してくれます。`value_counts`時にソートをOFFにしているのは、ラベルとの整合性を保つためです(自動でラベル出力されるのですが、「0」と「1」だと意味がすぐにわからない
ので追加)。

|位置|オプション|
|:-:|:--|
|左|オプションなし|
|中央|ラベルあり|
|右|ラベル、割合データラベル(autpct)あり|

```python
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 3))

fig.subplots_adjust(wspace=0.5, hspace=0.5)
labels_survived = ["0: Dead","1: Survived"]
train_data.Survived.value_counts(sort=False).plot.pie(ax=axes[0])
train_data.Survived.value_counts(sort=False).plot.pie(labels=labels_survived, ax=axes[1])
train_data.Survived.value_counts(sort=False).plot.pie(labels=labels_survived, autopct="%1.1f%%", ax=axes[2])
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/d1e327a4-8e18-9c6d-2619-fb8d862911f4.png)

### 基本: 棒グラフ(積上でない)
棒グラフに`set_xticklabels`でラベルを追加し、向きを傾けています。また、`grid`でY軸にグリッドを追加します。同じグラフの3つを並列で比較しています。
`value_counts`時にソートをOFFにしているのは、ラベルとの整合性を保つためです。
4列目のデータラベルを出力するコードは少しトリッキーです。`text`関数を使ってX座標とY座標を指定して主力しています(少し見にくい)。

|位置|オプション|
|:-:|:--|
|1列目|オプションなし|
|2列目|ラベルあり|
|3列目|ラベルあり、ラベル向き調整|
|4列目|ラベルあり、ラベル向き調整、データラベルあり|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/1301b814-aa3a-d08b-2733-1533c2b7850d.png)

```python
LABELS_SURVIVED = ['0: Dead', '1: Survived']
sr_survived = train_data.Survived.value_counts(sort=False)

fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(16, 3))
fig.subplots_adjust(wspace=0.5, hspace=0.5)

sr_survived.plot.bar(ax=axes[0])
sr_survived.plot.bar(ax=axes[1])
sr_survived.plot.bar(ax=axes[2])
sr_survived.plot.bar(ax=axes[3])

axes[1].set_xticklabels(labels=LABELS_SURVIVED)
axes[1].grid(axis='y', linestyle='dotted')

axes[2].set_xticklabels(labels=LABELS_SURVIVED, rotation=30, horizontalalignment='center')
axes[2].grid(axis='y', linestyle='dotted')

axes[3].set_xticklabels(labels=LABELS_SURVIVED, rotation=30, horizontalalignment='center')
axes[3].grid(axis='y', linestyle='dotted')
# 2つのcountは順にy軸の座標と出力テキスト
[axes[3].text(i, count, count, horizontalalignment='center', color='green')
 for i, count in enumerate(sr_survived)]

plt.show()
```

### 基本: 棒グラフ(2種類データを積上含めて出力)
2種類のデータがあるので、`ax.legend`で凡例出力してわかりやすくします。

|位置|オプション|
|:-:|:--|
|1行目1列目|積上なしでデータ横並び|
|1行目2列目|積上なしでデータ横並びにしてデータラベル追加|
|2行目1列目|積上(stackedをTrue)|
|2行目2列目|積上(stackedをTrue)にしてデータラベル追加|
|2行目3列目|積上を100%表示(value_counts時にnormalizeをon)|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/fa9b37f0-9bcd-dadb-8523-0a94637d1277.png)

```python
def arrange_bar(ax, x_labels, y_labels=[]):
    ax.set_xticklabels(labels=x_labels, rotation=30, horizontalalignment="center")    
    hans, labs = ax.get_legend_handles_labels()
    ax.legend(handles=hans, labels=y_labels)
    ax.grid(axis='y', linestyle='dotted')

LABELS_SURVIVED = ['0: Dead', '1: Survived']
LABELS_PCLASS = ["1: 1st(Upper)", "2: 2nd(Middle)", "3: 3rd(Lower)"]
df_pclass = train_data.groupby(["Pclass"])["Survived"].value_counts(sort=False).unstack()
    
fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(16, 3))
fig.subplots_adjust(wspace=0.5, hspace=0.5)

df_pclass.plot.bar(ax=axes[0])
df_pclass.plot.bar(ax=axes[1], stacked=True)
df_pclass.plot.bar(ax=axes[2], stacked=True)
train_data.groupby(["Pclass"])["Survived"].value_counts(sort=False, normalize=True).unstack().plot.bar(ax=axes[3], stacked=True)

arrange_bar(axes[0], LABELS_PCLASS, LABELS_SURVIVED)
arrange_bar(axes[1], LABELS_PCLASS, LABELS_SURVIVED)
arrange_bar(axes[2], LABELS_PCLASS, LABELS_SURVIVED)
[axes[2].text(i, item.sum(), item.sum(), horizontalalignment='center', color='green') 
 for i, (_, item) in enumerate(df_pclass.iterrows())]

arrange_bar(axes[3], LABELS_PCLASS, LABELS_SURVIVED)

plt.show()
```

### 実践
基本をもっと書こうかとも思ったのですが、時間ないので今は書きません(いつ書くかわからない)。実際にKaggle Titanic Challengeしたときのグラフ出力系コードです。
グラフ上に数値ではなくラベルを出したいので、以下のように辞書でラベルを定義。

```python
# 今後も使うラベル
DICT_SURVIVED = {0: '0: Dead', 1: '1: Survived'}
DICT_PCLASS = {1: '1: 1st(Upper)', 2: '2: 2nd(Middle)', 3: '3: 3rd(Lower)'}
DICT_EMBARK = {'C': 'Cherbourg', 'Q': 'Queenstown', 'S': 'Southampton'}
```


#### 円グラフと棒グラフの並列
生死をの数を単純に見たいときに円グラフと棒グラフを並べました。`arrange_bar`はこの後でも使います。
タイタニック号の事件のことなどまるで知りませんでしたが、思ったより助かっているのですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/4f310955-6224-ca95-4327-362e86fbdfe9.png)

```python
# 棒グラフのX軸ラベルを回転させ、Y軸にグリッド追加
def arrange_bar(ax):
    ax.set_xticklabels(labels=ax.get_xticklabels(), 
                       rotation=30, # xラベルを回転(完全に縦だと見にくい)
                       horizontalalignment="center")
    ax.grid(axis='y', linestyle='dotted')  # Y軸グリッド追加

# 生死の数をカウントし、グラフでわかりやすいように名称変更
sr_survived = train_data['Survived'].value_counts().rename(DICT_SURVIVED)

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 3))
fig.subplots_adjust(wspace=0.5, hspace=0.5)
sr_survived.plot.pie(autopct="%1.1f%%", ax=axes[0])  # 円グラフ

sr_survived.plot.bar(ax=axes[1]) # 棒グラフ
arrange_bar(axes[1])
[axes[1].text(i, count, count, horizontalalignment='center') 
 for i, count in enumerate(sr_survived)]  # データラベル追加(x座標i, y座標countにcountを表示)

plt.show()
```

#### 円グラフ・積上棒グラフ・積上でない棒グラフ・100%棒グラフ
円グラフ・積上棒グラフ・積上でない棒グラフ・100%棒グラフを並べます。
まずはコードから。積み上げでない棒グラフにデータラベルを出力する部分がトリッキーです。また、凡例がすでにテキストでわかりやすいやつはいいのですが、数字で「なんだっけ？」というやつは辞書で変換して出力しています。

```python
def output_bars(column, index={}):
    
    fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 8))
    fig.subplots_adjust(wspace=0.5, hspace=0.5)    

    # 列の Value Count
    sr_vc = train_data[column].value_counts()
    
    # 列ごとの生死 Value Count
    df_vc = train_data.groupby([column])['Survived'].value_counts(sort=False).unstack()
    
    # 列ごとの生死 Value Count(標準化あり)
    df_vc_norm = train_data.groupby([column])['Survived'].value_counts(
        sort=False, normalize=True).unstack()
    
    # index(リネームする凡例)がない場合
    if len(index) == 0:
        df_vc.rename(columns=DICT_SURVIVED, inplace=True)
        df_vc_norm.rename(columns=DICT_SURVIVED, inplace=True)

        # index(リネームする凡例)がある場合
    else:
        sr_vc.rename(index, inplace=True)
        df_vc.rename(index=index, columns=DICT_SURVIVED, inplace=True)
        df_vc_norm.rename(index=index, columns=DICT_SURVIVED, inplace=True)

    sr_vc.plot.pie(ax=axes[0, 0], autopct="%1.1f%%")  # 円グラフ
    df_vc.plot.bar(ax=axes[0, 1], stacked=True) # 積上棒グラフ
    df_vc.plot.bar(ax=axes[1, 0]) # 棒グラフ
    df_vc_norm.plot.bar(ax=axes[1, 1], stacked=True) # 100%棒グラフ

    arrange_bar(axes[0, 1])
    arrange_bar(axes[1, 0])
    arrange_bar(axes[1, 1])

    # 積上棒グラフにデータラベル追加
    [axes[0, 1].text(i, item.sum(), item.sum(), horizontalalignment='center') 
     for i, (_, item) in enumerate(df_vc.iterrows())]
    
    # 棒グラフにデータラベル追加
    for rect in axes[1, 0].patches:
        height = rect.get_height()
    
        axes[1, 0].annotate('{:.0f}'.format(height),
                        xy=(rect.get_x() + rect.get_width() / 2, height),
                        xytext=(0, 3),  # 3 points vertical offset
                        textcoords="offset points",
                        ha='center', va='bottom')

    plt.show()
```

##### PClass(チケットクラス)
先程定義した関数`output_bars`を呼び出します。カテゴリ変数1, 2, 3だとわかりにくいので、辞書も渡します。
3(Lower)の安いチケットだと死にやすいのが一目瞭然ですね。

```python
output_bars('Pclass', DICT_PCLASS)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/afa8c1b5-94dc-3288-2a99-c72a16d85fe6.png)

##### Sex(性別)
今度は、テキストが入っている列「Sex」なのでラベル辞書は渡していません。
女性は8割弱助かっているのが興味深いです。レディファーストの精神で、救命ボートに先に乗せていったのでしょうか。

```python
output_bars('Sex')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/2afd8256-9eb4-62c1-b2d5-de404b866afe.png)

##### Embarked(出港地)
タイタニック号に乗った港です。乗った港によってなぜ生死の確率が左右するのかまるで理解できないです。

```python
output_bars('Embarked', DICT_EMBARK)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8c33c26e-d24f-49a8-26bf-5f22e7838436.png)

#### 箱ひげ図とヒストグラムの並列
数値系項目で分布をみたいときに箱ひげ図とヒストグラムで可視化しました。
ヒストグラムは、3行目に範囲を絞って見られるようにもしています。

```python
# グラフで欠損値は自動除去されている
def output_box_hist(column, bins=20, query=None):
    
    # 3行目に範囲を絞ってヒスとグラムを出さない場合
    if query == None:
        fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 8))

    # 3行目に範囲を絞ってヒスとグラムを出す場合
    else:
        fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(12, 12))
        
        train_data.query(query)[column].hist(ax=axes[2, 0], bins=bins) # 範囲限定ヒストグラム
        train_data.query(query).groupby('Survived')[column].plot.hist(
            ax=axes[2, 1], bins=bins, alpha=0.5, legend=True, grid=True) # 範囲限定ヒストグラム(生死別)
        axes[2, 1].legend(labels=[DICT_SURVIVED[int(float((text.get_text())))] 
                                  for text in axes[2, 1].get_legend().get_texts()]) # 凡例表示

    fig.subplots_adjust(wspace=0.5, hspace=0.5)

    train_data.boxplot(ax=axes[0, 0], column=[column]) # 箱ひげ図
    train_data.boxplot(ax=axes[0, 1], column=[column], by='Survived') # 箱ひげ図(生死別)
    axes[0, 1].set_xticklabels([DICT_SURVIVED[int(float(xticklabel.get_text()))] 
                                for xticklabel in axes[0, 1].get_xticklabels()]) # x軸にラベル表示
    train_data[column].hist(ax=axes[1, 0], bins=bins) # ヒストグラム
    train_data.groupby('Survived')[column].plot.hist(
        ax=axes[1, 1], bins=bins, alpha=0.5, grid=True, legend=True) #ヒストグラム
    axes[1, 1].legend(labels=[DICT_SURVIVED[int(float((text.get_text())))] 
                              for text in axes[1, 1].get_legend().get_texts()]) # ヒストグラム(生死別)

    plt.show()
```

##### Age(年齢)
年齢平均値は生死によって関係なさそうですが、15才以下だと生存確率が高いです。これも、女性は生存確率が高いのと同様、子供を優先的に助けたのでしょうか。

```python
output_box_hist('Age')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/13547b37-8b54-6ec9-b5bd-6f8bc2878af8.png)

##### Fare(運賃)
データが200以下に集中していたので、3行目に200以下のデータを出力しました。
安い料金を払っている人は死ぬ確率が非常に高いですね。貧乏人は粗末に扱われた、のでしょうか。

```python
output_box_hist('Fare', 20, 'Fare < 200')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/eafe8a0f-fe81-6b64-8096-ae15785207e3.png)

### 左右縦棒グラフとテーブル
種類が多いと横棒グラフはラベル出力の関係上、見にくくなります。そのため、縦棒グラフを出しました。

#### 前処理
Titanicのファイル読み込みしたデータを加工します。この記事の本題と異なるのでスキップしてもらえれば。
上の方で書きましたが、名前からタイトル(Mrなど)を抜き出し、新しい列に設定します。その後に、タイトルごとに人数と死亡率のDataFrameを作ります。

```python
# 'Name'に'Titleリストの文字を含んでいた場合は列'Title'に設定
for title in titles:
    train_data.loc[train_data.Name.str.contains(title, regex=False), 'Title'] = title
    
# Title出力用dfを作成し、トータルと死亡率計算
train_title = train_data.groupby(['Title'])['Survived'].value_counts().unstack().rename(columns=DICT_SURVIVED)
train_title = pd.concat([train_title, pd.DataFrame(train_title.sum(axis=1), columns=['Total'])], axis=1)
train_title['Death Rate'] = round(train_title['0: Dead'] / train_title['Total'], 2)
print(train_title)
```

#### タイトルごとの死亡率表示
縦棒グラフを左右にして表示。一番右の列にテーブルを表示します。1行目は総数で降順ソート、２行目は死亡率で降順ソート。
2行目のテーブルはソート順がグラフと完全に一致していません。「Death Rate」が同じ値だとうまくソート順がうまくいかず、何か方法があるのでしょうが、調べていません。
1行目の「Master」より下は数が少なすぎて、あまり機械学習に使えなそうです。「Master」は「少年ないし青年男性」の
ことのようです(スターウォーズのヨダみたいなイメージかと思っていたら違いました)。若年層は男性であってもそこそこ生存確率高いということですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/82451542-747a-8d73-742d-8e0d8d64c009.png)

```python
def arrange_barh(ax, title):
    ax.set_ylabel('')
    ax.grid(axis='x', linestyle='dotted')
    ax.set_title(title)

def show_two_barh(axes, field, df):
    df.plot.barh(ax=axes[0], stacked=True, y=[0, 1])
    df['Death Rate'].plot.barh(ax=axes[1], color='green')
    axes[0].invert_xaxis()
    axes[0].set_yticklabels([])
    arrange_barh(axes[0], 'Total')
    arrange_barh(axes[1], 'Death Rate')

    axes[2].axis('tight')
    axes[2].axis('off')
    df.sort_values(field, ascending=False, inplace=True)
    axes[2].table(cellText=df.values,
                  colLabels=df.columns,
                  rowLabels=df.index, loc='center')

def output_barh(df, height):
    fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(18, height))
    fig.subplots_adjust(wspace=0.3, hspace=0.2)

    show_two_barh(axes[0], 'Total', df.sort_values('Total')) # 1行目はTotalでソート
    show_two_barh(axes[1], 'Death Rate', df.sort_values('Death Rate', na_position='first')) #2行目は死亡率でソート

    plt.show()
```

### ある項目ごとのヒストグラム
タイトルごとの年齢ヒストグラムです。日本人に馴染みにくいタイトルの理解一助になりそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/23a5dc3e-d61d-a76e-89df-95570204d7d8.png)

```python
_, ax = plt.subplots(figsize=(16, 15))
train_data.Age.hist(ax=ax, by=train_data['Title'])
plt.show()
```