[未解決]pythonスクリプトとjupyterとでは、matplotlibの図のサイズが違うので注意

jupyter notebook上で、図を作って保存するコードを少しずつ試しながら書いてた。
その後pythonにコードをコピーしたら、保存された図がjupyterのときと違っていたので、原因を調べた。

環境

バージョン Python、Jupyter、matplotlib

> python --version
# Python 3.7.4

import matplotlib
print(matplotlib.__version__)
# 3.1.1

> jupyter notebook --version
# 6.0.1

発生事象

matplotlibで図を書いて保存するソースコードpythonとjupyterで実行した場合、保存した図のサイズは両者で異なる。 以下にサンプルコードを載せる。

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

x = np.linspace(0, 2, 100)

plt.figure(figsize=(10, 8))

plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')

plt.xlabel('x label')
plt.ylabel('y label')

plt.title("Simple Plot")

plt.legend()

plt.savefig("samplt_plt.png")

matplotlibの公式ページのチュートリアルの最初 からコピーして一部改変した。
コードの詳しい説明は省略するが、一点だけ注意しておく。 plt.figure(figsize=(10, 8)) で画像サイズを横10インチ×縦8インチに指定している。

上記のコードをpythonで実行すると、保存された図のサイズ(ピクセル)は横1000×縦800になる。
しかし、jupyter notebookで実行すると、保存された図のサイズ(ピクセル)は横720×縦576になる。

理由・原因

どうして全く同じコードなのに、図のサイズが違うのだろうか?
jupyter側がmatplotlibの設定を書き換えるからである。

plt.figure(figsize=(10, 8)) で指定した画像サイズの単位は「インチ」である。 インチが同じなのに、出来上がった画像のサイズ(ピクセル数)が違う。これは、dpi = dots per inchが異なるせいである。
上記のピクセルをインチで割ると分かる通り、pythonで実行するとdpiは100、jupyter notebookで実行するとdpiは72になる。

「jupyter matplotlib dpi」で検索すると、matplotlib上のissueがヒットした。

Weirdness with inline figure DPI settings in Jupyter Notebook · Issue #9217 · matplotlib/matplotlib · GitHub

Move overiding matplotlib RC to using a theme. · Issue #267 · ipython/ipykernel · GitHub

分かったこと

    rc = Dict({'figure.figsize': (6.0,4.0),
        # play nicely with white background in the Qt and notebook frontend
        'figure.facecolor': (1,1,1,0),
        'figure.edgecolor': (1,1,1,0),
        # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
        'font.size': 10,
        # 72 dpi matches SVG/qtconsole
        # this only affects PNG export, as SVG has no dpi setting
        'figure.dpi': 72,
        # 10pt still needs a little more room on the xlabel:
        'figure.subplot.bottom' : .125
        },
        help="""Subset of matplotlib rcParams that should be different for the
        inline backend."""
    ).tag(config=True)> 

ipykernel/config.py at 29abbedcd0ed09ceb7ea5a179cae767f8df91e8f · ipython/ipykernel · GitHub より)

  • 上記のコードのすぐ上のコメントは

The typical default figure size is too large for inline use, so we shrink the figure size to 6x4, and tweak fonts to make that fit.

と書いてある。すなわち 「デフォルトの図のサイズはinlineで使うには大きすぎるので6x4(インチ)に縮めて、フォントサイズも収まるように変更するわ」という意図でこのコードは挿入されている。

  • 上記のコードから分かる通り、画像のdpi以外にもいくつかのパラメータを変更している。

分からないこと

  • 調べている限り、%matplotlib inline がこの問題を引き起こすというコメントは少なくない。(例:Make %matplotlib inline side effect free · Issue #10383 · ipython/ipython · GitHub)だが俺が試してみると%matplotlib inlineではなくimport matplotlibと書いても解像度が変更されている。何のコードを書くと一連のパラメータ書き換えが実行されるのか不明。明示的に「解像度を変えてくれ」って俺が書いたわけでもないのにいきなり変わってるから、予期せぬ作用が起きてるように見えるんだよね……

    • ちなみにjupyterのサンプルコードからimport matplotlibを削除すると、pythonと同じ横1000×縦800の画像が保存された。
  • もっといえば、同一のコードなのに違う解像度になるのが納得いかない。どこかで「現在の実行環境がjupyter notebookだったらこのコードを実行」っていう分岐があるってこと?

  • pythonとjupyterとで同じ図を出力するための設定方法。

(分からないことだらけだけど、強引にまとめ) 試行錯誤してる段階ではjupyterはとても便利だ。ちょっと書き換えてすぐ実行ができる。 しかし、いざpythonに移植しようとして、jupyterのソースコードPythonスクリプトにコピーして実行すると、出力は同じにならない……という ことには留意すべきだろう。

それでは。