Matplotlib의 인라인 레이블


102

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();이렇게하면 레이블 중 하나가 왼쪽 상단 모서리에 배치됩니다. 이 문제를 해결하는 방법에 대한 아이디어가 있습니까? 문제는 선이 너무 가깝다는 것입니다.
egpbos 2014 년

죄송합니다 x2 = np.linspace(0,0.5,100)..
egpbos

scipy없이 이것을 사용할 수있는 방법이 있습니까? 내 현재 시스템에서는 설치하기가 어렵습니다.
AnnanFay

이것은 Python 3.6.4, Matplotlib 2.1.2 및 Scipy 1.0.0에서 작동하지 않습니다. print명령을 업데이트 한 후 4 개의 플롯이 실행되고 생성됩니다.이 중 3 개는 픽셀 화 된 횡설수설 (아마 32x32와 관련이 있음)으로 표시되고 네 번째 플롯은 이상한 위치에 레이블이 있습니다.
Y Davis

84

업데이트 : 사용자 cphyc 은이 답변의 코드에 대한 Github 저장소를 친절하게 만들었고 ( 여기 참조 ) pip install matplotlib-label-lines.


예쁜 사진:

반자동 플롯 라벨링

에서 matplotlib그것은 꽤 쉽게 윤곽 플롯 레이블 (자동 또는 수동으로 마우스 클릭으로 라벨을 배치하여 중). (아직) 이런 방식으로 데이터 시리즈에 레이블을 지정하는 것과 동등한 기능이없는 것 같습니다! 내가 놓친이 기능을 포함하지 않는 의미 론적 이유가있을 수 있습니다.

그럼에도 불구하고 반자동 플롯 라벨링을 허용하는 다음 모듈을 작성했습니다. numpy표준 math라이브러리 의 몇 가지 기능 만 필요합니다 .

기술

labelLines함수 의 기본 동작은 x축을 따라 레이블을 균등하게 배치하는 것 y입니다 (물론 올바른 값에 자동으로 배치됨). 원하는 경우 각 레이블의 x 좌표 배열을 전달할 수 있습니다. 원하는 경우 레이블 하나의 위치를 ​​조정하고 (오른쪽 하단 플롯에 표시된대로) 나머지는 균등하게 배치 할 수도 있습니다.

또한이 label_lines함수는 plot명령에 레이블이 지정되지 않은 행을 고려하지 않습니다 (또는 레이블에가 포함 된 경우 더 정확하게 '_line').

키워드 인수 가 함수 호출 에 전달 labelLines되거나 함수 호출 labelLine에 전달됩니다 text(호출 코드가 지정하지 않도록 선택한 경우 일부 키워드 인수가 설정 됨).

이슈

  • 주석 경계 상자는 때때로 다른 곡선과 원치 않게 간섭합니다. 에 의해 표시된 바와 같이110왼쪽 상단 플롯 및 주석으로 . 나는 이것이 피할 수 있는지조차 확신하지 못합니다.
  • 지정하는 것이 좋을 것입니다. y때로는 위치 .
  • 올바른 위치에 주석을 가져 오는 것은 여전히 ​​반복적 인 프로세스입니다.
  • x-axis 값이 floats 일 때만 작동합니다.

Gotchas

  • 기본적 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

1
@Liza 내 Gotcha를 읽으십시오 나는 이것이 왜 일어나는지에 대해 방금 추가했습니다. 귀하의 경우 ( 이 질문 의 목록 과 비슷하다고 가정합니다)의 목록을 수동으로 만들지 않는 한 코드를 약간 xvals수정 labelLines하고 if xvals is None:범위 에서 코드를 변경하여 다른 기준을 기반으로 목록을 만듭니다. 당신은 시작할 수xvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
NauticalMile

1
@Liza 당신의 그래프는 나를 흥미롭게 만듭니다. 문제는 데이터가 플롯에 균등하게 분산되어 있지 않고 서로 거의 위에있는 많은 곡선이 있다는 것입니다. 내 솔루션을 사용하면 많은 경우 레이블을 구분하기가 매우 어려울 수 있습니다. 가장 좋은 해결책은 플롯의 다른 빈 부분에 쌓인 레이블 블록을 갖는 것입니다. 두 개의 누적 레이블 블록이있는 예는 이 그래프 를 참조하십시오 (1 개의 레이블이있는 블록과 4 개가있는 다른 블록). 이것을 구현하는 것은 약간의 작업이 될 것이고, 나는 미래의 어느 시점에서 그것을 할 것입니다.
NauticalMile

1
참고 :하기 matplotlib 2.0 이후, .get_axes()그리고 .get_axis_bgcolor()사용되지 않습니다. .axes.get_facecolor(), resp로 바꾸십시오 .
Jiāgěng

1
또 다른 멋진 점은 labellines속성이 관련 plt.text되거나 ax.text적용된다는 것입니다. 설정할 수있는 의미 fontsizebbox의 매개 변수 labelLines()기능.
tionichm

53

@Jan Kuiken의 대답은 확실히 잘 생각되고 철저하지만 몇 가지주의 사항이 있습니다.

  • 모든 경우에 작동하지 않습니다
  • 상당한 양의 추가 코드가 필요합니다.
  • 플롯마다 상당히 다를 수 있습니다.

훨씬 더 간단한 방법은 각 플롯의 마지막 지점에 주석을 추가하는 것입니다. 강조를 위해 포인트를 원으로 표시 할 수도 있습니다. 이것은 하나의 추가 라인으로 수행 할 수 있습니다.

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

1
@rocarvaj 다른 설정에 따라 다릅니다. 레이블이 플롯 상자 외부로 튀어 나올 수 있습니다. 이 동작을 피하는 두 가지 방법은 다음과 같습니다. 1)와 다른 인덱스 사용 -1, 2) 레이블 공간을 허용하도록 적절한 축 제한 설정.
요안 Filippidis

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의 코드 파이썬 3

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.