Eje secundario con twinx (): ¿cómo agregar a la leyenda?

288

Tengo una trama con dos ejes y, usando twinx(). También le doy etiquetas a las líneas y quiero mostrarlas legend(), pero solo consigo obtener las etiquetas de un eje en la leyenda:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')
ax.legend(loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Entonces solo obtengo las etiquetas del primer eje en la leyenda, y no la etiqueta 'temp' del segundo eje. ¿Cómo podría agregar esta tercera etiqueta a la leyenda?

ingrese la descripción de la imagen aquí

joris
fuente
44
[ No haga esto en ningún lugar remotamente cercano a ningún código de producción ] Cuando mi único objetivo es generar una hermosa trama con la leyenda apropiada lo antes posible, utilizo un truco feo de trazar una matriz vacía axcon el estilo que uso en ax2: en su caso, ax.plot([], [], '-r', label = 'temp'). Es mucho más rápido y sencillo que hacerlo correctamente ...
Neinstein

Respuestas:

370

Puede agregar fácilmente una segunda leyenda agregando la línea:

ax2.legend(loc=0)

Obtendrás esto:

ingrese la descripción de la imagen aquí

Pero si desea todas las etiquetas en una leyenda, debe hacer algo como esto:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(10)
temp = np.random.random(10)*30
Swdown = np.random.random(10)*100-10
Rn = np.random.random(10)*100-10

fig = plt.figure()
ax = fig.add_subplot(111)

lns1 = ax.plot(time, Swdown, '-', label = 'Swdown')
lns2 = ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
lns3 = ax2.plot(time, temp, '-r', label = 'temp')

# added these three lines
lns = lns1+lns2+lns3
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Lo que te dará esto:

ingrese la descripción de la imagen aquí

Pablo
fuente
2
Esto falla con las errorbartramas. Para obtener una solución que los maneje correctamente, consulte a continuación: stackoverflow.com/a/10129461/1319447
Davide
1
Para evitar que se superpongan dos leyendas, como en mi caso, donde especifiqué dos .legend (loc = 0), debe especificar dos valores diferentes para el valor de ubicación de la leyenda (ambos distintos de 0). Ver: matplotlib.org/api/legend_api.html
Roalt el
Tuve algunos problemas para agregar una sola línea a alguna subtrama con varias líneas ax1. En este caso, utilice lns1=ax1.linesy luego agregue lns2a esta lista.
Little Bobby Tables
Los diferentes valores utilizados por locse explican aquí
Dror
1
Consulte la respuesta a continuación para obtener una forma más automática (con matplotlib> = 2.1): stackoverflow.com/a/47370214/653364
joris
183

No estoy seguro de si esta funcionalidad es nueva, pero también puede usar el método get_legend_handles_labels () en lugar de realizar un seguimiento de las líneas y las etiquetas usted mismo:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

pi = np.pi

# fake data
time = np.linspace (0, 25, 50)
temp = 50 / np.sqrt (2 * pi * 3**2) \
        * np.exp (-((time - 13)**2 / (3**2))**2) + 15
Swdown = 400 / np.sqrt (2 * pi * 3**2) * np.exp (-((time - 13)**2 / (3**2))**2)
Rn = Swdown - 10

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')

# ask matplotlib for the plotted objects and their labels
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()
zgana
fuente
1
Esta es la única solución que puede manejar los ejes donde las parcelas se superponen con las leyendas (el último eje es el que debería trazar las leyendas)
Amelio Vazquez-Reina
55
Esta solución también funciona con errorbargráficas, mientras que la aceptada falla (mostrando una línea y sus barras de error por separado, y ninguna de ellas con la etiqueta correcta). Además, es más simple.
Davide
pequeño inconveniente: no funciona si desea sobrescribir la etiqueta ax2y no tiene un conjunto desde el principio
Ciprian Tomoiagă
Observación: Para trazados clásicos, no necesita especificar el argumento de la etiqueta. Pero para otros, por ejemplo. barras que necesitas.
belka
Esto también facilita mucho las cosas si no sabe de antemano cuántas líneas se trazarán.
Vegard Jervell
77

Desde matplotlib versión 2.1 en adelante, puede usar una leyenda de figura . En lugar de ax.legend(), que produce una leyenda con los mangos de los ejes ax, se puede crear una leyenda de figura

fig.legend (loc = "superior derecha")

que reunirá todos los identificadores de todas las subtramas de la figura. Dado que es una leyenda de figura, se colocará en la esquina de la figura, y el locargumento es relativo a la figura.

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,10)
y = np.linspace(0,10)
z = np.sin(x/3)**2*98

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y, '-', label = 'Quantity 1')

ax2 = ax.twinx()
ax2.plot(x,z, '-r', label = 'Quantity 2')
fig.legend(loc="upper right")

ax.set_xlabel("x [units]")
ax.set_ylabel(r"Quantity 1")
ax2.set_ylabel(r"Quantity 2")

plt.show()

ingrese la descripción de la imagen aquí

Para volver a colocar la leyenda en los ejes, uno debería suministrar ay bbox_to_anchora bbox_transform. El último sería la transformación de los ejes de los ejes en los que debería residir la leyenda. El primero puede ser las coordenadas del borde definidas por las coordenadas de los locejes.

fig.legend(loc="upper right", bbox_to_anchor=(1,1), bbox_transform=ax.transAxes)

ingrese la descripción de la imagen aquí

Importancia de ser ernesto
fuente
Entonces, ¿la versión 2.1 ya está lanzada? Pero en Anaconda 3, probé conda upgrade matplotlibno encontré nuevas versiones, todavía estoy usando v.2.0.2
StayFoolish
1
Esta es una forma más limpia de lograr el resultado final.
Goutham
1
hermosa y pitónica
DanGoodrick
1
Esto no parece funcionar cuando tiene muchas subtramas. Agrega una sola leyenda para todas las subtramas. Normalmente, se necesita una leyenda para cada subtrama, que contiene series en los ejes primario y secundario en cada leyenda.
sancho.s ReinstateMonicaCellio
@sancho Correcto, eso es lo que está escrito en la tercera oración de esta respuesta, "... que reunirá todos los identificadores de todas las subtramas de la figura".
ImportanceOfBeingErnest
38

Puede obtener fácilmente lo que desea agregando la línea en ax:

ax.plot([], [], '-r', label = 'temp')

o

ax.plot(np.nan, '-r', label = 'temp')

Esto no trazaría nada más que agregar una etiqueta a la leyenda de ax.

Creo que esta es una manera mucho más fácil. No es necesario rastrear líneas automáticamente cuando solo tiene unas pocas líneas en los segundos ejes, ya que la fijación a mano como la anterior sería bastante fácil. De todos modos, depende de lo que necesites.

El código completo es el siguiente:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(22.)
temp = 20*np.random.rand(22)
Swdown = 10*np.random.randn(22)+40
Rn = 40*np.random.rand(22)

fig = plt.figure()
ax = fig.add_subplot(111)
ax2 = ax.twinx()

#---------- look at below -----------

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')

ax2.plot(time, temp, '-r')  # The true line in ax2
ax.plot(np.nan, '-r', label = 'temp')  # Make an agent in ax

ax.legend(loc=0)

#---------------done-----------------

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

La trama es la siguiente:

ingrese la descripción de la imagen aquí


Actualización: agregue una versión mejor:

ax.plot(np.nan, '-r', label = 'temp')

Esto no hará nada mientras plot(0, 0)pueda cambiar el rango del eje.


Un ejemplo extra para dispersión

ax.scatter([], [], s=100, label = 'temp')  # Make an agent in ax
ax2.scatter(time, temp, s=10)  # The true scatter in ax2

ax.legend(loc=1, framealpha=1)
Syrtis Major
fuente
3
Me gusta esto. Es un poco feo en la forma en que "engaña" al sistema, pero es muy sencillo de implementar.
Daniel Power
Esto es realmente simple de implementar. Pero cuando se usa esto con dispersión, el tamaño de dispersión resultante en la leyenda es solo un pequeño punto.
greeeeeeen
@greeeeeeen Entonces solo debe especificar el tamaño del marcador al hacer el diagrama de dispersión :-)
Syrtis Major
@SyrtisMajor I, por supuesto, lo intenté. Pero eso no cambió el tamaño del marcador en la leyenda.
greeeeeeen
@greeeeeeen ¿Ha cambiado el tamaño del marcador de la dispersión del agente? Vea mi publicación, agregué un fragmento de código de ejemplo.
Syrtis Major
7

Un truco rápido que puede satisfacer tus necesidades.

Retire el marco de la caja y coloque manualmente las dos leyendas una al lado de la otra. Algo como esto..

ax1.legend(loc = (.75,.1), frameon = False)
ax2.legend( loc = (.75, .05), frameon = False)

Donde la tupla loc es porcentajes de izquierda a derecha y de abajo hacia arriba que representan la ubicación en el gráfico.

usuario2105997
fuente
5

Encontré un siguiente ejemplo oficial de matplotlib que usa host_subplot para mostrar múltiples ejes y y todas las diferentes etiquetas en una leyenda. No hay solución alternativa necesaria. La mejor solución que encontré hasta ahora. http://matplotlib.org/examples/axes_grid/demo_parasite_axes2.html

from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt

host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()

offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
                                    axes=par2,
                                    offset=(offset, 0))

par2.axis["right"].toggle(all=True)

host.set_xlim(0, 2)
host.set_ylim(0, 2)

host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")

p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")

par1.set_ylim(0, 4)
par2.set_ylim(1, 65)

host.legend()

plt.draw()
plt.show()
gerrit
fuente
¡Bienvenido a Stack Overflow! Indique la parte más relevante del enlace, en caso de que no se pueda acceder al sitio de destino o se desconecte permanentemente. Vea ¿Cómo escribo una buena respuesta ? Concéntrese en preguntas más actuales en el futuro, esta tiene casi 4 años.
ByteHamster
De hecho, es un buen hallazgo, pero desearía que hubieras tomado lo que aprendiste del ejemplo, aplicado al MWE del OP e incluido una imagen.
aeroNotAuto