
matplotlibで積み上げ棒グラフ
現在、基本情報技術者の試験でも受けようかと勉強中ですが、今後統計学にも足を突っ込んでみようかと思い、以下のサイトで学んでます。
とはいえ、ただ学ぶだけでもつまらないので、解説されていることをPythonのコードで実装したら、より理解が深まるのではないかと思いました。
「1−2.データからグラフを作ってみよう1」ですでにドンばまりしたので、解決方法と共に記録を残すものです。
なお、グラフの描画はmatplotlibというライブラリを使います。データフレームはpandasというライブラリを使いますが、長くなってもアレなので、今回解説は触れません。極力ピンポイントに帯グラフの書き方を残します。
1.matplotlibで帯グラフが書けない
以下の部分ですね。

ここより上の、棒グラフや円グラフを描き(円グラフもちょっとつまづいたけど、、、)
帯グラフってどうやって書くかを調べたけど、みんなサラッとコードと解説が書いてあって、作りが理解出来ず。。。
2.積み上がらない棒グラフ
なんとなく、積み上げで書く棒グラフが近そうだなと思い。そちらの書き方を真似して見ることに。
ちなみに対象データはこちらです。

猫が100匹いたとして、毛色ごとに何匹いるかをpandasのデータフレームで表しています。
pandasの解説はすっ飛ばしますが、コードは以下の通りです。
import pandas as pd
import matplotlib.pyplot as plt
#データフレームを作成
df = pd.DataFrame(
{"度数":[27, 20, 18, 15, 10, 7, 3, 100],
index=["白", "黒", "灰色", "オレンジ", "茶トラ", "キジトラ", "サビ", "合計"]
)
#インデックスのタイトル
df.index.name = "毛色"
#インデックス列を配列で取得し、index_list変数に格納
index_list = df.index[0:7]
#度数列を配列で取得し、data_list変数に格納
data_list = df["度数"][0:7]
これを普通の棒グラフで描画すると・・・
fig = plt.figure()
ax = plt.axes()
#日本語を表示できるようにする
plt.rcParams['font.family'] = 'Meiryo'
#グラフタイトル
ax.set_title('毛色')
ax.set_ylabel('匹')
#X軸のラベルを設定
ax.set_xticklabels(
index_list,
fontsize = 12
)
#引数に応じて棒グラフの色を変える(10未満=青、20以上=赤、それ以外=緑)
color = [('b' if i < 10 else ('r' if i >= 20 else 'g')) for i in data_list]

matplotlibの公式ドキュメントを漁ると、積み上げる棒グラフについての記載がありました。

ドキュメント記載内容から、なるほどax.barを複数書いて「bottom」というプロパティにどこから書き始めるかを渡してあげればできそうだぞと。
そこで以下のようなコードを試してみました。
fig = plt.figure()
plt.rcParams['font.family'] = 'Meiryo'
ax = plt.axes()
#毛色が7色なので7回ループ処理を実施
for i in range(7):
#1回目はbottomを指定しない
if i == 0:
ax.bar('毛色の割合', data_list[0])
#2回目以降bottomにdata_listの一個前の数値を指定
else:
ax.bar('毛色の割合', data_list[i], bottom=data_list[i-1])
#判例を表示
ax.legend(index_list, bbox_to_anchor=(1.2, 1), loc='upper left', borderaxespad=1)
出来上がったグラフは・・・

猫の数は合計100のはず・・・。要素は積み上がっているようですが、間違いなく様子がおかしい。そして、めっちゃ不恰好・・・。
ここから、色々なサイトを見て皆様の解説を読んだりしたのですが、誰もそんなとこでつまづかないらしく・・・。
ぜーんぜん前に進めない。。。
結局その日は解決策が思い浮かばず・・・、次の日も解決の様子が無いまま端末に向かっていましたが・・・
3.気づきは突然に
「あ・・・、そりゃそうじゃん・・・。」
bottom属性に前のリストの値を設定してましたが、そりゃ正しく積み上がるはずもありません。例えば、配列の3番目の要素を積み上げで描画するには、1番目と2番目の要素の合計値をbottom属性として指定しないとダメですよね・・・。
つまり、すごく実直に書くならば以下のように書くべきです。
fig = plt.figure()
plt.rcParams['font.family'] = 'Meiryo'
ax = plt.axes()
ax.bar('毛色の割合', data_list[0])
ax.bar('毛色の割合', data_list[1], bottom=datas[0])
ax.bar('毛色の割合', data_list[2], bottom=datas[0] + data_list[1])
ax.bar('毛色の割合', data_list[3], bottom=datas[0] + data_list[1] + data_list[2])
・
・
・
(以下略)
あと、plt.figure()の引数にfigsizeという属性を設定して、グラフの描画エリアを調整できること、textメソッドでグラフ上にテキスト表示できることも分かりました。
そこで、以下のようにコードを修正しました。
#figsizeで描画領域を縦長にする
fig = plt.figure(figsize=(1, 10))
plt.rcParams['font.family'] = 'Meiryo'
ax = plt.axes()
#変数totalを定義
total = 0
#毛色が7色なので7回ループ処理を実施
for i in range(7):
#1回目はbottomを指定しない
if i == 0:
ax.bar('毛色の割合', data_list[0])
#グラフ上に%表示
ax.text(0.01, data_list[0]/2, f'{data_list[i]}')
#2回目以降bottomにdata_listの一個前の数値を指定
else:
#totalに配列の直前の値を加算
total += data_list[i - 1]
#bottom属性にtotal変数を渡す
ax.bar('毛色の割合', data_list[i], bottom=total)
#グラフ上に%表示
ax.text(0.01, data_list[0]/2 + total, f'{data_list[i]}')
#判例を表示
ax.legend(index_list, bbox_to_anchor=(1.2, 1), loc='upper left', borderaxespad=1)
グラフを表示してみると・・・

大分それっぽくなりました!
あとは、こいつを横に倒して統計Webのような帯グラフにしたいところ・・・。
4.帯グラフにする(横に倒す)
調べてみると、横棒にする際はbarでなく、barhメソッドを利用することが分かりました。
なお、縦に積み上げるのでなく、横に足していくことになるのでbarhメソッドにはbottom属性はありません。ただし、積み上げ自体は可能で、代わりにleft属性を利用すればいいことが分かりました。
そこで、以下のようにコードを書いてみます。
#figsizeで描画領域を横長にする
fig = plt.figure(figsize=(10, 2))
plt.rcParams['font.family'] = 'Meiryo'
ax = plt.axes()
#変数totalを定義
total = 0
#毛色が7色なので7回ループ処理を実施
for i in range(7):
#1回目はbottomを指定しない
if i == 0:
ax.bar('毛色の割合', data_list[0])
#グラフ上に%表示
ax.text(0.01, data_list[0]/2, f'{data_list[i]}')
#2回目以降bottomにdata_listの一個前の数値を指定
else:
#totalに配列の直前の値を加算
total += data_list[i - 1]
#bottom属性にtotal変数を渡す
ax.bar('毛色の割合', data_list[i], left=total)
#グラフ上に%表示
ax.text(0.01, data_list[0]/2 + total, f'{data_list[i]}')
#判例を表示
ax.legend(index_list, bbox_to_anchor=(1.2, 1), loc='upper left', borderaxespad=1)
では表示してみます。

なんとか、クリアできました!
同じところでつまづく方が、もしかしたら、いるかもしれないと思い記事を書きました。
同じ志を持つ方のお役に立てれば、幸いです。