Matplotlib'deki bir noktanın üzerine geldiğinizde etiketlerin görünmesi mümkün müdür?


148

Dağılım grafikleri yapmak için matplotlib kullanıyorum. Dağılım grafiğindeki her nokta adlandırılmış bir nesneyle ilişkilendirilir. İmlecimi o nesneyle ilişkili dağılım grafiğinin üzerine getirdiğimde, bir nesnenin adını görebilmek istiyorum. Özellikle, aykırı olan noktaların adlarını hızlı bir şekilde görmek güzel olurdu. Burada arama yaparken bulabildiğim en yakın şey ek açıklama komutudur, ancak bu çizimde sabit bir etiket oluşturuyor gibi görünüyor. Ne yazık ki, sahip olduğum puanların sayısı ile, her noktayı etiketlersem dağılım grafiği okunamaz olur. Herkes yalnızca imleç bu noktanın yakınındayken görünen etiketler oluşturmanın bir yolunu biliyor mu?


2
Burada arama yoluyla biten kişiler de oldukça karmaşık olan ancak bu gereksinimlere bağlı olarak uygun olabilecek bu cevabı kontrol etmek isteyebilirler.
ImportanceOfBeingErnest

Yanıtlar:


133

Görünüşe göre buradaki diğer cevapların hiçbiri soruyu cevaplamıyor. Yani burada bir kullanan bir koddur dağılmanın ve gösterileri bir açıklama üzerine gelerek dağılım noktaları üzerinde.

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)

x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)

norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn

fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)

annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"),
                    arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)

def update_annot(ind):

    pos = sc.get_offsets()[ind["ind"][0]]
    annot.xy = pos
    text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))), 
                           " ".join([names[n] for n in ind["ind"]]))
    annot.set_text(text)
    annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
    annot.get_bbox_patch().set_alpha(0.4)


def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

resim açıklamasını buraya girin

İnsanlar da bu çözümü plotbir dağılım yerine bir çizgi için kullanmak istediklerinden , aşağıdakiler için aynı çözüm olacaktır plot(bu biraz farklı çalışır).

Birinin ikiz eksenlerdeki çizgiler için bir çözüm araması durumunda, bkz . Birden fazla eksende bir nokta üzerinde gezindiğinizde etiketlerin görünmesi nasıl yapılır?

Birisinin çubuk grafikler için bir çözüm araması durumunda, lütfen bu cevaba bakınız .


1
Çok hoş! Bir not, ind["ind"]aslında imleç altındaki tüm noktalar için bir dizin listesi olduğunu fark ettim . Bu, yukarıdaki kodun, yalnızca en üst noktaya değil, belirli bir konumdaki tüm noktalara erişmenizi sağladığı anlamına gelir. Örneğin, iki çakışan noktanız varsa, metin okuyabilir 1 2, B Cveya 1 2 3, B C D3 çakışan noktanız olsa bile .
Jvinniec

@Jvinniec Yukarıdaki grafikte kasıtlı olarak böyle bir durum vardır (x ve 0.4'deki yeşil ve kırmızı nokta). Fareyle üzerine gelirseniz görüntülenir 0 8, A I( resme bakın ).
ImportanceOfBeingErnest

@ImportanceOfBeingErnest bu harika bir kod, ancak bir noktaya gelip hareket ederken fig.canvas.draw_idle()birçok kez çağırıyor (imleci boşta bile değiştiriyor). Önceki dizini saklayıp çözüp çözmediğini çözdüm ind["ind"][0] == prev_ind. Ardından yalnızca bir noktadan diğerine geçtiğinizde (metni güncelleyin), fareyle üzerine gelindiğinde durun (ek açıklamayı görünmez yapın) veya fareyle üzerine gelindiğinde (ek açıklamayı görünür yapın) güncelleyin. Bu değişiklikle çok daha temiz ve verimli.
Sembei Norimaki

3
@Konstantin Evet, bu çözüm %matplotlib notebookbir IPython / Jupyter not defterinde kullanıldığında çalışır.
ImportanceOfBeingErnest

1
@OriolAbril (ve diğer herkes), Bu yanıttaki kodu değiştirirken ortaya çıkan bir sorununuz varsa, lütfen bu konuda bir soru sorun, bu cevaba bağlantı verin ve denediğiniz kodu gösterin. Kodlarınızı görmeden her birinizde neyin yanlış olduğunu bilmem mümkün değil.
ImportanceOfBeingErnest

66

Bu çözüm, bir satırı tıklatmaya gerek kalmadan üzerinde çalışırken çalışır:

import matplotlib.pyplot as plt

# Need to create as global variable so our callback(on_plot_hover) can access
fig = plt.figure()
plot = fig.add_subplot(111)

# create some curves
for i in range(4):
    # Giving unique ids to each data member
    plot.plot(
        [i*1,i*2,i*3,i*4],
        gid=i)

def on_plot_hover(event):
    # Iterating over each data member plotted
    for curve in plot.get_lines():
        # Searching which data member corresponds to current mouse position
        if curve.contains(event)[0]:
            print "over %s" % curve.get_gid()

fig.canvas.mpl_connect('motion_notify_event', on_plot_hover)           
plt.show()

1
Çok yararlı + 1ed. Motion_notify_event, eğri alanı içindeki hareket için tekrarlanacağından, muhtemelen bunu 'geri almanız' gerekir. Sadece eğri nesnesinin önceki eğriye eşit olup olmadığını kontrol etmek işe yarıyor gibi görünüyor.
bvanlew

5
Hmm - bu benim için hazır değildi (çok az şey ... ile matplotlibişe yarıyor) - bu ipython/ jupyternotebook'larla çalışıyor mu? Birden fazla alt grafik olduğunda da çalışır mı? Çizgi grafik yerine çubuk grafiklere ne dersiniz?
dwanderson

12
Bu, gezinirken etiketi konsola yazdırır. Gezinirken etiketin resimde görünmesini sağlamaya ne dersiniz ? Sorunun bu olduğunu anladım.
Nikana Reklawyks

@mbernasocchi çok teşekkür ediyor, eğer histogramı (dağılımdaki her nokta için farklı bir tane) veya daha da iyisi bir 2D histogramın ısı haritasını görmek istiyorsam gid argümanında ne beslemeliyim?
Amitai

@NikanaReklawyks Aslında soruyu cevaplayan bir cevap ekledim .
ImportanceOfBeingErnest

37

Gönderen http://matplotlib.sourceforge.net/examples/event_handling/pick_event_demo.html :

from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand


if 1: # picking on a scatter plot (matplotlib.collections.RegularPolyCollection)

    x, y, c, s = rand(4, 100)
    def onpick3(event):
        ind = event.ind
        print('onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind))

    fig = figure()
    ax1 = fig.add_subplot(111)
    col = ax1.scatter(x, y, 100*s, c, picker=True)
    #fig.savefig('pscoll.eps')
    fig.canvas.mpl_connect('pick_event', onpick3)

show()

Tam da ihtiyacım olan şey bu, teşekkürler! Bir bonus olarak, uygulanmasını sağlamak için, programımı yeniden yazdım, böylece aynı veri üzerinde iki veri kümesini temsil etmek için farklı renklerde iki ayrı dağılım grafiği oluşturmak yerine, örneğin bir noktaya renk atama yöntemini kopyaladım. Bu, programımı okumayı biraz daha basit ve daha az kod haline getirdi. Şimdi bir rengi bir sayıya dönüştürmek için bir rehber bulmak için kapalı!
jdmcbr

1
Bu dağılım grafikleri içindir. Çizgi grafikleri ne olacak? Onlar üzerinde çalışmasını sağlamaya çalıştım ama olmadı. Bir çalışma alanı var mı?
Sohaib

@Sohaib Cevabımı gör
texasflood

Bu konuda bir sorum var. Puanlarımı bu şekilde dağıtırken: plt.scatter (X_reduced [y == i, 0], X_reduced [y == i, 1], c = c, label = target_name, picker = True) i, c ve target_name, dizinlerimin sırası bozuldu mu? Ve artık hangi veri noktasına ait olduğunu arayamıyorum?
Chris

Bu ipython 5 ile jupyter 5 dizüstü bilgisayarlar için çalışmıyor gibi görünüyor. Bunu düzeltmek için kolay bir yolu var mı? printAçıklamada ayrıca piton 3 ile uyumluluk için parens kullanmalıdır
nealmcb

14

Http://matplotlib.org/users/shell.html adresinde sağlanan bir örnek üzerinde küçük bir düzenleme :

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click on points')

line, = ax.plot(np.random.rand(100), '-', picker=5)  # 5 points tolerance


def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    print('onpick points:', *zip(xdata[ind], ydata[ind]))


fig.canvas.mpl_connect('pick_event', onpick)

plt.show()

Sohaib'in sorduğu gibi, bu düz bir çizgi çiziyor


5

mpld3 benim için çöz. DÜZENLE (KOD EKLENDİ):

import matplotlib.pyplot as plt
import numpy as np
import mpld3

fig, ax = plt.subplots(subplot_kw=dict(axisbg='#EEEEEE'))
N = 100

scatter = ax.scatter(np.random.normal(size=N),
                 np.random.normal(size=N),
                 c=np.random.random(size=N),
                 s=1000 * np.random.random(size=N),
                 alpha=0.3,
                 cmap=plt.cm.jet)
ax.grid(color='white', linestyle='solid')

ax.set_title("Scatter Plot (with tooltips!)", size=20)

labels = ['point {0}'.format(i + 1) for i in range(N)]
tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=labels)
mpld3.plugins.connect(fig, tooltip)

mpld3.show()

Bu örneği kontrol edebilirsiniz


Lütfen örnek kod ekleyin ve yalnızca bağlam veya bilgi içermeyen harici kaynaklara bağlantı vermeyin. Daha fazla bilgi için Yardım Merkezi'ne bakın .
Joseph Farah

5
ne yazık ki mpld3 Temmuz 2017 itibariyle artık aktif olarak tutulmuyor
Ben Lindsay

Kod örneği a ile başarısız oluyor TypeError: array([1.]) is not JSON serializable.
P-Gn

@ P-Gn sadece buradaki numarayı takip edin stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 bunun için basit bir çözümdür ve yukarıdaki cevap izlendikten sonra çalışır.
Zalakain

1
@Zalakain Ne yazık ki, mpl3d terk edilmiş gibi görünüyor .
P-Gn

5

mplcursors benim için çalıştı. mplcursors matplotlib için tıklanabilir ek açıklama sağlar. Mpldatacursor'dan çok esinlenmiştir ( https://github.com/joferkington/mpldatacursor basitleştirilmiş bir API ile )

import matplotlib.pyplot as plt
import numpy as np
import mplcursors

data = np.outer(range(10), range(1, 5))

fig, ax = plt.subplots()
lines = ax.plot(data)
ax.set_title("Click somewhere on a line.\nRight-click to deselect.\n"
             "Annotations can be dragged.")

mplcursors.cursor(lines) # or just mplcursors.cursor()

plt.show()

Bunu kendim kullanıyorum, acele eden biri için açık ara en kolay çözüm. Sadece 70 etiket çizdim ve matplotlibher 10. çizgiyi aynı renkte, böyle bir acı haline getirdim . mplcursorsolsa sıralar.
ajsp

5

Diğer cevaplar, Jupyter satır içi matplotlib figürünün son bir sürümünde araç ipuçlarını düzgün bir şekilde gösterme ihtiyacımı karşılamadı. Bu olsa çalışır:

import matplotlib.pyplot as plt
import numpy as np
import mplcursors
np.random.seed(42)

fig, ax = plt.subplots()
ax.scatter(*np.random.random((2, 26)))
ax.set_title("Mouse over a point")
crs = mplcursors.cursor(ax,hover=True)

crs.connect("add", lambda sel: sel.annotation.set_text(
    'Point {},{}'.format(sel.target[0], sel.target[1])))
plt.show()

Fare ile bir noktanın üzerinden geçerken aşağıdaki resim gibi bir şeye yol açar: resim açıklamasını buraya girin


3
Bunun kaynağı ( katılımsız
Victoria Stuart

Bunu jupyter laboratuvarında çalıştıramadım. Belki bir jupyter not defterinde çalışıyor, ancak jupyter laboratuvarında çalışmıyor mu?
MD004

3

Jupyter dizüstü bilgisayar kullanıyorsanız, çözümüm şu kadar basit:

%pylab
import matplotlib.pyplot as plt
import mplcursors
plt.plot(...)
mplcursors.cursor(hover=True)
plt.show()

Gibi bir şey alabilirsin resim açıklamasını buraya girin


Şimdiye kadar en iyi çözüm, OP'nin istediği şeyi sadece birkaç satır kod yapıyor
Tim Johnsen

0

Eklemek için çok satırlı bir ek açıklama sistemi yaptım: https://stackoverflow.com/a/47166787/10302020 . en güncel sürüm için: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Alt bölümdeki verileri değiştirmeniz yeterlidir.

import matplotlib.pyplot as plt


def update_annot(ind, line, annot, ydata):
    x, y = line.get_data()
    annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
    # Get x and y values, then format them to be displayed
    x_values = " ".join(list(map(str, ind["ind"])))
    y_values = " ".join(str(ydata[n]) for n in ind["ind"])
    text = "{}, {}".format(x_values, y_values)
    annot.set_text(text)
    annot.get_bbox_patch().set_alpha(0.4)


def hover(event, line_info):
    line, annot, ydata = line_info
    vis = annot.get_visible()
    if event.inaxes == ax:
        # Draw annotations if cursor in right position
        cont, ind = line.contains(event)
        if cont:
            update_annot(ind, line, annot, ydata)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            # Don't draw annotations
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()


def plot_line(x, y):
    line, = plt.plot(x, y, marker="o")
    # Annotation style may be changed here
    annot = ax.annotate("", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",
                        bbox=dict(boxstyle="round", fc="w"),
                        arrowprops=dict(arrowstyle="->"))
    annot.set_visible(False)
    line_info = [line, annot, y]
    fig.canvas.mpl_connect("motion_notify_event",
                           lambda event: hover(event, line_info))


# Your data values to plot
x1 = range(21)
y1 = range(0, 21)
x2 = range(21)
y2 = range(0, 42, 2)
# Plot line graphs
fig, ax = plt.subplots()
plot_line(x1, y1)
plot_line(x2, y2)
plt.show()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.