matplotlibでPDFドキュメントを作成する

/ Python

matplotlibはPythonの超有名ライブラリの1つであり、主にグラフを描画する機能を持っている。グラフに適切なアノテーションを付ける機能を始めとして、調べさえすればどんな図でも書ける。また、図の表示方法や出力形式においても様々なものをサポートしている。QtやTkなどのGUIライブラリのバックエンドでウィンドウを表示することが昔は多かったが、最近ではJupyterフレームワークでの呼び出しが主なのだろうか。

matplotlibはウィンドウだけではなくラスタ画像やベクタ形式のsvgやpdfに図を書き出すことも出来る。ベクタ形式の図はLaTeXに組み込んで論文やbeamerに載せるのに適している。適切なTeXディストリビューションをインストールしておけば図の中に数式を載せることも簡単に出来る。デフォルトの装飾だとmatplotlibによる図はすぐにそれだとわかるが、ひとたびデフォルトの装飾をやめればMATLABやgnuplotで作ったといってもわからないものとなる。もっとも、MATLABに関してはmatplotlib自体にMATLABのスタイルを模するオプションがあったと記憶しているが。

LaTeX/beamerにmatplotlib製のpdfを組み込むということをしていると、データに基づくグラフに限らない図形やアノテーションもmatplotlibで作ってしまえということがよく起こる。LaTeX自体にもTikzといった描画機能はあるものの、日ごろからmatplotlibを使い込んでいるとTikzなどをイチから覚えるよりもmatplotlibのアノテーションやパッチやアーティストを調べた方が全く同じクオリティのものがより素早く作れると考えるからだ。

であると、次に考えることはドキュメント自体をmatplotlibで作ってしまえないのか?ということであるが、これも当然出来るようだ。

figsizeの調整

オブジェクトマナーによるmatplotlibの使い方を念頭におく。オブジェクトマナーといっているのはpyplotからplotを呼び出してカレントのfigureに対してプロットを行うといった使い方ではなく、プロットはfigureオブジェクトに追加されているaxesオブジェクトに対して行うマナーのことだ。オブジェクト指向という言葉はいろいろ衝突するので使うことをやめた。

1ページを表現するのに1つのfigureオブジェクトを用いる。このfigureオブジェクトがpdfの1ページになるので、pdfドキュメントというからには適切なサイズ設定を行っておく。figureオブジェクトのサイズはpyplot.figure()に対してfigsizeキーワードで幅、高さのタプルを指定することで設定可能だが、これを以下のようにやる。

from matplotlib import pyplot

paperwidth_mm = 297
paperheight_mm = 210
mm_per_inch = 25.4
figsize = (paperwidth_mm/mm_per_inch, paperheight_mm/mm_per_inch)

fig = pyplot.figure(figsize=figsize)

ここではA4のドキュメントを例にした。figsizeに渡すべき幅と高さはインチ単位なので単にミリメートルと変換して渡してやればよい。beamerの場合はミリメートルではなく4:3や16:9などの比率を考慮することと、ピクセル数で指定したい場合は別途DPI(Depth per Inch)の決定が必要となる。DPIは印刷やディスプレイへの表示の文脈でよく出くわす出力系統の解像度を示しており用途に合わせて妥当な数字をもってきて計算する必要があるがここでは深入りすることはやめる。

複数figureの出力

結果は下記。

import matplotlib
matplotlib.use('pdf') # ウィンドウを表示させない

from matplotlib import pyplot

figures = []
# ページごとにfigureオブジェクトを作成し、中身を描画してfigures.append

from matplotlib.backends import backend_pdf
with backend_pdf.PdfPages('figure.pdf') as pdf:
    for fig in pdf_figures:
        pdf.savefig(fig)

pyplot.close('all')

Multipage PDFを参考にすればストレートに書ける内容となる。matplotlib.use('pdf')が無くとも動くが、これがあるとpdfでの出力しかしないのに実行時にウィンドウのフォーカスが変わる挙動やx11 forwardingが起きる挙動となるため抑制しておく。

狙った位置にaxesを配置

以上の内容で日ごろからfigureにアノテーションやaxesを置いたりしていれば同じようにドキュメンテーションを丁寧に読んでやるだけでなんちゃってLaTeXのような感じで図入りドキュメントが作れる。複数行の数式入りテキストを好きな場所に置くことも出来るので、段組みとか脚注とかタイプセットのコア機能を使わないような図8-9割のページが続くようなドキュメントだったらどうとでもなるのではないだろうか。別に複数ページである必要はないのだが、subplotsの次元が2を超えてくると(わかるかな、この表現)複数ページが超ありがたいと感じるようになると考えている。

良く使うフレーズとして、狙った位置にaxesを置くコードを置いておく。

# figsize: 前述のもの
# padding_mm: ミリメートル単位のパディング
padding = padding_mm / mm_per_inch
rect = (
    padding / figsize[0],
    padding / figsize[1],
    (figsize[0] - padding*2) / figsize[0],
    (figsize[1] - padding*2) / figsize[1]
)

fig.add_axes(rect=rect)

add_axesに渡すrectキーワード引数の4つのタプルの値の意味がパーセンテージであることと後半2つの引数の意味が座標ではなくて幅と高さだということを言っている。パディングは例だが適宜テキストを入れ込みたい側を広げるなりするのがよくあるやり方だろう。

おわりに

座標を直接指定するオブジェクトの配置場所をすぐに把握できるようにfigure全体に補助線を入れるとか考えられる。ただ、いろいろ凝りすぎには注意。補助線はまだグレーだが複数行テキストを差し込み始めたらbeamerとかに戻り始めた方がよさそうである。