[Matplotlib超入門:pyplot編]見やすく伝わるグラフに仕上げよう(サイズ、ラベル、凡例、複数表示など):Pythonデータ処理入門
グラフのサイズを調整したり、タイトルや軸ラベル、凡例、グリッド線、注釈などを追加したり、一度に複数のグラフを表示したりする方法をマスターしよう!
本シリーズと本連載について
本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。
なお、【Matplotlib超入門:pyplot編】では以下のバージョンを使用しています。
- Python 3.13
- Matplotlib 3.10.3
見やすく伝わるグラフを作るとは
Matplotlibのpyplotインタフェースを使うとさまざまなグラフを描画できることは前回に見た通りです。そのグラフをさらに見やすく、さらに分かりやすいものにするための手段もMatplotlibには用意されています。例えば、以下のようなことです。
今回の内容
今回はこのようなことをする方法を紹介していきます。
なお、以下では基本的にサイン関数のグラフを描画するものとします。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def plot_sine_curve():
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.plot(x, y)
plot_sine_curve()
plt.show()
このコードをVisual Studio Codeで実行した結果を以下に示します(以下では全てVisual Studio Codeで実行した結果を示すことにします)。
これをベースとしていろいろな装飾を加えて、グラフを見やすいものにしていきましょう。
グラフのサイズを調整するには:pyplot.figure関数
グラフのサイズを調整するにはpyplot.figure関数を使用できます*1。
*1 正確には、pyplot.figure関数は新たにFigureオブジェクトを作成するか、既存のFigureオブジェクトをアクティブにするものです。このときに、figsizeパラメーターでそのサイズを指定できます。
以下にpyplot.figure関数の書式を示します(グラフのサイズに関する部分のみ抜粋)。
pyplot.figure関数
pyplot.figure(figsize=None)
作成またはアクティブにしたグラフのサイズを設定する。figsizeパラメーターには横幅と縦幅をインチ単位で指定する(2個の浮動小数点数値を要素とするタプル)。省略した場合は(6.4, 4.8)が指定されたものとされる。
実際にサイズを変更してみましょう。
plt.figure(figsize=(5.2, 5.2))
plot_sine_curve()
plt.show()
このコードをVisual Studio Codeで実行した結果を以下に示します(以下同じ)。
グラフにタイトルや凡例を付けるには
グラフにはタイトルや凡例、軸ラベルを含めることが可能です。これらの要素をグラフに含めれば、どのグラフがどの系列のものであるかを明確にしたり、各軸が何を表しているのか示したりして、グラフがより伝わりやすいものになるでしょう。
グラフタイトルを追加するには:pyplot.title関数
グラフにタイトルを付加するには、pyplot.title関数を使用します。以下に書式を示します。
pyplot.title関数
pyplot.title(label, loc=None, pad=None)
グラフのタイトルを設定する。主なパラメーターは以下の通り。
- label:グラフのタイトルとなるテキスト(日本語を指定できるが、環境によってはフォントがないために文字化けすることがある)
- loc:タイトルの表示位置を指定する。'left'(左寄せ)、'center'(中央)、'right'(右寄せ)を指定可能。省略時は'center'が表示されたものとして解釈される
- pad:グラフとタイトルの間のパディング(ポイント単位)。省略時には6.0が指定されたものとして解釈される
以下に例を示します。
plt.figure(figsize=(10, 5.2))
plt.subplot(1, 2, 1)
plot_sine_curve()
plt.title('sine curve #1', loc='center')
plt.subplot(1, 2, 2)
plot_sine_curve()
plt.title('sine curve #2', loc='right')
plt.show()
この例は、サイン関数のグラフをタイトル付きで2つ作成するものです。先頭では先ほど紹介したpyplot.figure関数で描画領域のサイズを横長に変更しています。その次のpyplot.subplot関数は後で紹介しますが、ここではグラフの描画領域を1行2列にして、最初のsubplot関数呼び出しではその1つ目の描画領域を、次のsubplot関数呼び出しではもう1つの描画領域を選択していると考えてください。
それぞれの領域を選択後、plot_sine_curve関数を呼び出してサイン関数のグラフをプロットし、続けてpyplot.title関数を呼び出して、グラフのタイトルを設定しています。locパラメーターには'center'と'right'を指定しているので、タイトルの位置が違っていることを確認してください。
これを実行すると次のようになります。
タイトルの位置が中央と右端に表示されている点に注目してください。
以下はpadパラメーターを指定する例です。
plot_sine_curve()
plt.title('sine curve', loc='right', pad=72)
plt.show()
このコードを実行すると次のようになります。
グラフのタイトルの間(パディング)が大きく広がったことが分かります。
なお、タイトルの表示位置を詳細に設定したいときには、xパラメーターとyパラメーターを使えます。詳しい説明は省略しますが、以下に例を示します。
plot_sine_curve()
plt.title('sine curve', x=1.0, y=-0.15)
plt.show()
xパラメーターはグラフ描画領域(Matplotlibでは「Axes」と呼ばれるオブジェクトとして表現されています)の左端を0.0、右端を1.0とする相対的な位置で指定します。yパラメーターは同様に、下端を0.0、上端を1.0とする相対的な位置で指定します。
上の例ではxパラメーターに1.0を、yパラメーターに-0.15を指定しているので、タイトルはグラフの右下辺りに表示されるはずです。このように負数を指定することも可能ですが、その場合は他の設定次第でタイトルがきちんと表示されない場合もあることには注意してください。
また、xパラメーターとyパラメーターの指定はlocパラメーターの指定よりも優先されます。多くの場合、locパラメーターを使えば十分でしょうが、より細やかな調整が必要なときにはこれら2つのパラメーターを使ってください。
軸ラベルを追加するには:pyplot.xlabel関数/pyplot.ylabel関数
グラフに軸ラベルを追加するには、pyplot.xlabel関数およびpyplot.ylabel関数を使います。
pyplot.xlabel関数/pyplot.ylabel関数
pyplot.xlabel(xlabel, labelpad=None, *, loc=None)
pyplot.ylabel(ylabel, labelpad=None, *, loc=None)
グラフのX軸/Y軸のラベルを設定する。主なパラメーターは以下の通り。
- xlabel:X軸のラベルのテキスト
- ylabel:Y軸のラベルのテキスト
- labelpad:各軸とラベルの間のパディング(ポイント単位)
- loc:ラベルの位置。pyplot.xlabel関数の場合は'left'、'center'、'right'を指定可能。pyplot.ylabel関数の場合は'bottom'、'center'、'top'を指定可能
以下に例を示します。
plt.figure(figsize=(10, 5.2))
plt.subplot(1, 2, 1)
plot_sine_curve()
plt.title('sine curve #1')
plt.xlabel('x (radian)', loc='right')
plt.ylabel('sin(x)', loc='top')
plt.subplot(1, 2, 2)
plot_sine_curve()
plt.title('sine curve #2')
plt.xlabel('x (radian)', loc='left')
plt.ylabel('sin(x)', loc='bottom')
plt.tight_layout()
plt.show()
先ほどの例と同様に、ここではグラフを横に2つ並べています。それぞれの描画領域にはサイン関数のグラフを描いて、そこにX軸およびY軸のラベルをpyplot.xlabel関数とpyplot.ylabel関数を使って付加しています。locパラメーターに与えた値によって、ラベルの位置が変わっていることに注目してください。また、最後から2行目にあるpyplot.tight_layout関数は複数の描画領域間のパディングを調整するものです(これがないと、右側にあるグラフのY軸のラベルが、左側にあるグラフに重なって表示されてしまいます)。
実行結果は次の通りです。
labelpadパラメーターはそれぞれの軸にある目盛り(および目盛りの値)と軸ラベルとの距離を指定します。以下に例を示します。
plot_sine_curve()
plt.title('sine curve #1')
plt.xlabel('x (radian)', labelpad=20)
plt.ylabel('sin(x)', labelpad=20)
plt.show()
実行結果を以下に示します。軸ラベルが目盛りから離れた位置に表示されるようになったことを確認してください。
目盛りを設定するには:pyplot.xticks関数/pyplot.yticks関数
目盛りを設定するには、pyplot.xticks関数とpyplot.yticks関数を使用します。なお、これらの関数は引数なしで呼び出すと現在の設定を返します。
pyplot.xticks関数/pyplot.yticks関数
pyplot.xticks(ticks=None, labels=None)
pyplot.yticks(ticks=None, labels=None)
X軸またはY軸の目盛りを設定/取得する。主なパラメーターは以下の通り。
- ticks:目盛りを置く位置を配列や反復可能オブジェクトとして指定。空のリストを指定すると、目盛りが表示されなくなる
- labels:目盛りを表示した部分に表示するテキストを配列や反復可能オブジェクトとして指定する。ticksパラメーターを指定した場合にのみ、指定可能
以下に簡単な例を示します。
plt.figure(figsize=(10, 5.2))
plt.subplot(1, 2, 1)
plot_sine_curve()
plt.title('sine curve #1')
plt.xticks(ticks=range(0, 11, 2))
plt.yticks(ticks=(-1.0, -0.5, 0, 0.5, 1.0))
plt.subplot(1, 2, 2)
plot_sine_curve()
plt.title('sine curve #2')
plt.xticks(ticks=range(11))
ticks=(-1.0, -0.5, 0.0, 0.5, 1.0)
labels=('-max', '-0.5', 'zero', '0.5', 'max')
plt.yticks(ticks=ticks, labels=labels)
plt.tight_layout()
plt.show()
ここでも2つのグラフを作成しています。そして、pyplot.xticks関数とpyplot.yticks関数を使って目盛りの設定を行っています。1つ目の例ではX軸の目盛りにはrange(0, 11, 2)を指定しているので「0, 2, 4, 6, 8」と、Y軸の目盛りには(-1.0, -0.5, 0, 0.5, 1.0)と指定しているのでそれらがそのまま表示されるはずです。2つ目の例では(コードの幅の都合で)Y軸については変数ticksについては1つ目の例と同じ値を、変数labelsには('-max', '-0.5', 'zero', '0.5', 'max')を代入し、それらを使って目盛りを設定しています。X軸についてはrange(11)を指定しているので、今度は0から10までの整数値が表示されるでしょう。
実行結果は以下の通りです。
2つのグラフでのX軸の目盛りの取り方が違うこと、Y軸の目盛りのラベルが2つのグラフで違っていることに注目してください。
凡例を表示するには:pyplot.legend関数
凡例はpyplot.legend関数で表示します。
pyplot.legend関数
pyplot.legend()
pyplot.legend(handles, labels)
pyplot.legend(handles=handles)
pyplot.legend(labels)
凡例をグラフに表示するには幾つかの方法がある。
- グラフを描画する際にlabelパラメーターに凡例として表示するラベルを含めて、引数なしでpyplot.legend関数を呼び出す
- グラフを描画する関数の戻り値を変数に保存しておき(これをハンドルと呼ぶ)、ハンドルとそれに対応するラベルを引数に指定してpyplot.legend関数を呼び出す
- グラフを描画する際にlabelパラメーターに凡例として表示するラベルを含めて呼び出して、その戻り値(ハンドル)を変数に保存しておき、「handles=(ハンドル, ……)」のような形でこれを指定した上でpyplot.legend関数を呼び出す
- pyplot.legend関数を呼び出して凡例のテキストをlabelsパラメーターに引き渡す
他の主要なパラメーターとしてはlocがある。これには'upper left'(左上)、'upper center'(中央上)、'upper right'(右上)、'lower left'(左下)、'lower center'(中央下)、'lower right'(右下)、'center left'(中段左)、'center right'(中断→)、'center'(中央)、'best'(Matplotlibが判断)を指定可能。
ここではサイン関数に加えてコサイン関数を1つのグラフに描画して、その凡例をグラフに付加してみましょう。サイン関数とコサイン関数を描画する2つの関数を次のように定義しておきます。
def plot_sine_curve():
x = np.linspace(0, 10, 100)
y = np.sin(x)
return plt.plot(x, y, label='sin(x)')
def plot_cosine_curve():
x = np.linspace(0, 10, 100)
y = np.cos(x)
return plt.plot(x, y, label='cos(x)')
上で述べたように、引数なしでpyplot.legend関数を呼び出すには凡例として表示するラベルをlabelパラメーターに指定する必要があります。また、ハンドルとしてグラフを描画する関数の戻り値を保存しておく必要もあります。
以下は引数なしで凡例を表示する例です。
plot_sine_curve()
plot_cosine_curve()
plt.legend()
plt.show()
次に、グラフの描画に使用した関数の戻り値(ハンドル)を変数に保存しておき、そのハンドルと凡例のテキストを指定して、pyplot.legend関数を呼び出す例です。
line0, = plot_sine_curve()
line1, = plot_cosine_curve()
labels = ['sine', 'cosine']
plt.legend((line0, line1), labels)
plt.show()
ここでは、ラベルをpyplot.plot関数のlabelパラメーターに渡しているテキストとは別のものにしています。また、pyplot.plot関数はハンドルを要素とするリストを返送します。そのため、「line0 = plt.plot(……)」のようにするとline0にはリストが代入されます。しかし、pyplot.legend関数が受け取るのはリストを要素とする反復可能オブジェクトではなく、ハンドルを要素とする反復可能オブジェクトです。そのため、ここでは「line0, = plt.plot(……)」のように戻り値であるリストをアンパックして、ハンドルを変数line0(やline1)に代入している点にも注意してください。
グラフ描画で返されたハンドルだけをhandlesパラメーターに指定して、pyplot.legend関数を呼び出す例を以下に示します。
line0, = plot_sine_curve()
line1, = plot_cosine_curve()
plt.legend(handles=(line0, line1))
plt.show()
最後に、凡例のラベルだけを指定して、pyplot.legend関数を呼び出す例です。
plot_sine_curve()
plot_cosine_curve()
plt.legend(['sine', 'cosine'])
plt.show()
以上をまとめると次のようになります。ただし、locパラメーターも含めている点には注意してください。
plt.figure(figsize=(12, 10))
plt.subplot(2, 2, 1)
plot_sine_curve()
plot_cosine_curve()
plt.legend()
plt.subplot(2, 2, 2)
line0, = plot_sine_curve()
line1, = plot_cosine_curve()
labels = ['sine', 'cosine']
plt.legend((line0, line1), labels, loc='upper right')
plt.subplot(2, 2, 3)
line0, = plot_sine_curve()
line1, = plot_cosine_curve()
plt.legend(handles=(line0, line1), loc='lower right')
plt.subplot(2, 2, 4)
plot_sine_curve()
plot_cosine_curve()
plt.legend(['sine', 'cosine'], loc='upper center')
plt.show()
実行すると、次のような結果が得られます。凡例に表示されるテキストと、凡例が表示される場所に注目してください。
なお、グラフ描画時にlabelパラメーターを指定しなかった場合でも、そのハンドルを保存していれば、次のようにset_labelメソッドで後からラベルを設定可能です。
line0, = plot_sine_curve()
line0.set_label('sine curve')
グラフにグリッドや注釈を追加するには
タイトルや軸ラベル、目盛り、凡例だけではなく、グリッド線を表示したり、任意の位置にテキストを表示したり、特定の箇所を指す注釈も加えられます。これらを使うことで、グラフの微少な値に注目しやすくなったり、特定の箇所に注目してもらったりということができるようになります。
グリッド線を表示して見やすくするには:pyplot.grid関数
グラフにグリッド線を表示するには、pyplot.grid関数を使います。
pyplot.grid関数
pyplot.grid(visible=None, which='major', axis='both')
グリッド線を構成する。主なパラメーターは以下の通り。
- visible:Trueなら表示、Falseなら非表示。引数なしでpyplot.grid関数を呼び出した場合、グリッド線の表示/非表示がトグルする
- which:表示/非表示を切り替える対象を'major'(主目盛り。切りのよい値で表示される太い目盛り)、'minor'(副目盛り。主目盛り間に表示される細い目盛り)、'both'(両方)のいずれかで指定する。'minor'なグリッド線を表示するにはpyplot.minorticks_on関数で副目盛りを表示するようにしておくこと
- axis:X軸/Y軸のどちらの線の表示/非表示を切り替えるかを'x'、'y'、'both'で指定する
以下に例を示します。
plot_sine_curve()
plt.minorticks_on()
plt.grid(which='minor', color='#cccccc')
plt.grid(which='major', color='#666666')
plt.show()
この例ではpyplot.minorticks_on関数で副目盛りを表示するようにしてから、pyplot.grid関数を二度呼び出しています。最初のpyplot.grid関数呼び出しでは、whichパラメーターに'minor'を指定して、副目盛りを明るいグレーで描くようにしています。次の呼び出しではwhichパラメーターに'major'を指定して、主目盛りを暗めのグレーで描くようにしています。
これによりグラフには以下のようなグリッド線が引かれます。
また、次のようにして主目盛り/副目盛りに対応するグリッド線の色に加えて、線種や太さなどを変更することも可能です(実行結果は省略)。
plot_sine_curve()
plt.minorticks_on()
plt.grid(which='minor', color='#cccccc', linestyle=':', linewidth=0.5)
plt.grid(which='major', color='#666666', linestyle='-', linewidth=1.0)
plt.show()
副目盛り(や対応するグリッド線)を非表示にするにはpyplot.minorticks_off関数を呼び出してください。
グラフ上にテキストを表示するには:pyplot.text関数
グラフ上の指定した位置にテキストを表示するにはpyplot.text関数を使います。
pyplot.text関数
pyplot.text(x, y, s)
xとyで指定された箇所に文字列sを表示する。主要なパラメーターは以下の通り。
- x:テキストを描画するX軸の座標
- y:テキストを描画するY軸の座標
- s:描画するテキスト
xとyはグラフ中のX軸とY軸の値に対応することに注意してください。以下に例を示します。
plot_sine_curve()
plt.text(0.2, -0.8, 'sample text', fontsize=15, color='red')
plt.show()
この例では、xに0.2、yに-0.8を指定して、'sample text'というテキストを表示しようとしています。また、フォントサイズや文字色を指定している点にも注目してください。実行結果は次の通りです。
指定された(x, y)座標からテキストが描画されていることが分かります。
注釈(矢印付き)を追加するには:pyplot.annotate関数
グラフ中の特定の箇所に対して注釈を含めたいときには、pyplot.annotate関数を使用します。
pyplot.annotate関数
pyplot.annotate(text, xy, xytext=None, arrowprops=None)
xyで示される座標にxytextで示される座標からarrowpropsで示される属性を持つ矢印を引き、xytextで示される位置にtextを表示する。主なパラメーターは次の通り。
- text:注釈の内容
- xy:矢印が指す位置をタプルで指定
- xytext:注釈を表示する座標
- arrowprops:矢印の属性を辞書形式で指定
簡単な例を以下に示します。
plot_sine_curve()
rad = 90 * np.pi / 180 # sin(90 degree) = 1.0なのでdegreesをradianに変換
plt.annotate('max == 1.0', xy=(rad, 1.0), xytext=(rad + 0.5, 1.2), color='red',
arrowprops=dict(facecolor='red', edgecolor='red'))
plt.show()
sin(90度)は1であり、サイン関数の最大値となります。その一方で、通常、numpy.sin関数にはラジアン単位の数値を渡すので、このグラフのX軸はラジアンで示されています。そこで、90度をラジアン単位に変換しています(およそ、1.57)。xyパラメーターにはこの値と最大値である1を要素とするタプルを指定し、xytext(注釈の表示位置)にはそこから少し離れた座標を指定しています。colorパラメーターは注釈の文字色です('red')。また、arrowpropsには矢印の属性として塗りつぶし色と境界線色に'red'を指定しています。
これを実行すると次のようになります。
複数のデータを分かりやすく描画するには
今回の最後に、既に見てきていますが、1つの描画領域に複数のグラフをプロットする方法と、1つの描画領域を複数の領域に分け、それらに別個にグラフをプロットする方法をまとめておきましょう。
複数のグラフを描くには:グラフを描画する関数を複数回呼び出す
グラフを描画する関数を複数回呼び出してからpyplot.show関数を呼び出すと、1つの描画領域に複数のグラフをまとめてプロットできます。これは先ほど、サイン関数とコサイン関数のグラフをプロットしたときに既に見た通りです。
ここでは、正規分布している2つのデータ群を1つの描画領域に描いてみましょう。それぞれのデータ群は平均、分散が異なっているものとします。
np.random.seed(2)
# 平均(2.2, 2.2)、標準偏差1のデータ群
x0 = np.random.normal(loc=2.2, scale=1.0, size=100)
y0 = np.random.normal(loc=2.2, scale=1.0, size=100)
# 平均(6.5, 4.8)、標準偏差0.5のデータ群
x1 = np.random.normal(loc=6.5, scale=0.5, size=100)
y1 = np.random.normal(loc=4.8, scale=0.5, size=100)
plt.scatter(x0, y0, label='Group A', color='red', marker='*')
plt.scatter(x1, y1, label='Group B', color='blue', marker='+')
plt.title('normal disributions')
plt.legend()
plt.grid()
plt.show()
ここでは平均が(2.2, 2.2)で標準偏差が1のデータ群と、平均が(6.5, 4.8)で標準偏差が0.5のデータ群を作成してそれをプロットしています。実行結果は次の通りです。
このように複数のデータ系列を1つの散布図にまとめて表示すると、その特徴(平均や標準偏差など)が可視化できます。
複数のグラフを並べて比較するには:pyplot.subplot関数
一方、1つのデータ系列を異なる手法で可視化することを考えてみましょう。この場合には、pyplot.subplot関数で複数の描画領域を作成して、そのそれぞれに別の種類のグラフを描くのがよいでしょう。
pyplot.subplot関数
pyplot.subplot(nrows, ncols, index)
現在のFigureオブジェクト(グラフを描画するための土台)を、nrowsで指定された行数、ncolsで指定された列数のグリッドに分割し、indexで指定される領域に新たにAxesオブジェクト(実際のグラフ描画領域)を追加して、それを選択し、戻り値とする。パラメーターの意味は次の通り。
- nrows:現在のFigureオブジェクトを何行に分割するかを表す
- ncols:現在のFigureオブジェクトを何列に分割するかを表す
- index:分割した領域のどこを選択するかを示すインデックス。インデックスは1から始まり。左上が1、その右隣が2、……のように左から右にインデックスが増え、ある行の領域が終わると、次の行に進み、最終的に右下の領域に辿り着く
また、nrows/ncols/indexを結合して、3桁の整数値として指定することも可能。例えば、pyplot.subplot(2, 3, 1)はpyplot.subplot(231)のように書いてもよい。
FigureオブジェクトやAxesオブジェクトについては本連載ではまだ深くは扱いません。今のところはFigureが土台であり、その上に1個以上のAxesが割り付けられ、そこにグラフが描画されると考えてください。
ここでは1行2列のグラフ描画領域を作成して、1つには折れ線グラフを、もう1つにはヒストグラムを描画してみましょう。
np.random.seed(8)
data = np.random.normal(50, 20, 100)
plt.figure(figsize=(10, 5.2))
plt.subplot(1, 2, 1)
plt.plot(data)
plt.grid()
plt.subplot(1, 2, 2)
plt.hist(data, bins=range(0, 110, 10))
plt.grid()
plt.show()
ここでは正規分布するランダムなデータを生成しています。そのため、個々のデータを線でつなぐ意味は(ホントは)あまりありませんし、実際に折れ線グラフを見ても、これが正規分布していることは分かりません。しかし、ヒストグラムを見ると、これが正規分布していることが容易に推測できるでしょう。
このように、あるデータ系列をさまざまな視点から視覚化するときには、pyplot.subplot関数で複数のグラフを1つにまとめて表示するのがオススメです。ノートブックの個別のセルで複数のグラフを何度も描画していると、「さっきのグラフはどこいった?」なんてことにもなるかもしれません。pyplot.subplot関数の使い方をマスターすれば、コンパクトに幾つものグラフを描画できるようになるはずです。
今回もちょっと長くなりました。次回はpandasとMatplotlibの連携などについて取り上げる予定です。余裕があれば、これまで紹介できていないグラフについても紹介したいと思っています。
Copyright© Digital Advantage Corp. All Rights Reserved.