【すぐに使える】matplotlibで複合グラフ(棒と折れ線)を簡単に描くクラスを作ってみました。

当ページのリンクには広告が含まれています。

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))

まとめ

今回は折れ線、棒グラフ、積み上げグラフの複合グラフを簡単に描画できるクラスについて紹介しました。

特に棒グラフは描画するのが面倒ですので、このクラスを使うと非常に簡単に描画できます。

グラフの体裁(補助線の有無や色、線種、グラフの上限下限の設定、ラベル凡例のデザインなど)については人それぞれ違うので、手を加えやすいよう考慮したつもりです。

クラスはコピペで使えるようになっていますので、もしご自身のニーズに合うものがあれば、是非ご活用下さい。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次