ম্যাটপ্লোটিলেবে ইনলাইন লেবেল


103

ম্যাটপ্ল্লোব-তে, কোনও কিংবদন্তি তৈরি করা খুব শক্ত নয় ( example_legend()নীচে), তবে আমি মনে করি যে আঁকাবাঁকাগুলি আঁকা হয়েছে তার উপর লেবেল লাগানো আরও ভাল স্টাইল ( example_inline()নীচে হিসাবে )। এটি খুব স্পষ্টভাবে হতে পারে, কারণ আমাকে হাত দ্বারা স্থানাঙ্কগুলি নির্দিষ্ট করতে হবে এবং, আমি যদি প্লটটিকে পুনরায় ফর্ম্যাট করি তবে আমাকে সম্ভবত লেবেলগুলি প্রতিস্থাপন করতে হবে। ম্যাটপ্ললটিব-এ কার্ভে স্বয়ংক্রিয়ভাবে লেবেল তৈরি করার কোনও উপায় আছে? বক্রের কোণ অনুসারে একটি কোণে পাঠ্যকে অভিমুখী করতে সক্ষম হওয়ার জন্য বোনাস পয়েন্ট।

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

ইনলাইন লেবেলযুক্ত চিত্র

উত্তর:


29

সুন্দর প্রশ্ন, কিছুক্ষণ আগে আমি এটি নিয়ে কিছুটা পরীক্ষা-নিরীক্ষা করেছি, তবে এটি এখনও বেশি ব্যবহার করিনি কারণ এটি এখনও বুলেটপ্রুফ নয়। আমি প্লট অঞ্চলটি 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)
egpbos

স্কিপি ছাড়াই এটি ব্যবহার করার কোনও উপায় আছে? আমার বর্তমান সিস্টেমে এটি ইনস্টল করতে ব্যথা।
আনানফায়

পাইথন ৩.6.৪, ম্যাটপ্ল্লোলিব ২.১.২, এবং স্কিপি ০.০.০ এর অধীনে এটি আমার পক্ষে কাজ করে না। printকমান্ডটি আপডেট করার পরে , এটি চলতে থাকে এবং 4 টি প্লট তৈরি করে, যার মধ্যে 3 পিক্সেলিটেড গিব্বারিশ বলে মনে হয় (সম্ভবত 32x32 এর সাথে কিছু করতে পারে), এবং চতুর্থটি অদ্ভুত জায়গায় লেবেলযুক্ত।
ওয়াই ডেভিস

84

আপডেট: ব্যবহারকারী সিএফাইক এই উত্তরটিতে কোডটির জন্য দয়া করে একটি গিথুব সংগ্রহশালা তৈরি করেছেন (দেখুন এখানে ) এবং কোডটি একটি প্যাকেজে বান্ডিল করেছে যা ব্যবহার করে ইনস্টল করা হতে পারে pip install matplotlib-label-lines


সুন্দর ছবি:

আধা-স্বয়ংক্রিয় প্লট-লেবেলিং

এতে কনট্যুর প্লটগুলি লেবেল করাmatplotlib বেশ সহজ (হয় স্বয়ংক্রিয়ভাবে বা ম্যানুয়ালি ক্লিক দিয়ে ম্যানুয়ালি লেবেল রেখে)। এই ফ্যাশনে ডেটা সিরিজ লেবেল করার জন্য (এখনও) কোনও সমমানের সামর্থ্য বলে মনে হয় না! এই বৈশিষ্ট্যটি অন্তর্ভুক্ত না করার জন্য কিছু অর্থপূর্ণ কারণ থাকতে পারে।

নির্বিশেষে, আমি নিম্নলিখিত মডিউলটি লিখেছি যা আধা-স্বয়ংক্রিয় প্লট লেবেলিংয়ের জন্য কোনও অনুমতি নেয়। numpyস্ট্যান্ডার্ড mathলাইব্রেরি থেকে এটির জন্য এবং কয়েকটি ফাংশন প্রয়োজন ।

বর্ণনা

labelLinesফাংশনের ডিফল্ট আচরণটি হ'ল xঅক্ষগুলি বরাবর সমানভাবে লেবেলগুলিকে স্থান দেওয়া (স্বয়ংক্রিয়ভাবে yঅবশ্যই সঠিক- মূল্যকে রেখে দেওয়া )। আপনি যদি চান তবে কেবলমাত্র প্রতিটি লেবেলের এক্স কো-অর্ডিনেটের একটি অ্যারে পাস করতে পারেন। এমনকি আপনি একটি লেবেলের অবস্থানটি (এমনকি নীচের ডানদিকের প্লটটিতে দেখানো হয়েছে) ঝাঁকুনি করতে পারেন এবং আপনার পছন্দ মতো স্থানটিকে সমানভাবে স্থান দিতে পারেন।

তদুপরি, label_linesফাংশনটি লাইনগুলিতে হিসাব করে না যার plotকমান্ডে লেবেল বরাদ্দ নেই (বা আরও সঠিকভাবে যদি লেবেলটি থাকে তবে '_line')।

কীওয়ার্ড আর্গুমেন্টগুলি ফাংশন কলটিতে পাস করা labelLinesবা labelLineপাস করা হয় text(কলিং কোডটি নির্দিষ্ট না করার জন্য কিছু কীওয়ার্ড আর্গুমেন্ট সেট করা থাকে)।

ইস্যু

  • টীকাগুলির বাউন্ডিং বাক্সগুলি কখনও কখনও অন্যান্য কার্ভগুলির সাথে অনস্বীকার্যভাবে হস্তক্ষেপ করে। উপরের বাম চক্রান্তে টীকাগুলি 1এবং 10টীকাগুলি দ্বারা দেখানো হয়েছে । আমি এও নিশ্চিত না যে এড়ানো যায়।
  • এটি yপরিবর্তে কখনও কখনও একটি অবস্থান নির্দিষ্ট করা ভাল হবে ।
  • সঠিক স্থানে টিকা পেতে এটি এখনও পুনরাবৃত্তি প্রক্রিয়া
  • এটা শুধুমাত্র কাজ করে x-axis মান floatগুলি

গটছস

  • ডিফল্টরূপে, labelLinesফাংশনটি ধরে নিয়েছে যে সমস্ত ডেটা সিরিজ অক্ষ সীমা দ্বারা নির্দিষ্ট রেঞ্জের স্প্যান করে। সুন্দর ছবির উপরের বাম প্লটের নীল বক্ররেখায় একবার দেখুন। যদি এই xব্যাপ্তির জন্য কেবলমাত্র ডেটা উপলব্ধ ছিল 0.5- 1তবে আমরা সম্ভবত পছন্দসই স্থানে একটি লেবেল রাখতে পারিনি (যা এর চেয়ে কিছুটা কম 0.2)। একটি বিশেষভাবে বাজে উদাহরণের জন্য এই প্রশ্নটি দেখুন । এখনই, কোডটি বুদ্ধিমানভাবে এই দৃশ্যটি সনাক্ত করতে পারে না এবং লেবেলগুলি পুনরায় সাজায় না, তবে যুক্তিসঙ্গত কাজ রয়েছে। লেবেলাইনস ফাংশনটি xvalsযুক্তিটি গ্রহণ করে ; xপ্রস্থ জুড়ে ডিফল্ট লিনিয়ার বিতরণের পরিবর্তে ব্যবহারকারী দ্বারা নির্ধারিত মূল্যগুলির একটি তালিকা । সুতরাং ব্যবহারকারী কোনটি সিদ্ধান্ত নিতে পারেxপ্রতিটি ডেটা সিরিজের লেবেল স্থাপনের জন্য ব্যবহারের মূল্য।

এছাড়াও, আমি বিশ্বাস করি যে লেবেলগুলি বদ্ধ রয়েছে তার সাথে সারিবদ্ধ করার বোনাস উদ্দেশ্যটি সম্পূর্ণ করার এটি প্রথম উত্তর । :)

লেবেল_লাইন.পি:

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

4
@ ব্লুজয় আমি আনন্দিত যে আপনি এটি আপনার প্রয়োজনের সাথে খাপ খাইয়ে নিতে পেরেছিলেন। আমি এই সমস্যাটিকে সমস্যা হিসাবে যুক্ত করব as
নটিক্যালমাইল

4
@ লিজা আমার গোছা পড়ুন আমি কেন এমনটি ঘটছে তার জন্য কেবল যোগ করেছি। আপনার ক্ষেত্রে (আমি এটি এই প্রশ্নের মতোই ধরে নিচ্ছি ) যদি না আপনি ম্যানুয়ালি একটি তালিকা তৈরি করতে চান তবে আপনি কোডটি কিছুটা xvalsসংশোধন করতে চাইতে পারেন labelLines: if xvals is None:একটি তালিকা ভিত্তিক অন্যান্য মানদণ্ড তৈরির সুযোগের আওতায় কোডটি পরিবর্তন করুন । আপনি শুরু করতে পারেনxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
নটিক্যালমাইল

4
@ লিজা আপনার গ্রাফটি যদিও আমাকে চক্রান্ত করে। সমস্যাটি হ'ল আপনার ডেটা সমানভাবে প্লট জুড়ে ছড়িয়ে নেই, এবং আপনার অনেকগুলি বক্ররেখা রয়েছে যা একে অপরের শীর্ষে রয়েছে। আমার সমাধানের সাথে লেবেলগুলি অনেক ক্ষেত্রে পৃথক করে বলা খুব কঠিন হতে পারে। আমি মনে করি আপনার প্লটের বিভিন্ন ফাঁকা অংশে সজ্জিত লেবেলগুলির ব্লক থাকা সবচেয়ে ভাল সমাধান। স্ট্যাকড লেবেলের দুটি ব্লক (1 টি লেবেলযুক্ত একটি ব্লক এবং 4 টি দিয়ে অন্য একটি ব্লক) উদাহরণ সহকারে এই গ্রাফটি দেখুন । এটি কার্যকর করা লেগওয়ার্কটি মোটামুটি হবে, আমি ভবিষ্যতে কোনও সময় এটি করতে পারি।
নটিক্যালমাইল

4
দ্রষ্টব্য: ম্যাটপ্লটলিব ২.০ থেকে .get_axes()এবং .get_axis_bgcolor()অবচিত করা হয়েছে। দয়া করে .axesএবং .get_facecolor()সম্মানের সাথে প্রতিস্থাপন করুন ।
জিঘাং

4
আরেকটি ভয়ঙ্কর জিনিস সম্পর্কে labellinesএর সাথে সম্পর্কিত যে বৈশিষ্ট্য plt.textবা ax.textএটা প্রযোজ্য। অর্থ আপনি ফাংশন মধ্যে সেট fontsizeএবং bboxপরামিতি করতে পারেন labelLines()
tionichm

53

@ জান কুইকেনের উত্তর অবশ্যই সুচিন্তিত এবং পূর্ণ, তবে কিছু সতর্কতা রয়েছে:

  • এটি সব ক্ষেত্রে কার্যকর হয় না
  • এটির জন্য অতিরিক্ত কোডের ন্যায্য পরিমাণ প্রয়োজন
  • এটি এক প্লট থেকে পরের প্লটে যথেষ্ট পরিবর্তিত হতে পারে

প্রতিটি প্লটের শেষ পয়েন্টটি বর্নিত করা খুব সহজ পদ্ধতির। জোর দেওয়ার জন্য পয়েন্টটিও বৃত্তাকারে করা যেতে পারে। এটি একটি অতিরিক্ত লাইন দ্বারা সম্পন্ন করা যেতে পারে:

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


4
+1! এটি দেখতে একটি দুর্দান্ত এবং সাধারণ সমাধানের মতো। অলসতার জন্য দুঃখিত, তবে এটি কেমন হবে? পাঠ্যটি কি প্লটের ভিতরে বা ডান y অক্ষের উপরে থাকবে?
rocarvaj

4
@ocarvaj এটি অন্যান্য সেটিংসের উপর নির্ভর করে। লেবেলগুলির পক্ষে প্লট বাক্সের বাইরে প্রসারিত করা সম্ভব। এই আচরণটি এড়াতে দুটি উপায় হ'ল: 1) -1লেবেলগুলির জন্য স্থান অনুমোদনের জন্য উপযুক্ত অক্ষ সীমা নির্ধারণ করুন, 2 এর চেয়ে আলাদা একটি সূচক ব্যবহার করুন।
ইওনিস ফিলিপিসিস

4
এটি একটি জগাখিচুড়ি হয়ে যায়, যদি প্লটগুলি কিছু y মানের উপর মনোনিবেশ করে
প্রান্তটি টেক্সটটি

@ ল্যাজিক্যাট: এটি সত্য। এটির সমাধানের জন্য, কেউ টীকা টেনে নিয়ে যেতে সক্ষম করতে পারে। কিছুটা ব্যথা অনুমান করি তবে তা কৌশলটি করবে।
প্লাসিডল্যাশ

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

কোড পাইথন 3 সেজসেল-এ

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.