Wie erstelle ich mit matplotlib eine einzelne Legende für viele Nebenhandlungen?


166

Ich zeichne die gleiche Art von Informationen, aber für verschiedene Länder, mit mehreren Unterplots mit matplotlib. Das heißt, ich habe 9 Diagramme in einem 3x3-Raster, alle mit den gleichen für Linien (natürlich unterschiedliche Werte pro Linie).

Ich habe jedoch nicht herausgefunden, wie ich eine einzelne Legende (da alle 9 Nebenhandlungen die gleichen Linien haben) nur einmal auf die Figur setzen soll.

Wie mache ich das?

Antworten:


160

Es gibt auch eine nette Funktion, die get_legend_handles_labels()Sie auf der letzten Achse aufrufen können (wenn Sie darüber iterieren), die alles, was Sie brauchen, aus label=Argumenten sammelt :

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

13
Dies sollte die beste Antwort sein.
naught101

1
Dies ist in der Tat eine viel nützlichere Antwort! In einem komplizierteren Fall hat es für mich einfach so funktioniert.
gmaravel

1
perfekte Antwort!
Dorgham

4
Wie entferne ich die Legende für die Nebenhandlungen?
BND

5
Nur um diese großartige Antwort zu ergänzen. Wenn Sie eine sekundäre y-Achse in Ihren Plots haben und beide zusammenführen müssen, verwenden Sie diese:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Bill

114

figlegend könnte das sein, wonach Sie suchen: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Beispiel hier: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Ein anderes Beispiel:

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

oder:

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

1
Ich kenne die Zeilen, die ich in die Legende einfügen möchte, aber wie bekomme ich die linesVariable, für die das Argument eingefügt werden soll legend?
Patapouf_ai

1
@patapouf_ai linesist eine Liste von Ergebnissen, von denen zurückgegeben wird axes.plot()(dh jede axes.plotoder eine ähnliche Routine gibt eine "Zeile" zurück). Siehe auch das verknüpfte Beispiel.

17

Für die automatische Positionierung einer einzelnen Legende in a figuremit vielen Achsen, wie sie mit erhalten wurden subplots(), funktioniert die folgende Lösung sehr gut:

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

Mit bbox_to_anchorund bbox_transform=plt.gcf().transFiguredefinieren Sie einen neuen Begrenzungsrahmen mit der Größe Ihrer figureals Referenz loc. Mit (0,-0.1,1,1)bewegt wird diese Bouding-Box leicht nach unten bewegt, um zu verhindern, dass die Legende über anderen Künstlern platziert wird.

OBS: Verwenden Sie diese Lösung NACH der Verwendung fig.set_size_inches()und VOR der Verwendungfig.tight_layout()


1
Oder einfach loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFigureund es wird sich nicht sicher überlappen.
Davor Josipovic

2
Ich bin mir immer noch nicht sicher warum, aber Everts Lösung hat bei mir nicht funktioniert - die Legende wurde immer wieder abgeschnitten. Diese Lösung (zusammen mit davor's Kommentar) funktionierte sehr sauber - die Legende wurde wie erwartet platziert und vollständig sichtbar. Vielen Dank!
Sudo Make Install

16

Sie müssen nur einmal außerhalb Ihrer Schleife nach der Legende fragen.

In diesem Fall habe ich zum Beispiel 4 Nebenhandlungen mit denselben Zeilen und eine einzelne Legende.

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, wie von Evert vorgeschlagen, scheint eine viel bessere Lösung zu sein;)
Carla

11
Das Problem fig.legend()ist, dass für alle Linien (Diagramme) eine Identifizierung erforderlich ist. Da ich für jedes Unterplot eine Schleife zum Generieren der Linien verwende, besteht die einzige Lösung, die ich gefunden habe, um dies zu überwinden, darin, zuvor eine leere Liste zu erstellen die zweite Schleife, und dann die Zeilen anhängen, während sie erstellt werden ... Dann verwende ich diese Liste als Argument für die fig.legend()Funktion.
Carla

Eine ähnliche Frage hier
emmmphd

Was ist dadosda
Shyamkkhadka

1
@Shyamkkhadka, in meinem ursprünglichen Skript dadoswar ein Datensatz aus einer netCDF4-Datei (für jede der in der Liste definierten Dateien ficheiros). In jeder Schleife wird eine andere Datei gelesen und der Abbildung ein Unterplot hinzugefügt.
Carla

13

Mir ist aufgefallen, dass keine Antwort ein Bild mit einer einzelnen Legende anzeigt, die auf viele Kurven in verschiedenen Untergrundstücken verweist. Deshalb muss ich Ihnen eine zeigen ... um Sie neugierig zu machen ...

Geben Sie hier die Bildbeschreibung ein

Nun wollen Sie sich den Code ansehen, nicht wahr?

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

Die zwei Zeilen

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

Eine Erklärung verdienen - zu diesem Zweck habe ich den kniffligen Teil in einer Funktion zusammengefasst, nur 4 Codezeilen, aber stark kommentiert

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 Ich erkenne, dass dies sum(list_of_lists, [])eine wirklich ineffiziente Methode ist, um eine Liste von Listen zu reduzieren, aber ① Ich liebe ihre Kompaktheit, ② normalerweise ein paar Kurven in ein paar Unterplots und ③ Matplotlib und Effizienz? ;-);


3

Während ich ziemlich spät zum Spiel komme, werde ich hier eine andere Lösung geben, da dies immer noch einer der ersten Links ist, die auf Google angezeigt werden. Mit matplotlib 2.2.2 kann dies mithilfe der Gridspec-Funktion erreicht werden. Im folgenden Beispiel sollen vier Unterzeichnungen 2x2 angeordnet sein, wobei die Legende unten angezeigt wird. Unten wird eine "Faux" -Achse erstellt, um die Legende an einer festen Stelle zu platzieren. Die 'Faux'-Achse wird dann ausgeschaltet, sodass nur die Legende angezeigt wird. Ergebnis: 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

Wenn Sie Unterzeichnungen mit Balkendiagrammen verwenden, mit unterschiedlichen Farben für jeden Balken. Es kann schneller sein, die Artefakte selbst zu erstellenmpatches

Angenommen, Sie haben vier Balken mit unterschiedlichen Farben, da r m c kSie die Legende wie folgt festlegen können

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 Das Beste! Ich habe es auf diese Weise verwendet und direkt zu hinzugefügt plt.legend, um eine Legende für alle meine Nebenhandlungen zu haben
Benutzer

Es ist schneller, die automatischen Griffe und handgefertigten Etiketten zu kombinieren : handles, _ = plt.gca().get_legend_handles_labels(), dannfig.legend(handles, labels)
smcs

1

Diese Antwort ist eine Ergänzung zu @ Everts auf der Legendenposition.

Mein erster Versuch mit der Lösung von @ Evert schlug aufgrund von Überschneidungen der Legende und des Titels der Nebenhandlung fehl.

Tatsächlich werden die Überlappungen durch verursacht fig.tight_layout(), wodurch das Layout der Untergrundstücke ohne Berücksichtigung der Figurenlegende geändert wird. Jedoch,fig.tight_layout() jedoch notwendig.

Um die Überlappungen zu vermeiden, können wir durch anweisen fig.tight_layout(), Leerzeichen für die Legende der Figur zu lassen fig.tight_layout(rect=(0,0,1,0.9)).

Beschreibung der Parameter tight_layout () .


1

Um auf der Antwort von @ gboffi und Ben Usman aufzubauen:

In einer Situation, in der man verschiedene Linien in verschiedenen Untergrundstücken mit derselben Farbe und Beschriftung hat, kann man etwas in der Richtung von tun

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,
)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.