pyplotインタフェースとOOインタフェースの違いがどこにあるのかを、幾つかのグラフを描画しながら実際に見てみましょう。また、グラフの装飾についても説明します。
本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。
【Matplotlib超入門:OOインタフェース編】は【Matplotlib超入門:pyplot編】に続きオブジェクト指向インタフェースを用いて、グラフを描画する方法を紹介する連載です。pyplot編と同様、以下のバージョンのPythonとMatplotlibを使用しています。
なお、以下ではMatplotlibのpyplotインタフェースやNumPy、pandasをインポートする以下の行を実行してあるものとします。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
前回はMatplotlibのオブジェクト指向インタフェース(以下、OOインタフェース)を紹介しました。特に、その基盤となるFigure、Axes、Artistという3つのオブジェクトを紹介し、OOインタフェースを用いたグラフの簡単な描画を見ました。さらに、幾つかの関数とメソッドについても紹介しました。今回は同じグラフをpyplotインタフェースとOOインタフェースで書いてみながら、両者の違いがどこにあるのかを確認してみます。
まずは簡単な折れ線グラフを1つだけ描いてみることにしましょう。pyplotインタフェースで折れ線グラフを描く例を以下に示します。
x = np.arange(5)
y = x ** 2
plt.plot(x, y, color='red')
plt.title('y = x ^ 2')
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.show()
これはpyplotインタフェースを使ってy = x2のグラフを描くコードです。実行結果は次のようになります。
同じことを、OOインタフェースを使うと次のようになります(このコードを実行した結果は上と変わらないので画像は省略します)。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots()
ax.plot(x, y, color='red')
ax.set_title('y = x ^ 2')
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
plt.show()
その違いは主に以下の点にあります。
これはpyplotインタフェースではどのAxesオブジェクトにグラフを描くか(現在、アクティブなAxesオブジェクトがどれか)はpyplotインタフェースの内部で管理されているのに対して、OOインタフェースではコードを書く側がどのAxesオブジェクトを描画対象とするのかを明示的に指定する必要があることから生まれる差です。
グラフを1つ描くだけであれば、見た目の差はあまりありません。pyplotインタフェースではplot関数を呼ぶだけでグラフが描けます。しかし、OOインタフェースではpyplot.subplots関数などを使ってAxesオブジェクトを取得しないとグラフを描画できません。そのため、OOインタフェースの方が手間がかかるといえば手間がかかります。すなわち、簡単なグラフの描画ならpyplotインタフェースを使うのがよいということでもあります。
では、次にグラフを2つ描いてみます。先ほどと同じくまずはpyplotインタフェースの例から見てください。
x = np.arange(5)
y = x ** 2
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(x, y, color='red')
plt.title('plot')
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.subplot(1, 2, 2)
plt.bar(x, y, color='blue')
plt.title('bar')
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.show()
ここでは、最初にpyplot.figure関数でグラフのサイズを指定しています。その後、pyplot.subplot関数で現在のFigureオブジェクトに2つのAxesオブジェクトを追加して、1つ目のAxesオブジェクトに折れ線グラフを描いています(plt.plot関数呼び出し)。その後、2つ目のAxesオブジェクトに棒グラフを描いています(plt.bar関数呼び出し)。
実行結果は次の通りです。
なお、上のコードはグラフを描くコードをダラダラと記述していますが、次のようにfor文にまとめることも可能です。
x = np.arange(5)
y = x ** 2
plt.figure(figsize=(10, 4))
plot_funcs = [plt.plot, plt.bar]
colors = ['red', 'blue']
titles = ['plot', 'bar']
for idx, (func, color, title) in enumerate(zip(plot_funcs, colors, titles), 1):
plt.subplot(1, 2, idx)
func(x, y, color=color)
plt.title(title)
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.show()
ここでは個々のグラフで変化する要素(グラフ描画に使う関数、描画色、グラフのタイトル)をそれぞれリストに格納しているのがポイントです。そして、zip関数でそれらの組(関数、描画色、タイトル)を反復するとともに、enumerate関数で描画対象のAxesオブジェクトを指定するためのインデックスをループ変数idxに代入しています。
グラフが2つだけなら1つ目のようなコードでも構わないでしょうが、多数のグラフをまとめて描きたいのであれば、このような書き方が(pyplotインタフェースでも)できることは覚えておくとよいでしょう。
一方、OOインタフェースを使うとどうなるでしょう。最初にpyplotインタフェースと同様にグラフを描くコードをダラダラと描いてみます(画像は省略)。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
ax[0].plot(x, y, color='red')
ax[0].set_title('plot')
ax[0].set_xlabel('x axis')
ax[0].set_ylabel('y axis')
ax[1].bar(x, y, color='blue')
ax[1].set_title('bar')
ax[1].set_xlabel('x axis')
ax[1].set_ylabel('y axis')
plt.show()
pyplotインタフェースとの違いは、OOインタフェースではやはり「ax[0]」「ax[1]」のようにAxesオブジェクトを明示的に指定して、グラフを描いているところです。では、for文にまとめたバージョンはどうなるでしょうか。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = [ax[0].plot, ax[1].bar]
colors = ['red', 'blue']
titles = ['plot', 'bar']
for i, (func, color, title) in enumerate(zip(plot_funcs, colors, titles)):
func(x, y, color=color)
ax[i].set_title(title)
ax[i].set_xlabel('x axis')
ax[i].set_ylabel('y axis')
plt.show()
plot_funcsリストに「ax[0].plot」「ax[1].bar」とAxesオブジェクトのメソッドを格納しているところと、「ax[i]」とインデックスを使ってタイトルやラベルの設定を行うAxesオブジェクトを指定しているところを除けば、pyplotインタフェースのコードとよく似ています。
これでもよいのですが、enumerate関数を使って、「ax[i]」のような形でAxesオブジェクトを指定したり、plot_funcsリストに「ax[0].plot」のようにインデックス付きでAxesオブジェクトのメソッドを格納したりしている点があまりキレイではないと感じる人もいるでしょう。
もっとキレイな書き方もできるので、紹介だけしておきましょう。
from matplotlib.axes import Axes
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = [Axes.plot, Axes.bar]
colors = ['red', 'blue']
titles = ['plot', 'bar']
for a, func, color, title in zip(ax, plot_funcs, colors, titles):
func(a, x, y, color=color)
a.set_title(title)
a.set_xlabel('x axis')
a.set_ylabel('y axis')
plt.show()
ここでしているのは次の3点です。
ちょっとトリッキーなやり方ですが、こうすることでインデックスを使う必要がなくなり、コードがさらにキレイになりました(と筆者は感じています)。なお、pyplotインタフェースを使って同様なことを書くと次のようになります。
x = np.arange(5)
y = x ** 2
plt.figure(figsize=(10, 4))
plot_funcs = [plt.plot, plt.bar]
colors = ['red', 'blue']
titles = ['plot', 'bar']
for i, (func, color, title) in enumerate(zip(plot_funcs, colors, titles), 1):
plt.subplot(1, 2, i)
func(x, y, color=color)
plt.title(title)
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.show()
詳しい説明は省略しますが、構造はOOインタフェースとほぼ同様です。Axesオブジェクトがない代わりに、enumerate関数を使っていることと、plot_funcsに格納しているのがpyplotモジュールの関数になっているところが異なっている点といえます。
なお、OOインタフェースを使う場合、実際には、上で紹介した「Axes.plot(a, x, y, ……)」のような書き方をせずに、辞書を使うなどして描くグラフを切り替えるのがよいでしょう。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = {
'plot': lambda a, x, y, **kw: a.plot(x, y, **kw),
'bar': lambda a, x, y, **kw: a.bar(x, y, **kw)
}
plot_types = ['plot', 'bar']
colors = ['red', 'blue']
for a, plot_type, color in zip(ax, plot_types, colors):
plot_funcs[plot_type](a, x, y, color=color)
a.set_title(plot_type)
a.set_xlabel('x axis')
a.set_ylabel('y axis')
plt.show()
これについても詳しい説明は省略します。plot_funcsは何のグラフを描きたいかを文字列のキーとして与えると、対応するメソッド呼び出しが行われるようなラムダ式がその値として返されるようになっています。そして、plot_typesは2つあるAxesオブジェクトで何のグラフを描くかを指定しています(ここではグラフのタイトルにもこれを流用しています)。
ここまでpyplotインタフェースとOOインタフェースの違いを見てきましたが、重要なのはOOインタフェースではプログラマーの側がどのAxesオブジェクトを描画対象とするかを管理するのに対して、pyplotインタフェースではライブラリ任せになるという点です。
ここまでpyplotインタフェースとOOインタフェースの違いを見てきましたが、これはちょっとお休みにして、以下ではグラフの装飾について見ていきます。といっても、既にタイトルや軸ラベルは付加してきたので、まずはそれらについてざっくりとまとめておきましょう。
上ではグラフにタイトルや軸ラベルを設定していました。これらを以下にまとめます。
| メソッド | 説明 | pyplotインタフェースでの同様な機能 |
|---|---|---|
| Axes.set_titleメソッド | そのAxesに描いたグラフのタイトルを設定する | pyplot.title関数 |
| Figure.suptitleメソッド | Figure全体のタイトルを設定する | pyplot.suptitle関数 |
| Axes.set_xlabelメソッド | そのAxesに描いたグラフのX軸のラベルを設定する | pyplot.xlabel関数 |
| Axes.set_ylabelメソッド | そのAxesに描いたグラフのY軸のラベルを設定する | pyplot.ylabel関数 |
| タイトルと軸ラベルに関連するメソッド | ||
上では使っていなかったFigure.suptitleメソッドもここには含めました。これはFigure全体のタイトル、つまり複数のグラフに対して付ける全体的な名前を設定するために使用します。これに対応するのはpyplot.suptitle関数です。
これらを使ってタイトルとラベルを付加するコードの例を以下に示します。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = {
'plot': lambda a, x, y, **kw: a.plot(x, y, **kw),
'bar': lambda a, x, y, **kw: a.bar(x, y, **kw)
}
plot_types = ['plot', 'bar']
colors = ['red', 'blue']
for a, plot_type, color in zip(ax, plot_types, colors):
plot_funcs[plot_type](a, x, y, color=color)
a.set_title(plot_type)
a.set_xlabel('x axis')
a.set_ylabel('y axis')
fig.suptitle('title for Figure')
plt.show()
ここでは先ほどのコードにFigure.suptitleメソッドで全体のタイトルを設定する行を付け加えただけです。これらのメソッドにタイトルまたは軸ラベルを与えると、それらが対応する位置に描かれます。上のコードを実行すると次のような結果になります。
この他にラベルを表示する位置を指定するlocパラメーターなどもありますが、ここでは説明を省略します。気になる方はpyplot編の「見やすく伝わるグラフに仕上げよう」などを参照してください(以下、同様)。
グラフに凡例を追加するにはAxes.legendメソッドを、グリッド線を引くにはAxes.gridメソッドを使います。
| メソッド | 説明 | 対応するpyplotインタフェースの機能 |
|---|---|---|
| Axes.legendメソッド | 凡例を追加する | pyplot.legend関数 |
| Axes.gridメソッド | グリッドを表示する | pyplot.grid関数 |
| 凡例やグリッドを追加するメソッド | ||
legendメソッドに引数としてテキストを与えると、それが凡例のテキストとして使われます。legendメソッドに引数を渡さなかったときには、グラフを描画するメソッドの呼び出し時にlabelパラメーターに与えた引数の値が凡例として使われます。また、その他の指定方法もありますが、ここでは省略します。凡例を表示する位置はlocパラメーターに'best'/'upper right'/'lower left'などの値を与えます。
gridメソッドには次のようなパラメーターがあります(一部)。
以下に例を示します。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = {
'plot': lambda a, x, y, **kw: a.plot(x, y, **kw),
'bar': lambda a, x, y, **kw: a.bar(x, y, **kw)
}
plot_types = ['plot', 'bar']
colors = ['red', 'blue']
for a, plot_type, color in zip(ax, plot_types, colors):
plot_funcs[plot_type](a, x, y, color=color)
a.set_title(plot_type)
a.set_xlabel('x axis')
a.set_ylabel('y axis')
# 凡例の追加
a.legend([plot_type])
# グリッドの設定
a.minorticks_on()
a.grid(True, 'major', color='gray')
a.grid(True, 'minor', color='lightgray', linestyle='--')
fig.suptitle('title for Figure')
plt.show()
上の例では描画するグラフの種類を指定するのに使っていたplot_funcsの内容を凡例にも流用しています。また、Axes.minorticks_onメソッドを呼び出しているので、主目盛り線と副目盛り線の両方が表示されるようになります(ここでは主目盛り線と副目盛り線を分けて設定していますが、同一の線種でよければ'both'を指定することで1行で設定できます)。
上の例では凡例のテキストを直接指定していますが(リストとして渡している点に注意)、グラフ描画時にラベルを指定すると、引数なしでlegendメソッドを呼び出した場合にそのラベルが凡例のテキストとして使われます。
x = np.arange(5)
y = x ** 2
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plot_funcs = {
'plot': lambda a, x, y, **kw: a.plot(x, y, **kw),
'bar': lambda a, x, y, **kw: a.bar(x, y, **kw)
}
plot_types = ['plot', 'bar']
colors = ['red', 'blue']
for a, plot_type, color in zip(ax, plot_types, colors):
plot_funcs[plot_type](a, x, y, color=color, label=plot_type)
a.set_title(plot_type)
a.set_xlabel('x axis')
a.set_ylabel('y axis')
# 凡例の追加
a.legend()
# グリッドの設定
a.minorticks_on()
a.grid(True, 'major', color='gray')
a.grid(True, 'minor', color='lightgray', linestyle='--')
fig.suptitle('title for Figure')
plt.show()
1つのAxesに複数の系列のグラフを描画した場合の例も示しておきましょう。
x = np.arange(5)
y = x ** 2
y2 = x ** 3
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
line1, = ax[0].plot(x, y, color='red')
line2, = ax[0].plot(x, y2, color='green')
ax[0].set_title(‘plot’)
ax[0].set_xlabel('x axis')
ax[0].set_ylabel('y axis')
ax[0].legend([line1, line2], ['squared', 'cubed'])
ax[1].bar(x, y, color='blue', label='bar')
ax[1].set_title(‘bar’)
ax[1].set_xlabel('x axis')
ax[1].set_ylabel('y axis')
ax[1].legend()
fig.suptitle('title for Figure')
plt.show()
plotメソッドやbarメソッドは戻り値として、描画されたデータを表現するオブジェクトの配列を返送します。ここではそれらのリストの要素が1つだけなので、それをline1とline2に保存しておきます(「line1,」のようになっているのは、リストの要素を多重代入しているからです。これは「line1, *_ = ax[0].plot(……)」と同様です)。
そして、legendメソッドにはline1とline2を要素とするリストと、それぞれに対応した凡例用のテキストを含んだリストを渡しています。これにより、以下のような結果が得られます。
なお、このときにもグラフの描画時にlabelパラメーターにラベルを指定しておけば、legendメソッドに凡例のテキストを指定しなかったときには、そのラベルが自動的に使われます。
今回はpyplotインタフェースとOOインタフェースの構造の違いを幾つかのグラフを描画しながら検討し、その次にグラフの装飾について説明しました。次回はOOインタフェースを使って、より細かくグラフを管理したり、コードを関数として再利用したりしてみることにしましょう。
Copyright© Digital Advantage Corp. All Rights Reserved.