Choropleth maps with time sliders using Plotly

Lucas Bromerchenkel
6 min readApr 15, 2022

--

You can virtually plot maps anywhere!

Animated choropleth map of snow plowing in Toronto
This article was written on the assumption that you know your way around python programming and Jupyter notebooks. Also, as you'll see, for this example you will need the geometries for the maps you'll want to plot.

Animated plots with plotly

Plotly is great for generating beautiful visuals, and lately I’ve been experimenting with a very capable possibility within plotly: animated geographical data.

There are great articles out there showing you how to plot geographical data over time, such as this one, by Shinichi Okada, or this one, by Maeliza S. However, one thing is common among these articles: they make use of plotly’s built-in geographical data for plotting the geometries, which makes them limited to locations such as USA states and the world map, including others that can be found here.

For our case, we have a dataset showing the behaviour of snow plowing across the neighbourhoods of the city of Toronto. Well, if the city of Toronto is not included on Plotly’s built-in maps, how do we do? We use our own geometry!

What we want to plot, and what we have

The data linked in this article is part of a larger project availble on GitHub.

For this article, we have a dataframe that shows the ratio of roads that have been plowed per neighbourhood, per hour, for the city of Toronto, during the snowstorm of February 17, 2022.

In this github repository you’ll find a GeoDataFrame covering 5 hours of snow plowing data. The rows represent the ratio (in %) of roads that were plowed per neighbourhood in Toronto every hour, from Feb. 17th at 8PM to Feb 18th at 1AM. Such a short time period was chosen due to file size constraints on Github. The original project contains over 40 hours of plow data.

This is how the DataFrame looks:

DataFrame with data on roads plowed in Toronto.

This data has been previously collected from Toronto’s PlowTO service, and aggregated by neighbourhood, by hour, and by type of road. These are the columns of interest:

completed_time: the data is shown aggregated by hour, however this is not in datetime format yet.

neighbourhood: self-explanatory; this is the identifier we’ll use later for the geometry referencing.

route_name: this relates to the type of road we’re talking about: local road, collector road, arterial road etc.

length: length of ploowed roads of that type of road, inside that neighbourhood.

total_length: the total length of that type of road inside that neighbourhood, used to calculate the…

length_ratio: ratio, in %, of roads plowed inside that neighbourhood.

The pipeline for cleaning the df after it has been imported works this way:

What we are doing is making sure that the data is ordered by time (increasing), dropping anything we don’t want, and creating a string to be used on the time slider. This is the result:

Cleaned DataFrame with snowplow information.

So we have data aggregated by hour, and we will use this information for the slider. We also have the name of the neighbourhoods, which you’ll soon learn that we use to plot the actual map. And finally, we have the percentage of plowed streets per neighbourhood, and we’ll use that to color each neighbourhood on the map (darker the color, more plowed roads).

But we’re still missing one very important element. Let’s talk about the…

Geometry data

The caveat for this example is that you need to have the geometry you want to plot. Plotly is handy since it has a few geometries built-in, but that’s it! You’re limited to what Plotly has to offer. If you can get your hands on some geometry data from any other source, your maps can go a lot further.

For this analysis, I downloaded the Toronto Neighbourhoods geometry data available on the Open Data Portal from the City of Toronto. This is how it looks:

Neighbourhood geometries

name_neigh: This is the name of the neighbourhood. It has to be the exact match to the identifier on the plow dataframe. The neighbourhood name is the feature that Plotly will use to link both datasets.

area: Area of the neighbourhood

geometry: A polygon geometry for each neighbourhood in the city of Toronto.

The cleaning pipeline looks like this:

Our final product has to be a json object, with the neighbourhoods name as the index, and the geometries as a ‘geometry’ column. This json object will be loaded when plotting the map.

And the final result is shown below.

Now we have the missing piece for our choropleth map puzzle: geometries to create the actual map! It’s time to start…

Plotting the map with Plotly Express

Let’s first look at Plotly’s code for this example, and we’ll discuss each component.

First, notice we’re using px.choropleth_mapbox() as our plotting function. This will allow us to pass our own geometries for map plotting.

In the data_frame argument you’ll pass the dataframe with color information for each neighbourhood. This is our snowplow dataframe.

The geojson argument is why we are all here: it allows us to pass geometries to be plotted by Plotly. We will pass our json file here.

The locations argument is the column on our dataframe that has the neighbourhood name. This argument needs to be an exact match to the json indexes. This is how Plotly will link the dataframe and the json.

color will indicate to plotly how to color each neighbourhood. We are using the ‘% plowed’ feature since we would like to investigate the plowing behaviour.

center will tell Plotly where to center your map. Plotly can show anywhere in the world, and this argument tells it where to start the interactive map. These coordinates relate to downtown Toronto.

mapbox_style is a very powerful plotly functionality. You can use different layers for the background map. I chose ‘open-street-map’ since it shows the names of neighbouring cities.

zoom will tell Plotly the initial zoom of the map. Differing values depending on the size of your plot.

color_continuos_scale is used since we want a continuous scale, and the ‘Blues’ argument can be changed according to your prefere. I just liked how the blue looked on the map.

range_color is passed when you don’t want plotly to automatically calculate the coloring range on your map.

animation_frame is where you will pass data for the time slider. We are passing the strings for day and hour.

Lastly we are passing arguments for width and height just to be sure of the size of the map.

And Violà!

Animated choropleth map of snow plowing in Toronto

When running, plotly usually takes a few seconds in the background before plotting the map, but it should work fine, and the map is interactive, so you can zoom in, check a specific neighbourhood and move around.

--

--