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がヒットした。
Move overiding matplotlib RC to using a theme. · Issue #267 · ipython/ipykernel · GitHub
分かったこと
- jupyterがmatplotlibの設定を書き換えているらしい。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のサンプルコードから
もっといえば、同一のコードなのに違う解像度になるのが納得いかない。どこかで「現在の実行環境がjupyter notebookだったらこのコードを実行」っていう分岐があるってこと?
- pythonとjupyterとで同じ図を出力するための設定方法。
(分からないことだらけだけど、強引にまとめ) 試行錯誤してる段階ではjupyterはとても便利だ。ちょっと書き換えてすぐ実行ができる。 しかし、いざpythonに移植しようとして、jupyterのソースコードをPythonスクリプトにコピーして実行すると、出力は同じにならない……という ことには留意すべきだろう。
それでは。