matplotlibで論文やプレゼン資料用の図を作るためのテクニック4選

# 2017年9月9日「PythonユーザのためのJupyter[実践]入門」出版のお知らせ（2017/08/29 追記）　    

　ご縁あって書籍「[PythonユーザのためのJupyter[実践]入門](http://amzn.to/2wUGPQb)」を共著で執筆させていただきました。

　タイトルには入っていませんが、pandas、Matplotlib、Bokehの使い方についても詳しく解説しています。この記事に書かれていることも、よりわかりやすく書いてあります。Jupyter、pandas、Matplotlib、Bokehを使う際の必携の書となることを目指して執筆しましたので、ぜひお手に取ってごらんください。

「[PythonユーザのためのJupyter[実践]入門](http://amzn.to/2wUGPQb)」（池内孝啓、片柳薫子、岩尾 エマ はるか、@driller著、技術評論社）

------
     
  この記事は [Python Advent Calendar 2015 - Adventer](http://www.adventar.org/calendars/846) 13日目の記事です。

# はじめに  
　　Pythonでデータ解析を始めてから、論文やプレゼン資料に使う図もPythonで作りたいと思うようになりました。作図ツールはいろいろとありますが、論文など印刷物に利用する単純なグラフとしてはとりあえずmatplotlibがよさそうという結論に至り、matplotlibで作図をいろいろと試してきました。今日は試行錯誤の結果学んだもののうち、1) 重ね棒グラフを書く、2) 色を細かく指定する、3) 凡例を枠外に配置する、4) 図をキャンバス内に納めるという4つのテクニックを紹介したいと思います。
  
# 環境   
Windows7 + [Anaconda](https://www.continuum.io/why-anaconda) (Python3.5)

# デモ用データセット作成
今回は適当に正規分布する数値を使ってデモを作りました。データの生成には平均値と標準偏差、生成個数を指定できるNumpyのrandom.normalを使用。

```py3:
import numpy as np  # デモ用データ生成用。本題とはあまり関係ない。  
import matplotlib.pyplot as plt  # 今日の話題の中心となる2Dプロッティングライブラリ   
import matplotlib.cm as cm  # グラフに使う色を細かく指定するためのクラス
from IPython.display import Image  # 図をノートブックにインポートするためのクラス

plt.rcParams['font.size'] = 14 #フォントサイズを設定

x = np.array(range(1, 25))
y1 = np.random.normal(20, 5, 24)
y2 = np.random.normal(30, 5, 24)
y3 = np.random.normal(40, 5, 24)
y4 = np.random.normal(50, 5, 24)
y5 = np.random.normal(60, 5, 24)
```

__テクニック1  重ね棒グラフを書く際にやっておくべきこと__  
  matplotlibで単純に数値を次々と一つの図に描画していくと、後に描画した棒で先に描画した棒が上書きされてしまいます。そのため、n個の値（V1, V2, V3, ..., Vn）を重ねて描画する場合は、V1 + V2 + ... + Vn, V1 + V2 + ... + Vn-1, ... と積算値を作成して多い方から順に描画していく必要があります。もっとクールな書き方はたくさんありますが、単純に書くと以下のような感じで描画用データセットを作ります。  

```py3
# 重ね棒グラフ用データセットを作成
dataset = {'dat1':(y1+y2+y3+y4+y5), 
           'dat2':(y2+y3+y4+y5), 
           'dat3':(y3+y4+y5), 
           'dat4':(y4+y5), 
           'dat5':y5}
```

__テクニック2 細かいカラーセットの指定__  
基本のレッド (r)とかグリーン (g)とかブルー(b)だけで書くともっさい感じのグラフになってしまうので、少しおしゃれ感を出すためにmatplotlibのcmクラスを使います。詳しいカラー情報は[こちら](http://matplotlib.org/examples/color/colormaps_reference.html )を参照してください。   
 
今回はいくつかのカラースケールから色を抜粋して利用しました。  

```py3
# カラーセット作成
colors = [cm.RdBu(0.85), cm.RdBu(0.7), cm.PiYG(0.7), cm.Spectral(0.38), cm.Spectral(0.25)]
```

# グラフ描画
　それではいよいよ描画に入ります。今回のデモでは条件を変更して繰り返し実行するため関数を作りました。    
  
 ```py3
 # 描画用関数の作成
def plot(bbax, bbay, bap, adj, adjl, adjr):
    
    fig, ax1 = plt.subplots(1, 1, figsize=(12, 5))

    ax1.bar(x, dataset['dat1'], color=colors[0], edgecolor='w', align='center', label='Data1')
    ax1.bar(x, dataset['dat2'], color=colors[1], edgecolor='w', align='center', label='Data2')
    ax1.bar(x, dataset['dat3'], color=colors[2], edgecolor='w', align='center', label='Data3')
    ax1.bar(x, dataset['dat4'], color=colors[3], edgecolor='w', align='center', label='Data4')
    ax1.bar(x, dataset['dat5'], color=colors[4], edgecolor='w', align='center', label='Data5')
    
    # 凡例描画(位置指定)
    if bap == 999:
        ax1.legend(bbox_to_anchor=(bbax, bbay))
    else:
        ax1.legend(bbox_to_anchor=(bbax, bbay), borderaxespad=bap)

    # サイズ調整
    if adj != 0:
        plt.subplots_adjust(left = adjl, right = adjr)

    # ファイル出力
    fname = 'fig' + str(bbax) + str(bbay) + str(bap) + str(adj) + str(adjl) + str(adjr) + '.png'
    plt.savefig(fname, dpi = 300, format='png')
```

__テクニック3 凡例の位置設定__
    凡例の位置は[所定の場所に配置することもできます](http://matplotlib.org/api/legend_api.html) が、ここではアンカー位置を指定して位置決めする方法をとります。まず、特に位置指定をしなかった場合、以下の図のように右上内側に凡例がきます。
![Fig. 1](https://qiita-image-store.s3.amazonaws.com/0/61179/c8c54e34-fea8-3a78-35d7-55b105c37eab.png)  
　Fig. 1 デフォルトの凡例配置図  

   そこで、凡例のアンカー指定は bbox_to_anchorでおこないます。今回のコードだと、
    
```py3
    ax1.legend(bbox_to_anchor=(bbax, bbay))
```
        
   の部分です（このコードでは後述のborderaxespadを指定しない場合は999を指定するようにしてあります）。アンカー位置を(0, 0), (0, 1), (1, 0), (1, 1)に指定して作図すると、以下のような位置関係で凡例が配置されます。
   
![Fig. 2](https://qiita-image-store.s3.amazonaws.com/0/61179/4fafd883-6117-204a-1a89-20537bfc1ffd.png)    
　
 Fig. 2 凡例配置の模式図
    
つまり凡例の右上の角が図の枠線のどこにくるかを指定していることになります。ところで、枠線と凡例との間に微妙な隙間があります。これはデフォルト設定で少し隙間をあけるようになっているようです。この隙間を調整するためにはborderaxepadオプションを使います。

コードの

```py3
borderaxespad=bap
```

の部分です。bap=0と設定するとFig. 2の微妙な隙間がなくなります。模式図にすると以下のような位置関係になります。

![Fig. 3](https://qiita-image-store.s3.amazonaws.com/0/61179/628b7a17-d1ca-1cf5-f6a9-605a8cb60e5c.png)

Fig. 3 borderaxepad=0に設定した場合の凡例の配置
  
   今回はbaq=0と指定したので、枠線の角と凡例の角がぴったり重なっていますが、数値を変えれば枠線からの距離が調節できます。数字を大きくするとアンカーの位置から指定した分だけ離れます。

　以上で凡例と図の枠線との位置関係がつかめたと思いますが、位置情報を図示すると以下のような図になります。

 ![graph_anchor.png](https://qiita-image-store.s3.amazonaws.com/0/61179/48a643a1-2138-27aa-d786-a387ad5e02c7.png)
Fig. 4 グラフの持つ位置情報
  
つまり、左側枠線の位置がx=0、右側枠線の位置がx=1、下側がy=0、上側がy=1となります。つまり、枠線の右外側に書くならば、xの値を1より大きくすればいいのです。

　それではいよいよ外側に凡例を描画します。今回は右外側に持ってくるためにbbox_to_anchor=(1.2, 1)としました。

これで凡例を外側に書くことができます。
![fig1.210000.png](https://qiita-image-store.s3.amazonaws.com/0/61179/09714f49-5517-2ff6-3bb9-0f2ce0d92459.png)
Fig. 5 凡例が外側に配置された図
  
__テクニック4 キャンバス内への配置__
　位置関係もわかり、凡例をばっちり配置できて、これでめでたしめでたし、という感じもしますが、もちろんここで終わりではありません。Fig. 5を見ていただくとわかると思いますが、凡例が半分切れています。グラフをファイルに出力するとこのような図になります。
 
　これはグラフ＋凡例を描画しているキャンバスに対してグラフ＋凡例が大き過ぎるために起こっています。わかりやすく示すと、キャンバスの端は以下の図の青い線にの位置にあります。
 
 ![Fig. 6](https://qiita-image-store.s3.amazonaws.com/0/61179/3c782faf-6120-d4ba-7419-409819d766d5.png)
 Fig. 6 凡例が外側に配置された図（枠線付き）
 
 　これを調整するためにsubplots_adjustオプションを使います。コードでは以下の部分です。
  
```py3
 plt.subplots_adjust(left = adjl, right = adjr)
```
 
　今回はadjがゼロ以外の時に ```subplots_adjust``` が適用されるようにしてあります。
 
　```subplots_adjust``` もFig. 4 の位置情報を基準に設定されます。つまり、図の左枠線・右枠線をキャンバス上のどこに配置するのかを指定します。キャンバスも図と同じく左端をゼロ、右端を1としています。```subplot_adust=(0, 1)```なら左端がキャンバスのゼロの位置に、右端が1の位置にきます。図は必ず左端が右端よりも左側にくるので、左端の位置の値を右端の位置の値よりも大きくすることはできません。
 
ということで、先ほどの図がキャンバスに収まるように設定します。今回は```subplots_adjust =(0.1, 0.8)```と設定しました。出力結果は以下のようになります。

![Fig. 7](https://qiita-image-store.s3.amazonaws.com/0/61179/82b9b6ce-9872-74f1-0187-e9188e4129d0.png)
Fig.7 subplots_adjustを実施した図（枠線付き）
 
いい感じにおさまりました。

# おわりに
　matplotlibで論文やプレゼン資料用の図を作るためのテクニック4選、いかがだったでしょうか。今回の投稿のために作ったJupyter notebookも追ってGitHubにアップする予定ですので、ご興味のある方はそちらも見てみてください。

2015/12/13 22:18 Jupyter notebookをGitHubにアップしました。 
 https://github.com/nobolis/pydata_workbook/blob/master/matplotlib_tips/matplotlib_tips.ipynb