इन्टेल लेबल मैटलपोटलिब में


100

माटप्लोटलिब में, एक किंवदंती ( 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

क्षमा करें, भूल गए x2 = np.linspace(0,0.5,100)
इगपबोस

क्या बिना चीर-फाड़ के इसे इस्तेमाल करने का कोई तरीका है? मेरे मौजूदा सिस्टम में इसे स्थापित करने के लिए एक दर्द है।
अन्नानफे

यह मेरे लिए Python 3.6.4, Matplotlib 2.1.2 और Scipy 1.0.0 के तहत काम नहीं करता है। printकमांड को अपडेट करने के बाद , यह 4 प्लॉट चलाता है और बनाता है, जिनमें से 3 पिक्सेलेटेड जिबरिश (संभवतः 32x32 के साथ कुछ करने के लिए) प्रतीत होता है, और विषम स्थानों में लेबल के साथ चौथा है।
वाई डेविस

84

अपडेट: उपयोगकर्ता cphyc ने इस उत्तर में कोड के लिए कृपया एक Github रिपॉजिटरी बनाई है ( यहां देखें ), और कोड को एक पैकेज में बंडल किया, जिसका उपयोग करके इंस्टॉल किया जा सकता है pip install matplotlib-label-lines


सुन्दर तस्वीर:

अर्ध-स्वचालित प्लॉट-लेबलिंग

में matplotlibइसे करने के लिए बहुत आसान है लेबल समोच्च भूखंडों (स्वत: या मैन्युअल माउस क्लिक के साथ लेबल रखकर या तो)। इस शैली में डेटा श्रृंखला को लेबल करने की कोई समतुल्य क्षमता नहीं है (अभी तक)! इस सुविधा को शामिल न करने के कुछ अर्थिक कारण हो सकते हैं जो मुझे याद आ रहे हैं।

बावजूद, मैंने निम्नलिखित मॉड्यूल को लिखा है जो अर्ध-स्वचालित प्लॉट लेबलिंग के लिए कोई भी अनुमति देता है। इसके numpyलिए मानक mathपुस्तकालय से केवल और कुछ कार्यों की आवश्यकता है ।

विवरण

labelLinesफ़ंक्शन का डिफ़ॉल्ट व्यवहार समान रूप से xअक्ष के साथ लेबल को स्पेस करना है (स्वचालित रूप से सही y-वेल्यू पर रखकर )। यदि आप चाहें तो आप प्रत्येक लेबल के x सह-निर्देशांक की एक सरणी को पारित कर सकते हैं। आप एक लेबल के स्थान को भी ट्विक कर सकते हैं (जैसा कि नीचे दाएं प्लॉट में दिखाया गया है) और बाकी को समान रूप से स्पेस दें यदि आप चाहें तो।

इसके अलावा, label_linesफ़ंक्शन उन लाइनों के लिए खाता नहीं है जिनके पास plotकमांड में लेबल लेबल नहीं है (या यदि लेबल में अधिक सटीक है '_line')।

कीवर्ड तर्कों को पारित कर दिया labelLinesया labelLineको भेजी जाती हैं textसमारोह कॉल (कुछ कीवर्ड तर्क अगर बुला कोड चुनता निर्दिष्ट करने के लिए नहीं स्थापित कर रहे हैं)।

मुद्दे

  • एनोटेशन बाउंडिंग बॉक्स कभी-कभी अन्य घटता के साथ अवांछनीय रूप से हस्तक्षेप करते हैं। जैसा कि शीर्ष बाएँ प्लॉट में 1और 10एनोटेशन द्वारा दिखाया गया है । मुझे यकीन नहीं है कि इससे बचा जा सकता है।
  • yकभी-कभी इसके बजाय एक स्थिति निर्दिष्ट करना अच्छा होगा ।
  • सही स्थान पर एनोटेशन प्राप्त करना अभी भी एक पुनरावृति प्रक्रिया है
  • यह केवल तभी काम करता है जब x-axis मान floatएस

gotchas

  • डिफ़ॉल्ट रूप से, labelLinesफ़ंक्शन मानता है कि सभी डेटा श्रृंखला अक्ष सीमा द्वारा निर्दिष्ट सीमा का विस्तार करते हैं। सुंदर चित्र के शीर्ष बाएँ प्लॉट में नीले रंग की वक्र पर एक नज़र डालें। यदि xरेंज के लिए केवल डेटा उपलब्ध थे 0.5- 1तो हम संभवतः वांछित स्थान पर एक लेबल नहीं लगा सकते हैं (जो थोड़ा कम है 0.2)। इस प्रश्न को विशेष रूप से बुरा उदाहरण के लिए देखें । अभी, कोड बुद्धिमानी से इस परिदृश्य की पहचान नहीं करता है और लेबल को फिर से व्यवस्थित करता है, हालांकि एक उचित समाधान है। लेबललाइन फ़ंक्शन 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
@ लिज़ा मेरा गोत्र पढ़ो मैंने सिर्फ इसके लिए जोड़ा कि ऐसा क्यों हो रहा है। आपके मामले के लिए (मैं मान रहा हूँ कि यह इस प्रश्न में से एक की तरह है ) जब तक आप मैन्युअल रूप से एक सूची नहीं बनाना xvalsचाहते, आप labelLinesकोड को थोड़ा संशोधित करना चाह सकते हैं : if xvals is None:सूची आधारित अन्य मानदंडों को बनाने के लिए कोड को दायरे में बदलें । आप के साथ शुरू कर सकते हैंxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
NauticalMile

1
@ लिजा आपका ग्राफ मुझे हालांकि साज़िश करता है। समस्या यह है कि आपका डेटा प्लॉट में समान रूप से नहीं फैला है, और आपके पास बहुत सारे वक्र हैं जो लगभग एक दूसरे के ऊपर हैं। मेरे समाधान के साथ कई मामलों में लेबल को बताना बहुत मुश्किल हो सकता है। मुझे लगता है कि सबसे अच्छा समाधान आपके प्लॉट के विभिन्न खाली हिस्सों में स्टैक्ड लेबल के ब्लॉक होना है। देखें इस ग्राफ खड़ी लेबल के दो ब्लॉकों (1 लेबल के साथ एक ब्लॉक, और 4 के साथ एक और ब्लॉक) के साथ एक उदाहरण के लिए। इसे लागू करना काफी हद तक उचित होगा, मैं इसे भविष्य में किसी बिंदु पर कर सकता हूं।
NauticalMile

1
नोट: Matplotlib 2.0 के बाद से, .get_axes()और .get_axis_bgcolor()पदावनत किया गया है। कृपया बदलें .axesऔर .get_facecolor()सम्मान करें।
जिआगंज

1
इसके बारे labellinesमें एक और भयानक बात यह है कि इससे संबंधित गुण plt.textया ax.textलागू होते हैं। मतलब आप फंक्शन में सेट fontsizeऔर bboxपैरामीटर कर सकते हैं labelLines()
tionichm

52

@ जान कुइकेन का जवाब निश्चित रूप से अच्छी तरह से सोचा और पूरी तरह से है, लेकिन कुछ चेतावनी हैं:

  • यह सभी मामलों में काम नहीं करता है
  • इसके लिए उचित मात्रा में अतिरिक्त कोड की आवश्यकता होती है
  • यह एक प्लॉट से अगले तक काफी भिन्न हो सकता है

प्रत्येक भूखंड के अंतिम बिंदु को एनोटेट करना बहुत सरल तरीका है। जोर देने के लिए भी बिंदु परिक्रमा की जा सकती है। यह एक अतिरिक्त लाइन के साथ पूरा किया जा सकता है:

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 अक्ष के शीर्ष पर होगा?
रोजवराज

1
@rocarajaj यह अन्य सेटिंग्स पर निर्भर करता है। लेबल के लिए प्लॉट बॉक्स के बाहर फैलाना संभव है। इस व्यवहार से बचने के दो तरीके हैं: 1) -1लेबल की जगह की अनुमति देने के लिए 2) अलग अक्ष सीमा का उपयोग करें।
Ioannis Filippidis

1
यह एक गड़बड़ भी हो जाता है, अगर भूखंड कुछ y मान पर ध्यान केंद्रित करते हैं - समापन बिंदु पाठ को देखने के लिए बहुत करीब हो जाते हैं
LazyCat

@ आलसीकट: यह सच है। इसे ठीक करने के लिए, एनोटेशन को ड्रैग करने योग्य बनाया जा सकता है। थोड़ा सा दर्द मुझे लगता है लेकिन यह चाल होगा।
प्लेसिडलश

1

आयोनिस फिलिपिडिस जैसा सरल दृष्टिकोण:

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.