128

Matplotlib では、凡例 (example_legend()以下の ) を作成するのはそれほど難しくありませんが、プロットされている曲線にラベルを付ける方が適切だと思います (example_inline()以下の のように)。手動で座標を指定する必要があり、プロットを再フォーマットする場合は、おそらくラベルを再配置する必要があるため、これは非常に面倒です。Matplotlib の曲線にラベルを自動的に生成する方法はありますか? 曲線の角度に対応する角度でテキストを方向付けることができるというボーナス ポイント。

import numpy as np
import matplotlib.pyplot as plt

def example_legend():
    plt.clf()
    x = np.linspace(0, 1, 101)
    y1 = np.sin(x * np.pi / 2)
    y2 = np.cos(x * np.pi / 2)
    plt.plot(x, y1, label='sin')
    plt.plot(x, y2, label='cos')
    plt.legend()

凡例付き図

def example_inline():
    plt.clf()
    x = np.linspace(0, 1, 101)
    y1 = np.sin(x * np.pi / 2)
    y2 = np.cos(x * np.pi / 2)
    plt.plot(x, y1, label='sin')
    plt.plot(x, y2, label='cos')
    plt.text(0.08, 0.2, 'sin')
    plt.text(0.9, 0.2, 'cos')

インライン ラベル付きの図

4

5 に答える 5

110

更新:ユーザーcphycは、この回答のコードの Github リポジトリを親切に作成し (ここを参照)、コードをパッケージにバンドルしました。このパッケージは、を使用してインストールできますpip install matplotlib-label-lines


かわいい写真:

半自動プロットラベリング

等高線図にラベルを付けるのはmatplotlib非常に簡単です(自動またはマウス クリックで手動でラベルを配置します)。この方法でデータ系列にラベルを付ける同等の機能は (まだ) ないようです! 私が見逃しているこの機能を含めないことには、意味的な理由があるかもしれません。

とにかく、半自動のプロットラベル付けを可能にする次のモジュールを作成しました。numpy標準mathライブラリのいくつかの関数のみが必要です。

説明

関数のデフォルトの動作はlabelLines、ラベルをx軸に沿って均等に配置することです (もちろん、正しいy値に自動的に配置されます)。必要に応じて、各ラベルの x 座標の配列を渡すことができます。必要に応じて、1 つのラベルの位置を微調整して (右下のプロットに示されているように)、残りを均等に配置することもできます。

さらに、このlabel_lines関数は、コマンドでラベルが割り当てられていない行plot(ラベルに が含まれている場合はより正確です'_line') を考慮しません。

関数呼び出しに渡される、labelLinesまたは関数呼び出しlabelLineに渡されるtextキーワード引数 (呼び出し元のコードが指定しないことを選択した場合、一部のキーワード引数が設定されます)。

問題

  • 注釈バウンディング ボックスは、他の曲線と望ましくない干渉をすることがあります。左上のプロットの1との注釈で示されているように。10これを回避できるかどうかさえわかりません。
  • y場合によっては代わりに位置を指定するといいでしょう。
  • 適切な場所に注釈を付けるのは、依然として反復的なプロセスです。
  • x軸の値がfloats の場合にのみ機能します

落とし穴

  • デフォルトでは、labelLines関数はすべてのデータ系列が軸の範囲で指定された範囲にまたがっていると想定します。きれいな画像の左上のプロットにある青い曲線を見てください。xその範囲0.5で利用できるデータしか1ない場合は、ラベルを目的の位置 ( より少し小さい0.2) に配置できない可能性があります。特に厄介な例については、この質問を参照してください。現時点では、コードはこのシナリオをインテリジェントに識別してラベルを再配置しませんが、妥当な回避策があります。labelLines 関数はxvals引数を取ります。x幅全体のデフォルトの線形分布の代わりに、ユーザーが指定した値のリスト。したがって、ユーザーはどちらを決定できますかx-各データ系列のラベル配置に使用する値。

また、これは、ラベルをカーブに合わせるというボーナス目標を達成するための最初の答えだと思います。:)

label_lines.py:

from math import atan2,degrees
import numpy as np

#Label line with line2D label data
def labelLine(line,x,label=None,align=True,**kwargs):

    ax = line.axes
    xdata = line.get_xdata()
    ydata = line.get_ydata()

    if (x < xdata[0]) or (x > xdata[-1]):
        print('x label location is outside data range!')
        return

    #Find corresponding y co-ordinate and angle of the line
    ip = 1
    for i in range(len(xdata)):
        if x < xdata[i]:
            ip = i
            break

    y = ydata[ip-1] + (ydata[ip]-ydata[ip-1])*(x-xdata[ip-1])/(xdata[ip]-xdata[ip-1])

    if not label:
        label = line.get_label()

    if align:
        #Compute the slope
        dx = xdata[ip] - xdata[ip-1]
        dy = ydata[ip] - ydata[ip-1]
        ang = degrees(atan2(dy,dx))

        #Transform to screen co-ordinates
        pt = np.array([x,y]).reshape((1,2))
        trans_angle = ax.transData.transform_angles(np.array((ang,)),pt)[0]

    else:
        trans_angle = 0

    #Set a bunch of keyword arguments
    if 'color' not in kwargs:
        kwargs['color'] = line.get_color()

    if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs):
        kwargs['ha'] = 'center'

    if ('verticalalignment' not in kwargs) and ('va' not in kwargs):
        kwargs['va'] = 'center'

    if 'backgroundcolor' not in kwargs:
        kwargs['backgroundcolor'] = ax.get_facecolor()

    if 'clip_on' not in kwargs:
        kwargs['clip_on'] = True

    if 'zorder' not in kwargs:
        kwargs['zorder'] = 2.5

    ax.text(x,y,label,rotation=trans_angle,**kwargs)

def labelLines(lines,align=True,xvals=None,**kwargs):

    ax = lines[0].axes
    labLines = []
    labels = []

    #Take only the lines which have labels other than the default ones
    for line in lines:
        label = line.get_label()
        if "_line" not in label:
            labLines.append(line)
            labels.append(label)

    if xvals is None:
        xmin,xmax = ax.get_xlim()
        xvals = np.linspace(xmin,xmax,len(labLines)+2)[1:-1]

    for line,x,label in zip(labLines,xvals,labels):
        labelLine(line,x,label,align,**kwargs)

上記のきれいな画像を生成するコードをテストします。

from matplotlib import pyplot as plt
from scipy.stats import loglaplace,chi2

from labellines import *

X = np.linspace(0,1,500)
A = [1,2,5,10,20]
funcs = [np.arctan,np.sin,loglaplace(4).pdf,chi2(5).pdf]

plt.subplot(221)
for a in A:
    plt.plot(X,np.arctan(a*X),label=str(a))

labelLines(plt.gca().get_lines(),zorder=2.5)

plt.subplot(222)
for a in A:
    plt.plot(X,np.sin(a*X),label=str(a))

labelLines(plt.gca().get_lines(),align=False,fontsize=14)

plt.subplot(223)
for a in A:
    plt.plot(X,loglaplace(4).pdf(a*X),label=str(a))

xvals = [0.8,0.55,0.22,0.104,0.045]
labelLines(plt.gca().get_lines(),align=False,xvals=xvals,color='k')

plt.subplot(224)
for a in A:
    plt.plot(X,chi2(5).pdf(a*X),label=str(a))

lines = plt.gca().get_lines()
l1=lines[-1]
labelLine(l1,0.6,label=r'$Re=${}'.format(l1.get_label()),ha='left',va='bottom',align = False)
labelLines(lines[:-1],align=False)

plt.show()
于 2016-09-09T01:27:23.690 に答える
65

@Jan Kuikenの答えは確かによく考えられていて徹底的ですが、いくつかの注意事項があります:

  • すべての場合に機能するわけではありません
  • かなりの量の追加コードが必要です
  • プロットごとにかなり異なる場合があります

はるかに簡単な方法は、各プロットの最後の点に注釈を付けることです。強調するために、ポイントを丸で囲むこともできます。これは、次の 1 行を追加することで実現できます。

import matplotlib.pyplot as plt

for i, (x, y) in enumerate(samples):
    plt.plot(x, y)
    plt.text(x[-1], y[-1], f'sample {i}')

バリアントは、メソッドを使用matplotlib.axes.Axes.annotateすることです。

于 2015-04-19T01:37:37.477 に答える