HOWTO · Matplotlib

Matplotlib で水平線と垂直線をプロットする方法

このチュートリアルでは、axhline と axvline、hlines と vlines、および axhspan と axvspan の塗りつぶし関数を使って Matplotlib で水平線と垂直線を描く方法を紹介します。

このページの内容

Matplotlib で水平線や垂直線を引くには、線の長さを Axes の割合で指定する場合は axhline と axvline を、両端をデータ値で固定する場合は hlines と vlines を使います。単一の値ではなく帯全体を強調したい場合は、axhspan と axvspan が2つの位置の間の領域を塗りつぶします。各例は図を一時的な PNG ファイルに保存し、作成したオブジェクトの座標を出力するので、Matplotlib が線や帯をどこに配置したかを正確に確認できます。

Matplotlib で水平線と垂直線をプロットするための axhline と axvline

axhline で水平線をプロットする

axhline(y=...) は、データ座標の y の位置に水平線を引きます。xmin と xmax は、Axes の幅に対する割合で左右の端を指定する引数で、通常は 0 から 1 の値を使います。これらは x のデータ値ではありません。たとえば xmin=0.1、xmax=0.9 とすると、両端に Axes 幅の 10% ずつの余白ができます。

import tempfile
from pathlib import Path

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
line = ax.axhline(y=2, xmin=0.1, xmax=0.9, color="tab:red")
ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="x", ylabel="y")
ax.grid(True)
print("x extent (axes fraction):", line.get_xdata())
print("y position (data):", line.get_ydata())
with tempfile.TemporaryDirectory() as directory:
    fig.savefig(Path(directory) / "plot.png")
plt.close(fig)

出力:

x extent (axes fraction): [0.1, 0.9]
y position (data): [2, 2]

出力された値がこの分担を裏付けています。位置はデータ値 2 で、範囲は Axes の割合 0.1 と 0.9 として保存されます。

Axes 幅の 10% から 90% まで伸びる、y = 2 の赤い水平線を示す Matplotlib プロット

線はデータ値 y=2 の位置に留まります。x の範囲を変更しても、両端は Axes 幅の 10% から 90% を占め続けるため、線分は表示範囲に合わせて調整されます。

axvline で垂直線をプロットする

axvline(x=...) は、データ座標の x の位置に垂直線を引きます。ymin と ymax は、Axes の高さに対する割合で下端と上端を指定する引数で、通常は 0 から 1 の値を使います。たとえば ymin=0.1、ymax=0.9 とすると、両端に Axes の高さの 10% ずつの余白ができます。

import tempfile
from pathlib import Path

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
line = ax.axvline(x=2, ymin=0.1, ymax=0.9, color="tab:green")
ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="x", ylabel="y")
ax.grid(True)
print("x position (data):", line.get_xdata())
print("y extent (axes fraction):", line.get_ydata())
with tempfile.TemporaryDirectory() as directory:
    fig.savefig(Path(directory) / "plot.png")
plt.close(fig)

出力:

x position (data): [2, 2]
y extent (axes fraction): [0.1, 0.9]

こちらでは保存される値が対称になります。位置はデータ値 2 で、範囲は Axes の割合のペアです。

Axes の高さの 10% から 90% まで伸びる、x = 2 の緑の垂直線を示す Matplotlib プロット

線はデータ値 x=2 の位置に留まります。y の範囲を変更しても、両端は Axes の高さの 10% から 90% を占め続けます。このように、各 ax*line 関数はデータ座標の位置と、Axes の割合で測られる範囲を組み合わせています。

Matplotlib で水平線と垂直線をプロットするための hlines と vlines

線の位置と両端の両方にデータ座標を使いたい場合は、hlines と vlines を使います。hlines では、y が高さを与え、xmin と xmax が端点の x 値を与えます。vlines では、x が位置を与え、ymin と ymax が端点の y 値を与えます。プロットの範囲を変更すると表示される部分は変わりますが、データ座標での線の位置と端点は固定されたままです。どちらの関数も LineCollection を返し、通常のスタイル引数を受け付けるため、この例では破線と点線のスタイルも設定し、label 引数で凡例を追加します。

import tempfile
from pathlib import Path

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
horizontal = ax.hlines(y=2, xmin=1, xmax=3, color="tab:red", linestyles="--",
                       linewidth=2, label="horizontal segment")
vertical = ax.vlines(x=2, ymin=1, ymax=3, color="tab:green", linestyles=":",
                     linewidth=2, label="vertical segment")
ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="x", ylabel="y")
ax.grid(True)
ax.legend(loc="upper left")
print("horizontal segment endpoints:", horizontal.get_segments()[0].tolist())
print("vertical segment endpoints:", vertical.get_segments()[0].tolist())
with tempfile.TemporaryDirectory() as directory:
    fig.savefig(Path(directory) / "plot.png")
plt.close(fig)

出力:

horizontal segment endpoints: [[1.0, 2.0], [3.0, 2.0]]
vertical segment endpoints: [[2.0, 1.0], [2.0, 3.0]]

出力された端点は純粋なデータ座標です。水平線分は (1, 2) から (3, 2) まで、垂直線分は (2, 1) から (2, 3) まで伸びています。

凡例で識別される、y = 2 で x = 1 から x = 3 までの赤い破線の水平線分と、x = 2 で y = 1 から y = 3 までの緑の点線の垂直線分を示す Matplotlib プロット

4つの端点すべてがデータ座標を使うため、ズームしたり範囲を変更したりすると、各線分の端点が Axes の割合に合わせてリサイズされるのではなく、表示される部分が変わります。

axhspan と axvspan で Matplotlib の領域を塗りつぶす

1本の線では不十分な場合は、代わりに2つの値の間の領域を塗りつぶします。axhspan(ymin, ymax) は、2つの y データ値の間の水平な帯を Axes の幅全体にわたって塗りつぶし、axvspan(xmin, xmax) は、2つの x データ値の間の垂直な帯を高さ全体にわたって塗りつぶします。許容範囲、目標ゾーン、データの背後にある注目期間を示すのに使います。どちらの関数も Rectangle パッチを返します。

import tempfile
from pathlib import Path

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
band_y = ax.axhspan(ymin=1.5, ymax=2.5, color="tab:orange", alpha=0.3,
                    label="y between 1.5 and 2.5")
band_x = ax.axvspan(xmin=1.5, xmax=2.5, color="tab:blue", alpha=0.15,
                    label="x between 1.5 and 2.5")
ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="x", ylabel="y")
ax.grid(True)
ax.legend(loc="upper left")
print("horizontal band y range:", band_y.get_y(), "to", band_y.get_y() + band_y.get_height())
print("vertical band x range:", band_x.get_x(), "to", band_x.get_x() + band_x.get_width())
with tempfile.TemporaryDirectory() as directory:
    fig.savefig(Path(directory) / "plot.png")
plt.close(fig)

出力:

horizontal band y range: 1.5 to 2.5
vertical band x range: 1.5 to 2.5

凡例で識別される、y = 1.5 から y = 2.5 の間のオレンジ色の水平な帯と、x = 1.5 から x = 2.5 の間の青い垂直な帯を示す Matplotlib プロット

帯の端は常にデータ座標を使います。axhline と同様に、span 関数も交差方向については割合の引数を受け付けます。axhspan は帯の幅を制限するために xmin と xmax を Axes の割合として受け取り、axvspan は ymin と ymax を受け取りますが、この方法で描いた帯も開始点と終了点はデータ値です。

Matplotlib で水平線や垂直線が表示されない場合の理由

線が表示されないという報告のほとんどは、2つの確認で説明できます。1つ目は型エラーです。axhline と axvline の割合の引数は数値でなければなりません。値がフォーム、CSV ファイル、設定項目などから来た場合に起こりがちな事故として、これらを文字列として渡すと、何も描画される前に ValueError が発生します。

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
ax.axhline(y=2, xmin="0.1", xmax="0.9", color="tab:red")
plt.close(fig)

出力:

ValueError: xmin must be a single scalar value, but got 0.1

まず float(...) などで値を数値に変換すれば、同じ呼び出しが成功します。この記事の最初の例は、まさに数値の割合を使ったその呼び出しです。

2つ目の確認は表示範囲です。線は、その位置が現在の軸の範囲外にあってもデータ座標で作成されるため、図の中には存在しますが、表示からは切り取られます。

import tempfile
from pathlib import Path

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 2, 4], [1, 3, 2], marker="o")
line = ax.axhline(y=20, color="tab:red")
ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="x", ylabel="y")
ax.grid(True)
print("line y position:", line.get_ydata())
print("axes y limits:", tuple(float(v) for v in ax.get_ylim()))
with tempfile.TemporaryDirectory() as directory:
    fig.savefig(Path(directory) / "plot.png")
plt.close(fig)

出力:

line y position: [20, 20]
axes y limits: (0.0, 4.0)

線は y=20 の位置にありますが、軸は 4.0 で止まっています。位置を含むように範囲を拡張するか、20 が誤りであれば値を修正すれば、線が表示されます。同じ確認は、hlines と vlines の端点や、範囲外に落ちた span の端にも当てはまります。

Matplotlib で水平線と垂直線を描くことの結論

位置がデータ値で、線の範囲は Axes の割合に従わせたい場合は axhline または axvline を選んでください。位置と両端をデータ値のままにしたい場合は hlines または vlines を選び、1本の線ではなく2つの値の間の帯全体を強調したい場合は axhspan または axvspan を使ってください。この座標の選択によって、プロットの範囲を変更した後に線のどれだけが見えるかが決まります。そして、保存された座標と軸の範囲を比較すれば、見当たらない線の理由が説明できます。