Cómo agregar anotaciones flotantes a un gráfico

12 minutos de lectura

avatar de usuario
jdmcbr

Estoy usando matplotlib para hacer diagramas de dispersión. Cada punto en el diagrama de dispersión está asociado con un objeto con nombre. Me gustaría poder ver el nombre de un objeto cuando paso el cursor sobre el punto en el diagrama de dispersión asociado con ese objeto. En particular, sería bueno poder ver rápidamente los nombres de los puntos que son atípicos. Lo más parecido que he podido encontrar mientras buscaba aquí es el comando de anotación, pero parece crear una etiqueta fija en el gráfico. Desafortunadamente, con la cantidad de puntos que tengo, el diagrama de dispersión sería ilegible si etiquetara cada punto. ¿Alguien sabe de una manera de crear etiquetas que solo aparecen cuando el cursor pasa cerca de ese punto?

  • Es posible que las personas que terminan aquí a través de la búsqueda también deseen verificar esta respuesta, que es bastante compleja, pero podría ser adecuada según los requisitos.

    – La Importancia De Ser Ernesto

    7 de noviembre de 2017 a las 21:03

avatar de usuario
La Importancia De SerErnest

Parece que ninguna de las otras respuestas aquí responde realmente a la pregunta. Así que aquí hay un código que usa un dispersión y muestra un anotación al flotando sobre los puntos de dispersión.

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

ingrese la descripción de la imagen aquí

Porque la gente también quiere usar esta solución para una línea. plot en lugar de una dispersión, la siguiente sería la misma solución para plot (que funciona de forma ligeramente diferente).

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

x = np.sort(np.random.rand(15))
y = np.sort(np.random.rand(15))
names = np.array(list("ABCDEFGHIJKLMNO"))

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

fig,ax = plt.subplots()
line, = plt.plot(x,y, marker="o")

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):
    x,y = line.get_data()
    annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
    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_alpha(0.4)


def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = line.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()

En caso de que alguien esté buscando una solución para líneas en ejes gemelos, consulte ¿Cómo hacer que aparezcan etiquetas al pasar el mouse sobre un punto en varios ejes?

En caso de que alguien esté buscando una solución para gráficos de barras, consulte, por ejemplo, esta respuesta.

  • ¡Muy agradable! Una nota, me di cuenta de que ind["ind"] es en realidad una lista de índices para todos los puntos bajo el cursor. Esto significa que el código anterior en realidad le da acceso a todos los puntos en una posición determinada, y no solo al punto más alto. Por ejemplo, si tiene dos puntos superpuestos, el texto podría leer 1 2, B C o incluso 1 2 3, B C D si tuvieras 3 puntos superpuestos.

    – Jvinniec

    14 de noviembre de 2017 a las 13:45

  • @Jvinniec Exactamente, hay deliberadamente uno de esos casos en el gráfico anterior (el punto verde y rojo en x ~ 0.4). Si pasas el cursor sobre él, se mostrará 0 8, A I(ver imagen).

    – La Importancia De Ser Ernesto

    14/11/2017 a las 13:50

  • @ImportanceOfBeingErnest este es un gran código, pero cuando se desplaza y se mueve en un punto llama fig.canvas.draw_idle() muchas veces (incluso cambia el cursor a inactivo). Lo resolví almacenando el índice anterior y comprobando si ind["ind"][0] == prev_ind. Luego, actualice solo si se mueve de un punto a otro (actualizar texto), deja de desplazarse (hace que la anotación sea invisible) o comienza a desplazarse (hace que la anotación sea visible). Con este cambio es mucho más limpio y eficiente.

    – Sembei Norimaki

    14 de diciembre de 2017 a las 15:02


  • @Konstantin Sí, esta solución funcionará cuando se use %matplotlib notebook en un cuaderno IPython/Jupyter.

    – La Importancia De Ser Ernesto

    25 de agosto de 2018 a las 8:30

  • @OriolAbril (y todos los demás), si tiene un problema que surgió al modificar el código de esta respuesta, haga una pregunta al respecto, enlace a esta respuesta y muestre el código que ha intentado. No tengo forma de saber qué está mal con cada uno de sus códigos sin verlo.

    – La Importancia De Ser Ernesto

    26 de junio de 2019 a las 14:29

avatar de usuario
mbernasocchi

Esta solución funciona al pasar el cursor sobre una línea sin necesidad de hacer clic en ella:

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

  • Muy útil +1ed. Probablemente necesite ‘rebotar’ esto porque motion_notify_event se repetirá para el movimiento dentro del área de la curva. Simplemente comprobar que el objeto de la curva es igual a la curva anterior parece funcionar.

    – bvanlew

    5 de diciembre de 2016 a las 10:58

  • Hmm, esto no funcionó de inmediato para mí (tan pocas cosas funcionan con matplotlib…) – funciona esto con ipython/jupyter cuadernos? ¿Funciona también cuando hay varias subtramas? ¿Qué pasa con un gráfico de barras en lugar de un gráfico de líneas?

    – dwanderson

    24 de enero de 2017 a las 20:17

  • Esto imprime la etiqueta en la consola cuando se desplaza. Qué pasa hacer que la etiqueta aparezca en la imagen al flotar? Entendí que esa era la pregunta.

    – Nikana Reklawyks

    6 de febrero de 2017 a las 19:14

  • @mbernasocchi muchas gracias, ¿qué necesito para alimentar el argumento gid si quiero ver un histograma (uno diferente para cada punto de la dispersión) o, mejor aún, un mapa de calor de un histograma 2D?

    – Amitai

    28 de junio de 2017 a las 13:28


  • @NikanaReklawyks Agregué una respuesta que en realidad responde la pregunta.

    – La Importancia De Ser Ernesto

    7 de noviembre de 2017 a las 20:52

avatar de usuario
cíborg

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

  • Esto hace justo lo que necesito, gracias! Como beneficio adicional, para implementarlo, reescribí mi programa para que, en lugar de crear dos diagramas de dispersión separados en diferentes colores en la misma figura para representar dos conjuntos de datos, copie el método del ejemplo para asignar color a un punto. Esto hizo que mi programa fuera un poco más fácil de leer y menos código. ¡Ahora busque una guía para convertir un color en un número!

    – jdmcbr

    27 de octubre de 2011 a las 1:09


  • Esto es para diagramas de dispersión. ¿Qué pasa con los diagramas de líneas? Traté de hacer que funcione en ellos, pero no lo hace. ¿Hay una solución?

    – Sohaib

    28 de agosto de 2014 a las 5:12

  • @Sohaib Ver mi respuesta

    – Inundación de Texas

    1 de agosto de 2015 a las 17:14

  • Tengo una pregunta sobre esto. Cuando hago un diagrama de dispersión de mis puntos de esta manera: plt.scatter(X_reduced[y == i, 0]X_reducido[y == i, 1], c=c, label=target_name, picker=True) con un zip para i, c y target_name, ¿el orden de mis índices está en mal estado? ¿Y ya no puedo buscar a qué punto de datos pertenece?

    – Chris

    5 de noviembre de 2015 a las 13:01

  • Esto no parece funcionar para portátiles jupyter 5 con ipython 5. ¿Hay una manera fácil de solucionarlo? los print la declaración también debe usar parens para compatibilidad con python 3

    – nealmcb

    30 de abril de 2017 a las 3:15

avatar de usuario
yuchao jiang

  • La opción más fácil es utilizar el mplcursors paquete
  • Esto debe trazarse en una ventana interactiva, no en línea.
    • Para jupyter, ejecutando algo como %matplotlib qt en una celda activará el trazado interactivo. Consulte ¿Cómo puedo abrir la ventana interactiva de matplotlib en el cuaderno de IPython?
  • Probado en python 3.10, pandas 1.4.2, matplotlib 3.5.1, seaborn 0.11.2
import matplotlib.pyplot as plt
import pandas_datareader as web  # only for test data; must be installed with conda or pip
from mplcursors import cursor  # separate package must be installed

# reproducible sample data as a pandas dataframe
df = web.DataReader('aapl', data_source="yahoo", start="2021-03-09", end='2022-06-13')

plt.figure(figsize=(12, 7))
plt.plot(df.index, df.Close)
cursor(hover=True)
plt.show()

ingrese la descripción de la imagen aquí

pandas

ax = df.plot(y='Close', figsize=(10, 7))
cursor(hover=True)
plt.show()

ingrese la descripción de la imagen aquí

nacido en el mar

  • Funciona con gráficos a nivel de ejes como sns.lineploty diagramas de nivel de figura como sns.relplot.
import seaborn as sns

# load sample data
tips = sns.load_dataset('tips')

sns.relplot(data=tips, x="total_bill", y="tip", hue="day", col="time")
cursor(hover=True)
plt.show()

ingrese la descripción de la imagen aquí

Las otras respuestas no abordaron mi necesidad de mostrar correctamente la información sobre herramientas en una versión reciente de la figura matplotlib en línea de Jupyter. Este funciona sin embargo:

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

Lo que lleva a algo como la siguiente imagen al pasar sobre un punto con el mouse:
ingrese la descripción de la imagen aquí

  • La fuente de esto (sin atribuir) es mplcursors.readthedocs.io/en/stable/examples/hover.html

    – Victoria Estuardo

    19 de junio de 2019 a las 18:41


  • No pude hacer que esto funcionara en el laboratorio de jupyter. ¿Quizás funcione en un cuaderno jupyter pero no en el laboratorio jupyter?

    – MD004

    23 de enero de 2020 a las 19:19

  • Hmm… no estoy seguro de que sea un gran problema no atribuir fragmentos de código de la documentación de una biblioteca.

    – A la derecha

    12 de noviembre de 2020 a las 18:41

  • @ MD004 Consulte stackoverflow.com/questions/50149562/… para jupyterlab; puede agregar el “widget %matplotlib” para que esto funcione.

    – A la derecha

    12 de noviembre de 2020 a las 18:42

avatar de usuario
endolito

Una ligera edición en un ejemplo proporcionado en http://matplotlib.org/users/shell.html:

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

Esto traza una trama de línea recta, como preguntaba Sohaib

  • La fuente de esto (sin atribuir) es mplcursors.readthedocs.io/en/stable/examples/hover.html

    – Victoria Estuardo

    19 de junio de 2019 a las 18:41


  • No pude hacer que esto funcionara en el laboratorio de jupyter. ¿Quizás funcione en un cuaderno jupyter pero no en el laboratorio jupyter?

    – MD004

    23 de enero de 2020 a las 19:19

  • Hmm… no estoy seguro de que sea un gran problema no atribuir fragmentos de código de la documentación de una biblioteca.

    – A la derecha

    12 de noviembre de 2020 a las 18:41

  • @ MD004 Consulte stackoverflow.com/questions/50149562/… para jupyterlab; puede agregar el “widget %matplotlib” para que esto funcione.

    – A la derecha

    12 de noviembre de 2020 a las 18:42

mpld3 resolverlo para mí. EDITAR (CÓDIGO AÑADIDO):

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

Puedes comprobar este ejemplo

  • Incluya un código de muestra y no se limite a vincular a fuentes externas sin contexto ni información. Consulte el Centro de ayuda para obtener más información.

    -Joseph Farah

    9 de junio de 2017 a las 2:25

  • desafortunadamente, mpld3 ya no se mantiene activamente a partir de julio de 2017

    –Ben Lindsay

    25 de julio de 2017 a las 2:27

  • El ejemplo de código falla con un TypeError: array([1.]) is not JSON serializable.

    – P-Gn

    16 de febrero de 2018 a las 9:31

  • @P-Gn simplemente siga el truco aquí stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 es una solución simple para esto y una vez que se sigue la respuesta anterior, funciona.

    – Zalakain

    24 de abril de 2019 a las 15:05


  • @Zalakain Desafortunadamente, mpl3d parece estar abandonado.

    – P-Gn

    2 mayo 2019 a las 12:00

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad