COVID-19 Death Count Animation

Ömer Faruk Eker
Analytics Vidhya
Published in
4 min readApr 8, 2020

This notebook guides you to animate daily death counts due to coronavirus using Python libraries.

The dataset we use can be downloaded from the link below:

https://ourworldindata.org/grapher/covid-confirmed-deaths-since-5th-death

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="darkgrid",palette="tab10")
# plt.style.use("seaborn-pastel")
import warnings
warnings.filterwarnings("ignore")
%matplotlib widget

The data table is in the .csv format

df=pd.read_csv("covid-confirmed-deaths-since-5th-death.csv")
df.head()

The number of rows will increase daily, as of today (8th April 2020) it has 10,803 rows. The table has 5 columns:

  • Entity: Country Name
  • Code: Country Code
  • Date: Day
  • Total confirmed deaths: Total number of deaths
  • Days since the 5th total confirmed death: Number of days passed since the 5th death for that specific country
df.Date = pd.to_datetime(df.Date)df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10803 entries, 0 to 10802
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Entity 10803 non-null object
1 Code 9346 non-null object
2 Date 10803 non-null datetime64[ns]
3 Total confirmed deaths (deaths) 10795 non-null float64
4 Days since the 5th total confirmed death 2159 non-null float64
dtypes: datetime64[ns](1), float64(2), object(2)
memory usage: 422.1+ KB

Let’s retrieve a single country data and see how it looks:

df[df.Entity == "Turkey"]

Animation

Let’s make our animation which outputs a gif file which also shows the animation inside this notebook. First, we need to take non-country entries out of our data. Dataset not only has countries but also regions like “Asia”, “Europe”, etc.

import pycountrydata_all =  pd.pivot_table(values="Total confirmed deaths (deaths)",index="Date",columns="Entity", data=df)is_country = dict()
for cd in data_all:
try:
is_country[cd] = pycountry.countries.get(name=cd).alpha_3
except:
data_all.drop(cd,axis=1,inplace=True)

Select Top10 death toll countries to plot (default, last line below), or list the countries you want to animate

# selected = ['Italy', 'Spain', 'United States', 'France', 'United Kingdom', 'China']
# selected = ['France','Belgium', 'Germany', 'Turkey']
selected = data_all.iloc[-1,:].sort_values(ascending=False)[:10].index

Trim the data based on selection. We will also interpolate data points between days to have a smooth animation. The interpolated data will be hourly counts (linear interpolation used)

data = data_all[selected].round()
data = data[data.sum(axis=1)>0]
data.replace(0,np.nan,inplace=True)
#interpolation
data = data.resample("1H").interpolate(method="linear")

Import necessary packages for creating animation and saving them, and initiate the plots and their elements (lines, bars, texts, etc.)

from matplotlib.animation import FuncAnimation, FFMpegWriterfig,(ax,ax2) = plt.subplots(1,2,figsize=(15,7))
fig.subplots_adjust(wspace=0.3,bottom=0.2)
no_of_frames = data.shape[0] #Number of frames
#initiate the barplot with the first rows of the dataframe
#BARPLOT
bars = sns.barplot(y=data.columns,x=data.iloc[0,:],orient="h",ax=ax)
ax.set_xlim(0,30000)
txts = [ax.text(0,i,0,va="center") for i in range(data.shape[1])]
title_txt = ax.text(10000,-1,"Date: ")
ax.set_xlabel("Death Count")
ax.set_ylabel(None)
#LINEPLOT
lines = ax2.plot(data.iloc[:1,:])
xdata,ydata = [],[]
ax2.set_ylabel("Death Count (log)")
ax2.set_yscale("log")
ax2.set_ylim(-0.1,30000) #ylim has to be declared after defining log scale
ax2.set_xlim(data.index[0].date(),data.index[-1].date())
#LINEPLOT texts
txts_line = [ax2.text(0,0,data.columns[i],va="center") for i in range(data.shape[1])]

Define the animation function which will be called for generating each frame

def animate(i):
ax2.set_xticklabels(ax2.get_xticklabels(),rotation=45)

#get i'th row of data
y = data.iloc[i,:]

#update title of the barplot axis
title_txt.set_text(f"Date: {str(data.index[i].date())}")

#update elements in both plots
for j, b, in enumerate(bars.patches):
#update each bar's height
b.set_width(y[j])

#update text for each bar (optional)
txts[j].set_text((y[j].astype(int)))
txts[j].set_x(y[j])

#update lines
lines[j].set_ydata(data.iloc[:i,j])
lines[j].set_xdata(data.index[:i])

#update line texts
txts_line[j].set_text(data.columns[j])
txts_line[j].set_x(data.index[i])
txts_line[j].set_y(y[j])

Run the animation function with the desired number of frames and delay (interval in millisecond)

anim=FuncAnimation(fig,animate,repeat=False,frames=no_of_frames,interval=1,blit=False)
plt.show()

Finally, save the animation, many options are available such as mp4, gif, etc.

import time
import warnings
warnings.filterwarnings("ignore")
t1 = time.time()
print("Rendering started...")
#save the animation with desired frames per second
# smoothness of the video depends on the number of datapoints and fps, the interval parameter above does not affect the video
anim.save('covid_movie.mp4',writer=FFMpegWriter(fps=35))
# #save the animation in gif format
# anim.save("covid_gif.gif",writer="pillow",bitrate=5)
print(f"Saving of {no_of_frames} frames Took {time.time()-t1} seconds")

Alternatively, you can make the animation with the Celluloid package. It has a very simple API and easy to implement; however, it takes much longer to animate and create the output file

from celluloid import Camera
fig = plt.figure(figsize=(12,5))
fig.subplots_adjust(wspace=0.4,bottom=0.2)
ax = fig.add_subplot(121)
ax.set_yscale("log")
ax.set_ylabel("Death Count")
ax2 = fig.add_subplot(122)
ax2.set_xlabel("Death Count")
camera = Camera(fig)
for j,day in enumerate(data.index):
print("{0}".format(day),end="\r")
sns.barplot(x=data.iloc[j,:].values,y=data.columns,orient="h",ax=ax2)
for i,coun in enumerate(data.columns):
data[coun].loc[:day].plot(ax=ax,legend=False)
ax.text(day,data[coun].loc[day],coun,va="center")
try:
count = int(data[coun][day])
except:
count = data[coun][day]
ax2.text(count+1,i,count,va="center")
ax2.set_ylabel(None)
ax2.text(0.35,1.03,f"Date: {day.date()}",fontsize=12,transform=ax2.transAxes)
camera.snap()
anim = camera.animate(interval=50)
anim.save("Death Count per Country.gif",writer="pillow")

--

--