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.
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
ffmpegto 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
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:
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
Recently I discovered simpler alternative using
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
%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.