Matplotlib animation in Jupyter
For two of my books, Think Complexity and Modeling and Simulation in Python, many of the examples involve animation. Fortunately, there are several ways to do animation with Matplotlib in Jupyter. Unfortunately, none of them is ideal.
FuncAnimation
Until recently, I was using FuncAnimation
, provided by the matplotlib.animation
package, as in this example from Think Complexity. The documentation of this function is pretty sparse, but if you want to use it, you can find examples.
For me, there are a few drawbacks:
- It requires a back end like
ffmpeg
to display the animation. Based on my email, many readers have trouble installing packages like this, so I avoid using them. - It runs the entire computation before showing the result, so it takes longer to debug, and makes for a less engaging interactive experience.
- For each element you want to animate, you have to use one API to create the element and another to update it.
For example, if you are using imshow
to visualize an array, you would run
im = plt.imshow(a, **options)
to create an AxesImage
, and then
im.set_array(a)
to update it. For beginners, this is a lot to ask. And even for experienced people, it can be hard to find documentation that shows how to update various display elements.
As another example, suppose you have a 2-D array and plot it like this:
plot(a)
The result is a list of Line2D
objects. To update them, you have to traverse the list and invoke set_xdata()
on each one.
Updating a display is often more complicated than creating it, and requires substantial navigation of the documentation. Wouldn’t it be nice to just call plot(a)
again?
Clear output
Recently I discovered simpler alternative using clear_output()
from Ipython.display
and sleep()
from the time
module. If you have Python and Jupyter, you already have these modules, so there’s nothing to install.
Here’s a minimal example using imshow
:
%matplotlib inline import numpy as np from matplotlib import pyplot as plt from IPython.display import clear_output from time import sleep n = 10 a = np.zeros((n, n)) plt.figure() for i in range(n): plt.imshow(a) plt.show() a[i, i] = 1 sleep(0.1) clear_output(wait=True)
The drawback of this method is that it is relatively slow, but for the examples I’ve worked on, the performance has been good enough.
In the ModSimPy library, I provide a function that encapsulates this pattern:
def animate(results, draw_func, interval=None): plt.figure() try: for t, state in results.iterrows(): draw_func(state, t) plt.show() if interval: sleep(interval) clear_output(wait=True) draw_func(state, t) plt.show() except KeyboardInterrupt: pass
results
is a Pandas DataFrame that contains results from a simulation; each row represents the state of a system at a point in time.
draw_func
is a function that takes a state and draws it in whatever way is appropriate for the context.
interval
is the time between frames in seconds (not counting the time to draw the frame).
Because the loop is wrapped in a try
statement that captures KeyboardInterrupt
, you can interrupt an animation cleanly.
You can see an example that uses this function in this notebook from Chapter 22 of Modeling and Simulation in Python, and you can run it on Binder.
And here’s an example from Chapter 6 of Think Complexity, which you can also run on Binder.