【matplotlib】棒グラフを積み上げて表示する【Python】
![](https://max999blog.com/wp-content/uploads/2022/06/Python_matplotlib_EC2_800x450.png)
積み上げ式の棒グラフを作成する方法。
カテゴリー別に棒グラフを並べたい場合は、seabornでグラフを作成する際にhueを指定すれば、簡単にhue別に並べたグラフを作成できる。
一方、グラフを縦に積み上げたい場合、hue毎に並べて表示するような簡単な方法ではできない。
シンプルな積み上げ棒グラフは割と簡単に作成できるが、例えばhueで横並びにしつつ縦にも積上げるような棒グラフの作成は、専用の機能がないので少し面倒。しかし、matplotlibの勉強にはなる。
基本的な積み上げ棒グラフの作成
hueなどでカテゴリー別の棒グラフなどを作成しない、基本的な棒グラフを縦に積み上げていく。
データ準備
以下のようなデータを対象としてグラフを作成する。
1import pandas as pd
2# データフレームの作成
3data = pd.DataFrame({
4 '月': ['1月', '2月', '3月', '4月', '5月'],
5 'hue1': [20, 30, 40, 50, 60],
6 'hue2': [15, 25, 35, 45, 55],
7 'hue3': [10, 20, 30, 40, 50]
8})
9print(data)
10# 月 hue1 hue2 hue3
11# 0 1月 20 15 10
12# 1 2月 30 25 20
13# 2 3月 40 35 30
14# 3 4月 50 45 40
15# 4 5月 60 55 50
日付列と各月のデータ3種類(hue1, hue2, hue3)が並んでいるので、月毎に積み上げる。
簡単な積み上げ棒グラフの場合、matplotlibでもseabornでもどちらでもできる。
matplotlibを使った積上げ棒グラフ
積上げ棒グラフを作成する場合、matplotlibを使った方がやりやすい。
hue1、hue2、hue3の各列の棒グラフを作成する。plt.bar()を3回やる感じ。
棒グラフを作成する際に、bottom_dataという引数を指定することで、棒グラフのy軸の開始位置を設定できる。
hue2、hue3の棒グラフ作成時にはbottom_dataを指定することで棒グラフを積み上げていくことができる。
1import matplotlib.pyplot as plt
2
3# 積み上げ棒グラフの作成
4plt.bar(data['月'], data['hue1'], label='hue1')
5# 下から2段目以降はbottomで各月の棒グラフが始まる位置を指定する。
6# 下から2段目は一番下のグラフの上なのでbottomにhue1列の値を指定する。
7plt.bar(data['月'], data['hue2'], bottom=data['hue1'], label='hue2')
8# 下から3番目は下から1番目、2番目のグラフの上にあるので、bottomにhu1、hu2列を加算した値を指定する。
9plt.bar(data['月'], data['hue3'], bottom=data['hue1']+data['hue2'], label='hue3')
10title = 'シンプルな積み上げ棒グラフ_matplotlib使用'
11plt.title(title)
12# 凡例の表示
13plt.legend()
14# グラフの表示
15plt.show()
16
![](https://max999blog.com/wp-content/uploads/2023/12/5ef5350edcb1b1f49ec641c3a11e7ff9.png)
多少地味だが、カテゴリー別に横に並べつつ、縦にも積上げるような棒グラフを作成したい場合、この基本を理解しておく必要がある。
seabornを使った積上げ棒グラフ
seabornを使う場合も基本的にはmatplotlibを使う場合と同じ。
sns.barplot()ではbottomを使うことになる。
1import seaborn as sns
2import matplotlib.pyplot as plt
3
4# 最初のカテゴリーのグラフを作成
5sns.barplot(x=data['月'], y=data['hue1'], color='blue', label='hue1')
6
7# 2つ目のカテゴリーのグラフを作成
8bottom_hue = data['hue1']
9sns.barplot(x=data['月'], y=data['hue2'], color='orange', label='hue2', bottom=bottom_hue)
10
11# 3つ目のカテゴリーのグラフを作成
12bottom_hue += data['hue2']
13sns.barplot(x=data['月'], y=data['hue3'], color='green', label='hue3', bottom=bottom_hue)
14title = 'シンプルな積み上げ棒グラフ_seaborn使用'
15plt.title(title)
16# 凡例の表示
17plt.legend()
18
19# グラフの表示
20plt.show()
21
![](https://max999blog.com/wp-content/uploads/2023/12/819066448e4f92b60aa35c997f27bc90.png)
これぐらいのシンプルなグラフならseabornでも描画可能。
hue別に横に並べて積み上げ棒グラフを作成する
データ準備
seabornのtipsデータを使う。
グラフ作成用にgroupbyで集約する。
x, yはそれぞれx軸、y軸の値。
hueは横に並べる対象のカテゴリー列(seabornのhueと同じ扱いをする対象列)。
stackは縦に積上げる対象のカテゴリー列。
x, hue, stackをキーにyを集約する。
1import matplotlib.pyplot as plt
2import seaborn as sns
3import numpy as np
4import pandas as pd
5
6# データの読み込み
7df_tips = sns.load_dataset("tips")
8
9# 変数の定義
10x = "day"
11y = "tip"
12hue = "sex"
13stack = "smoker"
14# observedを指定しないとFutureWarningが出るが、TrueでもFalseでもグラフ作成には影響しない
15data = df_tips.groupby([x, hue, stack])[y].sum().reset_index()
16print(data)
17# day sex smoker tip
18# 0 Thur Male Yes 30.58
19# 1 Thur Male No 58.83
20# 2 Thur Female Yes 20.93
21# 3 Thur Female No 61.49
22# 4 Fri Male Yes 21.93
23# 5 Fri Male No 5.00
24# 6 Fri Female Yes 18.78
25# 7 Fri Female No 6.25
26# 8 Sat Male Yes 77.74
27# 9 Sat Male No 104.21
28# 10 Sat Female Yes 43.03
29# 11 Sat Female No 35.42
30# 12 Sun Male Yes 52.82
31# 13 Sun Male No 133.96
32# 14 Sun Female Yes 14.00
33# 15 Sun Female No 46.61
34
x軸はday(曜日)、hueにsex(性別)を指定し、smokerのYes、Noを積み上げる。
hue別に横に並べつつ縦に積上げる棒グラフを作成
hueで横に並べるがseabornの便利な機能は使えないので、matpliotlibの機能だけで、地道に位置を設定しながらグラフを作成していく。
コード全体とグラフを記載してから、個別に説明していく。
コード全体とグラフ
1# プロットのサイズを設定
2plt.figure(figsize=(8, 4.5))
3
4# 各x値のユニークな値を取得
5x_values = data[x].unique()
6
7# hueとstackのユニークな値を取得
8hue_values = data[hue].unique()
9stack_values = data[stack].unique()
10
11# バーの幅を設定。hueの数によって変更。
12bar_width = round(1 / (len(hue_values) + 1), 2)
13print(f"{bar_width=}")
14# bar_width=0.33
15
16# バーの色を設定。hueとstackの組み合わせ毎に色を塗り分ける。
17n_colors = len(hue_values) * len(stack_values)
18colors = sns.color_palette("husl", n_colors)
19color_mapping = {}
20for idx, (hue_value, stack_value) in enumerate(itertools.product(hue_values, stack_values)):
21 color_mapping[(hue_value, stack_value)] = colors[idx]
22print(color_mapping.keys())
23# dict_keys([('Male', 'Yes'), ('Male', 'No'), ('Female', 'Yes'), ('Female', 'No')])
24
25# バーをプロット
26for i, x_value in enumerate(x_values):
27 for j, hue_value in enumerate(hue_values):
28 _bottom = 0 # bottomの初期値を設定
29 for k, stack_value in enumerate(stack_values):
30 # 対応するデータを取得
31 _data = data[(data[x] == x_value) & (data[hue] == hue_value) & (data[stack] == stack_value)]
32 # バーのx位置を計算
33 # iは1, 2, 3...と各xの中心を表す。len(hue_values)/2はhueの中心を表す。
34 # (j - len(hue_values)/2 + 0.5)で各hueがhueの中心からどれだけ離れるかを表す(最終的にはbar_widthをかけた分だけの距離をxから離れることになる)
35 # +0.5は各hueのグラフがxグループ内で真ん中に表示されるように調整するための数値。iが1刻みなので、その半分の0.5となる。
36 _x = i + (j - len(hue_values)/2 + 0.5) * bar_width
37 # バーをプロット
38 color = color_mapping[(hue_value, stack_value)]
39 plt.bar(_x, _data[y].values, bar_width, bottom=_bottom, label=f'{hue_value}, {stack_value}', color=color)
40 # bottomの値を更新
41 _bottom += _data[y].values
42
43# 凡例を表示
44handles, labels = plt.gca().get_legend_handles_labels()
45new_labels, new_handles = [], []
46# デフォルトでは凡例に重複があるため、重複を取り除く。
47for handle, label in zip(handles, labels):
48 if label not in new_labels:
49 new_labels.append(label)
50 new_handles.append(handle)
51plt.legend(new_handles, new_labels, title=f'{hue}, {stack}')
52
53# x軸のラベルを設定
54plt.xticks(np.arange(len(x_values)), x_values)
55title = f"hueで横に並べてstackで縦に積み上げる棒グラフ"
56plt.title(title)
57# レイアウトを調整
58plt.tight_layout()
59
60# プロットを表示
61plt.show()
62
![](https://max999blog.com/wp-content/uploads/2023/12/79b3a7c899ae250f2b66a254ad5022f0.png)
x軸に曜日があり、曜日ごとに、Maleが左側、Femaleが右側になってグラフが並んでいる。
更に、MaleとFemaleで下側がSmokerのYesが、上側がSmokerのNoとなるようにグラフが積み重なっている。
(この形のグラフは何を主張したいのか、何を見せたいのかが分かりにくくなるので、あまり作成することはないが・・・)
ここからコードを分解して少し説明する。
バーの幅などの設定
1# プロットのサイズを設定
2plt.figure(figsize=(8, 4.5))
3
4# 各x値のユニークな値を取得
5x_values = data[x].unique()
6
7# hueとstackのユニークな値を取得
8hue_values = data[hue].unique()
9stack_values = data[stack].unique()
10
11# バーの幅を設定。hueの数によって変更。
12bar_width = round(1 / (len(hue_values) + 1), 2)
13print(f"{bar_width=}")
14# bar_width=0.33
x_values, hue_values, stack_valuesはグラフ描画時にforでループすることになる。
棒グラフのバーの幅は、hueのユニーク値数が増えると、その分、バーの幅を小さくする必要がある。でないと、隣のxの棒グラフが重なってしまう。
凡例の色の設定
1# バーの色を設定。hueとstackの組み合わせ毎に色を塗り分ける。
2n_colors = len(hue_values) * len(stack_values)
3colors = sns.color_palette("husl", n_colors)
4color_mapping = {}
5for idx, (hue_value, stack_value) in enumerate(itertools.product(hue_values, stack_values)):
6 color_mapping[(hue_value, stack_value)] = colors[idx]
7print(color_mapping.keys())
8# dict_keys([('Male', 'Yes'), ('Male', 'No'), ('Female', 'Yes'), ('Female', 'No')])
hueとstackのユニーク値の組み合わせの数だけ、グラフの領域が必要になるので、色の設定を行う。
itertools.product()でhueとstackのユニーク値の組み合わせを作り、各組合せをキーとして色の辞書を作成する。
各hueとstackののタプルをキー値とするのは、棒グラフ描画時にhueとstackでループするので、そのまま使えるため。
積上げ棒グラフの描画
グラフ描画のメインとなる部分。
1# バーをプロット
2for i, x_value in enumerate(x_values):
3 for j, hue_value in enumerate(hue_values):
4 _bottom = 0 # bottomの初期値を設定
5 for k, stack_value in enumerate(stack_values):
6 # 対応するデータを取得
7 _data = data[(data[x] == x_value) & (data[hue] == hue_value) & (data[stack] == stack_value)]
8 # バーのx位置を計算
9 # iは1, 2, 3...と各xの中心を表す。len(hue_values)/2はhueの中心を表す。
10 # (j - len(hue_values)/2 + 0.5)で各hueがhueの中心からどれだけ離れるかを表す(最終的にはbar_widthをかけた分だけの距離をxから離れることになる)
11 # +0.5は各hueのグラフがxグループ内で真ん中に表示されるように調整するための数値。iが1刻みなので、その半分の0.5となる。
12 _x = i + (j - len(hue_values)/2 + 0.5) * bar_width
13 # バーをプロット
14 color = color_mapping[(hue_value, stack_value)]
15 plt.bar(_x, _data[y].values, bar_width, bottom=_bottom, label=f'{hue_value}, {stack_value}', color=color)
16 # bottomの値を更新
17 _bottom += _data[y].values
18
x, hue, stackの順にループする。
xとhueが決まると、バーのx軸の位置を決められる。そこからstackごとに高さを決めていく。
_data = data[(data[x] == x_value) & (data[hue] == hue_value) & (data[stack] == stack_value)]
で対応するデータを取得する。
_x = i + (j - len(hue_values)/2 + 0.5) * bar_width
で、x軸の位置を決める。
color = color_mapping[(hue_value, stack_value)]
で、あらかじめ作成したカラーマップからバーの色を取得する。
plt.bar(_x, _data[y].values, bar_width, bottom=_bottom, label=f'{hue_value}, {stack_value}', color=color)
でバーを作成する。凡例の表示に使うlabelも忘れずに設定しておく。
最後に次のstackのためにbottomを更新する。
凡例の表示
1# 凡例を表示
2handles, labels = plt.gca().get_legend_handles_labels()
3new_labels, new_handles = [], []
4# デフォルトでは凡例に重複があるため、重複を取り除く。
5for handle, label in zip(handles, labels):
6 if label not in new_labels:
7 new_labels.append(label)
8 new_handles.append(handle)
9plt.legend(new_handles, new_labels, title=f'{hue}, {stack}')
10
バーの描画時にlabelを設定したが、そのまま何もせずに凡例を表示すると、同じ凡例が繰り返し表示されてしまうため、重複を削除する必要がある。
handlesとlabelsを取得してからループさせ、新規のlabelであれば残すだけ。
その他のグラフの設定
1# x軸のラベルを設定
2plt.xticks(np.arange(len(x_values)), x_values)
3title = f"hueで横に並べてstackで縦に積み上げる棒グラフ"
4plt.title(title)
5# レイアウトを調整
6plt.tight_layout()
7# プロットを表示
8plt.show()
ここの処理は他のグラフでもよく行う処理で、軸のラベルやグラフタイトルなどを設定しているだけ。
グラフ作成を関数化する
1回書くだけならべた書きでも良いが、複数回使うとなると、毎回コピペして少し修正するとかでも、面倒になってくるのである程度関数化しておくと便利。
(そもそもこの形のグラフを作成する機会がほとんどないが・・・)
hueで横並びにしてstackで積上げ棒グラフを作成する関数
1import matplotlib.pyplot as plt
2import seaborn as sns
3import numpy as np
4import pandas as pd
5
6def stacked_grouped_barplot(df, x, y, hue, stack, title="") -> plt.Axes:
7 data = df.groupby([x, hue, stack], observed=False)[y].sum().reset_index()
8
9 # プロットのサイズを設定
10 fig, ax = plt.subplots(figsize=(8, 4.5))
11
12 # 各x値のユニークな値を取得
13 x_values = data[x].unique()
14
15 # hueとstackのユニークな値を取得
16 hue_values = data[hue].unique()
17 stack_values = data[stack].unique()
18
19 # バーの幅を設定。hueの数によって変更。
20 bar_width = round(1 / (len(hue_values) + 1), 2)
21 print(f"{bar_width=}")
22
23 # バーの色を設定。hueとstackの組み合わせ毎に色を塗り分ける。
24 n_colors = len(hue_values) * len(stack_values)
25 colors = sns.color_palette("husl", n_colors)
26 color_mapping = {}
27 for idx, (hue_value, stack_value) in enumerate(itertools.product(hue_values, stack_values)):
28 color_mapping[(hue_value, stack_value)] = colors[idx]
29 print(color_mapping.keys())
30
31 # バーをプロット
32 for i, x_value in enumerate(x_values):
33 for j, hue_value in enumerate(hue_values):
34 _bottom = 0 # bottomの初期値を設定
35 for k, stack_value in enumerate(stack_values):
36 # 対応するデータを取得
37 _data = data[(data[x] == x_value) & (data[hue] == hue_value) & (data[stack] == stack_value)]
38 # バーのx位置を計算
39 # iは1, 2, 3...と各xの中心を表す。len(hue_values)/2はhueの中心を表す。
40 # (j - len(hue_values)/2 + 0.5)で各hueがhueの中心からどれだけ離れるかを表す(最終的にはbar_widthをかけた分だけの距離をxから離れることになる)
41 # +0.5は各hueのグラフがxグループ内で真ん中に表示されるように調整するための数値。iが1刻みなので、その半分の0.5となる。
42 _x = i + (j - len(hue_values)/2 + 0.5) * bar_width
43 # バーをプロット
44 color = color_mapping[(hue_value, stack_value)]
45 ax.bar(_x, _data[y].values, bar_width, bottom=_bottom, label=f'{hue_value}, {stack_value}', color=color)
46 # bottomの値を更新
47 _bottom += _data[y].values
48
49 # 凡例を表示
50 handles, labels = ax.get_legend_handles_labels()
51 print(f"{labels=}")
52
53 new_labels, new_handles = [], []
54 # デフォルトでは凡例に重複があるため、重複を取り除く。
55 for handle, label in zip(handles, labels):
56 if label not in new_labels:
57 new_labels.append(label)
58 new_handles.append(handle)
59 ax.legend(new_handles, new_labels, title=f'{hue}, {stack}')
60 _handles, _labels = ax.get_legend_handles_labels()
61 print(f"{_labels=}")
62
63 # x軸のラベルを設定
64 ax.set_xticks(np.arange(len(x_values)))
65 ax.set_xticklabels(x_values)
66 ax.set_xlabel(x)
67 ax.set_ylabel(y)
68 # title = f"hueで横に並べてstackで縦に積み上げる棒グラフ"
69 ax.set_title(title)
70 # レイアウトを調整
71 plt.tight_layout()
72
73 return ax
74
引数にDataFrame, x, y, hue, stackを指定するとplt.Axesを返してくれるようにしている。
基本的な処理の流れは上の方で説明したものと同じだが、pltよりもAxesの方が関数外での処理を色々追加しやすいのでAxesを返すようにしている。
そのため、全体的にplt.ではなくax.の書き方にしている。
関数を使ってグラフを書いてみる
以下のようにDataFrameやx、y、hue、stackを指定することでグラフを作成できる。
1# データの読み込み
2df_tips = sns.load_dataset("tips")
3
4# 変数の定義
5x = "day"
6y = "tip"
7hue = "sex"
8stack = "smoker"
9title = "stacked_grouped_barplotで作成したグラフ"
10
11ax = stacked_grouped_barplot(df=df_tips, x=x, y=y, hue=hue, stack=stack, title=title)
12plt.show()
13
![](https://max999blog.com/wp-content/uploads/2023/12/96a77cd8676a7874e4bfd3e849848a27.png)
凡例をグラフの外側に表示する
上記の関数だと凡例の位置はautoになっているので、凡例の種類が多いと、グラフに重なってしまい非常に見にくくなってしまう。
凡例が重なるグラフの例
1# データの読み込み
2df_tips = sns.load_dataset("tips")
3
4# 変数の定義
5x = "day"
6y = "tip"
7hue = "size"
8stack = "smoker"
9title = "判例が重なって見にくいグラフ"
10
11ax = stacked_grouped_barplot(df=df_tips, x=x, y=y, hue=hue, stack=stack, title=title)
12# ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
13plt.show()
14
![](https://max999blog.com/wp-content/uploads/2023/12/546c22b3d65a86afd7d66e94a0bed6a8.png)
凡例の重複を削除する関数
ax.legend()で凡例を位置設定しなおす場合、位置だけを設定することはできず、凡例のハンドラーとラベルも同時に設定する必要がある。
そのため、重複を削除したラベルを作成する必要がある。
1def get_unique_legend_items(ax):
2 handles, labels = ax.get_legend_handles_labels()
3 new_labels, new_handles = [], []
4 for handle, label in zip(handles, labels):
5 if label not in new_labels:
6 new_labels.append(label)
7 new_handles.append(handle)
8 return new_handles, new_labels
9
凡例の位置を変えたグラフ作成
stacked_grouped_barplotで作成したaxに対して、すでに設定済みの凡例を削除し、新たな凡例を設定する。
その際に、凡例をグラフ外に表示するようにする。
1# データの読み込み
2df_tips = sns.load_dataset("tips")
3
4# 変数の定義
5x = "day"
6y = "tip"
7stack = "smoker"
8hue = "size"
9title = "凡例を外に表示させたグラフ"
10
11ax = stacked_grouped_barplot(df=df_tips, x=x, y=y, hue=hue, stack=stack, title=title)
12ax.get_legend().remove() # 元の凡例を削除
13unique_handles, unique_labels = get_unique_legend_items(ax) # 重複を削除したハンドルとラベルを取得
14ax.legend(unique_handles, unique_labels, loc="upper left", bbox_to_anchor=(1, 1), title=f'{hue}, {stack}') # 新しい凡例を設定
15plt.tight_layout()
16# グラフを表示
17plt.show()
18
![](https://max999blog.com/wp-content/uploads/2023/12/94219f508f91ab69e2b38ee0a113e2b3-1.png)
(こんな面倒なことはせずに、凡例の位置を引数で指定できるようにすれば良いのにとも思う)
まとめ
今回はコードが長いのでコードの記載は割愛。ポイントは以下の通り。
- 積上げ棒グラフを作成する場合はplt.bar()の引数にbottomを指定する
- hueで横並びにしつつ縦にも積上げたい場合は、各バーのx軸の位置を決めてからbottomを決めてバーをプロットする