2015-09-07 11 views
7

Sto provando a tracciare diverse (molte migliaia) di oggetti circolari - Non ho molta esperienza con Python. Sono interessato a specificare la posizione, il raggio e il colore. C'è un modo più efficiente per ottenere lo stesso risultato ?:Come posso tracciare molte migliaia di cerchi velocemente?

import matplotlib.pyplot as plt 

xvals = [0,.1,.2,.3] 
yvals = [0,.1,.2,.3] 
rvals = [0,.1,.1,.1] 

c1vals = [0,.1,0..1] 
c2vals = [.1,0,.1,0] 
c3vals = [.1,.1,.1,.1] 

for q in range(0,4): 
    circle1=plt.Circle((xvals[q], yvals[q]), rvals[q], color=[0,0,0]) 
    plt.gcf().gca().add_artist(circle1) 
+2

si desidera utilizzare un 'EllipseCollection' potrebbe fare ciò che vuoi http://matplotlib.org/examples/pylab_examples/ellipse_collection.html – tacaswell

risposta

8

La chiave qui è quello di utilizzare un Collection. Nel tuo caso, vuoi fare un PatchCollection.

Matplotlib ottimizza il disegno di molti artisti simili mediante l'utilizzo di raccolte. È considerevolmente più veloce del disegnare ognuno individualmente. Inoltre, la trama non conterrà migliaia di singoli artisti, solo una collezione. Questo accelera molte altre operazioni varie che devono operare su ciascun artista ogni volta che viene tracciata la trama.

scatter in realtà è molto più veloce del tuo attuale approccio, in quanto aggiungerà una raccolta invece di artisti separati. Tuttavia, disegna anche indicatori con una dimensione che non è in coordinate di dati.

Per aggirare il problema, è possibile utilizzare lo stesso approccio scatter ma creare la raccolta manualmente.

Per fare un esempio:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.collections 

num = 5000 
sizes = 0.2 * np.random.random(num) 
xy = 50 * np.random.random((num, 2)) 

# Note that the patches won't be added to the axes, instead a collection will 
patches = [plt.Circle(center, size) for center, size in zip(xy, sizes)] 

fig, ax = plt.subplots() 

coll = matplotlib.collections.PatchCollection(patches, facecolors='black') 
ax.add_collection(coll) 

ax.margins(0.01) 
plt.show() 

enter image description here

Ciò rende piuttosto agevolmente per me.Solo per dimostrare che i cerchi sono in coordinate di dati, notare cosa succede se ingrandire un rettangolo stretto (nota: ciò presuppone che l'aspetto della trama è impostata auto):

enter image description here


Se sei veramente concentrato sulla velocità, puoi usare uno EllipseCollection come suggerito da @tcaswell.

Un EllipseCollection farà solo una percorso, ma scalerà e tradurlo in fase di sorteggio per essere nei luoghi/formati specificati.

Lo svantaggio è che mentre la dimensione può essere in coordinate di dati, il cerchio sarà sempre un cerchio, anche se il rapporto di aspetto del grafico non è 1. (cioè i cerchi non si allungheranno come fanno in la figura sopra).

Il vantaggio è che è veloce.

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.collections 

num = 5000 
sizes = 0.4 * np.random.random(num) 
xy = 50 * np.random.random((num, 2)) 

fig, ax = plt.subplots() 

coll = matplotlib.collections.EllipseCollection(sizes, sizes, 
               np.zeros_like(sizes), 
               offsets=xy, units='x', 
               transOffset=ax.transData, 
               **kwargs) 
ax.add_collection(coll) 
ax.margins(0.01) 
plt.show() 

enter image description here

notare la differenza come abbiamo ingrandire una regione simile alla seconda cifra. I cerchi diventano più grandi (la dimensione è in coordinate di dati), ma rimangono cerchi invece di allungarsi. Non sono una rappresentazione accurata di un cerchio nello spazio "dati".

enter image description here

Per dare un'idea della differenza di tempo, ecco il tempo per creare e disegnare una figura con gli stessi 5000 cerchi con ciascuno dei tre metodi:

In [5]: %timeit time_plotting(circles) 
1 loops, best of 3: 3.84 s per loop 

In [6]: %timeit time_plotting(patch_collection) 
1 loops, best of 3: 1.37 s per loop 

In [7]: %timeit time_plotting(ellipse_collection) 
1 loops, best of 3: 228 ms per loop 
+2

Questa domanda ha anche richiesto https://github.com/matplotlib/matplotlib/pull/5035 poiché _seems_ come 'CircleCollection' dovrebbe fare quello che vuoi, ma è hardcoded per essere area in punti^2. – tacaswell

+0

Questa è un'ottima risposta. Stavo per modificare con le prove a tempo, ma tu mi hai battuto :) FWIW, stavo confrontando cerchi vs patch_collection e ottenuto un simile rapporto di numeri. Grazie! – ConfusinglyCuriousTheThird

+0

@tcaswell - Bello! Grazie per quello! –

3

scatter è probabilmente meglio per voi che plt.Circle anche se non farà nulla correre più veloce.

for i in range(4): 
    mp.scatter(xvals[i], yvals[i], s=rvals[i]) 

Se si riesce a fare con i cerchi essendo la stessa dimensione allora mp.plot(xvals[i], yvals[i], marker='o') sarà più performante.

Ma questa è probabilmente una limitazione matplotlib, piuttosto che una limitazione della lingua. Esistono eccellenti librerie JavaScript per il tracciamento di migliaia di punti dati in modo efficiente (d3.js). Forse qualcuno qui ne saprà uno che puoi chiamare da Python.

+0

Sfortunatamente, scatter non ti consente di specificare il raggio nelle unità" dati ", ma piuttosto di inserire l'input in pixel. – ConfusinglyCuriousTheThird

1

Si vorrà sicuramente spostare ...gca() al di fuori del proprio ciclo. Puoi anche usare la comprensione delle liste.

fig = plt.figure() 
ax = plt.gcf().gca() 

[ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0])) 
for q in xrange(4)] # range(4) for Python3 

Qui di seguito sono alcuni test per generare 4.000 circoli utilizzando i diversi metodi:

xvals = [0,.1,.2,.3] * 1000 
yvals = [0,.1,.2,.3] * 1000 
rvals = [0,.1,.1,.1] * 1000 

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca() 
for q in range(4000): 
    circle1=plt.Circle((xvals[q], yvals[q]), rvals[q], color=[0,0,0]) 
    plt.gcf().gca().add_artist(circle1) 
5 loops, best of 3: 792 ms per loop 

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca() 
for q in xrange(4000): 
    ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0])) 
5 loops, best of 3: 779 ms per loop 

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca() 
[ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0])) for q in xrange(4000)] 
5 loops, best of 3: 730 ms per loop 
+0

questo sembra la giusta direzione per quello che voglio ... che cosa è esattamente la comprensione delle liste? – ConfusinglyCuriousTheThird

+0

La comprensione delle liste è generalmente più efficiente dei cicli 'for'. https://docs.python.org/2/tutorial/datastructures.html#list-comprehensions – Alexander

1

Non sei sicuro di ciò che si sta realmente cercando di fare, o che cosa i vostri problemi o preoccupazioni sono, ma qui è un totalmente diversa modalità di tracciare cerchi ... fanno un file SVG come questo e lo chiamano circles.svg

<?xml version="1.0" standalone="no"?> 
<svg width="500" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg"> 
    <circle cx="100" cy="175" r="200" stroke="lime" fill="coral" stroke-width="28"/> 
    <circle cx="25" cy="75" r="80" stroke="red" fill="yellow" stroke-width="5"/> 
    <circle cx="400" cy="280" r="20" stroke="black" fill="blue" stroke-width="10"/> 
</svg> 

e passarlo a ImageMagick a fare in un 01.233.505,855 milafile in questo modo:

convert circles.svg result.png 

enter image description here

+0

grazie per il suggerimento. Questo fa parte di un progetto più ampio in cui mi piacerebbe manipolare i dati, quindi preferirei tenerlo in Python. – ConfusinglyCuriousTheThird

+1

Per il downvoter ... se hai intenzione di downvotare, potresti almeno avere la cortesia di spiegare perché, in modo che tutti possiamo imparare qualcosa. All'interno dei parametri della domanda dell'OP, questa è una risposta perfettamente ragionevole. –

Problemi correlati