Ik ben wat spreidingsdiagrammen aan het maken en ik wil de grootte van de punten in de legenda instellen op een vaste, gelijke waarde.
Op dit moment heb ik dit:
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()
die dit produceert:
De afmetingen van de punten in de legenda zijn geschaald, maar niet hetzelfde. Hoe kan ik de grootte van de punten in de legenda op een gelijke waarde instellen zonder de grootte in de scatter
-plot te beïnvloeden?
Antwoord 1, autoriteit 100%
Ik heb de broncode van matplotlib
bekeken. Slecht nieuws is dat er geen eenvoudige manier lijkt te zijn om punten van gelijke grootte in de legenda in te stellen. Vooral met scatterplots is het lastig (fout: zie de update hieronder). Er zijn in wezen twee alternatieven:
- Wijzig de
maplotlib
-code - Voeg een transformatie toe aan de
PathCollection
-objecten die de punten in de afbeelding vertegenwoordigen. De transformatie (schaling) moet rekening houden met de oorspronkelijke grootte.
Geen van beide is erg leuk, hoewel #1 gemakkelijker lijkt te zijn. De scatter
plots zijn in dit opzicht bijzonder uitdagend.
Ik heb echter een hack die waarschijnlijk doet wat je wilt:
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()
Dit geeft:
Dat lijkt te zijn wat je wilde.
De wijzigingen:
scatter
is veranderd in eenplot
, waardoor de schaal van de marker verandert (vandaar desqrt
) en het onmogelijk wordt om de grootte van de marker te veranderen ( als dat de bedoeling was)- de grootte van de markering is handmatig gewijzigd in 6 punten voor beide markeringen in de legenda
Zoals je kunt zien, maakt dit gebruik van verborgen underscore-eigenschappen (_legmarker
) en is het bug-lelijk. Het kan bij elke update in matplotlib
kapot gaan.
Bijwerken
Ha, ik heb het gevonden. Een betere hack:
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()
Nu doet de _sizes
(een andere underscore-eigenschap) het. Het is niet nodig om de bron aan te raken, ook al is dit nogal een hack. Maar nu kun je alles gebruiken wat scatter
biedt.
Antwoord 2, autoriteit 56%
Vergelijkbaar met het antwoord, ervan uitgaande dat u alle markeringen met dezelfde grootte wilt:
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legendHandles:
handle.set_sizes([6.0])
Met MatPlotlib 2.0.0
Antwoord 3, autoriteit 16%
U kunt een Line2D-object maken dat lijkt op de door u gekozen markeringen, behalve met een andere markeringsgrootte naar keuze, en die gebruiken om de legenda te construeren. Dit is fijn omdat er geen object in je assen hoeft te worden geplaatst (waardoor mogelijk een resize-gebeurtenis wordt geactiveerd), en het vereist geen gebruik van verborgen attributen. Het enige echte nadeel is dat je de legenda expliciet moet samenstellen uit lijsten met objecten en labels, maar dit is een goed gedocumenteerde matplotlib-functie, dus het voelt redelijk veilig om te gebruiken.
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()
Antwoord 4, autoriteit 11%
Ik had niet veel succes met het gebruik van de oplossing van @DrV, hoewel mijn gebruiksscenario misschien uniek is. Vanwege de dichtheid van punten, gebruik ik de kleinste markeringsgrootte, dat wil zeggen plt.plot(x, y, '.', ms=1, ...)
, en ik wil dat de legendasymbolen groter zijn .
Ik heb de aanbeveling opgevolgd die ik vond op de matplotlib-forums:
- plot de gegevens (geen labels)
- limiet voor record-assen (
xlimits = plt.xlim()
) - plot valse gegevens ver weg van echte gegevens met bij de legenda passende symboolkleuren en -formaten
- aslimieten herstellen (
plt.xlim(xlimits)
) - legenda maken
Dit is hoe het is geworden (hiervoor zijn de puntjes eigenlijk minder belangrijk dan de lijnen):
Hopelijk helpt dit iemand anders.
Antwoord 5, autoriteit 8%
Nog een alternatief hier. Dit heeft het voordeel dat het geen “private” methoden zou gebruiken en zelfs werkt met andere objecten dan de scatters die in de legenda aanwezig zijn. De sleutel is om de scatter PathCollection
toe te wijzen aan een HandlerPathCollection
met een update-functie erop ingesteld.
def update(handle, orig):
handle.update_from(orig)
handle.set_sizes([64])
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})
Compleet codevoorbeeld:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D
colors = ["limegreen", "crimson", "indigo"]
markers = ["o", "s", r"$\clubsuit$"]
labels = ["ABC", "DEF", "XYZ"]
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=22, 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 updatescatter(handle, orig):
handle.update_from(orig)
handle.set_sizes([64])
def updateline(handle, orig):
handle.update_from(orig)
handle.set_markersize(8)
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=updatescatter),
plt.Line2D : HandlerLine2D(update_func = updateline)})
plt.show()