Gemeinsames xlabel / ylabel für matplotlib-Subplots

139

Ich habe die folgende Handlung:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

und jetzt möchte ich diesem Plot gemeinsame x-Achsen-Beschriftungen und y-Achsen-Beschriftungen geben. Mit "common" meine ich, dass es eine große Beschriftung der x-Achse unter dem gesamten Raster der Unterzeichnungen und eine große Beschriftung der y-Achse rechts geben sollte. Ich kann in der Dokumentation für nichts dazu finden plt.subplots, und meine Googler schlagen vor, dass ich zunächst einen großen machen muss plt.subplot(111)- aber wie füge ich dann meine 5 * 2-Nebenhandlungen in diese ein plt.subplots?

jolindbe
quelle
2
Mit der Aktualisierung der Frage und den Kommentaren in den Antworten unten ist dies ein Duplikat von stackoverflow.com/questions/6963035/…
Hooked
Nicht wirklich, da meine Frage für plt.subplots () ist und die Frage, auf die Sie verlinken, add_subplot verwendet - ich kann diese Methode nur verwenden, wenn ich zu add_subplot wechsle, was ich vermeiden möchte. Ich könnte die plt.text-Lösung verwenden, die in Ihrem Link als alternative Lösung angegeben ist, aber sie ist nicht die eleganteste Lösung.
Jolindbe
Soweit ich weiß, können plt.subplots keine Unterplots in einer vorhandenen Achsenumgebung generieren, sondern erstellen immer eine neue Figur. Richtig?
Jolindbe
Eine äußerst elegante Lösung finden Sie hier: stackoverflow.com/questions/6963035/…
Mr.H
Ihr Link wurde von Benutzer Hooked vor mehr als 4 Jahren bereitgestellt (nur ein paar Kommentare über Ihrem). Wie ich bereits sagte, bezieht sich diese Lösung auf add_subplot und nicht auf plt.subplots ().
Jolindbe

Antworten:

206

Das sieht so aus, wie Sie es tatsächlich wollen. Der gleiche Ansatz dieser Antwort wird auf Ihren speziellen Fall angewendet:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(nrows=3, ncols=3, sharex=True, sharey=True, figsize=(6, 6))

fig.text(0.5, 0.04, 'common X', ha='center')
fig.text(0.04, 0.5, 'common Y', va='center', rotation='vertical')

Mehrere Diagramme mit gemeinsamer Achsenbeschriftung

Divenex
quelle
4
Beachten Sie, dass 0,5 für die x-Koordinate des x-Labels das Label nicht in der Mitte des mittleren Unterplots platziert. Sie müssten etwas größer werden, um die yticklabels zu berücksichtigen.
dbliss
2
Schauen Sie sich diese Antwort für eine Methode an, die nicht verwendet wird plt.text. Sie erstellen Ihre Unterzeichnungen, fügen dann aber eine Bit-Darstellung hinzu, machen sie unsichtbar und beschriften ihre x und y.
James Owers
Danke, hat im Allgemeinen funktioniert. Irgendeine Lösung gegen Bruch bei der Verwendung tight_layout?
serv-inc
3
@ serv-inc mit tight_layoutErsetzen 0.04durch 0scheint zu funktionieren.
Divenex
3
Verwenden fig.textist keine gute Idee. Dies bringt Dinge durcheinander wieplt.tight_layout()
Friedlicher
52

Da ich es für relevant und elegant genug halte (es müssen keine Koordinaten angegeben werden, um Text zu platzieren), kopiere ich (mit einer geringfügigen Anpassung) eine Antwort auf eine andere verwandte Frage .

import matplotlib.pyplot as plt
fig, axes = plt.subplots(5, 2, sharex=True, sharey=True, figsize=(6,15))
# add a big axis, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axis
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")

Dies führt zu folgenden Ergebnissen (mit matplotlib Version 2.2.0):

Unterzeichnungen mit 5 Zeilen und 2 Spalten mit gemeinsamen Beschriftungen für die x- und y-Achse

bli
quelle
3
Aus Gründen der Einfachheit sollte dies die akzeptierte Antwort sein. Sehr einfach. Immer noch relevant für matplotlib v3.x.
Kyle Swanson
Ich würde gerne wissen, wie es mit mehreren Figurenobjekten verwendet werden kann. fig.xlabel ("foo") funktioniert nicht.
Horror Vacui
Zu Ihrer
Information
@xyzzyqed Ich wusste nicht, dass es im Stackoverflow so etwas wie "Themen" gibt, und ich erinnere mich nicht einmal daran, wie ich die Figur exportiert habe. Wie kann ich den Hintergrund beim Exportieren steuern?
bli
2
Das einzige Problem dieser Lösung ist, dass sie bei der Verwendung nicht funktioniert, constrained_layout=Trueda überlappende Beschriftungen erstellt werden. In diesem Fall müssen Sie die Ränder der Unterzeichnungen manuell anpassen.
Baccandr
35

Ohne dass sharex=True, sharey=Truedu bekommst:

Geben Sie hier die Bildbeschreibung ein

Damit sollten Sie es schöner bekommen:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))

plt.tight_layout()

Geben Sie hier die Bildbeschreibung ein

Wenn Sie jedoch zusätzliche Beschriftungen hinzufügen möchten, sollten Sie diese nur zu den Randdiagrammen hinzufügen:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))
        if i == len(axes2d) - 1:
            cell.set_xlabel("noise column: {0:d}".format(j + 1))
        if j == 0:
            cell.set_ylabel("noise row: {0:d}".format(i + 1))

plt.tight_layout()

Geben Sie hier die Bildbeschreibung ein

Das Hinzufügen eines Etiketts für jedes Diagramm würde es verderben (möglicherweise gibt es eine Möglichkeit, wiederholte Etiketten automatisch zu erkennen, aber mir ist keines bekannt).

Piotr Migdal
quelle
Dies ist viel schwieriger, wenn beispielsweise die Anzahl der Diagramme unbekannt ist (z. B. haben Sie eine Funktion zum Verallgemeinern von Plots, die für eine beliebige Anzahl von Unterplots funktioniert).
naught101
15

Seit dem Befehl:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

Sie haben ein Tupel zurückgegeben, das aus der Abbildung und einer Liste der Achseninstanzen besteht. Es reicht bereits aus, etwas zu tun wie (Gedanken, zu denen ich geändert fig,axhabe fig,axes):

fig,axes = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

for ax in axes:
    ax.set_xlabel('Common x-label')
    ax.set_ylabel('Common y-label')

Wenn Sie zufällig einige Details zu einem bestimmten Unterplot ändern möchten, können Sie darauf zugreifen, axes[i]indem Sie iüber Ihre Unterplots iterieren.

Es kann auch sehr hilfreich sein, a aufzunehmen

fig.tight_layout()

am Ende der Datei vor dem plt.show(), um überlappende Beschriftungen zu vermeiden.

Marius
quelle
5
Es tut mir leid, dass ich oben etwas unklar war. Mit "common" meinte ich ein einzelnes x-Label unter allen Plots und ein einzelnes y-Label links neben den Plots. Ich habe die Frage aktualisiert, um dies widerzuspiegeln.
Jolindbe
2
@JohanLindberg: Zu Ihren Kommentaren hier und oben: In der Tat plt.subplots()wird eine neue Figureninstanz erstellt . Wenn Sie sich an diesen Befehl halten möchten, können Sie einfach eine hinzufügen big_ax = fig.add_subplot(111), da Sie bereits eine Figur haben und eine weitere Achse hinzufügen können. Danach können Sie big_axdie Art und Weise ändern, wie sie im Link von Hooked angezeigt wird.
Marius
Vielen Dank für Ihre Vorschläge, aber wenn ich das mache, muss ich big_ax nach plt.subplots () hinzufügen, und ich bekomme dieses Subplot über alles andere - kann ich es transparent machen oder es irgendwie nach hinten senden? Selbst wenn ich alle Farben wie in Hooked's Link auf keine gesetzt habe, ist es immer noch eine weiße Box, die alle meine Nebenhandlungen abdeckt.
Jolindbe
2
@JohanLindberg, du hast recht, das hatte ich nicht überprüft. Sie können die Hintergrundfarbe der großen Achse jedoch ganz einfach auf Folgendes einstellen none: big_ax.set_axis_bgcolor('none')Sie sollten auch die Etikettenfarbe erstellen none(im Gegensatz zu dem von Hooked verknüpften Beispiel):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Marius
2
Ich bekomme eine Fehlermeldung: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'in der Anweisung ax.set_xlabel('Common x-label'). Kannst du es herausfinden?
Hengxin
5

Es sieht besser aus, wenn Sie Platz für die allgemeinen Beschriftungen reservieren, indem Sie unsichtbare Beschriftungen für die Unterzeichnung in der unteren linken Ecke erstellen. Es ist auch gut, die Schriftgröße von rcParams zu übergeben. Auf diese Weise ändern sich die Größe der allgemeinen Beschriftungen mit Ihrem RC-Setup, und die Achsen werden ebenfalls angepasst, um Platz für die allgemeinen Beschriftungen zu lassen.

fig_size = [8, 6]
fig, ax = plt.subplots(5, 2, sharex=True, sharey=True, figsize=fig_size)
# Reserve space for axis labels
ax[-1, 0].set_xlabel('.', color=(0, 0, 0, 0))
ax[-1, 0].set_ylabel('.', color=(0, 0, 0, 0))
# Make common axis labels
fig.text(0.5, 0.04, 'common X', va='center', ha='center', fontsize=rcParams['axes.labelsize'])
fig.text(0.04, 0.5, 'common Y', va='center', ha='center', rotation='vertical', fontsize=rcParams['axes.labelsize'])

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

EL_DON
quelle
1
Gute Verwendung des unsichtbaren Etiketts! Vielen Dank
colelemonz
3

Beim Zeichnen eines Diagrammrasters stieß ich auf ein ähnliches Problem. Die Grafiken bestanden aus zwei Teilen (oben und unten). Das y-Label sollte über beiden Teilen zentriert sein.

Ich wollte keine Lösung verwenden, die davon abhängt, die Position in der äußeren Figur zu kennen (wie fig.text ()), also habe ich die y-Position der Funktion set_ylabel () manipuliert. Es ist normalerweise 0,5, die Mitte des Diagramms, zu dem es hinzugefügt wird. Da der Abstand zwischen den Teilen (hspace) in meinem Code Null war, konnte ich die Mitte der beiden Teile relativ zum oberen Teil berechnen.

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

# Create outer and inner grid
outerGrid = gridspec.GridSpec(2, 3, width_ratios=[1,1,1], height_ratios=[1,1])
somePlot = gridspec.GridSpecFromSubplotSpec(2, 1,
               subplot_spec=outerGrid[3], height_ratios=[1,3], hspace = 0)

# Add two partial plots
partA = plt.subplot(somePlot[0])
partB = plt.subplot(somePlot[1])

# No x-ticks for the upper plot
plt.setp(partA.get_xticklabels(), visible=False)

# The center is (height(top)-height(bottom))/(2*height(top))
# Simplified to 0.5 - height(bottom)/(2*height(top))
mid = 0.5-somePlot.get_height_ratios()[1]/(2.*somePlot.get_height_ratios()[0])
# Place the y-label
partA.set_ylabel('shared label', y = mid)

plt.show()

Bild

Nachteile:

  • Der horizontale Abstand zum Plot basiert auf dem oberen Teil, die unteren Häkchen können sich bis in das Etikett erstrecken.

  • Die Formel berücksichtigt keinen Abstand zwischen den Teilen.

  • Löst eine Ausnahme aus, wenn die Höhe des Oberteils 0 beträgt.

Es gibt wahrscheinlich eine allgemeine Lösung, die das Auffüllen zwischen Zahlen berücksichtigt.

CPe
quelle
Hey, ich habe einen Weg gefunden, dies im Sinne Ihrer Antwort zu tun, könnte aber einige dieser Probleme lösen. siehe stackoverflow.com/a/44020303/4970632 (unten)
Luke Davis
2

Aktualisieren:

Diese Funktion ist jetzt Teil des Proplot-Matplotlib-Pakets , das ich kürzlich auf pypi veröffentlicht habe. Wenn Sie Zahlen erstellen, werden die Beschriftungen standardmäßig von den Achsen "geteilt".


Ursprüngliche Antwort:

Ich habe eine robustere Methode entdeckt:

Wenn Sie die bottomund topkwargs kennen, die in eine GridSpecInitialisierung eingegangen sind , oder wenn Sie die Kantenpositionen Ihrer Achsen auf andere Weise in FigureKoordinaten kennen , können Sie die ylabel-Position auch in FigureKoordinaten mit einer ausgefallenen "Transformations" -Magie angeben . Beispielsweise:

import matplotlib.transforms as mtransforms
bottom, top = .1, .9
f, a = plt.subplots(nrows=2, ncols=1, bottom=bottom, top=top)
avepos = (bottom+top)/2
a[0].yaxis.label.set_transform(mtransforms.blended_transform_factory(
       mtransforms.IdentityTransform(), f.transFigure # specify x, y transform
       )) # changed from default blend (IdentityTransform(), a[0].transAxes)
a[0].yaxis.label.set_position((0, avepos))
a[0].set_ylabel('Hello, world!')

... und Sie sollten sehen, dass das Etikett immer noch entsprechend von links nach rechts angepasst wird, um eine Überlappung mit Ticklabels zu vermeiden, genau wie normal - aber jetzt wird es so angepasst, dass es immer genau zwischen den gewünschten Unterplots liegt.

Wenn Sie nicht einmal verwenden set_position, wird das ylabel standardmäßig genau in der Mitte der Abbildung angezeigt . Ich vermute, das liegt daran, dass beim endgültigen Zeichnen der Beschriftung matplotlib0,5 für die yKoordinate verwendet wird, ohne zu überprüfen, ob sich die zugrunde liegende Koordinatentransformation geändert hat.

Luke Davis
quelle