Microentradas: Evitar ciertas etiquetas en la leyenda en Matplotlib

A veces, me llegan ficheros de datos con datos cada hora o cada día y los quiero representar en un plot. Para ello, podría acumular los ficheros en uno solo y luego pintarlo pero como lo debo hacer en 'tiempo casi-real' se puede meter todo en un bucle while que espera los ficheros cada hora/día/lo que sea y va pintando cada variable por tramos. Por ejemplo, una aproximación podría ser la siguiente:

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('bmh')
%matplotlib inline

plt.figure(figsize = (12, 6))
for i in range(10):
    x = np.arange(i * 10, i * 10 + 10)
    y_var1 = np.random.randint(1, 5, 10)
    y_var2 = np.random.randint(5, 8, 10)
    plt.plot(x, y_var1, color = 'k', label = 'variable1')
    plt.plot(x, y_var2, color = 'g', label = 'variable2')
    plt.legend()
    plt.ylim(0, 9)

Como véis, en la gráfica anterior hay varios problemas pero como esta es una MicroEntrada solo nos vamos a centrar en el problema de las etiquetas repetidas en la leyenda.

¿Cómo podríamos evitar el meter tantas veces una etiqueta repetida?

Mi problema es que el bucle es o podría ser 'infinito' y tengo que inicializar las etiquetas de alguna forma. Si miro en esta respuesta encontrada en Stackoverflow dice que en la documentación se indica que "If label attribute is empty string or starts with “_”, those artists will be ignored." pero si busco aquí o en el enlace que indican en la respuesta en Stackoverflow no veo esa funcionalidad indicada en ningún sitio. Eso es porque aparecía en la versión 1.3.1 pero luego desapareció... Sin embargo podemos seguir usando esa funcionalidad aunque actualmente no esté documentada:

plt.figure(figsize = (12, 6))
for i in range(10):
    x = np.arange(i * 10, i * 10 + 10)
    y_var1 = np.random.randint(1, 5, 10)
    y_var2 = np.random.randint(5, 8, 10)
    plt.plot(x, y_var1, color = 'k', label = 'variable1' if i == 0 else "_esto_no_se_pintará")
    plt.plot(x, y_var2, color = 'g', label = 'variable2' if i == 0 else "_esto_tampoco")
    plt.legend()
    plt.ylim(0, 9)
Espero que a alguien le resulte útil.

Kiko Correoso

Licenciado y PhD en Ciencias Físicas, especializado en temas de física, meteorología, climatología, energías renovables, estadística, aprendizaje automático, análisis y visualización de datos. Apasionado de Python y su comunidad. Fundador de pybonacci y editor del sitio en el que se divulga Python, Ciencia y el conocimiento libre en español.

More Posts

Follow Me:
TwitterLinkedIn

4 thoughts on “Microentradas: Evitar ciertas etiquetas en la leyenda en Matplotlib

  1. He seguido la pista y he encontrado la información en el método legend() de Axes (http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend). Lo interesante es que indica que es el comportamiento por defecto, por lo que, en lugar de pasar al “artista” las etiquetas como argumentos, puede ser mejor asignárselas a través del método set_label únicamente para las dos primeras líneas:

    for i in range(10):
       ...
        line1, = plt.plot(x, y_var1, color = 'k')
        line2, = plt.plot(x, y_var2, color = 'g')
        if i==0:
            line1.set_label("variable1")
            line2.set_label("variable2")
        plt.legend()
        ....

    (Por si no se ve bien, dejo también el código en https://gist.github.com/chemacortes/74a238cd5ae19fd78bf320581fe4d126)

    1. Tu forma es más POO y, considero, más correcta, solo he indicado un ejemplo rápido y práctico para indicar el comportamiento.

      En el enlace que indicas sigo sin encontrar la información completa de este comportamiento. Supongo que te refieres a esta línea: “Specific lines can be excluded from the automatic legend element selection by defining a label starting with an underscore.”, pero ahí no indica nada de strings vacios.

      Tambien se podría usar el método “Axes.get_legend_handles_labels” (http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.get_legend_handles_labels) pero no permitiría hacerlo a priori o dinámicamente, solo una vez que ya dispones de todas las labels para legend o, si se hace dinámico, supongo que sería bastante costoso obtener los ‘handles’ y ‘labels’ en cada iteración. Aunque si esperas una hora/día a que te llegue el fichero igual no es un problema 😛

      1. No, qué va. Más POO sería así:

        import numpy as np
        import matplotlib.pyplot as plt
        plt.style.use('bmh')
        
        plt.figure(figsize = (12, 6))
        
        class OneShot:
        
            def __init__(self, label):
                self.label = label
        
            def __str__(self):
                res = self.label
                self.label = "_"
                return res
        
        label1 = MyLabel("variable1")
        label2 = MyLabel("variable2")
                
        for i in range(10):
            x = np.arange(i * 10, i * 10 + 10)
            y_var1 = np.random.randint(1, 5, 10)
            y_var2 = np.random.randint(5, 8, 10)
            line1, = plt.plot(x, y_var1, color = 'k', label=label1)
            line2, = plt.plot(x, y_var2, color = 'g', label=label2)
            plt.legend()
            plt.ylim(0, 9)
        
      2. Ya, ya, me refería a que usas los métodos de los objetos de matplotlib en lugar de usar, principalmente, el estilo de funciones de pyplot

Leave a Reply to pych3m4 Cancel reply

Your email address will not be published. Required fields are marked *