Making Animations Doesn’t Have to Be That Hard

James Shaw
The Startup
Published in
6 min readAug 11, 2020

An Easy, No-Nonsense, Straightforward, Completely Accessible, Actually Readable Guide to Making & Viewing Matplotlib Animations in Python/Jupyter Notebooks

Part 1: Introduction and Using Celluloid

Python is great for a lot of reasons. One of the main ones is that so much is documented online. What’s less great, though, is that Python documentation is usually written by the creator at the level of understanding the creator is currently at; almost by definition a reader of the documentation is going to be at a lower level than this. It can thus be difficult to find out how to do a given task with python when you’re just starting out, as every new explanation requires you to add 20 new things to the list of things you need explained before you can go forward. Relying on stackexchange threads only compounds this, as they will center specifically on the particular question being asked and so it’s difficult to know how generalizable any particular piece of advice is. There’s another issue with some documentation/instruction being out-of-date, particularly for well-supported but still very much “capable of improvement” packages like Matplotlib. A final problem with guides is spending too long on the preface material, so let’s move on.

When I was starting out coding in Python, by way of Anaconda-Jupyter Notebooks, I was surprised at how bad the instructions I could find were for how to make animated plots. So, some time later and with a good bit better of a grasp on things, I’ll here lay out some simple to follow methods to create animations out of matplotlib graphs (or, at the end, other images) at three levels of complexity. After we make our first animation, I’ll also show how to easily view the animations we’re making in the same notebook we’re using to make them. So that’s the structure:

  1. Dead Simple Animations: Looping with Celluloid
    >Interlude: Seeing those animations
  2. A Little More Involved, But A Workhorse: Matplotlib’s FuncAnimation
  3. I’ll Put In The Effort, I Just Need This To Actually Work: Animations out of Pre-made Frames

To keep navigability, I plan on breaking this up over three articles. Like a Stalinist-era airbrushing, this might change later-on and I’ll re-edit this planned structure to accommodate that.

Going into this, I am assuming you’re familiar with making simple static plots in matplotlib. I’ve used a couple fancier functions in some of the graphics because, well, don’t try to tell me they don’t look great, but in every case they’ll be defined in a way clearly distinct from the animation “bits” and it’s my intent to make it clear how to modify the code to put in arbitrary functions. I’ll be using the standard import aliases here, i.e. I’m not a monster so I’m importing matplotlib.pyplot as plt.

Dead Simple Animations with Celluloid

Celluloid is a stupidly simple animation package to use, essentially just wrapping up a couple of matplotlib animation commands that we’ll see below. The essence of using Celluloid is simple. You just:

  1. Create a blank plot object as you normally would (plt.figure(), for instance)
  2. Create a “camera” that gets attached to your plot.
  3. Step through each frame you want to draw.
  4. “Take a snapshot” at each frame.
  5. Roll the resulting snapshots up into an animation.

Consider, for instance, an animation of the parametric curve given by

x=t cos(kt), y=t sin(kt) for 0≤t≤100

As we drag k from 0 to π. A few frames to show what is intended are given to the left. We’ll do essentially what we would want to do if we wanted to iterate through drawing every frame, and simply use a Celluloid’s .snap() feature after each iteration to “take a picture”. Celluloid will then clear out the figure and repeat for the next iteration, until we’re complete.

We’ll start by doing some standard imports:

matplotlib.pylab may be a little less familiar; it’s entirely optional but we’ll be using it here to make colors for our graph (which will look neat). Importing Video IPython.display will give us a nice way to view our animation.

The actual code we’ll use then looks like this:

And we’ll get the quite nice looking image to the right.

The saving and displaying of the animation merits some discussion. What exactly will be workable on a given system will depend on what you have installed and what your desires are, generally speaking mp4s will often be superior to gifs if hosting will not be a problem. It’s wise to just try out saving as an mp4 and to see if that works for you, if not then you can delve into matplotlib’s image writers, which would merit an entire blog post of its own. Understand that for matplotlib animations in general, the point at which you “animate” your creation does not “draw” an animation, it’s the point at which you save or otherwise export it that does that. We can thus control the speed at which our video plays by adjusting the fps passed to the save command. If you know the number of frames you have and your desired duration then your fps will just be the ratio of those two. Here I have 500 frames, and numerous studies have shown that the optimal time for an education gif is exactly 45 seconds, so the fps is just 500/45.

A note on adding animations to Medium.com:

Use gfycat. It’s fast, it lets you upload a ton of different filetypes quickly and painlessly, and it will give play controls (track, fast-forward, pause, etc) on Medium’s auto-embedded images, which many of the other big services won’t. When you’re composing your blog with gfycat’s images you’ll think the controls aren’t working, but they will be outside of “edit” mode.

For display, here we used Jupyter’s built-in “Video” method. This will take a saved video file and will display it directly in the notebook. We have two other, very similar, options to animate matplotlib animations directly: convert them to html5 videos or jshtml videos and display that inline. This is done entirely separate from the “.save()”, applied to the animation object itself. For the above code, for instance, rather than (or perhaps in addition to) the call to video, we could use: from IPython.display import HTML and then either HTML(animation.to_html5_video()) or HTML(animation.to_jshtml())

If you have the ability to host videos of either of the above formats you can also use those converted files directly, of course. The jshtml display is particularly nice to use within a notebook though, as it will create interactive controls for you to use.

Celluloid can be used to draw subplots with essentially the same syntax as was used above, with the only modifications to the code being the ones that you’d make to draw a still-frame shot of multiple subplots.

Notice that nothing has really changed here. Celluloid does have some limitations though, which all stem from it being a very nice way to handle matplotlib’s otherwise brutal animation modules. Celluloid’s basic assumption is that it can draw a single “normal” frame, with all the “background” stuff, and just move the “graph” part each time. As a result, we’ll run into trouble if we, for instance, change the size our plot in different frames or if we try to change elements that Celluloid assumes will be static, like labels or background colors. Why this happens and how to work-around it is touched on in very slight terms in Celluloid’s documentation; in doing more complicated graphing routines in later sections we’ll see what’s going on in the underlying code that calls this.

Notice as well that, for any significant challenge, Celluloid can get incredibly slow. This isn’t actually unique to Celluloid — in fact, Celluloid’s tricks to mitigate this are what cause the problems described above, and we’ll see some other ways to speed things up in later sections.

Further slowdown will be caused by the jupyter notebook itself though, especially as graphics objects accumulate. It’s wise to call plt.close(‘all’) routinely to make sure no unused plot objects are still hanging around, and expect to occasionally have to restart the kernel when making a lot of graphics objects.

--

--

James Shaw
The Startup

Former math/physics teacher with an interest in math, analytic philosophy, logic, Mesoamerican history, and Campbell’s Law. Aspiring data scientist.