1つのグラフに折れ線と棒グラフを同時に描きたい場合があります。Excelだと画面から簡単に設定できるのですが、matplotlib の場合は設定が多くて結構面倒です。
そこで、簡単に棒グラフと折れ線グラフを描画できるクラスを作ってみました。Y軸は2軸にも対応しています。
個人的に必要と思う設定はパラメータで変更できるようにしていますが、グラフのデザインは人ぞれぞれですし、使うシーンによっても変わってきますので、必要に応じてカスタマイズしてお使いください。
コピー&ペーストで簡単に張り付けられるようになっています。
matplotlib について詳しい内容が知りたい方は、こちらの記事をご一読下さい。
クラスを使って作れるグラフ
以下の通り、折れ線グラフ、棒グラフ、積み上げ棒グラフ、折れ線+棒グラフ、折れ線+積み上げ棒グラフが可能で、Y軸は1軸と2軸に対応しています。
X軸は数値、文字列、日付型に対応しています。
クラスの使い方
PlotHelperのインスタンスを生成し、add_x メソッドでX軸の値を、add_y メソッドでY軸の値を登録し、最後に show メソッドを呼びます。
実は、X軸が1~始まる連番でよければ add_x メソッドも省略できますが、一応記載しておきます。
必要最小限の引数を使ってグラフを書いた場合のサンプルは次の通りです。
1 2 3 4 5 6 7 8 9 |
pl = PlotHelper() pl.add_x([1,2,3,4,5]) #X軸が1から始まる連番でよければ、この行は省略可能 pl.add_y('chart1',[10,20,30,40,50]) pl.add_y('chart2',[40,30,20,30,40]) pl.add_y('chart3',[15,30,10,40,20]) pl.show() |
リファレンス
説明 | コンストラクタ |
呼び出し方 | PlotHelper(title='',width=None,height=None,facecolor=None,backcolor=None, linewidth=3,edgecolor=None,islegend=True,position='upper right'): |
引数 | title:str タイトル width:int ウィンド横サイズ(インチ単位) height:int ウィンド縦サイズ(インチ単位) facecolor:str ウインドウの背景色 edgewidth:int グラフ外形枠の太さ edgecolor:str グラフ外形枠の色 backcolor:str グラフ背景色 islegend:bool 凡例の表示/非表示設定 |
戻り値 | 無し |
説明 | X軸軸に表示する数値(int,float)を設定する。 ※ラベルを設定する場合は add_label メソッドを使う。 このメソッドを省略した場合、1から始まる連番が自動的に設定される。 |
呼び出し方 | add_X(vals) |
引数 | vals:list[int,float] X軸の値のリスト |
戻り値 | 無し |
説明 | X軸軸に表示するラベルを設定する。 ※数値を指定する場合は add_x メソッドを使う |
呼び出し方 | add_label(label) |
引数 | label:list ラベルのリスト (例 ['label1,'label2']) |
戻り値 | 無し |
説明 | X軸の数値/ラベルの表示に関する仕様を設定する。 X軸の値が日付型の場合、表示フォーマット('%Y/%m/%d'等)を設定する。 |
呼び出し方 | axis_formatter(step,rotation=0,formatter='') |
引数 | rotation:int X軸の角度 step:int X軸の間隔 formatter:str X軸のフォーマット(X軸データが日付の時) |
戻り値 | 無し |
説明 | X軸、Y軸のタイトルを設定する。 |
呼び出し方 | axis_title(x_title,y1_title,y2_title = '') |
引数 | x_title:str X軸のタイトル y1_title:str Y1軸のタイトル y2_title:str Y2軸のタイトル |
戻り値 | 無し |
説明 | Y軸の値を登録する。 charttypeを省略するか 'plot' を指定した場合は折れ線グラフが、 'bar' を指定した場合は棒グラフが表示される。 2個以上の ’bar' でデータを登録すると、自動的に積み上げグラフとなる。 axis を省略するか'left'を指定した場合は1軸(左側)に描画され、 'right'を指定した場合は2軸(右側)に表示される。 |
呼び出し方 | add_y(label,vals,chart_type='plot',linewidth=2,linestyle=None, color=None,markersize=0,marker='o',markerfacecolor=None,axis='left') |
引数 | label:str ラベル values:[int],[float] Y軸にプロットする値のリスト charttype:str チャートタイプ 'plot' 又は 'bar'を指定可能 linewidth:int 折れ線の太さ linestyle:str 折れ線の種類 (例:'-','--') color:str 折れ線の色 markersize:int マーカーサイズ marker:str マーカーの形状 markerfacecolor:str マーカーの色(例:'red','b') axis:str Y軸の指定 'left' は左軸、'right' は右軸を使用 |
戻り値 | 無し |
説明 | 指定したXの位置に縦線を描画する |
呼び出し方 | vline(width=2,linestyle='-',color=None) |
引数 | x:int,float X軸の値 linewidth:int 線の太さ linestyle:str 線の種類 (例:'-','--') color:str 線の色 |
戻り値 | なし |
説明 | 指定したYの位置に横線を描画する |
呼び出し方 | hline(width=2,linestyle='-',color=None) |
引数 | y:int,float y軸の値 linewidth:int 線の太さ linestyle:str 線の種類 (例:'-','--') color:str 線の色 |
戻り値 | なし |
説明 | X軸、Y軸で指定したデータを使ってグラフを表示する |
呼び出し方 | show() |
引数 | なし |
戻り値 | なし |
説明 | X軸、Y軸で指定したデータを使ってグラフを作成し、ファイルに保存する。 |
呼び出し方 | save(filename) |
引数 | filename:str グラフを保存するファイル名。拡張子によってグラフのフォーマットを指定。 |
戻り値 | なし |
では、これを使った複合グラフについて、実際のグラフイメージを例に解説していきます。
X軸を文字列ラベルにした複合グラフ(折れ線+棒グラフ)
X軸をラベル(文字列)にしたい場合、add_label メソッドにリスト形式で文字列をセットします。
pl.add_label(['東京','神奈川','新潟','静岡','愛知','大阪','京都','兵庫','三重',' 愛媛','岡山','熊本'])
折れ線、棒グラフの指定は pl.add_y メソッドの charttype 引数で指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pl = PlotHelper('グラフサンプル',8,6,linewidth=2,edgecolor='r',facecolor='gainsboro',islegend=True) pl.add_label(['東京','神奈川','新潟','静岡','愛知','大阪','京都','兵庫','三重',' 愛媛','岡山','熊本']) pl.axis_title('X軸','y1軸') pl.hline(70,color='gray',linestyle='--') pl.vline(3,color='black') pl.add_y('chart1',[90,99,90,99,80,80,99,60,90,80,70,90],'plot',linewidth=2,color='blue') pl.add_y('chart2',[13,20,30,60,10,20,30,60,13,20,20,30],'plot',marker='s',markersize=6,markerfacecolor='yellow') pl.add_y('chart3',[11,11,11,40,20,11,40,20,40,20,30,20],'bar',color='indianred') pl.show() |
X軸を日付にした2軸の複合グラフ(折れ線+積み上げ棒グラフ)
X軸に日付や時刻のデータを指定したい場合は、下記の様に add_x でdatetime型のデータを渡し、axis_formatter メソッドで表示するフォーマットを指定します。
pl.add_x([datetime.datetime(2020,10,1) ,datetime.datetime(2020,10,1),・・・])
pl.axis_formatter(2,30,'%Y/%m/%d')
積み上げグラフは pl.add_y(~,'bar') を複数実行することで作成できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
pl = PlotHelper('グラフサンプル',8,6,linewidth=2, edgecolor='r',facecolor='gainsboro',backcolor='cornsilk',islegend=True) pl.add_x([datetime.datetime(2020,10,1) + datetime.timedelta(days=i) for i in range(12)]) pl.axis_formatter(2,30,'%Y/%m/%d') pl.axis_title('X軸','y1軸') pl.hline(50,color='gray',linestyle='--') pl.vline(2,color='black') pl.add_y('chart1',[90,99,90,99,80,80,99,60,90,80,70,90],'plot',linewidth=2,linestyle=':',color='black') pl.add_y('chart2',[13,20,30,60,10,20,30,60,13,20,20,30],'plot',marker='s',markersize=6,markerfacecolor='yellow') pl.add_y('chart3',[11,11,11,40,20,11,40,20,40,20,30,20],'bar',color='indianred') pl.add_y('chart4',[18,10,11,10,10,10,12,10,20,10,10,10],'bar',color='salmon') pl.add_y('chart5',[20,30,50,10,30,25,30,40,10,20,20,30],'bar',color='burlywood') pl.show() |
X軸を数値にした3軸の複合グラフ(折れ線+積み上げ棒グラフ)
3軸目を指定したい場合は、add_y メソッドの axis引数に 'right' を指定します。
pl.add_y(x,y,axis='right')
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
pl = PlotHelper('グラフサンプル',8,6,linewidth=6,edgecolor='navy', facecolor='lightyellow',backcolor='lavender',islegend=True) pl.add_x([1,2,3,4,5,6,7,8,9,10,11,12]) pl.axis_title('X軸','y1軸','y2軸') pl.hline(50,color='gray',linestyle='--') pl.vline(2,color='black') pl.add_y('chart1',[90,99,90,99,80,80,99,60,90,80,70,90],'plot',linewidth=2,linestyle='--', color='black',axis='right') pl.add_y('chart2',[13,20,30,60,10,20,30,60,13,20,20,30],'plot',marker='o',markersize=8, markerfacecolor='yellow',axis='right') pl.add_y('chart3',[11,11,11,40,20,11,40,20,40,20,30,20],'bar',color='indianred') pl.add_y('chart4',[18,10,11,10,10,10,12,10,20,10,10,10],'bar',color='salmon') pl.add_y('chart5',[20,30,50,10,30,25,30,40,10,20,20,30],'bar',color='burlywood') #pl.save('d:/fig.jpg') pl.show() |
クラスのソースコード
以下が今回のクラスのソースコードになります。
汎用性を追求しすぎるとソースコードが複雑になり、第三者が手を加えづらくなるため、個人的に必要と思う機能のみに絞っています。
不足している機能があれば、随時追加して頂ければと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
import matplotlib.pyplot as plt from matplotlib import rcParams import matplotlib.dates as mdates import datetime class PlotHelper: ''' 複合グラフを簡単に作成するへルーパークラス ''' class Plot: ''' グラフの種類、プロットデータ等を保存するクラス ''' def __init__(self,label,values,charttype='plot', linewidth=2,linestyle=None,color=None, markersize=0,marker='o',markerfacecolor=None,axis='left'): ''' コンストラクタ Parameters ---------- label:str ラベル values:[int],[float] Y軸にプロットする値のリスト (例:[1,2,3,4,5]) charttype:str チャートタイプ 'plot' 又は 'bar'を指定可能 linewidth:int 折れ線の太さ linestyle:str 折れ線の種類 (例:'-','--') color:str 折れ線の色 markersize:int マーカーサイズ marker:str マーカーの形状 markerfacecolor:str マーカーの色(例:'red','b') axis:str Y軸の指定 'left' は左軸、'right' は右軸を使用 ''' self.label = label self.values = values self.charttype = charttype self.linewidth = linewidth self.linestyle = linestyle self.color = color self.marker = marker self.markersize = markersize self.markerfacecolor = markerfacecolor self.axis = axis def __init__ (self,title='',width=None,height=None,facecolor=None, backcolor=None,linewidth=3,edgecolor=None,islegend=True,position='upper right'): ''' コンストラクタ Parameters ---------- title:str タイトル width:int ウィンド横サイズ(インチ単位) height:int ウィンド縦サイズ(インチ単位) facecolor:str ウインドウの背景色 edgewidth:int グラフ外形枠の太さ edgecolor:str グラフ外形枠の色 backcolor:str グラフ背景色 islegend:bool 凡例の表示/非表示設定 ''' self.fig = None self.title = title self.width = width self.height = height self.facecolor = facecolor self.backcolor = backcolor self.edgewidth = linewidth self.edgecolor = edgecolor self.islegend = islegend self.position = position self.axis_y1 = None self.axis_y2 = None self.label = [] self.x_plots = [] self.y_plots = [] self.islegend = True self.v_line = None self.h_line = None self.x_title = '' self.axis_y1_title = '' self.axis_y2_title = '' self.formatter= {'rotation':0,'step':1,'formatter':''} def axis_title(self,x_title,y1_title,y2_title = ''): ''' X軸、Y軸のタイトルを設定 Parameters ---------- x_title:str X軸のタイトル y1_title:str Y軸のタイトル y2_title:str Y軸のタイトル ''' self.x_title = x_title self.axis_y1_title = y1_title self.axis_y2_title = y2_title def axis_formatter(self,step,rotation=0,formatter=''): ''' X軸の値を設定 Parameters ---------- rotation:int X軸の角度 step:int X軸の間隔 formatter: str X軸のフォーマット(X軸データが日付の時) ''' self.formatter = {'rotation':rotation,'step':step,'formatter':formatter} def add_label(self,label): ''' X軸の値の代わりに表示するラベルを設定 Parameters ---------- label:list ラベルのリスト ''' self.label = label def add_x(self,vals): ''' X軸の値を設定 Parameters ---------- vals:list X軸の値のリスト ''' self.x_plots = vals def add_y(self,label,vals,chart_type='plot', linewidth=2,linestyle=None,color=None, markersize=0,marker='o',markerfacecolor=None,axis='left'): ''' y軸の値を設定 Parameters ---------- label:str ラベル values:[int],[float] Y軸にプロットする値のリスト (例:[1,2,3,4,5]) charttype:str チャートタイプ 'plot' 又は 'bar'を指定可能 linewidth:int 折れ線の太さ linestyle:str 折れ線の種類 (例:'-','--') color:str 折れ線の色 markersize:int マーカーサイズ marker:str マーカーの形状 markerfacecolor:str マーカーの色(例:'red','b') axis:str Y軸の指定 'left' は左軸、'right' は右軸を使用 ''' self.y_plots.append(self.Plot(label,vals,chart_type,linewidth,linestyle,color, markersize,marker,markerfacecolor,axis)) def show(self): ''' グラフの表示 ''' self.__design() self.__draw() plt.show() def save(self,filename): ''' グラフのファイル保存 Parameters ---------- filename:str グラフを保存するファイル名。拡張子によってグラフのフォーマットを指定。 ''' self.__design() self.__draw() self.fig.savefig(filename) def vline(self,x,width=2,linestyle='-',color=None): ''' 垂直線の描画 Parameters ---------- x:int,float X軸の値 linewidth:int 線の太さ linestyle:str 線の種類 (例:'-','--') color:str 線の色 ''' self.v_line = {'x':x,'width':width,'linestyle':linestyle,'color':color} def hline(self,y,width=2,linestyle='-',color=None): ''' 水平線の描画 Parameters ---------- y:int,float y軸の値 linewidth:int 線の太さ linestyle:str 線の種類 (例:'-','--') color:str 線の色 ''' self.h_line = {'y':y,'width':width,'linestyle':linestyle,'color':color} def __draw(self): ''' グラフの描画処理 ''' #X軸の値が指定されていない場合、初期値として連番を設定 if self.x_plots == [] : self.x_plots = [i for i in range(len(self.y_plots[0].values))] bottom = [] #グラフの種類によって描画メソッドを切り替え for y in self.y_plots: #軸の決定 axis = self.axis_y2 if y.axis == 'right' and self.axis_y2 != None else self.axis_y1 #折れ線グラフ処理 if y.charttype == 'plot': #折れ線グラフの描画 axis.plot(self.x_plots,y.values,label=y.label, lw=y.linewidth,ls=y.linestyle,c=y.color, marker=y.marker,markersize=y.markersize, markerfacecolor=y.markerfacecolor ) #棒グラフ処理(積み上げ対応) elif y.charttype == 'bar': #積み上げグラフを作成する際のbootom値の初期化 if bottom == []: bottom = [0 for i in range(len(y.values))] #棒グラフの描画 axis.bar(self.x_plots,y.values,color=y.color, tick_label = self.label if self.label != [] else None, bottom = bottom,label=y.label) #積み上げグラフのbottom値の更新 bottom = [bottom[i] + y.values[i] for i in range(len(y.values))] #凡例の表示 if self.islegend: #1軸の凡例表示 if self.axis_y2 == None: self.axis_y1.legend(loc=self.position, borderaxespad=1) #2軸の凡例表示(1軸と2軸の凡例を合算) else: handler1, label1 = self.axis_y1.get_legend_handles_labels() handler2, label2 = self.axis_y2.get_legend_handles_labels() self.axis_y1.legend(handler1 + handler2, label1 + label2, loc=self.position, borderaxespad=1) def __design(self): ''' グラフのデザイン(体裁)を設定 ''' #漢字対応フォントの設定 rcParams['font.family'] = 'Meiryo' rcParams['font.size'] = 11 #figure の生成 figsize = None if self.width == None or self.height == None else (self.width,self.height) self.fig = plt.figure(figsize=figsize,facecolor=self.facecolor, linewidth=self.edgewidth,edgecolor=self.edgecolor) #グラフ表示領域の上下左右マージン設定 plt.subplots_adjust(left=0.12,right=0.92,top=0.92,bottom=0.2) #1軸のsubplotと軸タイトルを設定 plt.title(self.title) self.axis_y1 = plt.subplot() self.axis_y1.set_ylabel(self.axis_y1_title) self.axis_y1.set_xlabel(self.x_title) #formatter='' の場合、X軸の値は数値、そうでなければDatetimeと判断 if self.formatter['formatter'] != '': fmt = mdates.DateFormatter(self.formatter['formatter']) loc = mdates.DayLocator(interval=self.formatter['step']) self.axis_y1.xaxis.set_major_formatter(fmt) self.axis_y1.xaxis.set_major_locator(loc) self.axis_y1.set_xlim(self.x_plots[0], self.x_plots[len(self.x_plots)-1]) #X軸の角度を設定 plt.xticks(rotation=self.formatter['rotation']) #yの2軸目が指定されていたら、1軸と関連付けを行う if self.axis_y2_title != '': self.axis_y2= self.axis_y1.twinx() self.axis_y2.set_ylabel(self.axis_y2_title) #グリッド線の描画 plt.grid(True) #現在のfigureを取得し、グラフ表示部分の背景色を設定 if self.backcolor != None: self.axis_y1.set_facecolor(self.backcolor) #垂直線の描画 if self.v_line != None: plt.axvline(self.v_line['x'],0,1,lw=self.v_line['width'], ls=self.v_line['linestyle'],color=self.v_line['color']) #水平線の描画 if self.h_line != None: plt.axhline(self.h_line['y'],0,1,lw=self.h_line['width'], ls=self.h_line['linestyle'],color=self.h_line['color']) |
ソースのポイント
今回はソースコードの中に記述してしまいましたが、変更できるようメソッド化した方が良かったかもと思う点や、修正する上での補足説明について記載しておきます。
デザインと描画を担当するメソッド(__desine、 __draw) について
add_x、add_y メソッドで指定されたデータは一旦リストに格納し、__draw メソッドで読み出しながらグラフを描画しています。
また、グラフの体裁(Figure の生成、subplotの生成、グリッド線、2軸3軸の設定、グラフの上下左右マージンなど)に関するものは __desine の中に納めています。
もし新しいグラフ(グルーピング棒グラフなど)を追加する場合は、__draw メソッドに手を加えてください。
また、グリッド線の表示非表示をメソッドの引数で制御したい場合や、新たに補助線を描画したい場合、4軸目を設定したい場合は、__desineメソッドの中を修正してください。
グラフ表示エリアのマージン設定
3軸目を表示した場合、右側のマージンに余裕がなくなると思います。
_design メソッドの中にマージンを設定する記述があります。
plt.subplots_adjust(left=0.12,right=0.92,top=0.92,bottom=0.2)
ここは好みに合わせて設定してください。
今回はこの値を設定するメソッドを作っていませんが、例えば set_adjust というメソッドを追加し、引数を subplots_adjust に渡すようにすれば、メソッド経由で変更が可能になります。
yの3軸以降の追加
必要なケースは少ないと思いますが、Y軸を3軸に増やしたい場合は、下記の記述で増やすことが可能です。
self.axis_y3= self.axis_y1.twinx()
self.axis_y3.spines['right'].set_position(('axes',1.15))
1行目は、axis_y1.twinx() の戻り値を新しい3軸(axis_y3)にセットします。
4軸、5軸と増やす場合、この方法でY軸を増やすことが可能です。
ただし、軸は同じ場所に描画されるので、3軸目以降が重なって表示されます。
そこで、2行目で軸の表示位置をずらしています。
今は1.15にしていますが、軸毎に適切な値を設定する必要があります。
下記は、4軸目まで増やした場合のサンプルになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
#2軸が指定されていたら、1軸と関連付けを行う if self.axis_y2_title != '': self.axis_y2= self.axis_y1.twinx() self.axis_y2.set_ylabel(self.axis_y2_title) #3軸目を増やす self.axis_y3= self.axis_y1.twinx() self.axis_y3.set_ylabel(self.axis_y2_title) self.axis_y3.spines['right'].set_position(('axes',1.15)) #4軸目を増やす self.axis_y4= self.axis_y1.twinx() self.axis_y4.set_ylabel(self.axis_y2_title) self.axis_y3.spines['right'].set_position(('axes',1.20)) |
まとめ
今回は折れ線、棒グラフ、積み上げグラフの複合グラフを簡単に描画できるクラスについて紹介しました。
特に棒グラフは描画するのが面倒ですので、このクラスを使うと非常に簡単に描画できます。
グラフの体裁(補助線の有無や色、線種、グラフの上限下限の設定、ラベル凡例のデザインなど)については人それぞれ違うので、手を加えやすいよう考慮したつもりです。
クラスはコピペで使えるようになっていますので、もしご自身のニーズに合うものがあれば、是非ご活用下さい。
コメント