come posso creare un'unica legenda per molti sottotrame con matplotlib?


166

Sto tramando lo stesso tipo di informazioni, ma per paesi diversi, con più sottotrame con matplotlib. Cioè, ho 9 grafici su una griglia 3x3, tutti uguali per le linee (ovviamente, valori diversi per linea).

Tuttavia, non ho capito come mettere una sola legenda (poiché tutte e 9 le trame secondarie hanno le stesse linee) sulla figura solo una volta.

Come lo faccio?

Risposte:


161

C'è anche una bella funzione get_legend_handles_labels()che puoi chiamare sull'ultimo asse (se lo fai ripetutamente) che raccoglierebbe tutto ciò di cui hai bisogno dagli label=argomenti:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')

13
Questa dovrebbe essere la risposta migliore.
naught101

1
Questa è davvero una risposta molto più utile! Ha funzionato così in un caso più complicato per me.
Gmaravel,

1
risposta perfetta!
Dorgham,

4
Come rimuovo la legenda per le sottotrame?
BND,

5
Solo per aggiungere a questa grande risposta. Se hai un asse y secondario sui tuoi grafici e devi unirli entrambi usa questo:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Bill

114

figlegend potrebbe essere quello che stai cercando: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Esempio qui: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Un altro esempio:

plt.figlegend( lines, labels, loc = 'lower center', ncol=5, labelspacing=0. )

o:

fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )

1
Conosco le righe che voglio inserire nella legenda, ma come posso ottenere la linesvariabile da inserire nell'argomento legend?
patapouf_ai,

1
@patapouf_ai linesè un elenco di risultati che vengono restituiti axes.plot()(ovvero, ogni axes.plotroutine simile restituisce una "linea"). Vedi anche l'esempio collegato.

17

Per il posizionamento automatico di una singola legenda in una figurecon molti assi, come quelli ottenuti con subplots(), la seguente soluzione funziona davvero bene:

plt.legend( lines, labels, loc = 'lower center', bbox_to_anchor = (0,-0.1,1,1),
            bbox_transform = plt.gcf().transFigure )

Con bbox_to_anchore bbox_transform=plt.gcf().transFigurestai definendo un nuovo rettangolo di selezione della dimensione del tuo figureper essere un riferimento loc. Usando si (0,-0.1,1,1)sposta questa scatola bouding leggermente verso il basso per impedire che la leggenda venga posizionata sopra altri artisti.

OBS: utilizzare questa soluzione DOPO che si utilizza fig.set_size_inches()e PRIMA di utilizzarefig.tight_layout()


1
O semplicemente loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFiguree non si sovrapporrà di sicuro.
Assapora Josipovic il

2
Non sono ancora sicuro del perché, ma la soluzione di Evert non ha funzionato per me - la leggenda continuava a essere tagliata. Questa soluzione (insieme al commento di David) ha funzionato in modo molto pulito: la legenda è stata posizionata come previsto e completamente visibile. Grazie!
sudo make installa l'

16

Devi solo chiedere la legenda una volta, fuori dal tuo giro.

Ad esempio, in questo caso ho 4 sottotrame, con le stesse linee e una singola legenda.

from matplotlib.pyplot import *

ficheiros = ['120318.nc', '120319.nc', '120320.nc', '120321.nc']

fig = figure()
fig.suptitle('concentration profile analysis')

for a in range(len(ficheiros)):
    # dados is here defined
    level = dados.variables['level'][:]

    ax = fig.add_subplot(2,2,a+1)
    xticks(range(8), ['0h','3h','6h','9h','12h','15h','18h','21h']) 
    ax.set_xlabel('time (hours)')
    ax.set_ylabel('CONC ($\mu g. m^{-3}$)')

    for index in range(len(level)):
        conc = dados.variables['CONC'][4:12,index] * 1e9
        ax.plot(conc,label=str(level[index])+'m')

    dados.close()

ax.legend(bbox_to_anchor=(1.05, 0), loc='lower left', borderaxespad=0.)
         # it will place the legend on the outer right-hand side of the last axes

show()

3
figlegend, come suggerito da Evert, sembra essere una soluzione molto migliore;)
carla

11
il problema fig.legend()è che richiede l'identificazione per tutte le linee (grafici) ... poiché, per ogni sottotrama, sto usando un ciclo per generare le linee, l'unica soluzione che ho capito per superare questo è creare un elenco vuoto prima il secondo ciclo, quindi aggiungo le righe mentre vengono create ... Quindi uso questo elenco come argomento della fig.legend()funzione.
carla,

Una domanda simile qui
emmmphd

Che cosa dadosc'è?
Shyamkkhadka,

1
@Shyamkkhadka, nel mio script originale dadosc'era un set di dati da un file netCDF4 (per ciascuno dei file definiti nell'elenco ficheiros). In ogni ciclo, viene letto un file diverso e alla figura viene aggiunto un sottotrama.
carla,

14

Ho notato che nessuna risposta mostra un'immagine con una singola legenda che fa riferimento a molte curve in diverse sottotrame, quindi devo mostrartene una ... per renderti curioso ...

inserisci qui la descrizione dell'immagine

Ora, vuoi guardare il codice, vero?

from numpy import linspace
import matplotlib.pyplot as plt

# Calling the axes.prop_cycle returns an itertoools.cycle

color_cycle = plt.rcParams['axes.prop_cycle']()

# I need some curves to plot

x = linspace(0, 1, 51)
f1 = x*(1-x)   ; lab1 = 'x - x x'
f2 = 0.25-f1   ; lab2 = '1/4 - x + x x' 
f3 = x*x*(1-x) ; lab3 = 'x x - x x x'
f4 = 0.25-f3   ; lab4 = '1/4 - x x + x x x'

# let's plot our curves (note the use of color cycle, otherwise the curves colors in
# the two subplots will be repeated and a single legend becomes difficult to read)
fig, (a13, a24) = plt.subplots(2)

a13.plot(x, f1, label=lab1, **next(color_cycle))
a13.plot(x, f3, label=lab3, **next(color_cycle))
a24.plot(x, f2, label=lab2, **next(color_cycle))
a24.plot(x, f4, label=lab4, **next(color_cycle))

# so far so good, now the trick

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

# finally we invoke the legend (that you probably would like to customize...)

fig.legend(lines, labels)
plt.show()

Le due linee

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

merita una spiegazione - a questo scopo ho incapsulato la parte difficile in una funzione, solo 4 righe di codice ma pesantemente commentata

def fig_legend(fig, **kwdargs):

    # generate a sequence of tuples, each contains
    #  - a list of handles (lohand) and
    #  - a list of labels (lolbl)
    tuples_lohand_lolbl = (ax.get_legend_handles_labels() for ax in fig.axes)
    # e.g. a figure with two axes, ax0 with two curves, ax1 with one curve
    # yields:   ([ax0h0, ax0h1], [ax0l0, ax0l1]) and ([ax1h0], [ax1l0])

    # legend needs a list of handles and a list of labels, 
    # so our first step is to transpose our data,
    # generating two tuples of lists of homogeneous stuff(tolohs), i.e
    # we yield ([ax0h0, ax0h1], [ax1h0]) and ([ax0l0, ax0l1], [ax1l0])
    tolohs = zip(*tuples_lohand_lolbl)

    # finally we need to concatenate the individual lists in the two
    # lists of lists: [ax0h0, ax0h1, ax1h0] and [ax0l0, ax0l1, ax1l0]
    # a possible solution is to sum the sublists - we use unpacking
    handles, labels = (sum(list_of_lists, []) for list_of_lists in tolohs)

    # call fig.legend with the keyword arguments, return the legend object

    return fig.legend(handles, labels, **kwdargs)

PS Riconosco che sum(list_of_lists, [])è un metodo davvero inefficiente per appiattire un elenco di elenchi ma ① adoro la sua compattezza, ② di solito sono poche curve in alcune sottotrame e ③ Matplotlib ed efficienza? ;-)


3

Mentre piuttosto tardi per il gioco, darò un'altra soluzione qui poiché questo è ancora uno dei primi link da mostrare su Google. Usando matplotlib 2.2.2, questo può essere ottenuto usando la funzione gridspec. Nell'esempio seguente l'obiettivo è di disporre di quattro sottotrame disposte in modo 2x2 con la legenda mostrata in basso. Un asse "falso" viene creato nella parte inferiore per posizionare la legenda in un punto fisso. L'asse "finto" viene quindi disattivato in modo da mostrare solo la legenda. Risultato: https://i.stack.imgur.com/5LUWM.png .

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

#Gridspec demo
fig = plt.figure()
fig.set_size_inches(8,9)
fig.set_dpi(100)

rows   = 17 #the larger the number here, the smaller the spacing around the legend
start1 = 0
end1   = int((rows-1)/2)
start2 = end1
end2   = int(rows-1)

gspec = gridspec.GridSpec(ncols=4, nrows=rows)

axes = []
axes.append(fig.add_subplot(gspec[start1:end1,0:2]))
axes.append(fig.add_subplot(gspec[start2:end2,0:2]))
axes.append(fig.add_subplot(gspec[start1:end1,2:4]))
axes.append(fig.add_subplot(gspec[start2:end2,2:4]))
axes.append(fig.add_subplot(gspec[end2,0:4]))

line, = axes[0].plot([0,1],[0,1],'b')           #add some data
axes[-1].legend((line,),('Test',),loc='center') #create legend on bottommost axis
axes[-1].set_axis_off()                         #don't show bottommost axis

fig.tight_layout()
plt.show()

3

se si utilizzano sottotrame con grafici a barre, con colori diversi per ciascuna barra. potrebbe essere più veloce creare i manufatti usando te stessompatches

Supponi di avere quattro barre con colori diversi in quanto r m c kpuoi impostare la legenda come segue

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
labels = ['Red Bar', 'Magenta Bar', 'Cyan Bar', 'Black Bar']


#####################################
# insert code for the subplots here #
#####################################


# now, create an artist for each color
red_patch = mpatches.Patch(facecolor='r', edgecolor='#000000') #this will create a red bar with black borders, you can leave out edgecolor if you do not want the borders
black_patch = mpatches.Patch(facecolor='k', edgecolor='#000000')
magenta_patch = mpatches.Patch(facecolor='m', edgecolor='#000000')
cyan_patch = mpatches.Patch(facecolor='c', edgecolor='#000000')
fig.legend(handles = [red_patch, magenta_patch, cyan_patch, black_patch],labels=labels,
       loc="center right", 
       borderaxespad=0.1)
plt.subplots_adjust(right=0.85) #adjust the subplot to the right for the legend

1
+1 Il migliore! L'ho usato in questo modo aggiungendo direttamente al plt.legendper avere una legenda per tutte le mie sottotrame
Utente

È più veloce combinare le maniglie automatiche e le etichette fatte a mano handles, _ = plt.gca().get_legend_handles_labels()fig.legend(handles, labels)
:,

1

Questa risposta è un complemento di @ Evert nella posizione della legenda.

Il mio primo tentativo sulla soluzione di @Evert non è riuscito a causa della sovrapposizione della legenda e del titolo della sottotrama.

In effetti, le sovrapposizioni sono causate da fig.tight_layout(), il che modifica il layout delle sottotrame senza considerare la legenda della figura. Tuttavia, fig.tight_layout()è necessario.

Per evitare le sovrapposizioni, possiamo dire fig.tight_layout()di lasciare spazi per la leggenda della figura fig.tight_layout(rect=(0,0,1,0.9)).

Descrizione dei parametri tight_layout () .


1

Per basarsi sulla risposta di @ gboffi e Ben Usman:

In una situazione in cui si hanno linee diverse in sottotrame diverse con lo stesso colore ed etichetta, si può fare qualcosa lungo le linee di

labels_handles = {
  label: handle for ax in fig.axes for handle, label in zip(*ax.get_legend_handles_labels())
}

fig.legend(
  labels_handles.values(),
  labels_handles.keys(),
  loc="upper center",
  bbox_to_anchor=(0.5, 0),
  bbox_transform=plt.gcf().transFigure,
)
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.