カテゴリー

plt

【plt&fill_between】matplotlibで領域を塗りつぶし

2021年5月3日

(9 < x y2)の条件に合った塗りつぶし

こんな人にオススメ


matplotlib.pyplotplt)を使ってグラフとグラフの間に色をつけたいんだけど、どうしたらいいの?それに条件に当てはまるところだけとかってできるの?

ということで、今回はmatplotlib.pyplotで特定の領域を塗りつぶす方法について紹介する。該当範囲を見やすくする時に使える方法だ。具体的にはプロット2本の間を塗りつぶすというもの。

python環境は以下。

  • Python 3.9.2
  • matplotlib 3.3.4
  • numpy 1.20.1

運営者のメガネです。YouTubeTwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

曲線データとfill_between

まず初めに本記事で使用するデータとfill_betweenの基本的な使い方について紹介する。

下準備

まずはimport関連。pltのテンプレートに関しては以下の記事参照。

plt.rcParamsでデフォルトを変更した後のグラフ
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート

こんな人にオススメmatplotlib.pyplot(plt ...

続きを見る

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()

プロット間を塗りつぶし

fill_betweenのみのグラフ

次は基本的な塗りつぶしだ。使用する関数は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のグラフも追加すると以下のようになる。

fill_betweenとデータプロットを追加したグラフ

これだと元のグラフもあり、塗りつぶした領域がわかりやすい。このグラフの作成コードは以下。

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の領域を塗りつぶし

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」ということで、同じ値の時は塗りつぶさないようにした。ここでは「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の部分でy1y2が同じ値をとっていたのでそこに塗りつぶしが追加された。

さらにx=28, 29でも塗りつぶしが追加されていることに気が付いただろうか。>の状態だとx=29のみ該当し、x=28は該当しない。なのでx=29に塗りつぶしがありそうだが、1点ずつしか存在しないために塗りつぶせない。したがって、where_y関数ではx=29の塗りつぶしがなかった。

しかし、今回のwhere_y_above関数だと不等号が>=、すなわちであるのでx=28も該当し、領域として塗りつぶす事ができるようになった。したがって今回は塗りつぶし領域が4ヶ所になった。

x方向の条件を追加

9 < x ≤ 20に合った領域の塗りつぶし

先ほどは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両方に条件を追加

(9 < x <= 20)かつ(y1 > y2)の条件に合った塗りつぶし

これまでに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つについて解説する。

  1. fill_betweenを使用
  2. axvspanを使用

fill_betweenを使用

fill_betweenで四角領域

まずはここまでに行ったfill_betweenを使用した方法。fill_betweenを使用する際にはxyが2つ必要なのでそれぞれhorizonx, horizon1, horizon2で指定した。

四角領域を作成する先のグラフは先ほど使用したx, y1, y2y1, y2の最大値・最小値はどちらもそれぞれ1, 9なのでhorizon1, horizon2010と設定した。

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で四角領域

axvspanxを指定するだけで四角領域を作成する事ができる関数。以下の例では予めhorizonxlistで作成しておいて、axvspanxの範囲を指定する際に*で展開している。

また、引数の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の時は以下のようなグラフとなる。

axvspanで四角領域

fill_betweenaxvspan

fill_betweenaxvspanについて、ここまでのそれぞれのメリット、デメリットをまとめてみた。

関数名 メリット デメリット
fill_between 他の塗りつぶしと同じ関数でコードを書くことができる 領域の上限値と下限値を設定する必要がある

プロットの上を見たときに領域の端が見えてしまう

axvspan サクッと四角領域を作成することができる 凡例の順番をいじりたくなるかも

fill_betweenのデメリットの二つ目は以下の画像のようなこと。途中で途切れてしまうので不恰好。

fill_betweenでの四角領域は途切れる

axvspanの場合はグラフのプロット領域の一番下から一番上までを固定で塗りつぶしてくれるので、プロット上を移動させても途切れる事がない。

axvspanでの四角領域は途切れない

便利で危険

今回はmatplotlib.pyplotで領域の塗りつぶしを行った。塗りつぶしができることでグラフを視覚的に見やすくすることができる。が、多用しすぎると本当に大切なことがぼやけてしまうので、ここぞという時のみ使用するのがいいだろう。

関連記事

plt.rcParamsでデフォルトを変更した後のグラフ
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート

こんな人にオススメmatplotlib.pyplot(plt ...

続きを見る

スイッチボット

2022/9/11

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/10/25

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

完全ワイヤレスイヤホン(TWS)

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

完全ワイヤレスイヤホン(TWS)

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-plt
-, , ,