COVID-19 Geospatial Data Visualization with Plotly, Geopandas, and Folium

Featured Photo by Andrea Piacquadio on Pexels

The rapid diffusion of COVID-19 and data convenience had enforced the global community to work more vigorously on geospatial analysis of this pandemic.

This is a shining moment for GIS: responding to COVID-19 with maps and data-smart city solutions.

Best Practices:

In New Zealand, residents can stay abreast of changing business hours and other government services using the COVID-19 Public Information Map. The map categorizes each location by service type (e.g., Council Services and Facilities, Emergency Services) and open status. Similarly, the City of Baltimore built the COVID-19 Asset Map to help local residents, particularly those with lower socioeconomic status, find critical resources near them such as youth and senior food distribution sites, primary care facilities for the uninsured, and COVID-19 testing sites.

Following previous studies and above best practices, this post emphasizes the use of GIS and spatial analysis on environmental issues related to COVID-19.

Scope: Covid-19 Geospatial Data Visualization in Python using Plotly Express, Geopandas, and Folium.

Deliverables: Geo scatter Plotly Express plots, the Plotly density plot mapbox, the choropleth maps, the Geopandas world map, and the Folium geographic map.

The Road Ahead: PREDICTIVE COVID-19 ANALYTICS AND PROACTIVE ACTION.

Table of Contents:

  1. Input Data
  2. The WHO Regions
  3. Cumulative Deaths
  4. Cumulative Cases
  5. New Cases
  6. New Deaths
  7. Folium Map
  8. Social Impact Summary
  9. Tutorials
  10. Explore More
  11. Embed Socials

Input Data

Let’s set the working directory YOURPATH

import os
os.chdir(‘YOURPATH’)
os. getcwd()

and import the libraries

import plotly
import plotly.express as px
import pandas as pd
import io #to read uploaded files
import geopandas as gpd
import shapely as shp
from shapely.geometry import Polygon, LineString
import folium
from folium.plugins import MarkerCluster
from datetime import datetime

Let’s load the built-in dataset

country_data= px.data.gapminder()
country_data.head()

COVID-19 built-in dataset table

let’s rename the column that matches another column in the dataset
country_data.rename(columns={‘country’:’Country’}, inplace=True)
country_data.head()

COVID-19 built-in dataset table with the renamed column

Let’ read the WHO document
covid_data=pd.read_csv(‘WHO-COVID-19-global-data-2.csv’)
covid_data.head()

WHO COVID-19 global dataset

Let’s merge the above two datasets with the Country_code column being the common column
data =covid_data.merge(country_data[[‘Country’,’iso_alpha’]], on=[‘Country’], how=’left’)
data.head()

Merged two COVID-19 datasets with the Country_code column being the common column

Let’s read the following document
latlong_data=pd.read_csv(‘world_country_and_usa_states_latitude_and_longitude_values.csv’)
latlong_data.head()

World countries and USA states latitude and longitude values

Let’s merge the two datasets with the Country_code column being the common column
all_data =data.merge(latlong_data[[‘country’,’latitude’, ‘longitude’]], left_on=[‘Country’], right_on=[‘country’], how=’left’)
all_data.head()

Let's merge the two datasets with the Country_code column being the common column.

Let’s prepare the final dataframe

df = all_data.groupby([‘Country_code’,’Country’,’WHO_region’,’iso_alpha’, ‘latitude’, ‘longitude’]).sum().reset_index()

df.head()

The final dataframe grouped by Country_code.

The WHO Regions

Let’s perform Plotly visualization of the WHO regions around the world using px.scatter_geo

map_fig = px.scatter_geo(df,
locations=’iso_alpha’,
projection = ‘orthographic’,
color = ‘WHO_region’,
opacity = .8,
hover_name = ‘Country’,
hover_data = [‘New_cases’, ‘Cumulative_cases’, ‘New_deaths’, ‘Cumulative_deaths’],
title = “Visualization of the WHO regions around the world”
)
map_fig.show()

Plotly visualization of the WHO regions around the world
  • The globe above shows Africa is composed of two WHO regions. AFRO in most of Africa and EMRO on the northern part of Africa.
  • North and South America have one region called AMRO
  • Europe has one region (EURO)
  • Asia and Australia seem to have a mixture of AMRO, WPRO, and SEARO

In the above plot, every dot contains the following information:

Country, WHO_region, iso_alpha, New_cases, Cumulative_cases, new_deaths, and Cumulative_deaths.

Cumulative Deaths

Let’s plot cumulative deaths due to COVID-19 using px.density_mapbox

fig = px.density_mapbox(df, lat=’latitude’, lon=’longitude’,
z = ‘Cumulative_deaths’, radius = 10,
center = dict(lat = 9, lon=9),
zoom = 1,
hover_name = ‘Country’,
mapbox_style = ‘stamen-terrain’,
title = ‘Cumulative deaths due to Covid19’)
fig.show()

Cumulative deaths due to Covid19

We can get a country specific information by hovering over the country location as follows

  • Africa seems to be in a good position with respect to e.g. South America
  • America and Europe exhibit many cumulative deaths due to COVID-19
  • Some parts of South and SE Asia are also affected by COVID-19.

Cumulative Cases

Let’s look at cumulative cases

maxcc=df[‘Cumulative_cases’].max()
maxcc

140638479540

mincc=df[‘Cumulative_cases’].min()
mincc

21372156

We can plot the world COVID-19 Cumulative Cases using the Plotly choropleth map

fig = px.choropleth(df,
locations = ‘iso_alpha’,
locationmode = ‘ISO-3’,
scope = ‘world’,
color = ‘Cumulative_cases’,
hover_name = ‘Country’,
hover_data = [‘Country’,’WHO_region’, ‘New_cases’, ‘Cumulative_cases’, ‘New_deaths’, ‘Cumulative_deaths’],
range_color = [20000000,141000000000],
color_continuous_scale=’armyrose’,
title = ‘World Covid19 Cumulative Cases’)

fig.show()

World Covid19 Cumulative Cases

It appears that India has the highest number of cumulative cases.

New Cases

Let’s look at the world plots using Geopandas
world_map = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))

by reading the built-in country geometry data
world_map.head(2)

Built-in country geometry data

Let’s merge the two datasets with the Country_code column being the common column
geo_data =world_map.merge(df[[‘iso_alpha’, ‘New_cases’,’Cumulative_cases’,’New_deaths’,’Cumulative_deaths’]], left_on=[‘iso_a3’], right_on=[‘iso_alpha’], how=’left’)
geo_data.head()

Merged world_map.merge dataset the Country_code column being the common column

let’s check the data content

geo_data.columns[geo_data.isnull().any()]

Index(['iso_alpha', 'New_cases', 'Cumulative_cases', 'New_deaths',
       'Cumulative_deaths'],
      dtype='object')

geo_data.info()

<class 'geopandas.geodataframe.GeoDataFrame'>
Int64Index: 177 entries, 0 to 176
Data columns (total 11 columns):
 #   Column             Non-Null Count  Dtype   
---  ------             --------------  -----   
 0   pop_est            177 non-null    float64 
 1   continent          177 non-null    object  
 2   name               177 non-null    object  
 3   iso_a3             177 non-null    object  
 4   gdp_md_est         177 non-null    int64   
 5   geometry           177 non-null    geometry
 6   iso_alpha          114 non-null    object  
 7   New_cases          114 non-null    float64 
 8   Cumulative_cases   114 non-null    float64 
 9   New_deaths         114 non-null    float64 
 10  Cumulative_deaths  114 non-null    float64 
dtypes: float64(5), geometry(1), int64(1), object(4)
memory usage: 16.6+ KB

geo_data = geo_data.dropna(how=’any’,axis=0)
geo_data.info()

<class 'geopandas.geodataframe.GeoDataFrame'>
Int64Index: 114 entries, 3 to 175
Data columns (total 11 columns):
 #   Column             Non-Null Count  Dtype   
---  ------             --------------  -----   
 0   pop_est            114 non-null    float64 
 1   continent          114 non-null    object  
 2   name               114 non-null    object  
 3   iso_a3             114 non-null    object  
 4   gdp_md_est         114 non-null    int64   
 5   geometry           114 non-null    geometry
 6   iso_alpha          114 non-null    object  
 7   New_cases          114 non-null    float64 
 8   Cumulative_cases   114 non-null    float64 
 9   New_deaths         114 non-null    float64 
 10  Cumulative_deaths  114 non-null    float64 
dtypes: float64(5), geometry(1), int64(1), object(4)
memory usage: 10.7+ KB

Let’ plot the world map using the above Geopandas dataframe
geo_data.plot(column=’New_cases’)

New_cases world map

New Deaths

Let’s check whether our dataframe is truly a Geopandas dataframe
type(geo_data)

geopandas.geodataframe.GeoDataFrame

and check details of the dataframe in the first two rows
geo_data.head(2)

Details of the merged dataframe in the first two rows

Let’s plot the World COVID-19 New Deaths using px.choropleth_mapbox

fig = px.choropleth_mapbox(geo_data,
geojson = geo_data,
color = ‘New_deaths’,
locations = ‘name’,
featureidkey = ‘properties.name’,
center = {‘lat’:0, ‘lon’:0},
mapbox_style = ‘carto-positron’,
zoom =1,
title = ‘World Covid19 New Deaths’,
opacity = .3,
color_discrete_map = {
‘Beregon’:’cyan’,
‘Joly’:’magenta’,
‘Coderre’:’Yellow’
}
)
fig.show()

The World COVID-19 New Deaths using px.choropleth_mapbox

Folium Map

Finally, let’s invoke the base Folium OpenStreetMap
m = folium.Map(location=[0,0],tiles=’OpenStreetMap’, zoom_start = 2)
m

The base Folium OpenStreetMap

Let’s get the first/last recorded date
max=data[‘Date_reported’].max()
min=data[‘Date_reported’].min()

and get the difference between the dates

d1 = datetime.strptime(min, “%d/%m/%Y”)
d2 = datetime.strptime(max, “%d/%m/%Y”)
dataset_days = abs((d2 – d1).days)

dataset_days

251

We can now calculate the cases of new deaths per day
df[‘New_deaths_per_day’] = df[‘New_deaths’]/dataset_days

df.head(2)

New deaths per day

let’s print the lowest/highest number of recorded deaths in a day
print(df[‘New_deaths_per_day’].max())
print(df[‘New_deaths_per_day’].min())

30205.673306772907
0.7171314741035857

Let’s get the final Folium map

for i, row in df.iterrows():
lat = df.at[i, ‘latitude’]
lng = df.at[i, ‘longitude’]
info = ‘Country: ‘ + df.at[i, ‘Country’] + ‘
‘ + \
‘New_cases: ‘ + df.at[i, ‘New_cases’].astype(str) + ‘
‘ + \
‘Cumulative_cases: ‘ + df.at[i, ‘Cumulative_cases’].astype(str) + ‘
‘ + \
‘New_deaths: ‘ + df.at[i, ‘New_deaths’].astype(str) + ‘
‘ + \
‘Cumulative_deaths: ‘ + df.at[i, ‘Cumulative_deaths’].astype(str)

if df.at[i, ‘New_deaths_per_day’] > 100:
color = ‘red’
else:
color = ‘green’

folium.Marker(location=[lat,lng], popup=info,
icon = folium.Icon(color=color)).add_to(m)

m

The Folium world COVID-19 OpenStreetMap with Portugal zoom-in
The Folium world COVID-19 OpenStreetMap.

Most African countries recorded the lowest number of deaths per day as compared to other parts of the world.

Social Impact Summary

  • Using the highly interactive maps discussed above, everyday citizens can calculate the COVID-19 risk they are exposing themselves or others to.
  • Governments can use our maps not just for emergency preparedness and response services, but also for communicating with, and targeting messages to, residents. 
  • Results demonstrated how the geospatial content can help residents understand the challenges faced by their neighbors and helps to inspire a sense of shared responsibility to protect one another. 
  •  As users hover over the geospatial maps, they can see how many residents live in those neighborhoods with other pre-existing conditions that put them at higher risk of adverse outcomes from COVID-19.
  • Additionally, with COVID-19 patients taking priority, other patients with unrelated medical issues have been forced to wait prolonged periods of time for treatment or forgo treatment altogether. This poses a great risk to their health and the health of the public. 
  • Following the Northern Ireland best practices, we are currently working to monitor hospital wait times for non-COVID related medical needs. This geospatial map can be used by the public to determine which hospital is the best to go to in the event of a medical issue to reduce wait times and the accompanying exposure to the virus.

Tutorials

Mapping with Matplotlib, Pandas, Geopandas and Basemap in Python

Best Covid-19 Courses & Certifications [2023] – Coursera

Explore More

50 Coronavirus COVID-19 Free APIs

Interactive Global COVID-19 Data Visualization with Plotly

Comparing 4 Python Libraries for Interactive COVID-19 Data Science Visualization

How is Asia doing against the Covid-19?🦠[Bokeh]

DateSliders in Visualization of Covid-19 cases by Zipcodes with Python/Bokeh

Embed Socials

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

$5.00
$15.00
$100.00
$5.00
$15.00
$100.00
$5.00
$15.00
$100.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: