web-dev-qa-db-fra.com

Définition d'une taille fixe pour les points dans la légende

Je fais des diagrammes de dispersion et je veux définir la taille des points dans la légende à une valeur égale et fixe.

En ce moment j'ai ceci:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]


plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
plt.legend(loc="lower left", markerscale=2., scatterpoints=1, fontsize=10)
plt.show()

qui produit ceci:

enter image description here

Les tailles des points dans la légende sont mises à l'échelle mais pas les mêmes. Comment puis-je fixer les tailles des points de la légende à une valeur égale sans affecter les tailles du graphique scatter?

21
Gabriel

J'ai jeté un œil dans le code source de matplotlib. La mauvaise nouvelle est qu'il ne semble pas y avoir de moyen simple de définir des tailles égales de points dans la légende. C'est particulièrement difficile avec les nuages ​​de points ( faux: voir la mise à jour ci-dessous ). Il y a essentiellement deux alternatives:

  1. Changer le code maplotlib
  2. Ajoutez une transformation aux objets PathCollection représentant les points de l’image. La transformation (mise à l'échelle) doit prendre en compte la taille d'origine.

Ni l’un ni l’autre n’est très amusant, bien que le n ° 1 semble plus facile. Les graphiques scatter sont particulièrement difficiles à cet égard.

Cependant, j'ai un bidouillage qui fait probablement ce que vous voulez:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.plot(x1, y1, 'o', label='first', markersize=np.sqrt(20.), c='b')
plt.plot(x2, y2, 'o', label='second', markersize=np.sqrt(35.), c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", numpoints=1, fontsize=10)

#change the marker size manually for both lines
lgnd.legendHandles[0]._legmarker.set_markersize(6)
lgnd.legendHandles[1]._legmarker.set_markersize(6)
plt.show()

Cela donne:

enter image description here

Ce qui semble être ce que tu voulais.

Les changements:

  • scatter a été remplacé par plot, ce qui modifie la taille du marqueur (d'où le sqrt) et rend impossible l'utilisation d'une taille de marqueur changeante (si cela était prévu)
  • la taille du marqueur a été modifiée manuellement en 6 points pour les deux marqueurs de la légende

Comme vous pouvez le constater, cela utilise des propriétés de soulignement de soulignement cachées (_legmarker) et est très odieux. Il peut tomber en panne à n'importe quelle mise à jour dans matplotlib.

Mettre à jour

Haa, je l'ai trouvé. Un meilleur bidouillage:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
lgnd.legendHandles[0]._sizes = [30]
lgnd.legendHandles[1]._sizes = [30]
plt.show()

Maintenant, le _sizes (une autre propriété de soulignement) fait l'affaire. Pas besoin de toucher la source, même si c'est un sacré bidouillage. Mais maintenant, vous pouvez utiliser toutes les offres scatter.

enter image description here

39
DrV

De la même manière que pour la réponse, supposons que vous souhaitiez que tous les marqueurs aient la même taille:

lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legendHandles:
    handle.set_sizes([6.0])

Avec MatPlotlib 2.0.0

12
Bruno Morais

Je n'ai pas eu beaucoup de succès avec la solution de @ DrV, bien que mon cas d'utilisation soit peut-être unique. En raison de la densité de points, j’utilise la plus petite taille de marqueur, c’est-à-dire plt.plot(x, y, '.', ms=1, ...), et souhaite que les symboles de légende soient plus grands.

J'ai suivi la recommandation trouvée sur les forums de matplotlib :

  1. tracer les données (pas d'étiquettes)
  2. limite d'axes d'enregistrement (xlimits = plt.xlim())
  3. tracez de fausses données loin des données réelles avec des couleurs et des tailles de symbole appropriées à la légende
  4. restaurer les limites des axes (plt.xlim(xlimits))
  5. créer une légende

Voici comment il en est résulté (pour cela, les points sont en réalité moins importants que les lignes):  enter image description here

J'espère que ceci aide quelqu'un d'autre.

6
Steven C. Howell

Juste une autre alternative ici. Cela présente l’avantage de ne pas utiliser de méthodes "privées" et de fonctionner même avec d’autres objets que les objets dispersés présents dans la légende. La solution consiste à mapper la dispersion PathCollection à une HandlerPathCollection avec une fonction de mise à jour définie. 

def update(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})

Exemple de code complet:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection

colors = ["limegreen", "crimson", "Indigo"]
markers = ["o", "s", r"$\clubsuit$"]
labels = ["ABC", "DEF", "XYZ"]
plt.plot(np.linspace(0,1,8), np.random.Rand(8), label="A line")
for i,(c,m,l) in enumerate(Zip(colors,markers,labels)):
    plt.scatter(np.random.Rand(8),np.random.Rand(8), 
                c=c, marker=m, s=10+np.exp(i*2.9), label=l)

def update(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})

plt.show()

 enter image description here

Vous pouvez créer un objet Line2D qui ressemble aux marqueurs que vous avez choisis, sauf avec une taille de marqueur différente de votre choix, et l'utiliser pour construire la légende. C'est bien parce que cela ne nécessite pas de placer un objet dans vos axes (ce qui peut déclencher un événement de redimensionnement) et qu'il ne nécessite pas l'utilisation d'attributs cachés. Le seul inconvénient est que vous devez construire la légende de manière explicite à partir de listes d'objets et d'étiquettes, mais il s'agit d'une fonctionnalité bien documentée de matplotlib, de sorte que son utilisation est relativement sûre.

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')

# Create dummy Line2D objects for legend
h1 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='b', linestyle='None')
h2 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='r', linestyle='None')

# Set axes limits
plt.gca().set_xlim(-0.2, 1.2)
plt.gca().set_ylim(-0.2, 1.2)

# Plot legend.
plt.legend([h1, h2], ['first', 'second'], loc="lower left", markerscale=2,
           scatterpoints=1, fontsize=10)
plt.show()

Lien vers la figure résultante

0