Asse secondario con twinx (): come aggiungere alla legenda?


288

Ho una trama con due assi y, usando twinx(). Dò anche etichette alle linee e voglio mostrarle con legend(), ma riesco solo a ottenere le etichette di un asse nella legenda:

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

Quindi ho solo le etichette del primo asse nella legenda e non l'etichetta "temp" del secondo asse. Come potrei aggiungere questa terza etichetta alla legenda?

inserisci qui la descrizione dell'immagine


4
[ Non farlo da nessuna parte in remoto vicino a qualsiasi codice di produzione ] Quando il mio unico obiettivo è generare una trama meravigliosa con la leggenda appropriata APPENA POSSIBILE, uso un brutto hack di tracciare un array vuoto axcon lo stile che uso su ax2: in il tuo caso ax.plot([], [], '-r', label = 'temp'),. È molto più veloce e più semplice che farlo correttamente ...
Neinstein,

Risposte:


370

Puoi aggiungere facilmente una seconda legenda aggiungendo la riga:

ax2.legend(loc=0)

Otterrai questo:

inserisci qui la descrizione dell'immagine

Ma se vuoi tutte le etichette su una legenda, dovresti fare qualcosa del genere:

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

Che ti darà questo:

inserisci qui la descrizione dell'immagine


2
Questo fallisce con le errorbartrame. Per una soluzione che li gestisce correttamente, vedere di seguito: stackoverflow.com/a/10129461/1319447
Davide,

1
Per evitare due legende sovrapposte come nel mio caso in cui ho specificato due .legend (loc = 0), è necessario specificare due valori diversi per il valore della posizione della legenda (entrambi diversi da 0). Vedi: matplotlib.org/api/legend_api.html
Roalt

Ho avuto qualche problema ad aggiungere una singola linea a qualche sottotrama con più linee ax1. In questo caso, utilizzare lns1=ax1.linese quindi aggiungere lns2a questo elenco.
Tavolini Bobby,

I diversi valori usati locsono spiegati qui
Dror

1
Vedere la risposta qui sotto per un modo più automatico (con matplotlib> = 2.1): stackoverflow.com/a/47370214/653364
Joris

183

Non sono sicuro che questa funzionalità sia nuova, ma puoi anche utilizzare il metodo get_legend_handles_labels () piuttosto che tenere traccia delle linee e delle etichette:

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

1
Questa è l'unica soluzione in grado di gestire gli assi in cui le trame si sovrappongono alle legende (l'ultimo asse è quello che dovrebbe tracciare le legende)
Amelio Vazquez-Reina

5
Questa soluzione funziona anche con i errorbargrafici, mentre quella accettata fallisce (mostrando una linea e i suoi errorbar separatamente, e nessuno di loro con l'etichetta giusta). Inoltre, è più semplice.
Davide,

leggera cattura: non funziona se si desidera sovrascrivere l'etichetta ax2e non ha un set dall'inizio
Ciprian Tomoiagă

Nota: per i grafici classici, non è necessario specificare l'argomento etichetta. Ma per altri, ad es. bar di cui hai bisogno.
belka,

Questo rende anche tutto molto più semplice se non sai in anticipo quante linee verranno tracciate.
Vegard Jervell,

77

Dalla versione 2.1 di matplotlib in poi, puoi usare una legenda di figure . Invece ax.legend(), che produce una legenda con le maniglie degli assi ax, si può creare una legenda di figure

fig.legend (loc = "in alto a destra")

che raccoglierà tutti gli handle da tutti i sottotrame nella figura. Poiché è una legenda di una figura, verrà posizionata nell'angolo della figura e l' locargomento è relativo alla 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()

inserisci qui la descrizione dell'immagine

Per rimettere la legenda negli assi, si forniscono a bbox_to_anchore a bbox_transform. Quest'ultimo sarebbe la trasformazione degli assi degli assi in cui dovrebbe risiedere la legenda. Il primo potrebbe essere le coordinate del bordo definite da locdate nelle coordinate degli assi.

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

inserisci qui la descrizione dell'immagine


Quindi, la versione 2.1 è già stata rilasciata? Ma in Anaconda 3, non ho provato conda upgrade matplotlibnessuna versione più recente trovata, sto ancora usando la v.2.0.2
StayFoolish il

1
Questo è un modo più pulito per raggiungere il risultato finale.
Goutham,

1
bella e pitonica
DanGoodrick,

1
Questo non sembra funzionare quando si hanno molti sottotrame. Aggiunge una singola legenda per tutte le sottotrame. In genere è necessaria una legenda per ogni sottotrama, contenente serie in assi primario e secondario in ciascuna legenda.
sancho.s ReinstateMonicaCellio

@sancho Corretto, questo è ciò che è scritto nella terza frase di questa risposta, "... che raccoglierà tutti gli handle da tutti i sottotrame nella figura.".
ImportanceOfBeingErnest

38

Puoi facilmente ottenere quello che vuoi aggiungendo la linea in ax:

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

o

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

Ciò non complicherebbe altro che aggiungere un'etichetta alla legenda dell'ascia.

Penso che questo sia un modo molto più semplice. Non è necessario tracciare automaticamente le linee quando ci sono solo poche linee nei secondi assi, poiché il fissaggio a mano come sopra sarebbe abbastanza semplice. Comunque, dipende da cosa ti serve.

L'intero codice è il seguente:

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 è la seguente:

inserisci qui la descrizione dell'immagine


Aggiornamento: aggiungi una versione migliore:

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

Questo non farà nulla mentre plot(0, 0)potrebbe cambiare l'intervallo degli assi.


Un esempio in più per scatter

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)

3
Mi piace questo. È un po 'brutto nel modo in cui "inganna" il sistema, ma è così semplice da implementare.
Daniel Power,

Questo è davvero semplice da implementare. Ma quando si utilizza questo con scatter, la dimensione di scatter risultante nella legenda è solo un piccolo punto.
greeeeeeen,

@greeeeeeen Quindi devi solo specificare la dimensione del marker quando fai il grafico a dispersione :-)
Syrtis Major

@SyrtisMajor I, ovviamente, l'ho provato. Ma ciò non ha cambiato la dimensione del marker nella legenda.
greeeeeeen,

@greeeeeeen Hai modificato la dimensione del marker della dispersione dell'agente? Vedi il mio post, ho aggiunto uno snippet di codice di esempio.
Syrtis Major,

7

Un trucco rapido che può soddisfare le tue esigenze.

Togliere il telaio della scatola e posizionare manualmente le due legende una accanto all'altra. Qualcosa come questo..

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

Dove la tupla loc è percentuali da sinistra a destra e dal basso verso l'alto che rappresentano la posizione nel grafico.


5

Ho trovato un seguente esempio ufficiale di matplotlib che utilizza host_subplot per visualizzare più assi Y e tutte le diverse etichette in una legenda. Nessuna soluzione alternativa necessaria. La migliore soluzione che ho trovato finora. 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()

Benvenuto in Stack Overflow! Si prega di citare la parte più rilevante del collegamento, nel caso in cui il sito di destinazione non sia raggiungibile o sia permanentemente offline. Vedi Come posso scrivere una buona risposta . Concentrati sulle domande più attuali in futuro, questa ha quasi 4 anni.
ByteHamster

In effetti una buona scoperta, ma vorrei che avessi preso ciò che hai appreso dall'esempio, applicato al MWE del PO e incluso un'immagine.
aeroNotAuto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.