Sekundärachse mit twinx (): Wie füge ich der Legende hinzu?

288

Ich habe ein Diagramm mit zwei y-Achsen twinx(). Ich gebe den Linien auch Beschriftungen und möchte sie mit anzeigen legend(), aber es gelingt mir nur, die Beschriftungen einer Achse in der Legende zu erhalten:

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

Ich bekomme also nur die Beschriftungen der ersten Achse in der Legende und nicht die Beschriftung 'temp' der zweiten Achse. Wie könnte ich dieses dritte Label zur Legende hinzufügen?

Geben Sie hier die Bildbeschreibung ein

Joris
quelle
4
[ Tun Sie dies nirgendwo in der Nähe eines Produktionscodes ] Wenn mein einziges Ziel darin besteht, so schnell wie möglich einen schönen Plot mit der entsprechenden Legende zu erstellen, verwende ich einen hässlichen Hack, um ein leeres Array axmit dem Stil zu zeichnen, den ich verwende ax2: in Ihr Fall , ax.plot([], [], '-r', label = 'temp'). Es ist viel schneller und einfacher als es richtig zu machen ...
Neinstein

Antworten:

370

Sie können ganz einfach eine zweite Legende hinzufügen, indem Sie die folgende Zeile hinzufügen:

ax2.legend(loc=0)

Sie erhalten Folgendes:

Geben Sie hier die Bildbeschreibung ein

Wenn Sie jedoch alle Labels in einer Legende haben möchten, sollten Sie Folgendes tun:

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

Welches wird Ihnen dies geben:

Geben Sie hier die Bildbeschreibung ein

Paul
quelle
2
Dies schlägt bei errorbarPlots fehl . Eine Lösung, die sie korrekt handhabt, finden Sie unten: stackoverflow.com/a/10129461/1319447
Davide
1
Um zu vermeiden, dass sich zwei Legenden überschneiden, wie in meinem Fall, in dem ich zwei .legend (loc = 0) angegeben habe, sollten Sie zwei verschiedene Werte für den Legendenstandortwert angeben (beide außer 0). Siehe: matplotlib.org/api/legend_api.html
Roalt
Ich hatte einige Probleme beim Hinzufügen einer einzelnen Zeile zu einer Nebenhandlung mit mehreren Zeilen ax1. In diesem Fall verwenden lns1=ax1.linesund lns2an diese Liste anhängen .
Little Bobby Tables
Die verschiedenen von verwendeten Werte locwerden hier
Dror
1
Siehe die Antwort unten für einen automatischeren Weg (mit matplotlib> = 2.1): stackoverflow.com/a/47370214/653364
joris
183

Ich bin nicht sicher, ob diese Funktionalität neu ist, aber Sie können auch die Methode get_legend_handles_labels () verwenden, anstatt selbst Linien und Beschriftungen zu verfolgen:

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
quelle
1
Dies ist die einzige Lösung, die Achsen verarbeiten kann, bei denen sich die Diagramme mit den Legenden überschneiden (die letzten Achsen sollten die Legenden darstellen)
Amelio Vazquez-Reina
5
Diese Lösung funktioniert auch mit errorbarPlots, während das akzeptierte fehlschlägt (eine Linie und ihre Fehlerbalken werden separat angezeigt, und keine davon mit der richtigen Bezeichnung). Außerdem ist es einfacher.
Davide
kleiner Haken: Es funktioniert nicht, wenn Sie das Label überschreiben möchten ax2und es hat von Anfang an keinen Satz
Ciprian Tomoiagă
Anmerkung: Für klassische Diagramme müssen Sie das Label-Argument nicht angeben. Aber für andere, z. Bars, die Sie brauchen.
Belka
Dies macht auch alles viel einfacher, wenn Sie nicht vorher wissen, wie viele Linien gezeichnet werden sollen.
Vegard Jervell
77

Von matplotlib Version 2.1 ab, können Sie eine verwenden Figur Legende . Anstelle ax.legend()einer Legende mit den Ziehpunkten der Achsen axkann auch eine Figurenlegende erstellt werden

fig.legend (loc = "oben rechts")

Dadurch werden alle Handles aus allen Unterplots in der Abbildung gesammelt. Da es sich um eine Figurenlegende handelt, wird sie an der Ecke der Figur platziert, und das locArgument ist relativ zur Figur.

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

Geben Sie hier die Bildbeschreibung ein

Um die Legende wieder in die Achsen zu platzieren, würde man a bbox_to_anchorund a liefern bbox_transform. Letzteres wäre die Achsentransformation der Achsen, in denen sich die Legende befinden sollte. Ersteres können die Koordinaten der Kante sein, die durch locdie in Achsenkoordinaten angegebenen Werte definiert sind.

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

Geben Sie hier die Bildbeschreibung ein

Wichtigkeit von BeEnest
quelle
Also, Version 2.1 bereits veröffentlicht? Aber in Anaconda 3 habe ich versucht, conda upgrade matplotlibkeine neueren Versionen zu finden. Ich verwende immer noch v.2.0.2
StayFoolish
1
Dies ist ein sauberer Weg, um das Endergebnis zu erzielen.
Goutham
1
schön und pythonisch
DanGoodrick
1
Dies scheint nicht zu funktionieren, wenn Sie viele Nebenhandlungen haben. Es wird eine einzelne Legende für alle Nebenhandlungen hinzugefügt. Normalerweise benötigt man für jede Unterzeichnung eine Legende, die Reihen in der Primär- und Sekundärachse jeder Legende enthält.
sancho.s ReinstateMonicaCellio
@sancho Richtig, das steht im dritten Satz dieser Antwort: "... wodurch alle Handles aus allen Nebenhandlungen in der Abbildung gesammelt werden."
ImportanceOfBeingErnest
38

Sie können ganz einfach das bekommen, was Sie wollen, indem Sie die Zeile in ax hinzufügen:

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

oder

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

Dies würde nichts anderes darstellen, als der Legende der Axt ein Etikett hinzuzufügen.

Ich denke, das ist ein viel einfacherer Weg. Es ist nicht erforderlich, Linien automatisch zu verfolgen, wenn Sie nur wenige Linien in der zweiten Achse haben, da das Fixieren von Hand wie oben recht einfach wäre. Wie auch immer, es hängt davon ab, was Sie brauchen.

Der gesamte Code lautet wie folgt:

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

Die Handlung ist wie folgt:

Geben Sie hier die Bildbeschreibung ein


Update: eine bessere Version hinzufügen:

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

Dies führt zu keiner plot(0, 0)Änderung des Achsenbereichs.


Ein zusätzliches Beispiel für Streuung

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
quelle
3
Ich mag das. Es ist hässlich in der Art, wie es das System "austrickst", aber so einfach zu implementieren.
Daniel Power
Dies ist wirklich einfach zu implementieren. Wenn Sie dies jedoch mit Streuung verwenden, ist die resultierende Streugröße in der Legende nur ein winziger Punkt.
greeeeeeen
@greeeeeeen Dann sollten Sie nur die Markergröße angeben, wenn Sie das Streudiagramm erstellen :-)
Syrtis Major
@SyrtisMajor Das habe ich natürlich versucht. Aber das hat die Markierungsgröße in der Legende nicht geändert.
Griechisch
@greeeeeeen Haben Sie die Markierungsgröße der Agentenstreuung geändert? Siehe meinen Beitrag, ich habe einen Ausschnitt aus Beispielcode hinzugefügt.
Syrtis Major
7

Ein schneller Hack, der Ihren Bedürfnissen entspricht.

Nehmen Sie den Rahmen der Box ab und positionieren Sie die beiden Legenden manuell nebeneinander. Etwas wie das..

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

Dabei handelt es sich bei dem Loc-Tupel um Prozentsätze von links nach rechts und von unten nach oben, die die Position im Diagramm darstellen.

user2105997
quelle
5

Ich habe ein folgendes offizielles matplotlib-Beispiel gefunden, das host_subplot verwendet, um mehrere y-Achsen und alle verschiedenen Beschriftungen in einer Legende anzuzeigen. Keine Problemumgehung erforderlich. Beste Lösung, die ich bisher gefunden habe. 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
quelle
Willkommen bei Stack Overflow! Bitte geben Sie den relevantesten Teil des Links an, falls die Zielwebsite nicht erreichbar ist oder dauerhaft offline geht. Siehe Wie kann ich eine gute Antwort schreiben . Konzentrieren Sie sich auf aktuellere Fragen in der Zukunft, diese ist fast 4 Jahre alt.
ByteHamster
In der Tat ein guter Fund, aber ich wünschte, Sie hätten das, was Sie aus dem Beispiel gelernt haben, auf die MWE des OP angewendet und ein Bild beigefügt.
AeroNotAuto