How to animate a scatter plot
Asked Answered
H

5

103

I'm trying to do an animation of a scatter plot where colors and size of the points changes at different stage of the animation. For data I have two numpy ndarray with an x value and y value:

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

Now I want to plot a scatter plot of the type

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

and create an animation over the index i. How do I do this?

Haynie answered 22/2, 2012 at 19:32 Comment(1)
matplotlib: Rain Simulation with updated link.Foliation
H
190

Suppose you have a scatter plot, scat = ax.scatter(...), then you can

  • change the positions

          scat.set_offsets(array)
    

where array is a N x 2 shaped array of x and y coordinates.

  • change the sizes

          scat.set_sizes(array)
    

where array is a 1D array of sizes in points.

  • change the color

          scat.set_array(array)
    

where array is a 1D array of values which will be colormapped.

Here's a quick example using the animation module.
It's slightly more complex than it has to be, but this should give you a framework to do fancier things.

(Code edited April 2019 to be compatible with current versions. For the older code see revision history)

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

If you're on OSX and using the OSX backend, you'll need to change blit=True to blit=False in the FuncAnimation initialization below. The OSX backend doesn't fully support blitting. The performance will suffer, but the example should run correctly on OSX with blitting disabled.


For a simpler example, which just updates the colors, have a look at the following:

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()
Hoashis answered 23/2, 2012 at 16:4 Comment(16)
Hi Joe I've tried your first example but it does not work whereas the second one yes. Maybe I will try to debug the first option, this will help me to improve my python knowledge. Thank youHaynie
The first example works perfectly (except for handling resize) under the Enthought distribution (Win7, matplotlib 1.2.0, numpy 1.4.). Awesome code, Joe! I could watch it for hours :-).Emera
Unfortunately the first example doesn't display for me either using matplotlib 1.3.1 on OS X. I get the frame put no points are displayed. The second example works.Earth
I think this might be an OSX issue as I cannot get the 1st example to run either on Mountain Lion.Malinin
In this example, I don't understand the "i" argument that is passed to update_plot. fargs according to the documentation here - matplotlib.org/api/… - holds the arguments for update_plot. So why is there this i variable? What does it do? From experimentation, i seems to increment with each call... but then if fargs only has 1 item instead of 2, and we make update_plot only have 2 items, I get the error "update_plot() takes exactly 2 arguments (1 given)" - how is only 1 given?Caraviello
@Caraviello - The first argument passed in to the function by FuncAnimation is the frame number. The additional arguments specified by fargs are passed in after the frame number. The documentation could certainly be clearer about that.Hoashis
@AdamLewis : I have issues with certain animation codes not working on OS X as well. I've found (on this site, but can't remember the link) the solution is to place import matplotlib followed by the next line matplotlib.use('TkAgg') on top of all your other import statements. Haven't had issues since I've utilized that. Hope that helps you, and other OS X users.Palmary
For folks having problems on OSX, try specifying blit=False or switching backends. The OSX backend doesn't fully support blitting.Hoashis
@Matt, your matplotlib.use('TkAgg') trick didn't work for me (on Yosemite with Python 2.7 and Python 3.3 ) I have blit=False specified, and @Joe Kington's original code still doesn't work.Falsetto
How IN THE WORLD did you figure out that .set_array() would update dot color?!Fretful
The first example is not working you have to change the line self.Scat.set_offsets(data[:2, :]) to self.scat.set_offsets(data[:2, :].reshape(self.numpoints, 2))Continually
Is there any function that changes the markers of the scatter points?Consuetudinary
Additionally, I would like to know whether someone could use set_array successfully. I tried providing rgb tuples and string colors and keep getting the error 'str' object has no attribute 'view'Kile
Use scat.set_facecolors (and scat.set_edgecolors) for coloring animated items, please do not use set_array (have been several days to discover this). @Fretful is right!Kekkonen
if you're using this inside jupyter notebook, you'll need a = AnimatedScatter(); HTML(a.ani.to_html5_video()) (instead the lines at the bottom)Evince
As an alternative, one can also set the offsets, sizes and colors as: self.scat.set(offsets=data[:, :2], sizes=300 * abs(data[:, 2])**1.5 + 100, color=data[:, 3])Gad
D
21

I wrote celluloid to make this easier. It's probably easiest to show by example:

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

It uses ArtistAnimation under the hood. camera.snap captures the current state of the figure which is used to create the frames in the animation.

Edit: To quantify how much memory this uses I ran it through 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')

To summarize this:

  • Creating 100 plots used 1.9 MiB.
  • Making the animation used 2.3 MiB.
  • This method of making animations used 4.2 MiB of memory in sum.
Desex answered 20/11, 2018 at 20:16 Comment(5)
Since this uses ArtistAnimation, it will create 100 scatter plots in memory, which is rather inefficient. Only use this if performance is not critical for you.Humoral
The memory profiling is a good idea. Did you do the same for the FuncAnimation? What are the differences?Humoral
How do you distplay the animation (as apposed to saving it to file)?Cradlesong
Is it still working with matplotlib ?Plant
Just wanted to thank you, I needed to make a simple animation using matplotlib and this really helped! Great little library :-)Gastrotomy
M
9

TL/DR: If you are having trouble with the ax.set_... methods for animating your scatter plot, you can try to just clear the plot each frame (i.e., ax.clear()) and re-plot things as desired. This is slower, but might be useful when you want to change a lot of things in a small animation.


Here is an example demonstrating this "clear" approach:

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

The tutorials I have seen from matplotlib and other sources do not seem to use this approach, but I have seen others (as well as myself) suggest it on this site. I see some pros & cons, but I would appreciate anyone else's thoughts:

Pros

  • You can avoid using the set_... methods for the scatter plot (i.e. .set_offsets(), .set_sizes(), ...), which have more obscure and less-detailed documentation (though the leading answer will get you very far here!). Plus, there are different methods for different plot types (e.g. you use set_data for lines, but not for scatter points). By clearing the axis, you determine the plotted elements each frame like any other plot in matplotlib.
  • Even more so, it is unclear if some properties are set-able, such as the marker type (as commented) or the colormap. I wouldn't know how to create the above plot using ax.set_..., for example, because of the marker and colormap changes. But this is pretty basic with ax.scatter().

Cons

  • It can be much slower; i.e. clearing and redrawing everything appears to be more expensive than the set... methods. So for large animations, this approach can be kind of painful.
  • Clearing the axis also clears things like the axis labels, axis limits, other text, etc. So, those sorts of formatting things need to be included in update (else they will be gone). This can be annoying if you want some things to change, but others to stay the same.

Of course, the speed is a big con. Here is an example showing the difference. Given this data:

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

You can plot using the set... method:

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

Or the "clear" method:

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

To get this figure:

enter image description here

Using %%time, we can see that clearing and replotting takes (more than) twice as long:

  • for set...: Wall time: 1.33 s
  • for clear: Wall time: 2.73 s

Play with the frames parameter to test this at different scales. For smaller animations (less frames/data), the time difference between the two methods is inconsequential (and for me, sometimes causes me to prefer the clearing method). But for larger cases, using set_... can save significant time.

Materfamilias answered 20/8, 2021 at 23:1 Comment(0)
C
6

Here is the thing. I used to a user of Qt and Matlab and I am not quite familiar with the animation system on the matplotlib.

But I do have find a way that can make any kind of animation you want just like it is in matlab. It is really powerful. No need to check the module references and you are good to plot anything you want. So I hope it can help.

The basic idea is to use the time event inside PyQt( I am sure other Gui system on the Python like wxPython and TraitUi has the same inner mechanism to make an event response. But I just don't know how). Every time a PyQt's Timer event is called I refresh the whole canvas and redraw the whole picture, I know the speed and performance may be slowly influenced but it is not that much.

Here is a little example of it:

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

You can adjust the refresh speed in the

        self.timer = self.startTimer(100)

I am just like you who want to use the Animated scatter plot to make a sorting animation. But I just cannot find a so called "set" function. So I refreshed the whole canva.

Hope it helps..

Cavallaro answered 14/6, 2014 at 3:14 Comment(1)
Really nice! However I didn't get any change in the refresh rate by adjusting the self.startTimervalue... any tips about that? (Yeah, I know it's been a while...)Registered
C
-3

Why Not try this

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()
Cruciform answered 26/8, 2020 at 5:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.