matplotlib逆引きメモ（フレーム編）

matplotlibには、同じことをやるのに複数の方法が存在します。そのため、やりたいことを実現する手段を見つけにくいという難点があります。筆者は、せっかく見つけた方法をすぐに忘れてしまうため、目的からその実現方法を探しだせるようなメモを残しておくことにしました。

長くなりますので、テーマごとに別記事にします。初回は、グラフの枠の設定やデフォルトパラメータなど基礎的な部分です。以降、他のテーマを続けて書いていきますので、リストや図の番号は「X-Y」のように名付けます。Xは記事番号、Yは記事内番号です。

なお、クラスやメソッドなど必要な語句は、公式サイトの当該項目へのリンクを設定しています。参考にしてください。結果の出力や検証に用いたシステムは、matplotlib=3.3.3, numpy=1.19.4, Python=3.7.3, Debian10.7です[^1]。


## 逆引きメモ
+ フレーム編
+ [グラフの描き方](https://qiita.com/cnloni/items/49d42bfc0fd8071612d8)

### 1-1. 同じことをやるのに、全く違うコードがあるのはどうして？
matplotlibには、大きく分けて、

1. pyplotモジュールのメソッドのみを使用する方法
2. オブジェクト（特にFigureとAxes）を使用する方法

があります。そのため、ネット上のサンプルにも様々な実現方法が示されており、混乱の原因になります。

ここでは、pyplotのメソッドを直接に使用することは出来るだけ避け、主としてオブジェクトを利用することにします。


### 1-2. いちばん簡単なグラフは？
```py3:リスト1-1
10 import matplotlib.pyplot as plt
20
30 fig, ax = plt.subplots()
40 plt.show()
```
いちばん簡単なグラフは、何もないグラフです。グラフの枠だけを描きます。OSによって違いますが、図1-1のようなウィンドウが新しく作成されます。赤点線と青点線は説明のために後で付け加えたものです。

<div><img src='https://qiita-image-store.s3.amazonaws.com/0/52659/7d316ef0-b9c2-05cb-3f38-95987fa6575e.png' title='図1-1' width='40%' /><div><b>図1-1 枠線だけのグラフ</b></div><br/></div>

3行目でFigureクラスとAxesクラスのインスタンスを同時に作成しています。


### 1-3. pltて何？
matplotlibパッケージのpyplotモジュールの別名です。リスト1-1の1行目の書式でimportします。pltという名前は単なる習慣ですが、公式サイトを含め、ほとんど全てのプログラム例で、pltを別名にしています。公式サイトの解説文の中でさえ、matplotlib.pyplot.XXXと記述する代わりに、plt.XXXと書いていることもあります（この記事でも、この省略法を用います）。これ以降のプログラムリストでは、このimport文を省略することがあります。pltという名前が出てきたら、このimport文が冒頭にあるものとみなしてください。


### 1-4. Figureて何？
[Figure](https://matplotlib.org/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure)は、図1の赤点枠の中を描画するクラスです。印刷の場合の用紙に相当します。通常は、plt.subplotのようなメソッドで作成するので、Figureという名前が表にでることはありませんが、どんなメソッドがあるか覚えておくと便利です。

Figureのインスタンスは、「fig」という名前にすることが多いようです。


### 1-5. Axesて何？
[Axes](https://matplotlib.org/api/axes_api.html)は、1個のグラフを統括するクラスです。図1の青点枠を描画します。目盛や軸タイトルなど枠の外側への描画にも関係します。Figureと同様、Axesと言う名前が表に出ることは、ほとんどありません。

1個のFigureは複数のAxesを持つことができます。Axesのインスタンスは、1個の場合は「ax」、2個以上の場合は「ax1」、「ax2」などと名付ける習慣があります。


### 1-6. plt.showは、何をしているの？
全ての描画作業が終了したあとに、[plt.show()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.show.html?highlight=pyplot%20show#matplotlib.pyplot.show)を実行して、ウィンドウを表示します。これを書くのを忘れると、画面に何も表示されません。これは図をスクリーン上に表示するために必須のメソッドであるため、以降のプログラムリスト中では、plt.show()を省略する場合があります。


### 1-7. ディスプレイ上に描画される図のサイズを知りたい
バージョン2.0以降のデフォルト値は、ピクセル単位で640x480です（図1の赤点枠のサイズ）。


### 1-8. 図のサイズを指定したい
例えば、横幅を800ピクセル、高さを600ピクセルにしたい場合、リスト1-1の[plt.subplots()]()にfigsize属性（キーワード）を与えます。

```py3:リスト1-2
30 fig, ax = plt.subplots(figsize=(8.0, 6.0))
```

ここで、figはFigureオブジェクト、axはAxesオブジェクトのインスタンスです。なお、リスト1-2は次のように2行に分割しても同じことです。

```py3:リスト1-2b
30 fig = plt.figure(figsize=(8.0, 6.0))
31 ax = fig.add_subplot()
```


### 1-9. figsize属性の意味は？
figsizeキーワードを使って、図の横幅と高さ（図1の赤点枠）をインチ単位で指定することができます。タプルで指定します。指定しなかった場合は、デフォルト値の(6.4, 4.8)が採用されます。

[plt.subplots()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots)のfigsizeキーワードは、[plt.figure()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure)に渡されて、Figureオブジェクトの作成に使用されます。


### 1-10. インチ単位とピクセル単位の関係は？
インチ単位をピクセル単位にするにはdpiパラメータの値が必要ですが、dpiのデフォルト値は100です。リスト1-2の例なら、

```text
横幅 = 8.0 inch x 100 dpi = 800 px
高さ = 6.0 inch x 100 dpi = 600 px
```

になります。後述しますが、図を画像出力する際にはdpiの値を指定することができます。プリンタで出力することを考えるときには、dpiを600や1200などに設定するとよいでしょう。

ところで、[別記事](https://qiita.com/cnloni/items/49d42bfc0fd8071612d8#2-13-%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%A8%E3%81%AF%E4%BD%95)でも扱いますが、フォントやマーカのサイズを指定する際の単位は「ポイント(pt)」です。ポイントは

```text
1pt = 1/72 inch
```

です。ポイントはインチと同じく、実座標のピクセルとの関係はdpiにより変化します。dpi=100では、

```text
1pt = 1.39 px
```

ですが、dpi=600では

```text
1pt = 8.33px
```

です。　　


### 1-11. 1画面に複数のグラフ枠を描きたい？
plt.subplots()に2個の引数を指定します。グラフ枠はグリッド状に配置されます。第1引数はグリッドの行数、第2引数は列数です。

```py3:リスト1-3
30 fig, axlist = plt.subplots(2, 3)
40 id = 231
50 for arow in axlist:
60   for ax in arow:
70     ax.set_title(id)
80     ax.tick_params(labelbottom=False, labelleft=False, direction='in')
90     id += 1
```

ここで、グラフのタイトルにしている数字（23N, N=1..6）は、グリッドの位置を示しています。最初の「23」は2行3列を表し、3番目は左上から始まる番号です。fig.add_subplot()の引数としてこの数字を使うと、1個ずつグラフ枠を作成することもできます。

<div><img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52659/a838bd32-7636-5dc7-f17f-622665a29ab9.png' title='図1-2' width='320px'><div><b>図1-2 複数のグラフ枠</b></div><br/></div>


### 1-12. 図の中のグラフの位置や大きさを変更したい
<div><img src='https://qiita-image-store.s3.amazonaws.com/0/52659/26aad7a0-237c-95f6-d226-bef45c643715.png' title='図1-3' width='40%'><div><b>図1-3 グラフの位置と大きさを変更する</b></div><br/></div>

図3のように、横に細長い図の右側にグラフ枠を描画するには、リスト1-4のように、リスト1-2に1行加えます。

```py3:リスト1-4
import matplotlib.pyplot as plt

fig, ax = plt.figure(figsize=(6.0, 3.0))
ax.set_position([0.5,0.1,0.4,0.8])
plt.show()
```

ここで、ax.set_positionの引数は要素4個のリスト（またはタプル）で、枠の座標を指定します。

```py3
ax.set_position([枠の左辺, 枠の下辺, 枠の横幅, 枠の高さ])
```

4個の値は全て、実座標ではなく画面の横幅または高さに対する割合です[^2]。左右のサイズ（枠の左辺、枠の横幅）には図の横幅を基準に取り、上下のサイズ（枠の下辺、枠の高さ）には図の高さが基準になります。例えば、枠の横幅をピクセルに換算すると、以下のように、240ピクセルになります。

```text
グラフ枠の横幅（ピクセル）
    = 図の横幅（インチ） x DPI x グラフ枠の横幅（割合）
    = 6.0 x 100 x 0.4 = 240
```

ところで、リスト1-4はリスト1-5のようにすれば、実質2行ですますことができます。add_axesもAxesインスタンスを作成します。グラフが1個であり、グラフ枠を指定するときには、add_axesが簡単です。

```py3:リスト1-5
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(6.0, 3.0))
ax = fig.add_axes([0.5,0.1,0.4,0.8])
plt.show()
```


### 1-13. デフォルト値を知りたい
```py3:リスト1-6
import matplotlib as mpl

print(mpl.matplotlib_fname())
```

matplotlibでは、各種のパラメータのデフォルト値が定められています。システムに最初から格納されているファイルのパスは、リスト1-6のように、[matplotlib_fname](https://matplotlib.org/api/matplotlib_configuration_api.html#matplotlib.matplotlib_fname)で知ることができます。デフォルト値を示すファイルは、matplotlibrcという名前です。

Debian10.0でpipを使ってインストールした場合、システムのmatplotlibrcのパスは下記です。

```test
/usr/local/lib/python3.7/dist-packages/matplotlib/mpl-data/matplotlibrc
```

このファイルの一部をリスト1-7に示します。上に述べたように、figsizeのデフォルト値は(6.4, 4.8)、dpiのデフォルト値が100になっていることが分かります。システムのmatplotlibrcでは、backend以外のパラメータが全てコメントアウトされています。変更する場合は、当該行をアンコメントして、値を変更します。

```text:リスト1-7
(省略)
#backend: Agg
(省略)
## ***************************************************************************
## * FIGURE                                                                  *
## ***************************************************************************
## See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure
#figure.titlesize:   large     # size of the figure title (``Figure.suptitle()``)
#figure.titleweight: normal    # weight of the figure title
#figure.figsize:     6.4, 4.8  # figure size in inches
#figure.dpi:         100       # figure dots per inch
#figure.facecolor:   white     # figure facecolor
#figure.edgecolor:   white     # figure edgecolor
#figure.frameon:     True      # enable figure frame
#figure.max_open_warning: 20   # The maximum number of figures to open through
                               # the pyplot interface before emitting a warning.
                               # If less than one this feature is disabled.
#figure.raise_window : True    # Raise the GUI window to front when show() is called.

## The figure subplot parameters.  All dimensions are a fraction of the figure width and height.
#figure.subplot.left:   0.125  # the left side of the subplots of the figure
#figure.subplot.right:  0.9    # the right side of the subplots of the figure
#figure.subplot.bottom: 0.11   # the bottom of the subplots of the figure
#figure.subplot.top:    0.88   # the top of the subplots of the figure
```

なお、matplotlib_fnameは、matplotlib直属のメソッドです。mplは、matplotlibの別名として習慣的な名前です。このimport文も、これ以降省略する場合があります。


### 1-14. デフォルト値を変更したい
あなたが管理者であれば、システムのmatplotlibrcを変更しても構いませんが、一般ユーザである場合は、システムのmatplotlibrcを、ユーザ専用のconfigディレクトリにおき、リスト1-7を参考にデフォルト値を変更してください。リスト1-8に例をあげます。backendパラメータが設定されていないと、何も出力されなくなるので注意してください。

```py3:リスト1-8
backend: TkAgg
figure.figsize: 8.0, 6.0
```

ユーザ専用のconfigディレクトリのパスは、リスト9のように、get_configdirメソッドを使って知ることができます（「[Customizing matplotlib](https://matplotlib.org/tutorials/introductory/customizing.html?highlight=get_configdir)」参照）。matplotlibrcは、カレントディレクトリに置いても有効です（カレントディレクトリに置かれたものが優先されます）。

```py3:リスト1-9
import matplotlib as mpl

print(mpl.get_configdir())
```
なお、専用のmatplotlibrcを設定すると、matplotlib_fname()の戻り値は、システムのmatplotlibrcではなく、新たに設定したmatplotlibrcを指します。


### 1-15. プログラム中でデフォルト値を変更したい
プログラム中でデフォルト値を変更することもできます。リスト1ｰ4と同じことを、デフォルト値を変更することで実現したのが、リスト1ｰ10です。

matplotlibのデフォルト値は、実行時に[mpl.rcParams](https://matplotlib.org/api/matplotlib_configuration_api.html)に格納されます。rcParamsは、[RcParams](https://matplotlib.org/api/matplotlib_configuration_api.html#matplotlib.RcParams)クラスのインスタンスとして定義されていますが、辞書（dictionary）のように扱うことができます。

リスト1-10では、デフォルト値を変更するために、2種類の方法を使っています（あくまでも例です。実際には、こんなまわりくどいことはしません）。

```py3:リスト1-10
01 import matplotlib as mpl
02 import matplotlib.pyplot as plt
03
04 mpl.rcParams['figure.figsize'] = [6.0, 3.0]
05 mpl.rc('figure.subplot', left=0.5, right=0.9, bottom=0.1, top=0.9)
06 fig, ax = plt.subplots()
07 plt.show()
```

リスト1ｰ4において、figureメソッドでfigsizeを設定しています。これは、リスト1-7をみれば、figure.figsizeパラメータの変更に対応することが分かります。このパラメータはリストですので、4行目のようにリストとして代入します（タプルを代入しても可）。

リスト1-7のうち、figure.subplot.X (X = left, right, bottom, top...)は、グラフ枠を示す四角を決めます。ところで、このようなドットで継げた一連の変数をグループと呼びますが、グループの変数は5行目のように、[mpl.rc](https://matplotlib.org/api/matplotlib_configuration_api.html#matplotlib.rc)メソッドを使って、一括して変更することができます。もちろん、５行目の代わりに、各パラメータを別々に変更してもかまいません。

```py3:リスト1-11
05 mpl.rcParams['figure.subplot.left'] = 0.5
06 mpl.rcParams['figure.subplot.right'] = 0.9
07 mpl.rcParams['figure.subplot.bottom'] = 0.1
08 mpl.rcParams['figure.subplot.top'] = 0.9
```
ところで、rcParamsパラメータとrcメソッドは、plt（matplotlib.pyplotの別名）からもコールできます。pltを使った方が面倒でなくていいかも知れません。

```py3:リスト1-12
04 plt.rc('figure',figsize=[6.0, 3.0])
05 plt.rc('figure.subplot', left=0.5, right=0.9, bottom=0.1, top=0.9)
```


### 1-16. 画像ファイルを作成したい
plt.show()はディスプレイに出力するメソッドですが、画像ファイルを作成したい場合、代わりに[plt.savefig()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html)メソッドを使います。出力ファイルの形式は、ファイル名の拡張子から決められます。デフォルトでは画面出力と同じ大きさの画像が作成されます。

```py3:リスト1-13
# リスト1ｰ10を変更して
07 plt.savefig('list1-13.png')
```

### 1-17. 印刷用に精細な画像ファイルがほしい
画像ファイルを印刷用として作成する場合、デフォルトのままではかなり小さな画像しか出来ません。それは、ディスプレイ画面とプリンタのdpiが異なるからです。ディスプレイ画面はディスプレイのサイズによりますが、dpi=100程度とみなすことができます。

印刷用の画像を作成するには、dpi引数でDPIを指定する必要があります。プリンタのDPIに合わせて決めると良いでしょう。dpiを省略した場合には、plt.figureで設定した値、それもなければデフォルト値のdpi=100になります。

```py3:リスト1-14
# リスト1ｰ10を変更して
07 plt.savefig('list1-14.png', dpi=600)
```


## 変更履歴
2023-01-29: ポイントに関するの記述を加えました
2023-01-26: figure座標系についての注釈[^2]を加えました
2021-01-06: matplotlib-v3.3.3に対応しました。内容と章立てを一部、変更しました
2020-02-06: 説明をわかりやすくするため、1-17を分割して1-18を付加しました

[^1]: この記事の初版では、matplotlib=2.2.2でした
[^2]: 割合と表現しましたが、これをfigure座標系と呼びます。画面の左下を(0,0)とし、右上を(1,1)とした座標系です