Choropleth Maps in practice with Plotly and Python
By: Thibaud Lamothe
Tags: Data Visualization, Towards Data Science, Plotly, Maps, Python

That being said, the purpose of today is to:

See how easy it is to implement a new choropleth map with new data and new geographical zones

Even if it implies some refactoring, see how easy it is to do it when you already did once

Get insights about France second national lockdown, in November 2020

Hands-on 🦠 🇫🇷 🛏

Previously we worked with a worldwide dataset, presenting results over the globe. Today, in order to demonstrate the scalability of the process, we’ll have a zoom on a specific country: France.

So what do we need to plot that choropleth map?

A dataset with a KPI to compare in different geographical zones

A GeoJSON file to cover the geographical area to color on the map

Create a link between those

As requirements, you’ll also need a python development installed with the following two libraries: pandas , plotly . Installed and imported at the beginning of the script 😉

import pandas as pd

import plotly.express as px

1. The Dataset

The dataset comes from opendatasoft. Instead of the usual count on the number of confirmed, recovered, or dead persons, today we’ll observe a different KPI. We’ll track the percentage of occupied beds in hospitals. Indeed this a very important indicator, taken into account by the French government before announcing regional or national curfews and lockdowns.

So first, lest download the dataset and load it as a DataFrame. As the first step of processing, let’s rename the columns to be easier to process.

STEP 1: Loading the dataset

As the dataset tracks lots of information over time, we’ll have to make a selection of the date. Indeed a choropleth map is a snapshot of a given situation. In this example, we’ll select October the 15th, 2020.

STEP 2: Selecting data from a specific date and basic pre-processing — October 15th, 2020

If we have a glance at the data, the df_dt DataFrame looks like this:

Fig. 2 — Overview of pre-processed dataset

You might have noticed a few interesting things:

The creation of a color column. Indeed, as we want to have limited values for the color scale. As the occupancy rates vary between 0% and 250% in the full dataset, I arbitrarily decided to divide by 25. It is not mandatory but it is a sort of color normalization that I like to use, having color values between 0 and 10.

column. Indeed, as we want to have limited values for the color scale. As the occupancy rates vary between 0% and 250% in the full dataset, I arbitrarily decided to divide by 25. It is not mandatory but it is a sort of color normalization that I like to use, having color values between 0 and 10. The creation of a title column. We’ll use it for the hover data. It combines the code of the department, its name, and the associated rate.

column. We’ll use it for the hover data. It combines the code of the department, its name, and the associated rate. The two first lines have different dept value, but the same bed_occupancy_rate which seems a bit fortunate. It is not. In fact, in France, there are 18 Regions, separated into 101 departments. Here, a line represents a department, and data are aggregated by regions. By the way, you can see that both Creuse and Vienne are located in Nouvelle-Aquitaine. Which explains the same percentage value!

value, but the same which seems a bit fortunate. It is not. In fact, in France, there are 18 Regions, separated into 101 departments. Here, a line represents a department, and data are aggregated by regions. By the way, you can see that both Creuse and Vienne are located in Nouvelle-Aquitaine. Which explains the same percentage value! GeoJSON features are already specified in the dataset! What luck! We won’t have to find the geographical delimitations ourselves. But for information, they also are available here in a separate department’s geographical dataset. And for example, as a reference to what was said just before, if we’d wanted to plot the region on the map instead of departments, we could have loaded GeoJSON from this region's geographical dataset. And we’d have to use the code_region column to make the link. Indeed the codes in here are administrative ones and are pretty reliable. A little like the ISO-A2 or ISO-A3 normalization for the countries.

column to make the link. Indeed the codes in here are administrative ones and are pretty reliable. A little like the ISO-A2 or ISO-A3 normalization for the countries. There is an assertion test at the end of the script so that we’re sure that the department code is unique, which is important for further processing. One department must correspond to a single area of our GeoJSON, and vice versa.

2. The GeoJSON File

To plot our map, we’ll need a GeoJSON file. Hopefully, geographical data are already in the dataset, and thanks to the RFC 7946 norm, they are almost ready to use.

Nevertheless, the needed step for Plotly to make the link between numeric information and geographic information is to have an id value in the keys of each GeoJSON feature, which relates to a specific column of our dataset. To be sure that the link is correctly done, the latter column must contain unique values. In our case, we’ll use the department code as a unique value

As we could see, on the geoshape column we have part of the needed information. Then we’ll just have to transform it as a dictionary with the json.loads function. Indeed, by default when loaded with the pd.read_csv function, it is defined as a string.

So here we go! Let’s build that custom GeoJSON file from our data.

STEP 3: Building the GeoJSON file

Nothing special to mention here, the code is pretty straight forward!

3. Plotting the map

So here we are! The crucial part! Plotting!

To do so, we’ll use the plotly.express.choropleth_mapbox function. Mapbox is a company that partnered with Plotly so that they could provide very cool features such as the choropleth map that we’re exploring today. For most of the map plotting, you’d need an authentication token, but that’s not the case for basic choropleth maps 😃

For the plot, we have to specify a few things:

Which DataFrame to use

Which GeoJSON file to use

Which column of the DataFrame matches the id value of the GeoJSON

value of the GeoJSON [Optional] Which color scale to use for the plot

Which color scale to use for the plot [Optional][But highly recommended] Which are the maximum values for the color scale? Remember the previous normalization? Here I’d set max_color=10 (meaning bed_occupancy_rate=25*10=250(%) ). Then, whatever the values contained in df_dt for the selected date, the color would not fit those exact data, but the maximum value previously selected. The dark red color would always correspond to the historical maximum value, and if in some cases, the maximum value of the day over the country is 10%, all colored zones would be very light. It allows keeping consistency through days.

Which are the maximum values for the color scale? Here I’d set (meaning ). Then, whatever the values contained in for the selected date, the color would not fit those exact data, but the maximum value previously selected. The dark red color would always correspond to the historical maximum value, and if in some cases, the maximum value of the day over the country is 10%, all colored zones would be very light. It allows keeping consistency through days. [Optional] Which column to use as the major information of the hoverbox. Here we’ll use the title column which we built and contains all the necessary information.

Which column to use as the major information of the hoverbox. Here we’ll use the column which we built and contains all the necessary information. [Optional] Which column to display as minor information in the hoverbox. Here we’ll hide all of them.

Which column to display as minor information in the hoverbox. Here we’ll hide all of them. Which Mapbox style to use. A bunch of them is free, and some others would need an authentication token. Here we’ll use the carto-positron one which is very sober. It will likely put the country data in evidence.

one which is very sober. It will likely put the country data in evidence. Which Mapbox zoom to use. 1 being the whole world displayed. Here we’ll use 4 which fits very well the size of the country. To display the United States, we’d go for 3 .

being the whole world displayed. Here we’ll use which fits very well the size of the country. To display the United States, we’d go for . Where to center the map. This is much of an iterative process. If you have a precise place to select, you could use Google Maps 😉

STEP 4: Plotting the map

Note that, with the plotly layout , we can customize our color bar and title properties!

So now we’re ready to see the figure. Isn't it wonderful? From scratch, we only used 75 lines of gists! With much aeration and comments. In reality, in terms of code, it could fit in less than 30 lines.

Let’s analyze our maps right now 🤓

Results

I made the plot for different dates from July to the end of November. Here is what we got, based on numbers.

Fig. 3 — Evolution of the COVID’s bed’s occupancy rate per region in France — from July to November

We observe that in July, 2 months after the end of the first french national lockdown, Hospitals got a breathe and were not assaulted anymore by COVID’s patients.

And then until the beginning of October, the situation was still quite stable.

In the middle of the month, we started to observe the second wave of contamination, and by the beginning of November, the situation became critical in multiple regions.

And this is still going on y November the 27th. Hopefully, if you’d plotted all the data day by day, you’d see that at this date, the peak is passed and hospitals operations are getting better.

Now we better understand why president Macron decided the current map for the second lockdown!

Conclusion

Today we’ve seen how to efficiently plot a choropleth map with Python on a specific use case. And thanks to Plotly library we managed to do it with less than 30 lines of codes. Pretty efficient isn’t it?

In the results part, we saw part of the evolution, but couldn’t say with the last pictures if the peak was passed or not. A good solution for these would be to animate our Plotly map. To do so, I recommend you the following story where you’d learn how to track a KPI over time with Plotly.

Moreover, if today we focused on the map creation, Plotly also provides a framework called Dash, which allows integrating plots and maps in easy to use applications. If you wish to discover how to embed your choropleth maps and deploy it for free on the Internet thanks to Heroku, I’d suggest this reading

Finally, To discover more about GeoJSON and get more information about today’s code, I can only advise you to get to the main story which inspired this one.