検索
連載

[Matplotlib超入門:OOインタフェース編]FigureオブジェクトとAxesオブジェクトを使ってみようPythonデータ処理入門

Matplotlibが提供するオブジェクト指向インタフェースを使ってグラフを描画する方法をマスターしよう。その基礎となるFigureオブジェクトとAxesオブジェクトついて見ていきます。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「Pythonデータ処理入門」のインデックス

連載目次

本シリーズと本連載について

 本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。

 【Matplotlib超入門:OOインタフェース編】は【Matplotlib超入門:pyplot編】に続きオブジェクト指向インタフェースを用いて、グラフを描画する方法を紹介する連載です。pyplot編と同様、以下のバージョンのPythonとMatplotlibを使用しています。

  • Python 3.13
  • Matplotlib 3.10.3

 なお、以下ではMatplotlibのpyplotインタフェースやNumPy、pandasをインポートする以下の行を実行してあるものとします。

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

お約束のコード

pyplotインタフェースとOOインタフェース

 Matplotlibを使ってグラフを描画するためのインタフェースとしては、これまでに「pyplot編」で紹介してきたpyplotインタフェースの他に、もう一つ「オブジェクト指向インタフェース」と呼ばれるものがあります(以下、OOインタフェースと表記します)。

 Matplotlibは非常に多彩な機能を提供しています。それらの機能をプログラマーが全て管理してグラフを描画することはもちろん可能です。そのために用意されているのがOOインタフェースといえます。しかし、それにはそれなりのコスト(時間や手間)がかかります。コストはかかるけれど、好きなようにMatplotlibを使いこなせるのは魅力的ですが、「ここらで少しデータを可視化してみようか」と思ったときに手間をかけたくはないでしょう。pyplotインタフェースはそうしたときに適したインタフェースだといえます。

 pyplotインタフェースが簡単に使えるのには理由があります。それはpyplotインタフェースの内部でグラフ描画に関わる煩雑な管理をある程度行ってくれるからです。例えば、pyplotインタフェースを使って線を描く以下のコードを見てください。

# pyplotインタフェース
plt.plot([0, 1, 2, 3, 4], [1, 2, 3, 4, 5])
plt.show()

pyplotインタフェースで線を描くコードの例

 pyplot.plot関数を呼び出すだけで終わりです。一方、OOインタフェースで同じことをしようとすると次のようになります。

# OOインタフェース
fig, ax = plt.subplots()
ax.plot([0, 1, 2, 3, 4], [1, 2, 3, 4, 5])
plt.show()

OOインタフェースで線を描くコードの例

 「fig, ax = plt.subplots()」という行が増えています。また、線を描くときに「pyplot」ではなく「ax」なるものが使われているところも違っています。前者はFigureオブジェクトとAxesオブジェクトを新規に作成することを指示するもので、後者は新規に作成したAxesオブジェクトである「ax」を使って線を描画することをプログラマーの側が明示的に指示しています。なお、実行結果はどちらも同じで次のようになります。

実行結果
実行結果

 ここで注意点が一つ。ノートブック環境でOOインタフェースを使っている場合、pyplot.show関数を呼び出さなくてもグラフが自動的に描画され(ることがあり)ます。一方、pyplotインタフェースではこれを呼び出さないとグラフが描画されないことがあります(どのような振る舞いになるかはコードを実行する環境にもよります)。以下ではなるべく最後に「plt.show()」を実行するようにしておきましょう。以下で紹介するコードを試してみたときにグラフが表示されなかったら、「plt.show()」を実行してみてください。

 話を元に戻しましょう。pyplot.plot関数は内部でFigureオブジェクトやAxesオブジェクトを作成し、それらを使って線の描画を取り計らってくれると考えるとよいでしょう。その代わりに、figやaxを使って細かくグラフを調整しようといったことはできません。OOインタフェースではFigureオブジェクト(fig)やAxesオブジェクト(ax)がプログラマーに渡されるので、それらを使って例えば複数のグラフを描画したり、グラフの位置を細かく調整したりといったことができます。

 簡単なのを取るか、全てを自分で管理するか、どちらがよいかで使うインタフェースが違ってくるということです。単純な可視化であれば、pyplotインタフェースを使うのがもちろんオススメですが、そうはいかないこともあるのでOOインタフェースについても知っておくのがよいでしょう。

 まとめると次のようになります。

  • pyplotは内部で状態を管理してくれる
  • OOインタフェースはプログラマーの側が構造を明示して描画を指示する

 本連載は超入門なので、pyplotインタフェースを先に紹介して「あんなことしたい」という的に手軽にグラフを描く方法を見てきたというわけです。今回からはOOインタフェースについて見ていくことにしましょう。

 では、まず今出てきたFigureオブジェクトやAxesオブジェクトとは何かについて簡単に紹介します。

Figure/Axes/Artistsとは?

 Matplotlibではグラフは次の3種類のオブジェクトの組み合わせとして作成されます。

  • Figureオブジェクト:図全体を表す土台のようなモノ
  • Axesオブジェクト:実際にグラフが描画される領域
  • Artistオブジェクト:グラフ描画で使われる個々の要素(線や点、X軸やY軸など)

 Figureオブジェクトに複数のAxesオブジェクトを配置することで、複数のグラフを1つのグラフ画像の中に描画できます。多くの場合、個々のグラフ(Axesオブジェクト)はグリッド形状に並べられますが、この配置方法や配置された個々のAxesオブジェクトを「サブプロット」と呼ぶこともあります。ここでいう「サブ」とは「部分的」といった意味で使われています。つまり、Figureという大きな領域の一部を個別のグラフの描画領域とする=「sub plot area」と考えることができるでしょう。

FigureオブジェクトとAxesオブジェクト、Artistオブジェクト
FigureオブジェクトとAxesオブジェクト、Artistオブジェクト

 例えば、上の画像では水色で描かれている部分が単一のFigureオブジェクトです。その上には3つのAxesオブジェクト(Axes 1からAxes 3)があります。ここにグラフが個別に描画されます。この描画に使われる部品の一つ一つがArtistオブジェクトです(Artistオブジェクトとは線を表すLine2Dオブジェクトやグラフなどのタイトルに使われるTextオブジェクトなどの総称といえます)。

 Figureオブジェクトはpyplotインタフェースのfigure関数を使うことで作成できます。あるいはやはりpyplotインタフェースのsubplots関数を使うとFigureオブジェクトとそこに含まれるAxesオブジェクト(サブプロット)を作成できます。また、Figureオブジェクトが持つadd_subplotメソッドを使ってAxesオブジェクトを一つ一つFigureオブジェクトに追加していくことも可能です。

 このようにFigureオブジェクトとAxesオブジェクトを作成する方法は多岐にわたりますが、以下ではその幾つかを見てみましょう。

pyplot.figure関数とadd_subplotメソッド

 pyplot.figure関数はFigureオブジェクトを新規に作成する関数です(既存のFigureオブジェクトの取得にも使えますが、ここでは説明を省略します)。その引数には以下を指定できます(一部抜粋)。

  • figsize:Figureオブジェクトのサイズを(x, y)形式で指定。デフォルト値は(6.4, 4.8)
  • facecolor:背景色を指定。デフォルト値は'white'
  • layout:'constrained'/'compressed'/'tight'などを指定可能。デフォルト値はNone

 「layout='constrained'」「layout='tight'」の代わりに「constrained_layout=True」「tight_layout=True」のように指定することも可能です(「compressed_layout=True/False」は不可)。layout引数などはFigure全体で個々のAxesをどのように配置するかを決定するものです。ここではどのような表示になるかは明示しません。興味のある方は「layout='constrained'」「layout='tight'」を指定して確認してみてください。

 ところで、先ほどOOインタフェースを使ってグラフをプロットするコードでは次のようにAxesオブジェクト(ax)を取得して、それを使っていました。しかし、pyplot.figure関数が返すのはFigureオブジェクトだけです(以下ではpyplot.subplots関数を使っていますが、これについてはこの後で取り上げます)。

# OOインタフェース
fig, ax = plt.subplots()
ax.plot([0, 1, 2, 3, 4], [1, 2, 3, 4, 5])
plt.show()

OOインタフェースで線を描くコードの例(再掲)

 ということは、Figureオブジェクトが手に入っただけではグラフを描画できないということです。実際に試してみましょう。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue')
plt.show()

Figureオブジェクトを取得してグラフ全体を表示したつもり

 以下に実行結果を示します。

何も表示されない
何も表示されない

 「<Figure size 800x600 with 0 Axes>」というのはJupyterが自動的に出力しているもので、作成されたFigureオブジェクトに関する情報を示したものです。それはともかくとして、サイズや背景色を指定したにもかかわらず、それらしきものが表示されないことに注目してください。Figureオブジェクトは土台となるだけで、そこにAxesオブジェクトがないと何も表示されないというわけです。

 そこで、FigureオブジェクトにAxesオブジェクトを追加してみましょう。ここではFigure.add_subplotメソッドを使ってこれをやってみます。

 Figure.add_subplotメソッドの典型的な使い方は次の通りです。

# Axesオブジェクトを作成する位置を3つの整数値で指定
ax = fig.add_subplot(nrows, ncols, index)

# Axesオブジェクトを作成する位置を3桁の整数値で指定
ax = fig.add_subplot(pos)

add_subplotメソッドの使い方の例

 最初のパターンはFigureオブジェクトを何行何列の区画に区切るかをnrowsとncolsで指定して、今から作成するAxesオブジェクトをそのどこに置くかをindexで指定するものです。indexは1始まりの整数値にするか、(start, end)というタプルで複数領域にまたがるように範囲指定します。整数値で指定する場合は、左上から右下へとインデックスが増えていきます。省略時はnrows=ncols=index=1が指定されたもの、つまり「fig.add_subplot(1, 1, 1)」もしくは「fig.add_subplot(111)」と指定されたものと見なされます。

 試してみましょう。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue')
ax = fig.add_subplot(1, 1, 1# ax = fig.add_subplot(111)
plt.show()

1行1列のレイアウトでその1番目のグリッドにAxesオブジェクトを作成

 これを実行すると次のようになります。

今度はFigureオブジェクトの上にAxesオブジェクトが表示された
今度はFigureオブジェクトの上にAxesオブジェクトが表示された

 ご覧の通り、1行1列のグリッドの1番目にAxesオブジェクトが作成され、先ほどとは異なり、グラフ全体が表示されました。

 もう1つの(start, end)という形式のタプルを渡すと、startからendの範囲にまたがる領域に単一のAxesオブジェクトを作成できます。こちらも試してみましょう。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue')
ax0 = fig.add_subplot(1, 3, (1, 2))
ax0.set_title('span 2 grids')

ax1 = fig.add_subplot(1, 3, 3)
ax1.set_title('span 1 grid')
plt.show()

複数の領域にまたがるAxesオブジェクトを作成

 ここでは2つのAxesオブジェクトを作成していますが、最初に作成しているax0は「ax0 = fig.add_subplot(1, 3, (1, 2))」のように1行3列のグリッドのうち、最初の2つにまたがるように作成しています。もう1つのax1は残りのグリッドに作成しています。

 これを実行すると次のようになります。

2つのグリッドにまたがるAxesオブジェクトを作成したところ
2つのグリッドにまたがるAxesオブジェクトを作成したところ

 add_subplotメソッドは一度に1つのAxesオブジェクトしか作成できないことには注意してください。そのため、上のコード例では2つのAxesオブジェクトを作成するためにadd_subplotメソッドを2回呼び出しているのです。ということは、3つのAxesオブジェクトをFigureオブジェクトに追加するには、素直に「ax = fig.add_subplot(……)」のようなコードを3つ書くことになりそうです。が、そうしたときには以下のようにfor文を使うことも考えてみてください。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue')

ax= []
for idx in range(1, 4):  # インデックスは1始まり
    tmp = fig.add_subplot(1, 3, idx)
    ax.append(tmp)

plt.show()

3つのAxesオブジェクトをFigureオブジェクトに追加

 これは1行3列のグリッドを作成し、add_subplotメソッドを使って、そこに順番にAxesオブジェクトを作成していくコードです。作成したAxesオブジェクトはaxに追加することで、後から使用できるようにしています。上のコードを少し変更して、作成したAxesオブジェクトを実際に使ってみましょう。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue', layout='tight')
fig.suptitle("Figure's title")

ax= []
for idx in range(1, 4):  # インデックスは1始まり
    tmp = fig.add_subplot(1, 3, idx, label=f'Axes {idx}')
    ax.append(tmp)

y_values = [[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [2, 5, 3, 4, 1]]

for idx, a in enumerate(ax, 1):
    a.plot([0, 1, 2, 3, 4], y_values[idx-1])
    a.set_title(a.get_label())

plt.show()

作成したAxesオブジェクトを使ってグラフをプロット

 変更点としては次のようなことが挙げられます。

  • figure関数呼び出しで「layout='tight'」の指定を追加
  • figureオブジェクトのsuptitleメソッドでグラフ全体にタイトルを追加
  • add_subplotメソッドの呼び出しで「label=f'Axes {idx}'」を追加
  • Y軸の値としてリストを用意
  • axに格納されているAxesオブジェクトを使ってグラフを描画するfor文を追加

 最初の点はグラフのレイアウトがうまくハマるようにするためのものです(興味のある方はこの指定を変更したり、取り除いたりして、実際にどうなるかを試してみてください)。次の指定はadd_subplotメソッドで追加するAxesオブジェクトにラベルを作成するものです。実際には「Axes1」「Axes2」「Axes3」のようなラベルが作成されて、それぞれのAxesオブジェクトに関連付けられます。作成したラベルは実際にグラフをプロットする際に「label=ax.get_label()」として取得しています。

 ここでは3つのAxesオブジェクトにplotメソッドでグラフを描画していますが、その値が常に同じなのもつまらないのでy_valuesにY軸の値となるようなリストを格納して、それらをplotメソッド呼び出しで使って、グラフの形状が異なるようにしています。最後のfor文ではaxに格納されたAxesオブジェクトを順番に取り出して、plotメソッドを使い、グラフをプロットしています。また、set_titleメソッドを使って、個々のグラフに「Axes 1」「Axes 2」「Axes 3」というタイトルを付加しています。

 実行結果を以下に示します。

3つのグラフが描画されたところ
3つのグラフが描画されたところ

 このようにpyplot.figure関数でfigureオブジェクトを作成し、そのadd_subplotメソッドでAxesオブジェクトを作成することで、グラフを実際にプロットできるようになります。しかし、Axesオブジェクトを何度も作成するのが面倒くさいという人もいるでしょう。実は複数のAxesオブジェクトをまとめて作成するメソッドもあります。それが次に紹介するFigure.subplotsメソッドです。

Figure.subplotsメソッド

 Figureオブジェクトのsubplotsメソッドは、1個以上のAxesオブジェクトをFigureオブジェクトにまとめて作成するものです。このメソッドにはさまざまな引数を指定できますが、ここでは作成するAxesオブジェクトを何行何列のレイアウトで配置するかを指定するnrowsとncolsだけを紹介することにします。戻り値は1個のAxesオブジェクトか(Axesオブジェクトを1個だけ作成する場合)、Axesオブジェクトを要素とする配列(複数のAxesオブジェクトを作成する場合)になります。

ax = figure.subplots(nrows, ncols)  # figureはFigureオブジェクト

Figure.subplotsオブジェクトの典型的な使い方

 これを使って、先ほどのコードを書き直すと次のようになります。

fig = plt.figure(figsize=(8, 6), facecolor='lightblue', layout='tight')
fig.suptitle("Figure's title")

ax = fig.subplots(1, 3)

y_values = [[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [2, 5, 3, 4, 1]]

for idx, a in enumerate(ax, 1):
    a.plot([0, 1, 2, 3, 4], y_values[idx-1])
    a.set_title(f'Axes {idx}')

plt.show()

作成したAxesオブジェクトを使ってグラフをプロット

 for文の中でadd_subplotメソッドを使い、Axesオブジェクトを順番にFigureオブジェクトに追加していたコードが「ax = fig.subplots(1, 3)」という1行に書き換わっていることに注目してください。ただし、add_subplotメソッドでAxesオブジェクトを作成する際にラベルをそれぞれのオブジェクトにひも付けていましたが、subplotsメソッドで一度にAxesオブジェクトを作成するときにはそうはいきません。そのため、Axesオブジェクトのset_titleメソッドで個々のグラフにタイトルを設定する際に「a.set_title(f'Axes {idx}')」のようにしてグラフのタイトルを指定するようになっています。また、上の典型的な使い方に合わせて、作成したAxesオブジェクトをaxに代入しているので、それを後で使う場面ではfor文の中でループ変数aを使うようにもしています。描かれるグラフは上と同様なので、実行結果は省略します。

 なお、2行2列以上のレイアウトを採用した場合、Axesオブジェクトを格納する2次元配列が戻り値になります。このときにfor文なので、全てのAxesオブジェクトにアクセスするには二重ループのような手段が必要になることには注意してください。

fig = plt.figure()
ax = fig.subplots(2, 3)

for i0, r in enumerate(ax, 1):
    for i1, c in enumerate(r, 1):
        print(f'{i0}, {i1}, {c}')

二重ループで全てのAxesオブジェクトにアクセス

 実行すると次のような結果になります。Print関数によって、各行の各列に配置されたAxesオブジェクトの情報が表示されていることに注目してください。

二重ループで全てのAxesオブジェクトにアクセス
二重ループで全てのAxesオブジェクトにアクセス

 あるいはAxesオブジェクトを含む配列のflat属性もしくはflattenメソッドを使うのもありです(こちらも同様な結果なので画像は省略します)。

fig = plt.figure()
ax = fig.subplots(2, 3)

for a in ax.flat:  # for a in ax.flatten():
    print(a)

flat属性またはflattenメソッドで配列を1次元化して全てのAxesオブジェクトにアクセス

 このようにpyplot.figure関数でFigureオブジェクトを作成し、そのadd_subplotメソッドやsubplotsメソッドでAxesオブジェクトを作成するという段取りを踏めば、グラフをプロットできるようになります。しかし、これらをひとまとめにやってくれる関数がpyplotインタフェースには用意されています。それが次に紹介するpyplot.subplots関数です。

pyplot.subplots関数

 pyplotインタフェースにはsubplots関数があります。上でも述べたように、これはFigureオブジェクトとそこに含まれる1個以上のAxesオブジェクトを一度に作成してくれるものです。

 pyplot.subplots関数にはさまざまな引数を渡せますが、ここではFigureオブジェクトのsubplotsメソッドと同様に、Axesオブジェクトを何行何列のレイアウトで配置するかを指定するnrowsとncolsだけを紹介します。戻り値はFigureオブジェクトと、Axesオブジェクト(1個だけ作成した場合)またはAxesオブジェクトを要素とする配列になります。

fig, ax = pyplot.subplots(nrows, ncols)

pyplot.subplots関数の典型的な使い方

 なお、pyplot.figure関数の呼び出しで指定する引数もpyplot.subplots関数に渡せます。それらはpyplot.subplots関数の内部でpyplot.figure関数を呼び出すときに使われます(実際にはpyplot.subplots関数は内部でpyplot.figure関数を呼び出してFigureオブジェクトを作成して、そのsubplotsメソッドを呼び出しています)。

fig, ax = plt.subplots(1, 3, facecolor='lightblue', layout='tight')
fig.suptitle("Figure's title")

y_values = [[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [2, 5, 3, 4, 1]]

for idx, a in enumerate(ax, 1):
    a.plot([0, 1, 2, 3, 4], y_values[idx-1])
    a.set_title(f'Axes {idx}')

plt.show()

作成したAxesオブジェクトを使ってグラフをプロット

 2行2列以上のレイアウトを採用した場合に、Axesオブジェクトが2次元配列に格納されるという注意点はFigure.subplotsメソッドと同様です(全てのAxesオブジェクトを反復するには、二重ループやflat属性、flattenメソッドを使う)。

 Figureオブジェクトの作成→Axesオブジェクトの作成を段階的に行う必要がないのであれば、pyplot.subplots関数を使って両者を一度に作成してしまうのが一番簡単でしょう。

まとめ

 最後にpyplotモジュールやFigureオブジェクト、Axesオブジェクトについて、今回使用した関数やメソッドをまとめておきます。

関数/メソッド 説明
pyplot.figure関数 Figureオブジェクトを作成する
pyplot.subplots関数 FigureオブジェクトとAxesオブジェクトを一度に作成する
Figure.add_subplotメソッド AxesオブジェクトをFigureオブジェクトに1つ追加する
Figure.subplotsメソッド 複数のAxesオブジェクトをFigureオブジェクトに追加する
Figure.suptitleメソッド グラフ全体のタイトルを指定する
Axes.set_titleメソッド Axesオブジェクトのタイトルを指定する
Axes.get_labelメソッド Axesに紐付けられたラベルを取得する
Axes.plotメソッド 折れ線グラフを描画する
今回紹介した関数やメソッド

 今回はpyplotインタフェースのfigure関数をスタート地点として、そこからadd_subplotメソッドでAxesオブジェクトを1つ作成する方法、subplotsメソッドで複数のAxesオブジェクトを作成する方法、pyplot.subplots関数でそれらをまとめて行う方法を紹介しました。また、その際にはAxes.plotメソッドで折れ線グラフを描画してみました。また、グラフ全体のタイトルや個別のグラフのタイトルを設定する方法も紹介しました。グラフを描くメソッドとして今回紹介したのはAxes.plotメソッドだけですが、次回はさらに多くのメソッドを紹介する予定です。

 pyplotインタフェースは内部でFigureオブジェクトやAxesオブジェクトを管理してくれるので、pyplot.plot関数などを使うことで簡単にグラフを描画できます。一方、OOインタフェースでは今回見たようにFigureオブジェクトとAxesオブジェクトを自分で管理しながらグラフを作成することになります。その分、自由にさまざまな調整を行えるのがメリットです。こうした点については次回以降に触れることにしましょう。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る