102 votes

Comment animer un nuage de points ?

J'essaie de faire une animation d'un diagramme de dispersion où les couleurs et la taille des points changent à différentes étapes de l'animation. Pour les données j'ai deux numpy ndarray avec une valeur x et une valeur y :

data.shape = (ntime, npoint)
x.shape = (npoint)
y.shape = (npoint)

Maintenant, je veux tracer un nuage de points du type

pylab.scatter(x,y,c=data[i,:])

et créer une animation sur l'index i . Comment dois-je m'y prendre ?

3 votes

Il existe un exemple dans la documentation de matplotlib : Simulation de pluie .

0 votes

matplotlib : Simulation de pluie avec un lien mis à jour.

188voto

Joe Kington Points 68089

Supposons que vous ayez un nuage de points, scat = ax.scatter(...) alors vous pouvez

  • changer les positions

          scat.set_offsets(array)

array est un N x 2 tableau de coordonnées x et y.

  • modifier les tailles

          scat.set_sizes(array)

array est un tableau 1D de tailles en points.

  • changer la couleur

          scat.set_array(array)

array est un tableau 1D de valeurs qui seront mises en couleur.

Voici un exemple rapide utilisant le module d'animation .
C'est un peu plus complexe que nécessaire, mais cela devrait vous donner un cadre pour faire des choses plus sophistiquées.

(Code édité en avril 2019 pour être compatible avec les versions actuelles. Pour l'ancien code, voir historique des révisions )

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

class AnimatedScatter(object):
    """An animated scatter plot using matplotlib.animations.FuncAnimation."""
    def __init__(self, numpoints=50):
        self.numpoints = numpoints
        self.stream = self.data_stream()

        # Setup the figure and axes...
        self.fig, self.ax = plt.subplots()
        # Then setup FuncAnimation.
        self.ani = animation.FuncAnimation(self.fig, self.update, interval=5, 
                                          init_func=self.setup_plot, blit=True)

    def setup_plot(self):
        """Initial drawing of the scatter plot."""
        x, y, s, c = next(self.stream).T
        self.scat = self.ax.scatter(x, y, c=c, s=s, vmin=0, vmax=1,
                                    cmap="jet", edgecolor="k")
        self.ax.axis([-10, 10, -10, 10])
        # For FuncAnimation's sake, we need to return the artist we'll be using
        # Note that it expects a sequence of artists, thus the trailing comma.
        return self.scat,

    def data_stream(self):
        """Generate a random walk (brownian motion). Data is scaled to produce
        a soft "flickering" effect."""
        xy = (np.random.random((self.numpoints, 2))-0.5)*10
        s, c = np.random.random((self.numpoints, 2)).T
        while True:
            xy += 0.03 * (np.random.random((self.numpoints, 2)) - 0.5)
            s += 0.05 * (np.random.random(self.numpoints) - 0.5)
            c += 0.02 * (np.random.random(self.numpoints) - 0.5)
            yield np.c_[xy[:,0], xy[:,1], s, c]

    def update(self, i):
        """Update the scatter plot."""
        data = next(self.stream)

        # Set x and y data...
        self.scat.set_offsets(data[:, :2])
        # Set sizes...
        self.scat.set_sizes(300 * abs(data[:, 2])**1.5 + 100)
        # Set colors..
        self.scat.set_array(data[:, 3])

        # We need to return the updated artist for FuncAnimation to draw..
        # Note that it expects a sequence of artists, thus the trailing comma.
        return self.scat,

if __name__ == '__main__':
    a = AnimatedScatter()
    plt.show()

enter image description here

Si vous êtes sous OSX et que vous utilisez le backend OSX, vous devrez modifier les paramètres suivants blit=True à blit=False dans le FuncAnimation initialisation ci-dessous. Le backend OSX ne supporte pas complètement le blitting. Les performances en souffriront, mais l'exemple devrait fonctionner correctement sous OSX avec le blitting désactivé.


Pour un exemple plus simple, qui ne fait que mettre à jour les couleurs, regardez ce qui suit :

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

def main():
    numframes = 100
    numpoints = 10
    color_data = np.random.random((numframes, numpoints))
    x, y, c = np.random.random((3, numpoints))

    fig = plt.figure()
    scat = plt.scatter(x, y, c=c, s=100)

    ani = animation.FuncAnimation(fig, update_plot, frames=range(numframes),
                                  fargs=(color_data, scat))
    plt.show()

def update_plot(i, data, scat):
    scat.set_array(data[i])
    return scat,

main()

1 votes

Bonjour Joe, j'ai essayé votre premier exemple mais il ne fonctionne pas alors que le second oui. Je vais peut-être essayer de déboguer la première option, cela m'aidera à améliorer mes connaissances en python. Je vous remercie

0 votes

Le premier exemple fonctionne parfaitement (sauf pour la gestion du redimensionnement) sous la distribution Enthought (Win7, matplotlib 1.2.0, numpy 1.4.). Un code génial, Joe ! Je pourrais le regarder pendant des heures :-).

2 votes

Malheureusement, le premier exemple ne s'affiche pas non plus pour moi qui utilise matplotlib 1.3.1 sous OS X. J'obtiens le cadre mis aucun point n'est affiché. Le deuxième exemple fonctionne.

20voto

Jacques Kvam Points 1315

J'ai écrit celluloïd pour rendre cela plus facile. Le plus simple est sans doute de montrer par l'exemple :

import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from celluloid import Camera

numpoints = 10
points = np.random.random((2, numpoints))
colors = cm.rainbow(np.linspace(0, 1, numpoints))
camera = Camera(plt.figure())
for _ in range(100):
    points += 0.1 * (np.random.random((2, numpoints)) - .5)
    plt.scatter(*points, c=colors, s=100)
    camera.snap()
anim = camera.animate(blit=True)
anim.save('scatter.mp4')

enter image description here

Il utilise ArtistAnimation sous le capot. camera.snap capture l'état actuel de la figure qui est utilisé pour créer les images dans l'animation.

Edit : Pour quantifier la quantité de mémoire utilisée, je l'ai fait passer par profileur de mémoire .

Line #    Mem usage    Increment   Line Contents
================================================
    11     65.2 MiB     65.2 MiB   @profile
    12                             def main():
    13     65.2 MiB      0.0 MiB       numpoints = 10
    14     65.2 MiB      0.0 MiB       points = np.random.random((2, numpoints))
    15     65.2 MiB      0.1 MiB       colors = cm.rainbow(np.linspace(0, 1, numpoints))
    16     65.9 MiB      0.6 MiB       fig = plt.figure()
    17     65.9 MiB      0.0 MiB       camera = Camera(fig)
    18     67.8 MiB      0.0 MiB       for _ in range(100):
    19     67.8 MiB      0.0 MiB           points += 0.1 * (np.random.random((2, numpoints)) - .5)
    20     67.8 MiB      1.9 MiB           plt.scatter(*points, c=colors, s=100)
    21     67.8 MiB      0.0 MiB           camera.snap()
    22     70.1 MiB      2.3 MiB       anim = camera.animate(blit=True)
    23     72.1 MiB      1.9 MiB       anim.save('scatter.mp4')

Pour résumer :

  • La création de 100 parcelles a utilisé 1,9 MiB.
  • La réalisation de l'animation a nécessité 2,3 MiB.
  • Cette méthode de création d'animations utilisait en tout 4,2 Mio de mémoire.

4 votes

Comme elle utilise ArtistAnimation, elle créera 100 diagrammes de dispersion en mémoire, ce qui est plutôt inefficace. N'utilisez cette fonction que si les performances ne sont pas critiques pour vous.

1 votes

Le profilage de la mémoire est une bonne idée. Avez-vous fait de même pour le FuncAnimation ? Quelles sont les différences ?

1 votes

Comment distraire l'animation (au lieu de l'enregistrer dans un fichier) ?

8voto

Tom Points 5832

TL/DR : Si vous rencontrez des difficultés avec le ax.set_... pour animer votre diagramme de dispersion, vous pouvez essayer d'effacer le diagramme à chaque image (c'est-à-dire, ax.clear() ) et réorganiser les choses comme vous le souhaitez. Ceci est plus lent mais peut s'avérer utile lorsque vous souhaitez modifier beaucoup de choses dans une petite animation.


Voici un exemple illustrant cette approche "claire" :

import itertools

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

# set parameters
frames = 10
points = 20
np.random.seed(42)

# create data
data = np.random.rand(points, 2)

# set how the graph will change each frame
sizes = itertools.cycle([10, 50, 150])
colors = np.random.rand(frames, points)
colormaps = itertools.cycle(['Purples', 'Blues', 'Greens', 'Oranges', 'Reds'])
markers = itertools.cycle(['o', 'v', '^', 's', 'p'])

# init the figure
fig, ax = plt.subplots(figsize=(5,5))

def update(i):
    # clear the axis each frame
    ax.clear()

    # replot things
    ax.scatter(data[:, 0], data[:, 1],
               s=next(sizes),
               c=colors[i, :],
               cmap=next(colormaps),
               marker=next(markers))

    # reformat things
    ax.set_xlabel('world')
    ax.set_ylabel('hello')

ani = animation.FuncAnimation(fig, update, frames=frames, interval=500)
ani.save('scatter.gif', writer='pillow')

enter image description here

Les didacticiels de matplotlib et d'autres sources que j'ai vus ne semblent pas utiliser cette approche, mais j'ai vu d'autres personnes (ainsi que moi-même) la suggérer sur ce site. Je vois des avantages et des inconvénients, mais j'aimerais connaître l'avis des autres :

Pour

  • Vous pouvez éviter d'utiliser le set_... méthodes pour le diagramme de dispersion (c'est-à-dire .set_offsets() , .set_sizes() ...), qui ont une documentation plus obscure et moins détaillée ( bien que la réponse principale vous mènera très loin ici ! ). De plus, il existe différentes méthodes pour différents types de parcelles (par exemple, vous utilisez set_data pour les lignes, mais pas pour les points de dispersion). En effaçant l'axe, vous déterminez les éléments tracés à chaque trame comme tout autre tracé dans matplotlib.
  • Plus encore, il n'est pas clair si certaines propriétés sont set -tels que le type de marqueur ( comme commenté ) ou la carte des couleurs. Je ne saurais pas comment créer le graphique ci-dessus en utilisant la fonction ax.set_... par exemple, en raison des changements de marqueurs et de cartes de couleurs. Mais c'est assez basique avec ax.scatter() .

Cons

  • Il peut être beaucoup plus lent ; c'est-à-dire tout effacer et redessiner, semble être plus coûteux que les set... méthodes. Ainsi, pour les grandes animations, cette approche peut être assez pénible.
  • L'effacement de l'axe efface également des éléments tels que les étiquettes d'axe, les limites d'axe, les autres textes, etc. Ces éléments de mise en forme doivent donc être inclus dans le processus d'effacement. update (sinon ils disparaîtront). Cela peut être gênant si vous souhaitez que certaines choses changent, mais que d'autres restent inchangées.

Bien sûr, la vitesse est un gros inconvénient. Voici un exemple qui montre la différence. Compte tenu de ces données :

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

np.random.seed(42)
frames = 40

x = np.arange(frames)
y = np.sin(x)
colors = itertools.cycle(['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
data = [(np.random.uniform(-1, 1, 10) + x[i],
         np.random.uniform(-1, 1, 10) + y[i])
        for i in range(frames)]

Vous pouvez tracer en utilisant le set... méthode :

fig, ax = plt.subplots()

s = ax.scatter([], [])

ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)

def update(i):
    s.set_offsets(np.column_stack([data[i][0], data[i][1]]))
    s.set_facecolor(next(colors))

ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('set.gif', writer='pillow')

Ou la méthode "claire" :

fig, ax = plt.subplots()

def update(i):
    ax.clear()
    ax.scatter(data[i][0], data[i][1], c=next(colors))
    ax.set_xlim(-2, frames+2)
    ax.set_ylim(min(y) - 1, max(y) + 1)

ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('clear.gif', writer='pillow')

Pour obtenir ce chiffre :

enter image description here

Utilisation de %%time on peut voir que le nettoyage et le recollement prennent (plus de) deux fois plus de temps :

  • pour set... : Wall time: 1.33 s
  • pour être clair : Wall time: 2.73 s

Jouez avec le frames pour tester cela à différentes échelles. Pour les petites animations (moins d'images/données), la différence de temps entre les deux méthodes est sans conséquence (et pour moi, elle me fait parfois préférer la méthode de compensation). Mais pour les cas plus importants, l'utilisation de set_... peut faire gagner beaucoup de temps.

6voto

tk_y1275963 Points 14

Voici la situation. J'ai l'habitude d'utiliser Qt et Matlab et je ne suis pas très familier avec le système d'animation de matplotlib.

Mais j'ai trouvé un moyen de faire n'importe quel type d'animation comme dans Matlab. C'est vraiment puissant. Pas besoin de vérifier les références du module et vous pouvez tracer tout ce que vous voulez. J'espère donc que cela pourra vous aider.

L'idée de base est d'utiliser l'événement temporel dans PyQt (je suis sûr que d'autres systèmes de gestion sur Python comme wxPython et TraitUi ont le même mécanisme interne pour répondre à un événement. Mais je ne sais pas comment). Chaque fois qu'un événement Timer de PyQt est appelé, je rafraîchis l'ensemble du canevas et redessine l'ensemble de l'image, je sais que la vitesse et les performances peuvent être lentement influencées mais ce n'est pas tant que ça.

En voici un petit exemple :

import sys
from PyQt4 import QtGui

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

import numpy as np

class Monitor(FigureCanvas):
    def __init__(self):
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)

        FigureCanvas.__init__(self, self.fig)
        self.x = np.linspace(0,5*np.pi,400)
        self.p = 0.0
        self.y = np.sin(self.x+self.p)

        self.line = self.ax.scatter(self.x,self.y)

        self.fig.canvas.draw()

        self.timer = self.startTimer(100)

    def timerEvent(self, evt):
        # update the height of the bars, one liner is easier
        self.p += 0.1
        self.y = np.sin(self.x+self.p)
        self.ax.cla()
        self.line = self.ax.scatter(self.x,self.y)

        self.fig.canvas.draw()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = Monitor()
    w.setWindowTitle("Convergence")
    w.show()
    sys.exit(app.exec_())

Vous pouvez régler la vitesse de rafraîchissement dans le

        self.timer = self.startTimer(100)

Je suis comme vous et je souhaite utiliser le nuage de points animé pour réaliser une animation de tri. Mais je ne trouve pas la fonction "set". J'ai donc rafraîchi l'ensemble de Canva.

J'espère que ça aidera

0 votes

Très bien ! Cependant, je n'ai pas obtenu de changement dans le taux de rafraîchissement en ajustant les paramètres de l'écran. self.startTimer valeur... des conseils à ce sujet ? (Oui, je sais que ça fait un moment...)

-3voto

Bharath C Points 27

Pourquoi ne pas essayer ceci

import numpy as np
import matplotlib.pyplot as plt

x=np.random.random()
y=np.random.random()

fig, ax = plt.subplots()
ax.scatter(x,y,color='teal')
ax.scatter(y,x,color='crimson')
ax.set_xlim([0,1])
ax.set_ylim([0,1])

for i in np.arange(50):
    x=np.random.random()
    y=np.random.random()
    bha=ax.scatter(x,y)
    plt.draw()
    plt.pause(0.5)
    bha.remove()

plt.show()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X