1つのグラフに折れ線と棒グラフを同時に描きたい場合があります。Excelだと画面から簡単に設定できるのですが、matplotlib の場合は設定が多くて結構面倒です。
そこで、簡単に棒グラフと折れ線グラフを描画できるクラスを作ってみました。Y軸は2軸にも対応しています。
個人的に必要と思う設定はパラメータで変更できるようにしていますが、グラフのデザインは人ぞれぞれですし、使うシーンによっても変わってきますので、必要に応じてカスタマイズしてお使いください。
コピー&ペーストで簡単に張り付けられるようになっています。
matplotlib について詳しい内容が知りたい方は、こちらの記事をご一読下さい。
クラスを使って作れるグラフ
以下の通り、折れ線グラフ、棒グラフ、積み上げ棒グラフ、折れ線+棒グラフ、折れ線+積み上げ棒グラフが可能で、Y軸は1軸と2軸に対応しています。
X軸は数値、文字列、日付型に対応しています。
クラスの使い方
PlotHelperのインスタンスを生成し、add_x メソッドでX軸の値を、add_y メソッドでY軸の値を登録し、最後に show メソッドを呼びます。
実は、X軸が1~始まる連番でよければ add_x メソッドも省略できますが、一応記載しておきます。
必要最小限の引数を使ってグラフを書いた場合のサンプルは次の通りです。
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 引数で指定します。
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') を複数実行することで作成できます。
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')
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()
クラスのソースコード
以下が今回のクラスのソースコードになります。
汎用性を追求しすぎるとソースコードが複雑になり、第三者が手を加えづらくなるため、個人的に必要と思う機能のみに絞っています。
不足している機能があれば、随時追加して頂ければと思います。
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軸目まで増やした場合のサンプルになります。
#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))
まとめ
今回は折れ線、棒グラフ、積み上げグラフの複合グラフを簡単に描画できるクラスについて紹介しました。
特に棒グラフは描画するのが面倒ですので、このクラスを使うと非常に簡単に描画できます。
グラフの体裁(補助線の有無や色、線種、グラフの上限下限の設定、ラベル凡例のデザインなど)については人それぞれ違うので、手を加えやすいよう考慮したつもりです。
クラスはコピペで使えるようになっていますので、もしご自身のニーズに合うものがあれば、是非ご活用下さい。
コメント