Make an animated graph for temperature change of Sydney using matplotlib
Tags: Climate Change, Sydney, Time Series Analysis, Matplotlib, Data Science

Structure and theme of the plot

Figure 1, Static version

As a standalone figure (Figure 1), it should contain at least three parts, i.e. title, plot, and footnote. It’s easy to understand the first two parts. As for the footnote, it is usually used to indicate the data source of this figure and special notes.

The plot is no doubt the most important part of the whole figure. In this case, I used two different y-axes to show temperature and day individually and they shared the same x-axis, which is year. matplotlib provides an easy way to create twin axes: ax.twinx() / ax.twiny() . Specifically, ax.twinx() creates a new axes that shares the x-axis with ax , but can have a different y-axis. Likewise, ax.twiny() returns an axes sharing the y-axis, but with a different x-axis. After constructing the figure framework, I set up a futuristic colour theme, which inspired from Tron Legancy. In matplotlib , we can easily configure the figure style via plt.rc() . rc stands for Configure file that automatically Run at startup.

Tron theme using plt.rc()

Create a function for static plotting

Let’s take a look at the lite function for creating an animated plot in matplotlib .

animation.FuncAnimation(fig, func, frames=None)

Essentially, animated fig is just a collection of static axes obtained by func with different values ( frames ). It very much just like the flipbook animation. In my case, there are in total 160 frames of axes (from year 1859 to 2019, i.e. range(1859, 2020, 1) ). With this sorted out, I then created a function that could be applied generally to different year values.

The principle of this function is to draw elements layer by layer (or I may say it is the principle in matplotlib plotting). Let me explain the above function section by section.

ax.clear() and ax2.clear() , the previous axes should be erased before plotting a new one, otherwise, old content in axes will be still left on the fig and recorded in the animation. This is also why I did not include fig, ax = plt.subplots(figsize=(12,4), dpi=300) and ax2 = ax.twinx() in the function body, but called them separately before animation.FuncAnimation() . The plotting space (i.e. fig , ax , and ax2 ) only need to be set once, just like a reusable whiteboard.

and , the previous should be erased before plotting a new one, otherwise, old content in will be still left on the and recorded in the animation. This is also why I did not include and in the function body, but called them separately before . The plotting space (i.e. , , and ) only need to be set once, just like a reusable whiteboard. df_tmp=df_sydney.loc[df_sydney[‘Year’] <= Num_Year] , the input for plotting should be data. The new axes should contain data of current year and all its previous year (i.e. <= ). Therefore, when animation.FuncAnimation call the draw_linechart using Num_Year in any given year, the lines in the static figure should span from the beginning (i.e. year 1859) to the called year (Figure 2).

Figure 2, One static figure

ax.plot(df_sydney.Year, df_sydney.max_tmp_year*0, label=’_1', color=’#222222', zorder=1) , this is the first line plot. Notice that the data used here is not df_tmp but full dataframe df_sydney . This section is to create a frame spanned from the beginning year (1859) to the end year (2020) for each axes of df_tmp . This ensures the x-axis of each axes has the same year range (i.e. 1860–2020) instead of changing along with the year range of df_tmp (i.e. 1860- Num_Year ). In addition, I only need the frame and the line should be hid for this layer so that I timed 0 for each value ( df_sydney.max_tmp_year ) in y-axis. You may notice that the value assigned to label contains an underscore, this is to exclude this layer from being displayed by the automatic legend element selection later ( ax.legend() ). Another argument used here is zorder , which can be considered as the layer sequence along z-axis and 1 means the bottom layer.

, this is the first line plot. Notice that the data used here is not but full dataframe . This section is to create a frame spanned from the beginning year (1859) to the end year (2020) for each of . This ensures the x-axis of each has the same year range (i.e. 1860–2020) instead of changing along with the year range of (i.e. 1860- ). In addition, I only need the frame and the line should be hid for this layer so that I timed 0 for each value ( ) in y-axis. You may notice that the value assigned to contains an underscore, this is to exclude this layer from being displayed by the automatic legend element selection later ( ). Another argument used here is , which can be considered as the layer sequence along z-axis and 1 means the bottom layer. From ax.plot() to ax2.plot() , these add two horizontal lines to make it easy for audience to estimate the value of maximum temperature and accumulated days each year.

to , these add two horizontal lines to make it easy for audience to estimate the value of maximum temperature and accumulated days each year. From ax.plot() to ax.text() this is the part for drawing most elements (lines, head dot, text running with head dot) in the fig . It is very straightforward to understand. The only thing I want to highlight again here is the power of ax.xxx() , which made the manipulations of each axes easy enough.

After setting up the draw_linechart function, let’s draw a static graph by input a specific year. If everything goes right, you will see Figure 2.

# run one frame to test the function

fig, ax = plt.subplots(figsize=(12,4), dpi=300)

ax2 = ax.twinx()

draw_linechart(1987)

Wrap-up for animated graph

Running the following code will generate all frames and assign them to the object animator . Then this object can be saved as whatever format you want by animator.save() . My personal preference is to save as mp4 . The last note is that fig background colour should be specified using savefig_kwargs={‘facecolor’:’#222222'} , otherwise it will be saved as white which is not consistent with the Tron legancy theme.

import matplotlib.animation as animation fig, ax = plt.subplots(figsize=(12,4), dpi=300)

ax2 = ax.twinx() animator = animation.FuncAnimation(fig, draw_linechart,

frames=np.arange(df_sydney.Year.values.min(),df_sydney.Year.values.max(),1), interval=300) animator.save('./animation5.mp4', savefig_kwargs={'facecolor':'#222222'})