Matplotlibのインラインラベル


100

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

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

回答:


28

いい質問です。少し前にこれを少し実験しましたが、まだ完全ではないため、あまり使用していません。プロットエリアを32x32グリッドに分割し、次のルールに従って各行のラベルの最適な位置の「ポテンシャルフィールド」を計算しました。

  • 空白はラベルに適した場所です
  • ラベルは対応する行の近くにある必要があります
  • ラベルは他の行から離れている必要があります

コードは次のようなものでした:

import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage


def my_legend(axis = None):

    if axis == None:
        axis = plt.gca()

    N = 32
    Nlines = len(axis.lines)
    print Nlines

    xmin, xmax = axis.get_xlim()
    ymin, ymax = axis.get_ylim()

    # the 'point of presence' matrix
    pop = np.zeros((Nlines, N, N), dtype=np.float)    

    for l in range(Nlines):
        # get xy data and scale it to the NxN squares
        xy = axis.lines[l].get_xydata()
        xy = (xy - [xmin,ymin]) / ([xmax-xmin, ymax-ymin]) * N
        xy = xy.astype(np.int32)
        # mask stuff outside plot        
        mask = (xy[:,0] >= 0) & (xy[:,0] < N) & (xy[:,1] >= 0) & (xy[:,1] < N)
        xy = xy[mask]
        # add to pop
        for p in xy:
            pop[l][tuple(p)] = 1.0

    # find whitespace, nice place for labels
    ws = 1.0 - (np.sum(pop, axis=0) > 0) * 1.0 
    # don't use the borders
    ws[:,0]   = 0
    ws[:,N-1] = 0
    ws[0,:]   = 0  
    ws[N-1,:] = 0  

    # blur the pop's
    for l in range(Nlines):
        pop[l] = ndimage.gaussian_filter(pop[l], sigma=N/5)

    for l in range(Nlines):
        # positive weights for current line, negative weight for others....
        w = -0.3 * np.ones(Nlines, dtype=np.float)
        w[l] = 0.5

        # calculate a field         
        p = ws + np.sum(w[:, np.newaxis, np.newaxis] * pop, axis=0)
        plt.figure()
        plt.imshow(p, interpolation='nearest')
        plt.title(axis.lines[l].get_label())

        pos = np.argmax(p)  # note, argmax flattens the array first 
        best_x, best_y =  (pos / N, pos % N) 
        x = xmin + (xmax-xmin) * best_x / N       
        y = ymin + (ymax-ymin) * best_y / N       


        axis.text(x, y, axis.lines[l].get_label(), 
                  horizontalalignment='center',
                  verticalalignment='center')


plt.close('all')

x = np.linspace(0, 1, 101)
y1 = np.sin(x * np.pi / 2)
y2 = np.cos(x * np.pi / 2)
y3 = x * x
plt.plot(x, y1, 'b', label='blue')
plt.plot(x, y2, 'r', label='red')
plt.plot(x, y3, 'g', label='green')
my_legend()
plt.show()

そして結果のプロット: ここに画像の説明を入力してください


非常に素晴らしい。ただし、完全に機能しない例があります。plt.plot(x2, 3*x2**2, label="3x*x"); plt.plot(x2, 2*x2**2, label="2x*x"); plt.plot(x2, 0.5*x2**2, label="0.5x*x"); plt.plot(x2, -1*x2**2, label="-x*x"); plt.plot(x2, -2.5*x2**2, label="-2.5*x*x"); my_legend();これにより、ラベルの1つが左上隅に配置されます。これを修正する方法に関するアイデアはありますか?問題のように思われるのは、線が近すぎることです。
egpbos 2014年

すみません、忘れましたx2 = np.linspace(0,0.5,100)
egpbos 2014年

scipyなしでこれを使用する方法はありますか?私の現在のシステムでは、インストールするのが面倒です。
AnnanFay 2016年

これは、Python 3.6.4、Matplotlib 2.1.2、およびScipy 1.0.0では動作しません。printコマンドを更新すると、4つのプロットが実行されて作成されます。そのうち3つはピクセル化された意味不明なもの(おそらく32x32と関係があるもの)で、4つ目はラベルが奇数の場所にあります。
Yデイビス

84

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


かわいい写真:

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

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

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

説明

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

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

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

問題

  • 注釈境界ボックスは、他の曲線に望ましくない干渉を起こすことがあります。左上のプロットの110アノテーションで示されています。これを回避できるかどうかさえわかりません。
  • 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()

1
@blujayニーズに合わせて調整できて良かったです。その制約を問題として追加します。
NauticalMile 2016年

1
@Liza私が読んだゴチャを読んでください。あなたの場合(私はそれがこの質問のものと同じだと思います)、のリストを手動で作成したい場合を除いてxvalslabelLinesコードを少し変更する必要があります:if xvals is None:スコープの下のコードを変更して、他の基準に基づいてリストを作成します。あなたが始めることができますxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
NauticalMile

1
@Lizaあなたのグラフは私にも興味をそそります。問題は、データがプロット全体に均一に広がっていないこと、そして互いにほぼ重なり合っている多くの曲線があることです。私のソリューションでは、多くの場合、ラベルを区別するのが非常に難しい場合があります。最善の解決策は、プロットの異なる空の部分に積み重ねたラベルのブロックを配置することだと思います。積み重ねられたラベルの2つのブロックの例については、このグラフを参照してください(1つのブロックに1つのラベル、もう1つのブロックに4)。これを実装することはかなりのレッグワークになるでしょう、私は将来のある時点でそれをするかもしれません。
NauticalMile 2017年

1
注:matplotlibの2.0以降、.get_axes()および.get_axis_bgcolor()廃止されました。.axesおよび.get_facecolor()に置き換えてください。
Jiāgěng

1
もう1つのすばらしい点labellinesは、それに関連する、plt.textまたはax.text適用されるプロパティです。関数内fontsizebboxパラメータを設定できることを意味しますlabelLines()
19

52

@Jan Kuikenの答えは確かによく考えられており完全ですが、いくつかの注意点があります。

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

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

from matplotlib import pyplot as plt

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

バリアントはを使用することax.annotateです。


1
+1!それは素晴らしくてシンプルなソリューションのように見えます。怠惰で申し訳ありませんが、これはどのように見えますか?テキストはプロットの内側にありますか、それとも右のy軸の上にありますか?
rocarvaj 2016年

1
@rocarvaj他の設定に依存します。ラベルがプロットボックスの外に突き出る可能性があります。この動作を回避するには、次の2つの方法があります。1)とは異なるインデックスを使用する-1、2)適切な軸の範囲を設定してラベルにスペースを確保する。
Ioannis Filippidis 2017

1
プロットがいくつかのy値に集中している場合、これも混乱します-端点が近すぎてテキストが見栄えしません
LazyCat

@LazyCat:そのとおりです。これを修正するには、注釈をドラッグ可能にします。少し苦痛だと思いますが、それでうまくいきます。
PlacidLush

1

Ioannis Filippidisが行うようなより単純なアプローチ:

import matplotlib.pyplot as plt
import numpy as np

# evenly sampled time at 200ms intervals
tMin=-1 ;tMax=10
t = np.arange(tMin, tMax, 0.1)

# red dashes, blue points default
plt.plot(t, 22*t, 'r--', t, t**2, 'b')

factor=3/4 ;offset=20  # text position in view  
textPosition=[(tMax+tMin)*factor,22*(tMax+tMin)*factor]
plt.text(textPosition[0],textPosition[1]+offset,'22  t',color='red',fontsize=20)
textPosition=[(tMax+tMin)*factor,((tMax+tMin)*factor)**2+20]
plt.text(textPosition[0],textPosition[1]+offset, 't^2', bbox=dict(facecolor='blue', alpha=0.5),fontsize=20)
plt.show()

sageCellのpython 3コード

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.