カテゴリー

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のグラフ作成テンプレート

続きを見る

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のグラフ作成テンプレート

続きを見る

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

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

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

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

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

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

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

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

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

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

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

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-plt
-, , ,