Basic Animation with Matplotlib and Plotly. | by Ben Bogart | Towards Data Science

Sometimes it just makes sense to animate a visual. Animation can help understand data in a more complete way. If your medium is the screen you may find yourself needing to animate occasionally. The next question you ask will be, “which toolkit should I use for the animation.” Here I compare the process for creating a basic animation like the one below with Matplotlib and Plotly.
If you don’t care about the data I won’t judge you— Skip to Animations
We are going to animate the fitting of a linear regression model with gradient descent. I chose this because the animation is simple, yet has two distinct components:
a scatter plot of the data points
an animated line to visualize the fitting process
I generated the random data using sklearn.datsets.make_regression like so:
from sklearn.datasets import make_regressionX, y = make_regression(n_features = 1, noise=50, random_state=543)
This is what our test data looks like. We are going to keep the code as simple as possible so I won’t be doing any extra formatting, despite the pain it causes both of us.
We can’t use statsmodels or sklearn’s LinearRegression models because we need to store each iteration of the regression, not just the final fit line. I instead borrowed the code from Joseph Bautista’s post Linear Regression Using Gradient Descent in 10 Lines of Code and modified it to return a list of of the slopes and intercepts for each of iteration of the fit.
This isn’t necessary to understand for the animation, but here is the regression function.
def linear_regression(X, y, m_current=0, b_current=0, epochs=1000, learning_rate=0.0001):    N = float(len(y))        ms = [m_current] # a place to store our m values    bs = [b_current] # a place to store our b values        for i in range(epochs):        y_current = (m_current * X) + b_current        cost = sum([data**2 for data in (y-y_current)]) / N        m_gradient = -(2/N) * sum(X * (y - y_current))        b_gradient = -(2/N) * sum(y - y_current)        m_current = m_current - (learning_rate * m_gradient)        b_current = b_current - (learning_rate * b_gradient)                ms.append(m_current)        bs.append(b_current)            return ms, bs
To generate the data for the animation of the line fitting, we feed our X data into the linear_regression function and calculate the sequential lines for plotting.
# run the linear regression and store the m and b valuesms, bs = linear_regression(X.ravel(), y,                            epochs=200, learning_rate=0.01)# build the lines, mx + b for all ms X's and b'sy_lines = (ms * X + bs).T # transpose for easier indexing
Now what you’ve all been waiting for....
The animation api for matplotlib is simple, but if you are in jupyter notebooks your animation will not run with the GUI backend set to inline. I set the GUI backend to notebook. There are other solutions as well.
The process for animation goes like this:
instantiate a matplotlib figure
plot the data with plt.scatter
plot the initial regression line and save it to line so we can update it later, The comma (,)after line unpacks the single Line2D object from the list returned by plt.plot.
# we will need the fig object laterfig = plt.figure)# plot our original dataplt.scatter(X,y)# create a line to updateline, = plt.plot(ms[0], bs[0], c='r', linewidth=5)
In order to animate the line we write a function which updates the data in the line object with the with its set_ydata method. Then we use Matplotlib’s FuncAnimation method to call that function for each frame in our animation.
from matplotlib.animation import FuncAnimation# update the frames based on the parameter idef animate(i):        # set the title to indicate the iteration    plt.title(f'Gradient Descent step {i}')        # change the data for the y axis points of the line    line.set_ydata(y_lines[i])return line,# run the animationanimation = FuncAnimation(fig, animate, frames=len(ms), interval=20)
Which gives us this! How cool is that?
The attributes of FuncAnimation attributes used here are:
fig: the figure object on which to apply the animation
func: the function to call to update each frame
frames: the total number of times to call the above function. This is equal to the total number of frames in the final animation.
interval: the delay in milliseconds between frames.
Saving the animation with matplotlib is arguably the most confusing part. Because matplot lib is generating rasterized images for each frame we will save this plot to mp4 file. The steps are:
instantiate a writer to define the output file parameters
use the writer to write an mp4 file to disc
# Set up formatting for the movie filesfrom matplotlib.animation import writersWriter = writers['ffmpeg']writer = Writer(fps=12.5)# save an mp4 of the animationanimation.save('matplotlib.mp4', writer=writer)
That wasn’t so bad. Let's try with Plotly
Now we will walk through creating the same plot with plotly. Because our animation will be made up of more than one plot (a scatter plot and a line plot) we will use plotly.graph_objects for the elements of our figure.
create a graph_object for our scatter plot — points
create an initial line — line. This is also done with a Scatter object in Plotly
create a layout object layout to set the title before animation (optional)
combine the above elements into a figure — fig
import plotly.graph_objects as go# create the scatter plotpoints = go.Scatter(x=X.flatten(), y=y, mode='markers')# create initial lineline = go.Scatter(x=X.flatten(), y=y_lines[0])# create a layout with out title (optional)layout = go.Layout(title_text="Gradient Descent Animation")# combine the graph_objects into a figurefig = go.Figure(data=[points, line])                                 # to see what we have so farfig.show()
To animate the line we need to create two more objects, a list of Frame objects and a Layout object. The Layout updates our title and adds a play button to figure which both starts the animation and sets the animation speed.
The process is
loop through the list of regression lines (y_lines) to create respective animation frames.
instantiate a Layout object which adds a play button and sets the title
# create a list of framesframes = []# create a frame for every line yfor i in range(len(y_lines)):    # update the line    line = go.Scatter(x=X.flatten(), y=y_lines[i])    # create the button    button = {        "type": "buttons",        "buttons": [            {                "label": "Play",                "method": "animate",                "args": [None, {"frame": {"duration": 20}}],            }        ],    }    # add the button to the layout and update the     # title to show the gradient descent step    layout = go.Layout(updatemenus=[button],                        title_text=f"Gradient Descent Step {i}")    # create a frame object    frame = go.Frame(        data=[points, line],         layout=go.Layout(title_text=f"Gradient Descent Step {i}")    )# add the frame object to the frames list    frames.append(frame)
Then we combine all of the above into a single Figure object
# combine the graph_objects into a figurefig = go.Figure(data=[points, line],                frames=frames,                layout = layout)                                 # show our animation!fig.show()
And just like that:
Plotly does not have mechanism to save your animation to a movie file. It does however have a very easy way to export html which you can embed in a webpage. In order to keep our animation speed we set with the play button we have to set auto_play = False.
# save the figurefig.write_html("plotly.html", auto_play=False)
Matplotlib and Plotly take similar yet different approaches to animation. I personally find Matplotlib’s FuncAnimation method to be more intuitive and would use that for inline animations in a jupyter notebook or for a medium article where the mp4 has to be converted to gif for inclusion.
Because the Plotly html file generates vector images with javascript we would expect the html file to be smaller than the movie export from Matplotlib. That is not the case. The Matplotlib mp4 export is 874 KB while the html export from Plotly is 5.2 MB (wow!). In the case of the Plotly html file, the size is due to the inclusion of the entire Plotly js library for rendering offline, which is convenient.
In the end, I would make the decision between Matplotlib and Plotly for animation based on where the animation will appear. If the animation is on a webpage where it would benefit from interaction I would use Plotly. For use in videos or websites where you cannot embed html (like Medium), I would recommend Matplotlib.
Which will you choose?
Matplotlib animation documentation: https://matplotlib.org/stable/api/animation_api.html
Plotly animation documentation: https://plotly.com/python/animations/
All code for this article is available at: https://github.com/benbogart/matplotlib-plotly-animations