¿Cómo animar un diagrama de dispersión?

83

Estoy tratando de hacer una animación de un diagrama de dispersión donde los colores y el tamaño de los puntos cambian en diferentes etapas de la animación. Para los datos, tengo dos ndarray numpy con un valor xy un valor y:

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

Ahora quiero trazar un diagrama de dispersión del tipo

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

y cree una animación sobre el índice i. ¿Cómo hago esto?

Nicola Vianello
fuente
2
Hay un ejemplo en la documentación de matplotlib: Simulación de lluvia .
ImportanceOfBeingErnest

Respuestas:

142

Suponga que tiene un diagrama de dispersión scat = ax.scatter(...), entonces puede

  • cambiar las posiciones

          scat.set_offsets(array)
    

donde arrayes una N x 2matriz con forma de coordenadas xey.

  • cambiar los tamaños

          scat.set_sizes(array)
    

donde arrayes una matriz 1D de tamaños en puntos.

  • cambiar el color

          scat.set_array(array)
    

donde arrayes una matriz 1D de valores que se mapearán en color.

Aquí hay un ejemplo rápido usando el módulo de animación .
Es un poco más complejo de lo que debería ser, pero esto debería darte un marco para hacer cosas más elegantes.

(Código editado en abril de 2019 para que sea compatible con las versiones actuales. Para el código anterior, consulte el historial de revisiones )

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

ingrese la descripción de la imagen aquí

Si está en OSX y usa el backend de OSX, deberá cambiar blit=Truea blit=Falseen la FuncAnimationinicialización a continuación. El backend de OSX no es totalmente compatible con blitting. El rendimiento se verá afectado, pero el ejemplo debería ejecutarse correctamente en OSX con blitting desactivado.


Para ver un ejemplo más simple, que solo actualiza los colores, eche un vistazo a lo siguiente:

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()
Joe Kington
fuente
Hola Joe, probé tu primer ejemplo pero no funciona mientras que el segundo sí. Tal vez intente depurar la primera opción, esto me ayudará a mejorar mi conocimiento de Python. Gracias
Nicola Vianello
1
Desafortunadamente, el primer ejemplo no se muestra para mí tampoco usando matplotlib 1.3.1 en OS X. Obtengo el marco puesto, no se muestran puntos. El segundo ejemplo funciona.
JoshAdel
9
¡¿Cómo EN EL MUNDO descubriste que .set_array()actualizaría el color de los puntos ?!
Lucas
1
El primer ejemplo no funciona, debe cambiar la línea self.Scat.set_offsets(data[:2, :]) a self.scat.set_offsets(data[:2, :].reshape(self.numpoints, 2))
AN O'Nyme
2
¿Existe alguna función que cambie los marcadores de los puntos de dispersión?
Constantinos
13

Escribí celuloide para hacerlo más fácil. Probablemente sea más fácil de mostrar con un ejemplo:

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

ingrese la descripción de la imagen aquí

Se usa ArtistAnimationdebajo del capó. camera.snapcaptura el estado actual de la figura que se utiliza para crear los fotogramas en la animación.

Editar: Para cuantificar cuánta memoria usa esto, lo ejecuté a través de memory_profiler .

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

Para resumir esto:

  • La creación de 100 parcelas usó 1,9 MiB.
  • Para hacer la animación se utilizaron 2,3 MiB.
  • Este método de hacer animaciones utilizó 4,2 MiB de memoria en total.
Jacques Kvam
fuente
1
Dado que esto usa ArtistAnimation, creará 100 diagramas de dispersión en la memoria, lo cual es bastante ineficiente. Úselo solo si el rendimiento no es crítico para usted.
ImportanceOfBeingErnest
1
El perfil de memoria es una buena idea. ¿Hiciste lo mismo con el FuncAnimation? ¿Cuáles son las diferencias?
ImportanceOfBeingErnest
1
¿Cómo se reproduce la animación (en lugar de guardarla en un archivo)?
argentum2f
5

Aquí está la cosa. Solía ​​ser un usuario de Qt y Matlab y no estoy muy familiarizado con el sistema de animación en matplotlib.

Pero he encontrado una manera de hacer cualquier tipo de animación que desees, como en matlab. Es realmente poderoso. No es necesario verificar las referencias del módulo y está listo para trazar cualquier cosa que desee. Así que espero que pueda ayudar.

La idea básica es usar el evento de tiempo dentro de PyQt (estoy seguro de que otro sistema Gui en Python como wxPython y TraitUi tiene el mismo mecanismo interno para hacer una respuesta de evento. Pero no sé cómo). Cada vez que se llama un evento de temporizador de PyQt, actualizo todo el lienzo y vuelvo a dibujar la imagen completa, sé que la velocidad y el rendimiento pueden verse influenciados lentamente, pero no tanto.

Aquí hay un pequeño ejemplo de ello:

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

Puede ajustar la velocidad de actualización en el

        self.timer = self.startTimer(100)

Soy como tú que quieres usar el diagrama de dispersión animado para hacer una animación de clasificación. Pero simplemente no puedo encontrar una función llamada "set". Así que refresqué todo el canva.

Espero eso ayude..

tk_y1275963
fuente
¡Muy agradable! Sin embargo, no obtuve ningún cambio en la frecuencia de actualización al ajustar el self.startTimervalor ... ¿algún consejo al respecto? (Sí, sé que ha pasado un tiempo ...)
H. Arponen
-1

¿Por qué no probar esto?

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()
Bharat C
fuente
¿Por qué se votó en contra? ¿No funciona?
eric hace