Axe secondaire avec twinx (): comment ajouter à la légende?


288

J'ai un tracé avec deux axes y, en utilisant twinx(). Je donne également des étiquettes aux lignes, et legend()je veux les montrer avec , mais je ne réussis qu'à obtenir les étiquettes d'un axe dans la légende:

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

Je ne reçois donc que les étiquettes du premier axe dans la légende, et non l'étiquette «temp» du deuxième axe. Comment pourrais-je ajouter cette troisième étiquette à la légende?

entrez la description de l'image ici


4
[ Ne faites pas cela dans un endroit à distance proche de tout code de production ] Lorsque mon seul objectif est de générer un beau complot avec la légende appropriée dès que possible, j'utilise un hack laid de tracer un tableau vide axavec le style que j'utilise sur ax2: dans votre cas ax.plot([], [], '-r', label = 'temp'),. C'est beaucoup plus rapide et plus simple que de le faire correctement ...
Neinstein

Réponses:


370

Vous pouvez facilement ajouter une deuxième légende en ajoutant la ligne:

ax2.legend(loc=0)

Vous obtiendrez ceci:

entrez la description de l'image ici

Mais si vous voulez toutes les étiquettes sur une légende, vous devriez faire quelque chose comme ceci:

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

Ce qui vous donnera ceci:

entrez la description de l'image ici


2
Cela échoue avec les errorbarparcelles. Pour une solution qui les gère correctement, voir ci-dessous: stackoverflow.com/a/10129461/1319447
Davide

1
Pour éviter que deux légendes se chevauchent, comme dans mon cas où j'ai spécifié deux .legend (loc = 0), vous devez spécifier deux valeurs différentes pour la valeur d'emplacement de la légende (toutes deux autres que 0). Voir: matplotlib.org/api/legend_api.html
Roalt

J'ai eu du mal à ajouter une seule ligne à un sous-tracé avec plusieurs lignes ax1. Dans ce cas, utilisez lns1=ax1.linespuis ajoutez lns2à cette liste.
Little Bobby Tables

Les différentes valeurs utilisées par locsont expliquées ici
Dror

1
Voir la réponse ci-dessous pour un moyen plus automatique (avec matplotlib> = 2.1): stackoverflow.com/a/47370214/653364
joris

183

Je ne sais pas si cette fonctionnalité est nouvelle, mais vous pouvez également utiliser la méthode get_legend_handles_labels () plutôt que de suivre vous-même les lignes et les étiquettes:

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
C'est la seule solution qui peut gérer les axes où les tracés se chevauchent avec les légendes (le dernier axe est celui qui devrait tracer les légendes)
Amelio Vazquez-Reina

5
Cette solution fonctionne également avec les errorbartracés, tandis que celui accepté échoue (montrant une ligne et ses barres d'erreur séparément, et aucun d'entre eux avec la bonne étiquette). De plus, c'est plus simple.
Davide du

léger hic: ça ne marche pas si vous voulez écraser le label ax2et il n'y en a pas de set depuis le début
Ciprian Tomoiagă

Remarque: pour les tracés classiques, vous n'avez pas besoin de spécifier l'argument label. Mais pour d'autres, par exemple. barres dont vous avez besoin.
belka

Cela rend également tout beaucoup plus facile si vous ne savez pas à l'avance combien de lignes vont être tracées.
Vegard Jervell

77

À partir de la version 2.1 de matplotlib, vous pouvez utiliser une légende de figure . Au lieu de ax.legend(), ce qui produit une légende avec les poignées des axes ax, on peut créer une légende de figure

fig.legend (loc = "en haut à droite")

qui rassemblera toutes les poignées de toutes les sous-parcelles de la figure. Puisqu'il s'agit d'une légende de figure, elle sera placée au coin de la figure et l' locargument est relatif à la figure.

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

entrez la description de l'image ici

Afin de replacer la légende dans les axes, on fournirait a bbox_to_anchoret a bbox_transform. Ce dernier serait la transformation des axes des axes dans lesquels la légende devrait résider. Le premier peut être les coordonnées de l'arête définies par les loccoordonnées des axes.

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

entrez la description de l'image ici


Alors, la version 2.1 est déjà sortie? Mais dans Anaconda 3, je n'ai essayé conda upgrade matplotlibaucune nouvelle version trouvée, j'utilise toujours la v.2.0.2
StayFoolish

1
C'est une façon plus propre d'atteindre le résultat final.
Goutham

1
belle et pythonique
DanGoodrick

1
Cela ne semble pas fonctionner lorsque vous avez plusieurs sous-tracés. Il ajoute une légende unique pour toutes les sous-parcelles. Il faut généralement une légende pour chaque sous-tracé, contenant des séries dans les axes principal et secondaire de chaque légende.
sancho.s ReinstateMonicaCellio

@sancho Correct, c'est ce qui est écrit dans la troisième phrase de cette réponse, "... qui rassemblera toutes les poignées de toutes les sous-parcelles de la figure.".
ImportanceOfBeingErnest

38

Vous pouvez facilement obtenir ce que vous voulez en ajoutant la ligne dans ax:

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

ou

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

Cela ne représenterait rien mais ajouterait une étiquette à la légende de la hache.

Je pense que c'est un moyen beaucoup plus simple. Il n'est pas nécessaire de suivre les lignes automatiquement lorsque vous n'avez que quelques lignes dans les seconds axes, car la fixation à la main comme ci-dessus serait assez facile. Quoi qu'il en soit, cela dépend de ce dont vous avez besoin.

Le code entier est comme ci-dessous:

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

L'intrigue est comme ci-dessous:

entrez la description de l'image ici


Mise à jour: ajoutez une meilleure version:

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

Cela ne fera rien tandis que plot(0, 0)peut changer la plage d'axe.


Un exemple supplémentaire pour 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
J'aime ça. Son genre de laid dans la façon dont il "trompe" le système, mais si simple à mettre en œuvre.
Daniel Power

C'est vraiment simple à mettre en œuvre. Mais lorsque vous l'utilisez avec scatter, la taille de dispersion résultante dans la légende n'est qu'un tout petit point.
greeeeeeen

@greeeeeeen Ensuite, vous devez juste spécifier la taille du marqueur lors de la création du nuage de points :-)
Syrtis Major

@SyrtisMajor J'ai bien sûr essayé cela. Mais cela n'a pas changé la taille du marqueur dans la légende.
greeeeeeen

@greeeeeeen Avez-vous modifié la taille du marqueur de la dispersion d'agent? Voir mon message, j'ai ajouté un extrait de code d'exemple.
Syrtis Major,

7

Un hack rapide qui peut répondre à vos besoins ..

Retirez le cadre de la boîte et placez manuellement les deux légendes l'une à côté de l'autre. Quelque chose comme ça..

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

Où le tuple de localisation correspond aux pourcentages de gauche à droite et de bas en haut qui représentent l'emplacement dans le graphique.


5

J'ai trouvé un exemple matplotlib officiel suivant qui utilise host_subplot pour afficher plusieurs axes y et toutes les différentes étiquettes dans une légende. Aucune solution de contournement nécessaire. La meilleure solution que j'ai trouvée jusqu'à présent. 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()

Bienvenue dans Stack Overflow! Veuillez citer la partie la plus pertinente du lien, au cas où le site cible serait inaccessible ou se déconnecterait définitivement. Voir Comment écrire une bonne réponse . Concentrez-vous sur des questions plus actuelles à l'avenir, celle-ci a presque 4 ans.
ByteHamster

En effet, une bonne trouvaille, mais j'aurais aimé que vous preniez ce que vous avez appris de l'exemple, que vous l'appliquiez au MWE de l'OP et que vous incluiez une image.
aeroNotAuto
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.