Build a COVID-19 Dashboard in Python with Plotly Dash | Towards Data Science

Ever since the first cases of COVID-19 appeared on American soil, it was unclear exactly how many people were exposed to the virus. Fortunately, the U.S. has dramatically increased our testing capacity since then.
But we’re still playing catch-up. And in the face of conflicting reports of the number of confirmed cases, our team of data scientists and web developers built a dashboard to show the most accurate data we could find, in the cleanest way possible.
And now that we’ve finished & launched our dashboard, we wrote this high-level code overview for data scientists & programmers to follow along.
When we started designing the above dashboard, we knew that some major features we wanted to include were the following:
Number of tested, confirmed cases, and deaths in the US, all 50 states, and all US counties (plus Washington D.C. & US territories)*
A heatmap of the largest concentrations of confirmed COVID-19 cases
Locations of public testing centers in each state
News updates from major health org’s via Twitter and their websites
*We’d also like to include the number of recovered cases but we’re searching for a reliable and accurate dataset before we do.
Broken down into all its parts, a dashboard is simply a bunch of datasets that are shown in a visually-appealing way. Every chart, graph, and table in our website was a dataset that we found, cleaned, and styled using CSS.
With that in mind, here are all the major steps that we’ll cover in this tutorial:
Decide which inputs we need for our app (tech stack & data sources)Initialize the Plotly Dash web appWeb page routing for desktop/mobile usersScrape & clean the data for our visualizationDisplay & style the data as a graph or chart
Decide which inputs we need for our app (tech stack & data sources)
Initialize the Plotly Dash web app
Web page routing for desktop/mobile users
Scrape & clean the data for our visualization
Display & style the data as a graph or chart
(Github repo in case you’d like to follow-along)
When our team was deciding which stack to use, we knew that our primary goal of cleaning & displaying healthcare data would best be accomplished if we wrote our application in Python. We thought about writing the app with R, but the ease & readability of a combined Flask server / Dash-rendered front-end caught our attention.
In that case, we knew that Plotly Dash and some of its libraries (Dash Core, HTML, and Bootstrap Components) could offer the tools we need and save us a ton of development time. And last but not least, MongoDB led to flexibility & a scalable backend for our hosted & cached data.
As far as the data’s concerned, we recognized that the majority of news outlets refer to the COVID-19 patient data from Johns Hopkins University Center for Systems Science and Engineering. This repo of daily .csv files is compiled from 17 different worldwide health organizations, so between these files and data from COVIDTracking, our team is confident in the numbers reported on our web application.
Now that our tech stack & libraries are settled, let’s cobble together some code! Step one: design the core application in our /app.py file.
First we’ll import Flask (for our server which we create on line 11), and Dash as our front-end app. Dash uses Flask for deployment, so when we write:
server = flask.Flask(__name__)
...this allows us to deploy our app like we would any other Flask application!
Next, let’s assign our server, stylesheets, and meta tags for SEO:
app = dash.Dash(__name__,server = server,external_stylesheets = [dbc.themes.SLATE,  # Bootswatch theme"https://use.fontawesome.com/releases/v5.9.0/css/all.css",],meta_tags = [{"name": "description","content": "Live coronavirus news, statistics, and visualizations tracking the number of cases and death toll due to COVID-19, with up-to-date testing center information by US states and counties. Also provides current SARS-COV-2 vaccine progress and treatment research across different countries. Sign up for SMS updates."},{"name": "viewport", "content": "width=device-width, initial-scale=1.0"},],)
Great, now our application’s ready to be accessed as a web page!
Now that we’ve built the application shell in /app.py, let’s create a /run.py file to handle when a user visits the URL & route them in the right direction.
For example, if an Android mobile user visits our site, then any code inside of our default layout — that’s build_desktop_layout, the variable on line 10 — won’t be appropriate for their screen size because it’s made for bigger screens.
We use Flask’s Request object on line 17 to check the HTTP headers our app just received from our user, and confirm that we are seeing a mobile device. In that case, is_mobile is set to True on line 20, which changes our default app layout to build_mobile_layout instead of the desktop version. Nice!
But we’re not out of the woods just yet...we still need to actually serve our users the correct nav, dashboard body, and footer for their device!
So our last step is to write the display_page() function on line 52, which works with a Dash callback (line 48) to examine the URL visited by our user.
We’ll have to check whether the user visited an appropriate URL pathname (“/” is our dashboard, or “/about” or “/resources”), and then we’ll use our trusty is_mobile variable (which was a True or False boolean value) to return the correct mobile/desktop imported files stored as variable names.
Why don’t we take a second to review: so far, we’ve built the outer layer of our web app, and we’re able to route the user to the correct page content. However if we navigated to the site right now we would see...a blank page!
Our next priority will be locating, scraping, and importing our COVID-19 patient data. After that’s done, and we have something to publish, then we can style and make our site look halfway decent. Let’s get to work!
For any readers who aren’t data scientists, data scraping is important because all of the raw data inputs our application consumes (like confirmed COVID cases, tweets, published article titles) will come with bad data formatting (empty values, wrong column names, etc).
In order to handle the data scraping, we used Jupyter notebooks and the Pandas library, along with Plotly.Express and Plotly.Graph_Objects for visualizing our graphs. Let’s see an example!
When a user clicks on North Dakota in our application, we want to show them all the recent news related to ‘coronavirus’ in their state. In order to do this, the above table of article titles, URLs, timestamps, state, & sources is custom-made with all the data our application needs. And it’s coming from a simple Google search that we’re making inside a Jupyter notebook:
What we’re looking at is one function that’s written to make a Google search for a US state and topic (say, “North Dakota” / “Coronavirus”).
We clean the search results, and then we return a formatted list of recent new articles. The function takes in two parameters (‘state’ and ‘topic’), and we add these parameters to a custom news.google search inside the curly brackets in the url variable...right after google.com/rss/, you’ll see search?q={}+{}.
Next, we use Beautiful Soup to separate each individual news article based on its XML tags, such as <item><title> in this example.
After splitting each news article into our xml variable, we split each of the columns into separate Python lists (for example, the list_of_article_links variable will be filled with any <title><link> combo).
The last step we’ll take, is to create a Pandas dataframe — which is basically just a spreadsheet of columns & data rows — and fill it with all of our article lists. We’ll eventually use this dataframe when we convert our data to JSON format so that the front-end can read & display it.
For the purposes of this high-level walkthrough, we’ll gloss over our back-end & API endpoints since the server’s data-cleaning process is pretty similar to what we just covered. In summary, our Pandas dataframe is stored on our MongoDB backend, cleaned again & re-formatted as web-friendly JSON objects, then sent to our front-end web application with a GET request.
Now that our patient data is scraped, cleaned, & available to the front-end, we can tie this whole thing together! So how will we show it to our users?
Earlier we cleaned the data for our news articles, but let’s pivot and work on the visualizer for our map data. This way we can show the confirmed number of COVID-19 cases reported by every U.S. county.
First we’ll write a function called confirmed_scatter_mapbox(). We’ll display it in our application using a Graph from our Dash Core Components library.
Lines 180–189 in scatter_mapbox.py
First, lines 181 & 182 will call our back-end server, where we’ve stored all our scraped patient data for every US county. Then we’ll store the response inside of a Pandas dataframe.
Using our data variable — the Pandas dataframe — we can read useful info about every county! This ranges from which state our county’s in (the “State Name” on line 186) to its latitude & longitude values, and we can also read the number of confirmed cases & deaths due to COVID-19. For reference, here’s a list of the relevant column names in our dataframe:
County Name
State Name
Confirmed
Deaths
Fatality Rate
Latitude
Longitude
Last Update
And lastly, the color_scale on line 189 gives us a range of colors to display, from pink to dark red, depending on the severity of a county’s outbreak (relative to all the other counties in the dataset).
But how does the map get created?
Lines 195–207 in scatter_mapbox.py
On line 195, “px” stands for Plotly Express, which we use for their scatter_mapbox() function which creates a default scatterplot. This scatterplot will consume our patient data in order to show all those yellow circles.
Next, we pass our Pandas dataframe into the scatterplot on line 196. After that, all of our code is just setting the custom parameters for every row (county) in our spreadsheet.
There are 3,007 counties in the United States — each county has both a latitude and longitude value, and we reference those column names in lines 197 and 198 in order to pick a location on the scatterplot for our county circle. Lines 202 and 203 make it so that when a user hovers over a county, they’ll see the state & county name along with the number of confirmed cases & deaths, like so:
Now that we have our map displaying and rendering data, we’re all finished with this feature! Every chart, table, and dataset that you see on our site was built with this same process. So if you’d like to create your own dashboard, well, there’s a lot of data in the world and your display options are endless.
Good luck!
There are a plenty of features in our dashboard that we haven’t covered, but our goal here was to give you a high-level overview of:
Scraping COVID-19 data
Displaying it in a human-friendly way.
As a team we’ve already learned a ton from working on this dashboard. We’re hoping not only to publish some insights from the data, but also to roll out these features:
Vaccine tracker: listing the updates on clinical trials and the latest milestones in COVID-19 vaccination efforts.
SMS integration: sending you messages to your mobile phone with daily updates for your county, state, and new statistics or news updates.
Across the country, we’re all feeling the uncertain, confusing nature of this pandemic. That’s exactly why our team is committed to parsing the data, to making sense from the confusion, and to sharing what we find with you.
Visit our full dashboard here, and you can follow my personal journey here as I write code for some more freelance projects.