[Seaborn]積み上げ棒グラフを出力してみた

# はじめに

最近、seabornというpythonでグラフを作るためのライブラリを使い始めたのですが、積み上げ棒グラフを作るのに少し手間かかったので、この機会にまとめてみることにしました。

サンプルソースを作成したので、困っている方の参考になれば幸いです。

（22/12/20 19:33追記）
コメント欄にて、簡潔な積み上げ棒グラフの作り方を教えて頂きました。ありがとうございます！



# 積み上げ棒グラフを出力

作成したサンプルソースは次のようになっています。

```python
# パッケージのインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rc
import seaborn as sns

# グラフの日本語対応
rc('font', family='BIZ UDGothic')

# データの用意
study_history = pd.DataFrame({'年': [2021, 2021, 2021, 2022, 2022, 2022],
                              '学習内容': ['英語', 'プログラミング', '資格', '英語', 'プログラミング', '資格'],
                              '学習時間': [30, 150, 100, 150, 100, 100]})

# 勉強時間の全期間の合計が多い順に積み上げるための工夫
df = study_history.groupby('学習内容', as_index=False) \
    .agg({ '学習時間': 'sum' }) \
    .sort_values('学習時間', ascending=False) \
    .drop(columns='学習時間')
df['積み上げ順序'] = range(1, df.shape[0]+1)

study_history = pd.merge(study_history, df, on=['学習内容'], how='left')
study_history.sort_values(['年', '積み上げ順序'], inplace=True)

# メモリ開放
del df

# 棒グラフを積み上げたときのtopの高さを作成
study_history['top'] = study_history.groupby('年')['学習時間'].cumsum()

# カラーパレットの作成
colors = sns.color_palette(n_colors=3)

# 学習内容で分割してリストに格納
labels = study_history['学習内容'].unique()
dfs = [study_history.loc[study_history['学習内容']==label] for label in labels] 

# 積み上げ棒グラフの表示
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
for i, _ in enumerate(labels):
    if i == 0:
        sns.barplot(x='年', y='学習時間', data=dfs[i], ax=ax, label=labels[i], color=colors[i])
    else:
        sns.barplot(x='年', y='学習時間', data=dfs[i], ax=ax, bottom=dfs[i-1]['top'], label=labels[i], color=colors[i])
ax.legend(loc='upper left')

```

上記サンプルソースを実行すると次のグラフが生成されます。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526990/46808322-bb4a-50d7-868b-76ef3d5fae85.png)

上から順番に解説します。

必要なパッケージをインポートしています。

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rc
import seaborn as sns
```

今回用意したデータに日本語を含めました。

デフォルトだとグラフが文字化けするので、日本語の文字化け対応で次のコードを書いています。

```python
rc('font', family='BIZ UDGothic')
```

積み上げ棒グラフに使うデータとして、架空のA君の学習履歴（年度と学習内容で勉強時間がサマリされたデータ）を用意してみました。  

```python
study_history = pd.DataFrame({'年度': [2021, 2021, 2021, 2022, 2022, 2022],
                              '学習内容': ['英語', 'プログラミング', '資格', '英語', 'プログラミング', '資格'],
                              '学習時間': [30, 150, 100, 150, 100, 100]})
```

学習履歴をデータフレームで表示すると次のようになっています。

| | 年 | 学習内容 | 学習時間 |
|- | - | - | - |
|  0| 2021 | 英語 | 30 |
|  1| 2021 | プログラミング | 150 |
|  2| 2021 | 資格勉強 | 100 |
|  3| 2022 | 英語 | 150 |
|  4| 2022 | プログラミング | 100 |
|  5| 2022 | 英語 | 100 |

積み上げ棒グラフでは、学習時間の大きい棒グラフから積み上がってほしいので、順位を付与した後、昇順で並び替えてみました。

```python
# 勉強時間の全期間の合計が多い順に積み上げるための工夫
df = study_history.groupby('学習内容', as_index=False) \
    .agg({ '学習時間': 'sum' }) \
    .sort_values('学習時間', ascending=False) \
    .drop(columns='学習時間')
df['積み上げ順序'] = range(1, df.shape[0]+1)

study_history = pd.merge(study_history, df, on=['学習内容'], how='left')
study_history.sort_values(['年', '積み上げ順序'], inplace=True)

# メモリ開放
del df
```

sns.barplotの引数「bottom」に渡すためのデータを作成します。

対象の学習内容を含めが棒グラフの高さを出しておき、あとの話になりますが、bottomにはひとつ前の学習内容の棒グラフの高さを渡すようにします。

```python
# 棒グラフを積み上げたときのtopの高さを作成
study_history['top'] = study_history.groupby('年')['学習時間'].cumsum()
```

色を指定しないと異なる学習内容にも拘わらず、同色になってしまうので、カラーパレットを作っておきます。

```python
# カラーパレットの作成
colors = sns.color_palette(n_colors=3)
```

学習内容ごとリストに格納し、学習内容のリストを使って学習内容別にデータを分割してリストに格納しておきます。

データをリストに格納しておくことで、ひとつ前の学習内容の棒グラフの高さ（top）に簡単にアクセスできるようにしています。

```python
# 学習内容で分割してリストに格納
labels = study_history['学習内容'].unique()
dfs = [study_history.loc[study_history['学習内容']==label] for label in labels] 
```

for文で回してグラフを積み上げていきます。

一つ目はbottomが不要なので、処理を分けています。

```python
# 積み上げ棒グラフの表示
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
for i, _ in enumerate(labels):
    if i == 0:
        sns.barplot(x='年', y='学習時間', data=dfs[i], ax=ax, label=labels[i], color=colors[i])
    else:
        sns.barplot(x='年', y='学習時間', data=dfs[i], ax=ax, bottom=dfs[i-1]['top'], label=labels[i], color=colors[i])
ax.legend(loc='upper left')
```

以上になります。

# おわりに


今回、積み上げ棒グラフを作るだけにも拘わらず、コードが長くなってしまいました。

簡潔な書き方がわかり次第追記しようと思います。