The world’s leading publication for data science, AI, and ML professionals.

Animate Maps with Plotly Express

Invigorate Your Infographics!

Animated maps are a great tool for grabbing attention and conveying information. Whether you’re a businessperson preparing a presentation, a journalist preparing infographics, or a teacher preparing class lessons, animations will increase your audience’s engagement, focus, and level of retention. And even if you don’t plan to show a live animation, the functionality is still useful for preparing static displays over different time frames.

In this Quick Success Data Science project, we’ll use Python, pandas, and Plotly Express to visualize the evolution of the United States. Specifically, we’ll use choropleth maps to animate the entry of US states into the union by day, by year, and over larger timeframes.


The Code

The following code was written in Jupyter Lab and is presented by cell.

Installing and Importing Libraries

Plotly Express is a high-level version of the Plotly graphing library and requires Plotly as a dependency. You can install it with either conda or pip.

Here’s the conda installation:

conda install -c plotly plotly_express

And here’s the pip version:

pip install plotly

To install the pandas data analysis package, use either:

conda install pandas

or:

pip install pandas

We’ll also need the NumPy (Numerical Python) library. You shouldn’t need to install it directly as it’s included with pandas.

Here are the imports. We’re using aliases for easier typing:

import numpy as np
import pandas as pd
import plotly.express as px

Loading and Preparing the Data

For convenience, I’ve created a CSV file of the required data and stored it in this Gist. The file includes columns for the state name, its official abbreviation, the date it entered the union, and the order in which it entered. The file is sorted in ascending order by date. We’ll load it as a pandas DataFrame because Plotly Express plays very well with pandas.

In addition to the day, we’ll want to animate the data using the year of statehood. This will make it easier to recognize states that entered the union in the same year. To do this, we’ll make a new DataFrame column, named Year, and typecast it as an integer.

# Load the statehood data and prepare a "Year" column:
df = pd.read_csv('https://bit.ly/44qtTyk')
df['Year'] = df['date_entered'].str[-4:].astype('int')
df.head(3)

Plotting a Static Choropleth Map Colored by Date of Entry

Creating a Choropleth map in Plotly Express couldn’t be easier. Just call the built-in [choropleth()](https://plotly.com/python/choropleth-maps/) method.

The first argument is the DataFrame that we want to animate. After that, we just need to reference columns in the DataFrame.

Plotly Express also comes with some built-in geospatial datasets. To select the one for American states, we’ll use the scope, locationmode, and locations parameters. The abbr argument, used with the locations parameter, represents the state’s official abbreviation, such as NY for New York and TX for Texas.

For color, I chose the Portland color scale. if you want to try something different, then visit the Plotly docs to see the available options.

Pro Tip: to reverse a Plotly color bar, append "_r" to the end of its name.

# Plot the DataFrame as a static map:
fig = px.choropleth(df, 
                    locations='abbr', 
                    locationmode='USA-states', 
                    color='Year', 
                    color_continuous_scale=px.colors.diverging.Portland, 
                    scope='usa',
                    hover_data=['state', 'order', 'Year'],
                    title='States by Date of Entry into Union')
fig.update_layout(width=750, height=500)

The resulting map is currently static (non-animated), but it comes with many interactive features. You can hover the cursor over a state to see a pop-up window of its metadata. The toolbar at the upper right permits actions such as taking a screenshot, zooming, panning, box selecting, and resetting to the "home" screen.

Despite all this functionality, the map is a little boring. Let’s jazz it up by adding animation.

Animating by Entry Date

You can animate the previous map with just one extra parameter, animation_frame. Here, we animate using the data_entered column, which captures the date the state entered the union.

To ensure that the color bar includes the data’s minimum and maximum values, we need to include an argument for the range_color parameter. Otherwise, the scale will default to the value of the state actively being displayed. This time, I chose the Earth color bar, because it has an antique, "historical" look.

# Plot the Dataframe as an animation showing each state in order of admission:
fig = px.choropleth(df, 
                    locations='abbr', 
                    locationmode='USA-states', 
                    color='Year', 
                    color_continuous_scale=px.colors.diverging.Earth, 
                    scope='usa',
                    hover_data=['state', 'order'],
                    animation_frame='date_entered',
                    range_color= (min(df.Year), max(df.Year)),
                    title='The Date Each State Entered the Union')

fig.update_layout(width=750, height=500)

To run the animation, either press the play button or use the slider. Only one state will display at a time. The interesting thing here is that the states don’t enter in a logical progression from east to west, but "jump around" a lot from north to south and from east to west.

Animating by Entry Year

To see all the states that entered the union in a given year, just change the animation_frame argument to the Year column.

# Animate the states grouped by year of admission:
fig = px.choropleth(df, 
                    locations='abbr', 
                    locationmode='USA-states', 
                    color='Year', 
                    color_continuous_scale=px.colors.diverging.Earth, 
                    scope='usa',
                    hover_data=['state', 'order'],
                    animation_frame='Year',
                    range_color= (min(df.Year), max(df.Year)))
fig.update_layout(width=750, height=500)

Animating the States Grouped Over a Large Timeframe

While animating the states by their day or year of entry is interesting, it’s hard to get the big picture. In the following example, we’ll lump the states together over four significant timespans:

  1. When the thirteen original colonies entered the union
  2. The subsequent expansion up to the Civil War
  3. The settling of the wild west
  4. The 20th century

To group the states over these timespans, we’ll use NumPy’s select() method to create a new column in the DataFrame. This requires three steps:

  1. Create a list of the conditions for the new column using date ranges,
  2. Create a list of values for the new column for each condition,
  3. Create the new column by calling the np.select() method and passing it the lists from the two previous steps.

We’ll name the new column Timespan. To use it for the animation, change the animation_frame argument to Timespan.

# Animate the states grouped by large timeframes:
conditions = [(df['Year'] >= 1787) & (df['Year'] <= 1790),
              (df['Year'] >= 1791) & (df['Year'] <= 1860),
              (df['Year'] >= 1861) & (df['Year'] <= 1900),
              (df['Year'] >= 1901) & (df['Year'] <= 1959)]

values = ['Original 13 Colonies', 'Antebellum Expansion', 
          'Westward Expansion', 'Twentieth Century']

df['Timespan'] = np.select(conditions, values)

# Animate the states grouped by year of admission:
fig = px.choropleth(df, 
                    locations='abbr', 
                    locationmode="USA-states", 
                    color='Year', 
                    color_continuous_scale=px.colors.diverging.Earth, 
                    scope="usa",
                    hover_data=['state', 'order'],
                    animation_frame='Timespan',
                    range_color= (min(df.Year), max(df.Year)))
fig.update_layout(width=750, height=500)

# Set the animation speed (in millseconds):
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 2000
fig.show()

Note that we changed the speed of the animation (in milliseconds) in the last line. You can use this code with any previous animations if you find them too fast.

Making Static Infographics

What if you want to capture the results as static images, for example, to make handouts?

Well, for jobs like this, I like to "hardwire" the results by copying and editing the master DataFrame and then looping through the copies to plot the maps. This provides for better control over things like figure titles and color bar ranges.

And to ensure that the color bar includes the end members for each time period, we’ll provide four lists that specify the dates we want to see annotated.

# Make static maps of the states grouped over large timeframes:

# Create new DataFrames for designated time spans
original_13 = df[(df['Year'] >= 1787) & (df['Year'] <= 1790)].copy()
antebellum_expansion = df[(df['Year'] >= 1791) & (df['Year'] <= 1860)].copy()
western_expansion = df[(df['Year'] >= 1861) & (df['Year'] <= 1900)].copy()
twentieth_century = df[(df['Year'] >= 1901) & (df['Year'] <= 1959)].copy()

# Make a list of DataFrames to loop through
time_frames = [original_13, antebellum_expansion, 
               western_expansion, twentieth_century]

# Make a list of the dates you want to see in the color bar
colorbar_dates = [[1787, 1788, 1789, 1790],
                  [1791, 1800, 1810, 1820, 1830, 1840, 1850, 1859],
                  [1861, 1870, 1880, 1890, 1896],
                  [1907, 1920, 1930, 1940, 1950, 1959]]

# Make a list of the figure titles for each DataFrame
figure_titles = ["Original 13 Colonies (1787-1790)", 
                 "Antebellum Expansion (1791-1860)", 
                 "Western Expansion (1861-1900)", 
                 "Twentieth Century (1901-1959)"]

# Loop through and plot the DataFrames
for i, data_frame in enumerate(time_frames):
    fig = px.choropleth(data_frame, 
                        locations='abbr', 
                        locationmode="USA-states", 
                        color='Year', 
                        color_continuous_scale=px.colors.diverging.Earth, 
                        scope="usa",
                        hover_data=['state', 'order'],
                        range_color=(min(data_frame['Year']), 
                                     max(data_frame['Year'])),
                        title=figure_titles[i])
    fig.update_layout(width=750, height=500)
    fig.update_layout(coloraxis_colorbar=dict(tickvals=colorbar_dates[i],
                                               ticks='outside',
                                               thickness=15))
    fig.show()

Static Maps with Persistence

We’re still not getting the (truly) big picture. It would be nice to see all the states in the union on a given date.

The code in the following cell "builds up" the map over time. The states added in previous timeframes remain visible on the map so that the last map shows all the states of the union.

To accomplish this, we need to refilter the master DataFrame so that the minimum value preserved is always 1787. We’ll also need a new dates list better suited for this new visualization.

  # Make static maps of the states grouped over large timeframes, with persistence:

# Create new DataFrames for designated time spans
original_13 = df[(df['Year'] >= 1787) & (df['Year'] <= 1790)].copy()
antebellum_expansion = df[(df['Year'] >= 1787) & (df['Year'] <= 1860)].copy()
western_expansion = df[(df['Year'] >= 1787) & (df['Year'] <= 1900)].copy()
twentieth_century = df[(df['Year'] >= 1787) & (df['Year'] <= 1959)].copy()

# Make a list of DataFrames to loop through
time_frames = [original_13, antebellum_expansion, 
               western_expansion, twentieth_century]

# Make a list of the dates you want to see in the color bar:
colorbar_dates = [1787, 1790, 1800, 1820, 1840, 1859, 
                  1880, 1896, 1910, 1920, 1940, 1959]

# Make a list of the figure titles for each DataFrame
figure_titles = ["States through 1790", 
                 "States through 1860", 
                 "States through 1900", 
                 "States through 1959"]

# Loop through and plot the DataFrames
for i, data_frame in enumerate(time_frames):
    fig = px.choropleth(data_frame, 
                        locations='abbr', 
                        locationmode="USA-states", 
                        color='Year', 
                        color_continuous_scale=px.colors.diverging.Earth, 
                        scope="usa",
                        hover_data=['state', 'order'],
                        range_color=(min(data_frame['Year']), 
                                     max(data_frame['Year'])),
                        title=figure_titles[i])
    fig.update_layout(width=750, height=500)
    fig.update_layout(coloraxis_colorbar=dict(tickvals=colorbar_dates,
                                               ticks='outside',
                                               thickness=15))
    fig.show()

For brevity, I’m only showing one of the four maps generated by the previous cell.


Summary

With only a single parameter, Plotly Express lets you convert beautiful static maps into beautiful moving maps. Besides being engaging, these animated maps also make it easy to spot outliers in a dataset. For example, despite being an American and fairly well-versed in history, I was surprised to see that:

  • Delaware was the first state to ratify the Constitution and is thus the first state in the union (who knew?),
  • Maine, though part of New England, was a relatively late addition,
  • Oklahoma did not become a state until the 20th century,
  • Arizona and New Mexico did not become states until 1912, despite being part of the Mexican Cessation of 1848.

Other datasets that would be fun to explore are the states ordered by secession during the American Civil War, the states ordered by population, or the states ordered by electoral college votes.

And if you want to use Plotly Express to build animated choropleths for other countries, here’s an example of how to do this for the Canadian provinces. The province polygons are built with Geojson files.


Thanks!

Thanks for reading and please follow me for more Quick Success Data Science projects in the future.


Related Articles