🎃

Matplotlibで見やすいグラフを描く

2023/06/22に公開

0. はじめに

https://matplotlib.org/stable/index.html

Pythonのデータ分析や可視化作業において、グラフ描画ライブラリであるMatplotlibは広く使用されているツールです。この記事では、Matplotlibで基本的なグラフを描く方法と、さらにその外観を細かく調整する方法やよく使用されるコードを紹介します。

「いちおう基本的なグラフは出力できるようになったけどもっと細かく調整して見やすくしていきたいな〜」という方を対象とし、Pythonの基本的な文法については理解していることを前提とします。

1. 描画スタイル

matplotlibには書き方に複数のスタイルがあります。まず最初にどちらのスタイルで書くのか決める必要があります。

オブジェクト指向スタイル

Explicit APIとも呼びます。FigureAxes等のオブジェクトを作成してから細かく調整していく方法になります。下記のようにaxs.plot() 等を使用する場合はこちらです。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-10, 10, 0.1)
fig, axs = plt.subplots()
axs.plot(x, np.sin(x))

plt.show()

MATLABスタイル

Implicit APIとも呼びます。plt.plot() を使用する場合はこちらになります。

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-10, 10, 0.1)
plt.plot(x, np.sin(x))

The pylab API(非推奨)

関数をグローバル名前空間にインポートする方法だったそうですが、今は非推奨です。


実際に使う場合はオブジェクト指向スタイルを強く推奨します。オブジェクトを作成してから調整を加えていくことで、細かい表示の制御をすることができます。MATLABスタイルは手軽に書けるので入門サイトでよく見ますが、オブジェクトを後から変更することができないので、細かい調整は苦手です。以降のコード例でもオブジェクトスタイルで書いていきます。

これらの情報については、公式ドキュメントのや以下の記事でもまとめられているので合わせてお読みください。

https://matplotlib.org/stable/api/index.html

https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9

https://zenn.dev/canard0328/articles/memorandum-of-matplotlib

2. オブジェクト

オブジェクトは下記のような親子関係を持っています。後述のサンプルコードでは、 Figure → Axes → Line → その他 といったように、階層構造の上から順番に作成して操作していくことになります。特に重要なオブジェクトは Figure, Axes, Line の3つです。とりあえずこれらを理解しておけば困ることはありません。

公式ドキュメントの Quick Start Guide の図も合わせて見るとわかりやすいでしょう。

Figure --+-- Axes --+-- Line
         |          +-- Axis(x) ---- Tick / Label
         |          +-- Axis(y) ---- Tick / Label
         |          +-- Grid
         |          +-- Title
         |          +-- Legend
         |          +-- Spine
         |
         +-- Axes --+-- ...
                    +-- ...

Figure

dpi(解像度)や画像サイズなどの全体の描画領域を制御します。

Axes

グラフの描画領域です。1つのFigureに対してデフォルトでは1つですが、グラフを複数並べるときはAxesを複数作成します。

外観を調整する場合はAxesのドキュメントからset_***という名前のメソッドを使用します。

Line

折れ線でプロットする際のオブジェクト本体です。axs.plotにより作成します。Line2Dクラスに属し、x,yのデータの他、線のスタイルlinestyle や太さlinewidth などのパラメータを持ちます。作成時にデータを投入するだけでなく、set_data により後からデータを変更することもできます。

外観を調整する場合はLine2Dのドキュメントからset_***という名前のメソッドを使用します。

散布図の場合はaxs.scatterによりMarkerオブジェクトを作成します

サンプル集

ticker

目盛りの位置やラベルの表示方法をカスタマイズします。x, yとmajor, minorがあり、合計4つあります。

Legend

グラフ内のデータ(線やマーカー)とラベルといったグラフの凡例を表示します。

コード例

ここまでの内容で実際にプロットするとこんな感じになります。

0〜5でFigure, Axes, Lineを設定しただけでもいい感じに出力してくれますが、6〜で目盛りやグリッドを調整するとより見やすいものを出力することができます。

import numpy as np
import matplotlib.pyplot as plt

# 0. データを作成する
x = np.arange(-10, 10, 0.1)
y_1 = np.sin(x)
y_2 = np.cos(x)

# 1. Figure, Axes, Lineオブジェクトを作成する
fig = plt.figure(figsize=(12, 8), dpi=80)
axs = fig.add_subplot(1, 1, 1)
line_1, = axs.plot([], [], label='Line label 1')
line_2, = axs.plot([], [], label='Line label 2')

# 2. 作成したデータをセットする
line_1.set_data(x, y_1)
line_2.set_data(x, y_2)

# 3. 表示範囲、軸ラベルを設定する
line_1.set_color('brown')
line_2.set_color('gray')
axs.set_xlim((-11, 11))
axs.set_xlabel('X Label')
axs.set_ylim((-2.0, 2.0))
axs.set_ylabel('Y Label')

# 4. 保存する
fig.savefig('figure_1.png')

# 5. 目盛りのラベルを設定する
axs.set_xticks([np.pi * i for i in range(-3, 4)])
axs.set_xticklabels(['${}\pi$'.format(i) for i in range(-3, 4)])
axs.set_yticks([-2, -1, 0, 1, 2])
axs.minorticks_on()

# 6. 目盛りのスタイルを設定する
axs.tick_params(which='both', width=2, direction='in')
axs.tick_params(which='major', length=7)
axs.tick_params(which='minor', length=4, color='r')

# 7. グリッド、タイトル、凡例を設定する
axs.grid(linestyle='--', linewidth=0.5, color='gray')
axs.set_title('Axes Title')
axs.legend(loc='upper right')

# 8. 保存する
fig.savefig('figure_2.png')

figure_1.png

figure_2.png

3. サンプル集

公式ドキュメントのギャラリーを見れば目的の設定はだいたい見つかります。とりあえずここを見ておけばいいでしょう。

https://matplotlib.org/stable/gallery/index.html

複数グラフを描画する

よく紹介されてるものですね。公式ドキュメントにも例があります

注意点として、plt.subplots(1, 1) だとAxes単体で返ってきますが、plt.subplots(n, m)で縦n横mに並べるとarray<Axes>で返ってきます。

import matplotlib.pyplot as plt

fig, axs = plt.subplots(1, 2)
axs[0].plot([1, 2, 3], [2, 3, 1])
axs[1].plot([1, 2, 3], [3, 1, 2])

fig.savefig('figure.png')

また、このようにFigure, Axesを別々に作成する書き方もあります。結果は全く同じです。

import matplotlib.pyplot as plt

fig = plt.figure()
axs1 = fig.add_subplot(1, 2, 1)
axs2 = fig.add_subplot(1, 2, 2)
axs1.plot([1, 2, 3], [2, 3, 1])
axs2.plot([1, 2, 3], [3, 1, 2])

fig.savefig('figure.png')

wxPythonに埋め込み

matplotlibは基本的には画像ファイルに出力するのが主な機能ですが、wxPythonのようなGUIツールキットのパーツとして組み込むこともできます。canvas.draw()を実行することで再描画できるので、ボタンやスライドバーでインタラクティブにグラフを操作することができます。

import random
import wx

import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')

from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure

app = wx.App()
frame = wx.Frame(None, wx.ID_ANY, 'Test Frame')
panel = wx.Panel(frame, wx.ID_ANY, pos=(0, 0))
layout = wx.BoxSizer(wx.VERTICAL)

fig = Figure()
axs = fig.add_subplot(111)
line, = axs.plot([], [], linestyle='None', marker='.')
axs.set_xlim((-11, 11))
axs.set_ylim((-11, 11))
canvas = FigureCanvasWxAgg(panel, wx.ID_ANY, fig)

def replot(even):
    x_data = []
    y_data = []
    for i in range(1000):
        x_data.append(random.uniform(-10, 10))
        y_data.append(random.uniform(-10, 10))
    line.set_data(x_data, y_data)
    canvas.draw()

button = wx.Button(panel, wx.ID_ANY, 'ボタン1')
button.Bind(wx.EVT_BUTTON, replot)

layout.Add(canvas, flag=wx.GROW)
layout.Add(button, flag=wx.GROW)
panel.SetSizer(layout)

frame.Show()
app.MainLoop()

PDFファイルに複数ページを埋め込み

同じようなグラフを複数作成していくと、ファイルがたくさんできてしまいちょっと面倒です。そんな時、PDFのページを利用すれば、1つのファイルにグラフを作成できて取り扱いが便利になります。

https://matplotlib.org/stable/gallery/misc/multipage_pdf.html

import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

pp = PdfPages('output_multiple.pdf')

fig1, axs1 = plt.subplots(1, 1,figsize=(7, 7),dpi=100)
fig2, axs2 = plt.subplots(1, 1,figsize=(7, 7),dpi=100)

axs1.plot([1, 2, 3], [2, 3, 1])
axs2.plot([1, 2, 3], [3, 1, 2])

pp.savefig(fig1)
pp.savefig(fig2)
pp.close()

2軸グラフの凡例

2軸グラフを作成した時に単純にfig.legend()すると、主軸の凡例しか作成されません。下記のようにすると、まとめて作成できます。

import matplotlib.pyplot as plt

fig, axs = plt.subplots()
axs_twin = axs.twinx()

axs.plot([1, 2, 3], [2, 3, 1], label='left axis plot')
axs_twin.plot([1, 2, 3], [3, 1, 2], label='right axis plot')

handler1, label1 = axs.get_legend_handles_labels()
handler2, label2 = axs_twin.get_legend_handles_labels()
axs.legend(handler1 + handler2, label1 + label2)

fig.savefig('figure.png')

Discussion