こんな人にオススメ
matplotlib.pyplot
(plt
)を使ってグラフとグラフの間に色をつけたいんだけど、どうしたらいいの?それに条件に当てはまるところだけとかってできるの?
ということで、今回はmatplotlib.pyplot
で特定の領域を塗りつぶす方法について紹介する。該当範囲を見やすくする時に使える方法だ。具体的にはプロット2本の間を塗りつぶすというもの。
python環境は以下。
- Python 3.9.2
- matplotlib 3.3.4
- numpy 1.20.1
曲線データとfill_between
まず初めに本記事で使用するデータとfill_between
の基本的な使い方について紹介する。
下準備
まずはimport
関連。plt
のテンプレートに関しては以下の記事参照。
-
-
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート
続きを見る
import sys import numpy as np import matplotlib.pyplot as plt sys.path.append('../../') import plt_layout_template as template
使用するデータ
今回使用するデータは適当に作成した30データの配列。
x = np.arange(30) y1 = np.array([ 1, 2, 4, 6, 8, 8, 7, 3, 3, 1, 1, 3, 3, 2, 4, 2, 3, 4, 5, 4, 3, 4, 6, 7, 8, 9, 8, 7, 8, 9 ]) y2 = np.array([ 4, 6, 2, 3, 4, 5, 7, 8, 3, 1, 7, 8, 7, 7, 6, 7, 8, 7, 4, 3, 7, 8, 9, 8, 3, 8, 9, 9, 8, 7 ])
このデータをグラフ化したものが上の画像だ。グラフ作成用のコードは以下。
def lines_only(): """y1, y2をグラフ化 """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('only lines') ax.plot(x, y1, 'o-', color='blue', label='y1') ax.plot(x, y2, '^-', color='green', label='y2') ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('lines_only.png') lines_only()
プロット間を塗りつぶし
次は基本的な塗りつぶしだ。使用する関数はplt.fill_between
。2本のプロットの間を塗りつぶしてくれる。
def fill_only(): """y1, y2の間の塗りつぶしのみ行う """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('only fill') ax.fill_between(x, y1, y2, facecolor='red', alpha=0.5, label='fill') ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_only.png')
ただfill_between
するだけだと塗りつぶしだけになり非常に見にくい。そこで、元のy1
, y2
のグラフも追加すると以下のようになる。
これだと元のグラフもあり、塗りつぶした領域がわかりやすい。このグラフの作成コードは以下。
def fill_lines(): """y1, y2のグラフとその間の塗りつぶし """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('fill + lines') ax.plot(x, y1, 'o--', color='blue', label='y1') ax.plot(x, y2, '^--', color='green', label='y2') ax.fill_between(x, y1, y2, facecolor='red', alpha=0.5, label='fill') ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_lines.png')
これでfill_between
の基本的な書き方は終わり。あとはカスタム内容になるので、その時々で使用していただければと思う。
y
方向の条件を追加
ここではy
に関する条件を追加することで、その条件に合った部分のみを塗りつぶすようなグラフを作成する。
「より大きい(超過)」の条件を追加
y1 > y2
という条件に当てはまる領域のみを塗りつぶすとグラフは上のようになる。コードは以下。
def where_y(): """y1 > y2の条件に合う領域のみを塗りつぶし """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('y1 > y2') ax.plot(x, y1, 'o-', color='blue', label='y1') ax.plot(x, y2, '^-', color='green', label='y2') # whereで条件指定 ax.fill_between( x, y1, y2, where=y1 > y2, facecolor='yellow', alpha=0.5, label='fill' ) ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_where_y.png')
今回は3領域が条件に当てはまるので黄色く塗りつぶされている。
「以上」の条件を追加
上の例では「y1 > y2
」ということで、同じ値の時は塗りつぶさないようにした。ここでは「y1 >= y2
」とすることで同じ値でも塗りつぶすようにした。コードは以下。
def where_y_above(): """y1 ≥ y2の条件に合う領域を塗りつぶし """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('y1 ≥ y2') ax.plot(x, y1, 'o-', color='blue', label='y1') ax.plot(x, y2, '^-', color='green', label='y2') # ここの条件を「>=」に変更 ax.fill_between( x, y1, y2, where=y1 >= y2, facecolor='yellow', alpha=0.5, label='fill' ) ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_where_y_above.png')
x=6
の部分でy1
とy2
が同じ値をとっていたのでそこに塗りつぶしが追加された。
さらにx=28, 29
でも塗りつぶしが追加されていることに気が付いただろうか。>
の状態だとx=29
のみ該当し、x=28
は該当しない。なのでx=29
に塗りつぶしがありそうだが、1点ずつしか存在しないために塗りつぶせない。したがって、where_y
関数ではx=29
の塗りつぶしがなかった。
しかし、今回のwhere_y_above
関数だと不等号が>=
、すなわち≥
であるのでx=28
も該当し、領域として塗りつぶす事ができるようになった。したがって今回は塗りつぶし領域が4ヶ所になった。
x方向の条件を追加
先ほどはy
についての条件だったが、ここではx
についての条件も試してみる。今回は9 < x ≤ 20
の範囲のみを塗りつぶすように設定した。「<
」「<=
」の違いについてはy
の時と同様。
なお、破線の垂線はx
の条件範囲の端を表している。
def where_x(): """9 < x ≥ 20の条件似合う領域を塗りつぶし """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('9 < x ≤ 20') ax.plot(x, y1, 'o-', color='blue', label='y1') ax.plot(x, y2, '^-', color='green', label='y2') # whereで条件指定 ax.fill_between( x, y1, y2, where=(9 < x) & (x <= 20), facecolor='yellow', alpha=0.5, label='fill' ) ax.axvline(9, ls='--', color='black') ax.axvline(20, ls='--', color='black') ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_where_x.png')
複数条件を指定する場合は(9 < x) & (x <= 20)
のようにかっこと&
記号でつなぐ必要がある。もし(9 < x <= 20)
のように一続きで書いた場合はエラーとなる。
where=(9 < x <= 20), ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
x
, y
両方に条件を追加
これまでにx
, y
の両方にそれぞれ条件を与えてそれぞれの条件にあったグラフを作成した。ここではx
, y
の両方に同時に条件を与えて、同時に満たす領域のみ塗りつぶすようなグラフを作成する。
ここではx
, y
それぞれでの条件を使用する。すなわち「(9 < x <= 20
かつ(y1 > y2)
」。
def where_xy(): """9 < x <= 20 かつ y1 > y2の条件に合った領域を塗りつぶし """ fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('(9 < x ≤ 20) & (y1 > y2)') ax.plot(x, y1, 'o-', color='blue', label='y1') ax.plot(x, y2, '^-', color='green', label='y2') # whereで条件指定 ax.fill_between( x, y1, y2, where=(9 < x) & (x <= 20) & (y1 > y2), facecolor='purple', alpha=0.5, label='fill' ) ax.axvline(9, ls='--', color='black') ax.axvline(20, ls='--', color='black') ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_where_xy.png')
作成されるグラフは上に示した通りで塗りつぶし色はpurple
にした。
領域を四角で塗りつぶし
最後に領域を四角で塗りつぶしという事で、あるx
の条件に合ったy
全部を塗りつぶしたい時にはどうするかについて解説する。他にも方法があるかもしれないがここでは以下の2つについて解説する。
fill_between
を使用axvspan
を使用
fill_between
を使用
まずはここまでに行ったfill_between
を使用した方法。fill_between
を使用する際にはx
とy
が2つ必要なのでそれぞれhorizonx
, horizon1
, horizon2
で指定した。
四角領域を作成する先のグラフは先ほど使用したx
, y1
, y2
。y1
, y2
の最大値・最小値はどちらもそれぞれ1
, 9
なのでhorizon1
, horizon2
は0
と10
と設定した。
def fill_region(): """y1, y2のグラフに四角領域を追加 """ horizonx = [10, 15] horizon1 = np.array([0] * 2) horizon2 = np.array([10] * 2) fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('fill + lines + region') ax.plot(x, y1, 'o--', color='blue', label='y1') ax.plot(x, y2, '^--', color='green', label='y2') ax.fill_between(x, y1, y2, facecolor='red', alpha=0.5, label='fill') # 領域fill ax.fill_between( horizonx, horizon1, horizon2, facecolor='gray', alpha=0.5, label='here', zorder=0 ) ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') ax.legend(bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig('fill_region.png')
zorder
はプロットする順番を指定する引数で、0
とする事で初めにプロットして一番奥に配置されるように設定した。また、alpha
が一ではないので投下して赤と灰色が混ざった色合いになっている。
fill_between
を使用して四角領域を設定する場合は、horizon1
, horizon2
を別で用意する必要がある。
axvspan
を使用
axvspan
はx
を指定するだけで四角領域を作成する事ができる関数。以下の例では予めhorizonx
をlist
で作成しておいて、axvspan
でx
の範囲を指定する際に*
で展開している。
また、引数のorder
は塗りつぶし領域の凡例の順番を変更するか否かを指定するもので、上の画像を見てもらえばわかるように塗りつぶし領域である「here
」は「fill
」の前に来ている。fill_between
の時は最後に来ていたので、その順番に合わせるための引数がorder
だ。
def fill_axvspan(order=False): """axvspanを使ってy1, y2のグラフに四角領域を追加 Parameters ---------- order : bool, optional axvspanで作成した塗りつぶしを凡例の最後に持っていくか否か, by default False """ horizonx = [10, 15] fig, ax = plt.subplots(1, 1) ax.set_xlim(-1, 30) ax.set_ylim(0, 10) ax.set_title('fill + lines + axvspan') ax.plot(x, y1, 'o--', color='blue', label='y1') ax.plot(x, y2, '^--', color='green', label='y2') ax.fill_between(x, y1, y2, facecolor='red', alpha=0.5, label='fill') # 領域fill ax.axvspan( *horizonx, facecolor='gray', alpha=0.5, label='here', zorder=0 ) ax.axvline(max(x), ls=':') ax.axvline(min(x), ls=':') h1, l1 = ax.get_legend_handles_labels() if order: h1[2], h1[3] = h1[3], h1[2] l1[2], l1[3] = l1[3], l1[2] ax.legend( handles=h1, labels=l1, bbox_to_anchor=(1, 1), loc='upper left', borderaxespad=0 ) ax.grid(which='major', ls='-') ax.grid(which='minor', ls='--', alpha=0.5) fig.show() fig.savefig(f"fill_axvspan_{order}.png")
order=True
の時は以下のようなグラフとなる。
fill_between
とaxvspan
fill_between
とaxvspan
について、ここまでのそれぞれのメリット、デメリットをまとめてみた。
関数名 | メリット | デメリット |
fill_between | 他の塗りつぶしと同じ関数でコードを書くことができる | 領域の上限値と下限値を設定する必要がある
プロットの上を見たときに領域の端が見えてしまう |
axvspan | サクッと四角領域を作成することができる | 凡例の順番をいじりたくなるかも |
fill_between
のデメリットの二つ目は以下の画像のようなこと。途中で途切れてしまうので不恰好。
axvspan
の場合はグラフのプロット領域の一番下から一番上までを固定で塗りつぶしてくれるので、プロット上を移動させても途切れる事がない。
便利で危険
今回はmatplotlib.pyplot
で領域の塗りつぶしを行った。塗りつぶしができることでグラフを視覚的に見やすくすることができる。が、多用しすぎると本当に大切なことがぼやけてしまうので、ここぞという時のみ使用するのがいいだろう。
関連記事
-
-
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート
続きを見る