Building a Streamlit app to visualise Covid-19 data | by Sam Ho | Towards Data Science

Our World in Data (OWID) is an open-source, online research publication. They have a wide range of public data sets covering topics such as disease, climate change, social injustice and poverty.
The goal of our work is to make the knowledge on the big problems accessible and understandable....Our World in Data is about Research and data to make progress against the world’s largest problems.
So you won’t be surprised to find that OWID has an extensive collection of Covid-19 data sets hosted on their GitHub repository.
Taking some of their Covid-19 data, I set out to build a Streamlit app that would allow users to visualise and analyse Covid-19 data. Of particular importance was the ability for users to be able to interact with the data allowing for bespoke analysis and answer questions such as:
Is there a relationship between population density and total cases?
Which countries are leading vaccination rollouts and who is lagging?
What features are most strongly correlated with deaths?
I wanted it to look easy on the eye so Plotly was my go-to charting library for this task. I specifically wanted to animate some of the visualisations and Plotly is one of a few libraries that can do this in a code efficient manner.
The final Streamlit is deployed and you can find it at the link below but I wanted to walk through some of the key steps I took to build it and talk about some of the thinking that took place throughout its development.
https://share.streamlit.io/kitsamho/covid-19-data/covid_streamlit_app.py
There are many datasets on the OWID GitHub repository however they have aggregated many of the key data points into one combined structure. This makes life considerably easier as transformations are in less intensive.
Let’s have a look at the CSV.
import pandas as pddf = pd.read_csv('owid-covid-data.csv')print(df.shape)(101752, 60)print('Total unique continents:',len(df.continent.unique()))print('Total unique countries:',len(df.location.unique()))print('Date span:',df.date.min(),df.date.max())Total unique continents: 7Total unique countries: 231Date span: 2020-01-01 2021-07-11
Let’s inspect the head of the data frame..
We have 60 columns and just over 101,000 rows (correct as of July 2021) with each row representing a day in the year
We have 231 countries spanning 6 continents
Almost all variables are continuous whereas country and continent are categorical.
There are a lot of null values (more on this in a bit).
Given the nature of our analysis some of these variables are dependent and some are best described as independent, especially if we are looking through the lens of risk factors and their impact on the Covid-19 pandemic.
Note — I’ve selected 24 variables that I feel are most useful to demo the dashboard, these are outlined below. If you want you can evolve this app and include more.
dependent_var = [‘total_deaths’,’total_deaths_per_million’,’total_cases_per_million’,’icu_patients_per_million’,‘people_vaccinated_per_hundred’,’total_vaccinations’,'hosp_patients_per_million]
independent_var = [‘gdp_per_capita’,’population’,’stringency_index’,’population’, ‘population_density’, ‘median_age’, ‘aged_65_older’,‘aged_70_older’, ‘gdp_per_capita’, ‘extreme_poverty’,‘cardiovasc_death_rate’, ‘diabetes_prevalence’, ‘female_smokers’,’male_smokers’, ‘handwashing_facilities’, ‘hospital_beds_per_thousand’,’life_expectancy’,'continent', 'location']
Most of these metrics are self explanatory. For a detailed breakdown of what they represent and how they are measured there is an extensive data dictionary here.
Often when we are preparing data we encounter missing values. This can be because of errors in collection pipelines or it could be because there wasn’t any data to begin with. When we are dealing with data that fits a gaussian distribution a valid strategy is to replace nulls with the mean and in other instances the median possibly. For data that is cumulative over time a more appropriate technique would be to use interpolation where we estimate and impute values based on its surrounding values. Pandas has a method pd.interpolate() and the default replacement is linear.
Inspection of the raw OWID data set revealed that missing values were commonplace. Here are some functions that solve this problem.
Below is the main function that produces clean/formatted data that can be used in the app.
Let’s have a look at the final DataFrame and see what it looks like for the UK.
Our Covid-19 dependent variables (e.g. total cases, deaths, hospital admissions, vaccinations) are grouped by week however you can see there is variance in the data as time progresses
Each independent feature (e.g GDP per capita, population density, smoking prevalence) is also grouped by week although the data doesn’t change as these things don’t move anywhere near as fast as Covid-19.
Great, now we have the data in the right format for our needs. Next we need to start thinking about how to present this data in a meaningful and useful way on our app!
This post assumes you have a working knowledge of how Streamlit works. If you don’t then please check this post out below for a primer
towardsdatascience.com
There were two useful ways of looking at this data:
Cross sectionally — exploring the relationships between multiple independent variables and dependent variables at a given point in time
Time Series — exploring relationships between multiple independent variables and dependent variables and how this changes over time
I wanted to keep the app light in use and didn’t want to have multiple plots showing different data points so I took the decision to only use one scatter plot visualisation but allow for a high level of customisation from the user. This would encourage interactivity and hopefully a degree of discovery.
I built two pages in the app, one for cross section visualisation and one for time series visualisation.
Note — navigating Streamlit apps using pages is not a native feature on the Streamlit platform but there is a work around to this outlined by Praneel Nihar here:
medium.com
I love scatter plots. If a scatter plot is static and in two dimensions, we have the ability to show up to five variables in just the one figure.
X axis
Y axis
Scatter point
Scatter Size
Scatter Colour
And if we animate these plots we can add a sixth dimension of time to the visualisation too
Finally if we can give the user the opportunity to change each one of these variables, then the scope of analysis can be widened even further. Widgets in Streamlit allow us to provide this feature.
In addition to X, Y and scatter data points I included some other visualisation options:
The option to mask by continent, i.e. only show countries from specific continents
For X and Y plot a vertical / horizontal line denoting a measure of central tendency for a feature. This allows you to see which markers are above or below the average for any plotted metric
The code block below outlines the steps needed to create the cross sectional scatter plot as above.
And this code block below outlines the steps needed to create the cross sectional scatter plot that is animated.
Hopefully this short walk through has given you some examples of how we can visualise data in a compelling way. By choosing the right tools to interact with the data and through using appropriate visualisations, exploratory analysis can be more efficient, more fun and more informative than using bar charts alone.
By all means please take my Streamlit code and build on it — there is a lot of data here and even though I have written some comprehensive code to visualise the data I’m sure there’s plenty of other avenues that can be explored and built upon.
Until next time!
Sam
To launch this app locally you should clone my forked OWID repo and in the command line run:
$ streamlit run covid_streamlit_app.py
Streamlit AppForked OWID repository with my appStreamlit app requirementsOriginal Our World In Data repo
Streamlit App
Forked OWID repository with my app
Streamlit app requirements
Original Our World In Data repo