Python
matplotlib
棒グラフ

Python matplotlibで棒グラフ(2軸グラフと積み上げグラフ)

0.はじめに

仕事の上で棒グラフを作ることは滅多に無いのだが,数表だけでは寂しいので,レポートの装飾のために作ってみた.
ここで紹介するのは以下の方法である.

  • 1.2軸棒グラフ(matplotlib)
  • 2.ファイル内容を画像化(ImageMagick)
  • 3.違う大きさの画像の連結(ImageMagick)
  • 4.積み上げ棒グラフ(matplotlib)

作業環境は以下の通り.

  • MacBook Pro (Retina, 13-inch, Mid 2014)
  • macOS High Sierra
  • Python 3.6.4
  • ImageMagick 7.0.7-22

作例はこんな感じ.

1.2軸棒グラフ(matplotlib)

(1)モジュール読み込み

import matplotlib.pyplot as plt
import numpy as np

(2)データ準備

元データをnumpy配列に格納.文字列も固定長にして,numpy配列に入れてしまう.
配列 Hdam の要素を小さい順に並び替えてプロットするため,np.argsort()Hdam の要素を小さい順に並び替えたインデックスの配列 ii を取得.この配列 ii を用いて全配列を並び替える.
xx はグラフ横軸の値.ここでは,1 から 10 に指定している.

# 元データ
Site=np.array(['L1','K ','M ','Q ','K1 ','J ','J1','G ','H ','I '])
Hdam=np.array([ 189, 108,  94, 186, 145, 138, 169, 108, 95,  90])
Lway=np.array([2304,4312,4300,2195,3999,3808,2987,3193,5034,5838])
Heff=np.array([538.5,702.1,659.6,438.8,655.2,488.2,439.3,381.8,530.6,551.6])

# データの並び替え
ii=np.argsort(Hdam)
Sp=Site[ii]
Hp=Hdam[ii]
Lp=Lway[ii]
He=Heff[ii]
xx=np.arange(len(Sp))+1

(3)データのファイル書き込み

並び替えたデータは後で画像化したいので,フォーマットを整えて文字列化し,テキストファイル bar1.txt に保存.

s0='--------------'
s1='Site name     '
s2='Dam height (m)'
s3='Waterway   (m)'
s4='Net head   (m)'
s5='He / Lw       '
for i in range(0,len(Sp)):
    s0=s0+'-------'
    s1=s1+'{0:>7s}'.format(Sp[i])
    s2=s2+'{0:7.0f}'.format(Hp[i])
    s3=s3+'{0:7.0f}'.format(Lp[i])
    s4=s4+'{0:7.1f}'.format(He[i])
    s5=s5+'{0:7.3f}'.format(He[i]/Lp[i])
fnameW='bar1.txt'
fw=open(fnameW,'w')
print(s0,file=fw)
print(s1,file=fw)
print(s0,file=fw)
print(s2,file=fw)
print(s3,file=fw)
print(s4,file=fw)
print(s5,file=fw)
print(s0,file=fw)
fw.close()

(4)画像ベースの定義

ここでは,subplot により,1つの領域に上下に2枚のグラフを配置する.

col1='#ffa0a0' # color of dam height
col2='#a0a0ff' # color of waterway length
fsz=12
fig=plt.figure(figsize=(7,7),facecolor='w')
plt.rcParams["font.size"] = fsz

(5)上側グラフの描画

私は個人的に pyplot をそのまま使うのが好みなので,全て plt でコードを作成している.
ここで,左軸プロットでは,plt.legend() はまだ使わない.
右軸プロットにおいて,左軸プロットのダミーを入れた上で,plt.legend() を実行する.(もっとうまいやり方があるとは思うが...)

# (1) 上側グラフの描画
plt.subplot(211)
plt.xticks(xx,Sp)
plt.xlim(0,len(xx)+1)
# (1-a) 左軸グラフの描画
plt.ylim(0,300)
plt.xlabel('Site name')
plt.ylabel('Dam height (m)')
plt.grid(color='#999999',linestyle='--')
plt.bar(xx-0.2,Hp,width=0.4,color=col1,align='center',label='Dam height (m)')

# (1-b) 右軸グラフの描画
plt.twinx()
plt.ylim(0,6000)
plt.ylabel('Waterway length (m)')
plt.bar([0],[0],width=0.0,color=col1,align='center',label='Dam height (m)') # 凡例作成のためのダミー
plt.bar(xx+0.2,Lp,width=0.4,color=col2,align='center',label='Waterway length (m)')
plt.legend(shadow=True,loc='upper right')
plt.title('Case: 1800MW x 8hr',loc='left',fontsize=fsz-1)

(6)下側グラフの描画

# 下側グラフの描画
plt.subplot(212)
plt.xticks(xx,Sp)
plt.xlim(0,len(xx)+1)
plt.ylim(0,0.3)
plt.xlabel('Site name')
plt.ylabel('$H_e \; / \; L_w$')
plt.grid(color='#999999',linestyle='--')
plt.bar(xx,He/Lp,width=0.4,color=col2,align='center')
ss='Plot of $H_e \; / \; L_w$'
ss=ss+'\n'+'$H_e$ : net head'
ss=ss+'\n'+'$L_w$ : waterway length'
props = dict(boxstyle='round', lw=0.8,facecolor='#ffffff', alpha=1)
plt.text(0.5,0.3-0.02, ss, color='#000000',fontsize=fsz-1,va='top', ha='left', bbox=props)

(7)画像の保存と画面描画

fnameF='fig_bar1.png'
plt.tight_layout()
plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0)
plt.show()

2.ファイル内容を画像化(ImageMagick)

上のプログラムで作成したテキストファイル bar1.txt の中身は以下の通り.

------------------------------------------------------------------------------------
Site name          I      M      H      K      G      J     K1      J1     Q      L1
------------------------------------------------------------------------------------
Dam height (m)     90     94     95    108    108    138    145    169    186    189
Waterway   (m)   5838   4300   5034   4312   3193   3808   3999   2987   2195   2304
Net head   (m)  551.6  659.6  530.6  702.1  381.8  488.2  655.2  439.3  438.8  538.5
He / Lw         0.094  0.153  0.105  0.163  0.120  0.128  0.164  0.147  0.200  0.234
------------------------------------------------------------------------------------

フォーマットを揃えてあるので,これを読み込みそのまま等幅フォントで画像化する.
画像化は,matplotlib でもできるが,ここでは簡単に処理するため ImageMagick を使う.
Jupyter notebook 上では,以下のImageMagick コマンドをそのまま実行すれば良い.

%%bash
for fn in bar1
do
    fr=${fn}.txt
    fw=ft_${fn}.png

    convert -font /System/Library/Fonts/Menlo.ttc -pointsize 16 -interline-spacing 5 label:@$fr $fw
#    convert -font courier-new -pointsize 16 -interline-spacing 0 label:@$fr $fw
done

やっていること

  • たくさんやりたいときのために for を使っている(ここではリストの中身は一つだけ)
  • 入力テキストファイルを bar1.txt,出力画像ファイルを ft_bar1.png にセット
  • フォントを Menlo 16 ポイントにセット,行間(interline-spacing)を 5px として画像を作成
  • コメント行は,フォントを courier-new とする場合のサンプル

3.違う大きさの画像の連結(ImageMagick)

別々に作成した画像ファイル ft_bar1.png と fig_bar1.png を縦に結合して新しい画像 fig_tbar1.png を作成する.
以下に ImageMagick のコマンドを示す.
事例では,たくさんやりたいときのために for を使っている(ここではリストの中身は一つだけ)
処理内容は,余白を削除(trim)・リサイズ(resize)・横幅を800pxに揃えた画像を2枚作成し,これを縦に結合(-append)する.
リサイズは,上側画像・下側画像ともは横760pxで縦横比を保つようにしている.

%%bash

for fn in bar1
do
    f1=ft_${fn}.png
    f2=fig_${fn}.png
    f3=fig_${fn}_a.png
    convert -trim -resize 760x -gravity center -background white -extent 800x200 $f1 _$f1
    convert -trim -resize 760x -gravity center -background white -extent 800x800 $f2 _$f2
    convert -append _$f1 _$f2 $f3
done
rm _*

4.積み上げ棒グラフ

(1)モジュール読み込み

import matplotlib.pyplot as plt
import numpy as np

(2)データ準備

Case=np.array(['L1_c1','L1_c2','L1_c3','I_c1 ','I_c2 ','I_c3 '])
Wway=np.array([ 308, 494, 388, 508, 707, 503])
Ures=np.array([2235,2235,1118, 456, 456, 228])
Psta=np.array([ 122, 122,  61, 122, 122,  61])
Coth=np.array([ 371, 371, 186, 358, 358, 179])
EadM=np.array([ 419, 463, 463, 420, 464, 464])
Bank=np.array([   0,  12,   6,   0,  12,   6])
Tsum=np.array([3754,3996,2491,2154,2409,1667])
Wway=Wway+Psta+Bank
Oths=Tsum-EadM-Coth-Ures-Wway
xx=np.array([1,2,3,5,6,7])

(3)作図領域定義

cef=1000
fsz=12
fig=plt.figure(figsize=(7,8),facecolor='w')
plt.rcParams["font.size"] = fsz
plt.subplot(111)
plt.xticks(xx,Case)
plt.xlim(0,8)
plt.ylim(0,6000/cef)
plt.xlabel('Case')
plt.ylabel('Cost Index')
plt.grid(color='#999999',linestyle='--')

(4)積み上げプロット

matplotlib の凡例は,描画した順番で上から配置されるので,plt.barbottom で指定するゲタの部分を b0b4 で予め定義しておく.
これを用いて,積み上げの上から下に向かって順次プロットすれば良い.

b0=np.array([0,0,0,0,0,0])
b1=b0+Oths/cef
b2=b1+EadM/cef
b3=b2+Coth/cef
b4=b3+Ures/cef
plt.bar(xx,Wway/cef,bottom=b4,width=0.8,align='center',label='Waterway')
plt.bar(xx,Ures/cef,bottom=b3,width=0.8,align='center',label='Upper Res.')
plt.bar(xx,Coth/cef,bottom=b2,width=0.8,align='center',label='Civil_others')
plt.bar(xx,EadM/cef,bottom=b1,width=0.8,align='center',label='E&M')
plt.bar(xx,Oths/cef,bottom=b0,width=0.8,align='center',label='Others')

(5)90度回転した説明書きの描画

stext=[' F-PSPP\n in Country-A',' S-PSPP\n in Country-A',' S-PSPP\n in Country-B']
plt.text(xx[0],Tsum[0]/cef,stext[0],rotation=90,va='bottom',ha='center',fontsize=fsz-2)
plt.text(xx[1],Tsum[1]/cef,stext[1],rotation=90,va='bottom',ha='center',fontsize=fsz-2)
plt.text(xx[2],Tsum[2]/cef,stext[2],rotation=90,va='bottom',ha='center',fontsize=fsz-2)
plt.text(xx[3],Tsum[3]/cef,stext[0],rotation=90,va='bottom',ha='center',fontsize=fsz-2)
plt.text(xx[4],Tsum[4]/cef,stext[1],rotation=90,va='bottom',ha='center',fontsize=fsz-2)
plt.text(xx[5],Tsum[5]/cef,stext[2],rotation=90,va='bottom',ha='center',fontsize=fsz-2)

(6)ボックス内説明書きの描画

ss1=        'Site: L1, 1000MW x 8hr'
ss1=ss1+'\n'+'Dam height     : 189m'
ss1=ss1+'\n'+'waterway length: 2304m'
ss2=        'Site: I , 1000MW x 8hr'
ss2=ss2+'\n'+'Dam height     : 90m'
ss2=ss2+'\n'+'waterway length: 5638m'
props = dict(boxstyle='round', lw=0.8,facecolor='#ffffff', alpha=1)
plt.text(2,5100/cef, ss1, color='#000000',fontsize=fsz-1,va='bottom', ha='center', bbox=props)
plt.text(6,3500/cef, ss2, color='#000000',fontsize=fsz-1,va='bottom', ha='center', bbox=props)

(7)凡例とタイトル描画

plt.legend(shadow=True,loc='upper right')
plt.title('Comparison of Cost Index between Site-L1 and Site-I',loc='left',fontsize=fsz-1)

(8)画像の保存と画面描画

fnameF='fig_bar2.png'
plt.tight_layout()
plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0)
plt.show()

以 上