How to Animate a Scatter Plot Using Python

I recently worked on a fun data visualization project where I created an animated scatter plot using Python and the plotly.express library. The goal was to visualize the relationship between GDP per capita and life expectancy over time for different countries. Along the way, I added several enhancements to make the visualization more interactive and practical.

Let me walk you through the process, explain the code, and show you how to take this project to the next level!

What Does This Code Do?

This project visualizes how GDP per capita (wealth) and life expectancy (health) evolve over time for countries in different continents. Each country’s population size is represented by the size of a bubble, and the bubbles are color-coded by continent.

By the end of this article, you’ll have an interactive plot that allows you to:

  1. Watch an animation of the data over the years.
  2. Play, pause, and control the animation.
  3. Filter the data for specific continents.
  4. Save the plot as an HTML file to share or revisit.

Let’s Break Down the Code

Importing the Library

import plotly.express as px

We start by importing plotly.express, a high-level Python library for creating beautiful and interactive visualizations with minimal code.

Loading the Dataset

data = px.data.gapminder()

The gapminder dataset is a built-in dataset in Plotly. It contains data about various countries’ GDP per capita, population, life expectancy, and continent across different years. It’s perfect for showcasing time-series animations.

Creating the Animated Scatter Plot

Here’s the core of the plot:

fig = px.scatter(
data,
x="gdpPercap",
y="lifeExp",
animation_frame="year",
animation_group="country",
size="pop",
color="continent",
hover_name="country",
log_x=True,
size_max=60,
range_x=[200, 60000],
range_y=[20, 90],
title="Animated Scatter Plot: Life Expectancy vs GDP Per Capita"
)

Here’s what each parameter does:

  • x="gdpPercap": The x-axis represents GDP per capita.
  • y="lifeExp": The y-axis represents life expectancy.
  • animation_frame="year": The animation progresses frame by frame for each year.
  • animation_group="country": Groups bubbles by country, keeping them consistent across frames.
  • size="pop": Bubble size represents the population of each country.
  • color="continent": Each continent gets a distinct color for clarity.
  • hover_name="country": Displays the country’s name when you hover over a bubble.
  • log_x=True: Uses a logarithmic scale for the x-axis, which is better for data with wide value ranges like GDP.
  • size_max=60: Limits the maximum size of bubbles for better readability.
  • range_x and range_y: Sets the range for the x-axis and y-axis.
  • title: Adds a title to the plot.

Displaying the Plot

fig.show()

This renders the plot in an interactive format where you can play the animation, hover over data points, and explore the story the data tells.

Enhancements for a More Practical Visualization

To make this project more user-friendly and insightful, I made some enhancements.

Adding Custom Filtering

I decided to focus on specific continents (Asia and Europe) for a clearer analysis.

filtered_data = data[data['continent'].isin(['Asia', 'Europe'])]

This filters the dataset to include only countries from Asia and Europe.

Adding Custom Labels and Units

I updated axis labels to make the visualization more descriptive:

labels={
"gdpPercap": "GDP Per Capita (log scale)",
"lifeExp": "Life Expectancy (years)",
"pop": "Population"
}

Adding Play and Pause Buttons

I added custom buttons to control the animation, giving the user the ability to pause and resume at any time.

fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": "Play",
"method": "animate",
"args": [None, {"frame": {"duration": 500, "redraw": True}, "fromcurrent": True}]
},
{
"label": "Pause",
"method": "animate",
"args": [[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate"}]
}
],
"direction": "left",
"pad": {"r": 10, "t": 87},
"showactive": False,
"type": "buttons",
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top"
}
]
)

Saving the Plot

To make the plot shareable, I added functionality to save it as an HTML file:

fig.write_html("enhanced_animated_scatter_plot.html")

You can now share this file or revisit it anytime.

Full Enhanced Code

Here’s the complete enhanced version:

import plotly.express as px

# Load dataset
data = px.data.gapminder()

# Filter for Asia and Europe
filtered_data = data[data['continent'].isin(['Asia', 'Europe'])]

# Create animated scatter plot
fig = px.scatter(
filtered_data,
x="gdpPercap",
y="lifeExp",
animation_frame="year",
animation_group="country",
size="pop",
color="continent",
hover_name="country",
log_x=True,
size_max=60,
range_x=[200, 60000],
range_y=[20, 90],
labels={
"gdpPercap": "GDP Per Capita (log scale)",
"lifeExp": "Life Expectancy (years)",
"pop": "Population"
},
title="Enhanced Animated Scatter Plot: Life Expectancy vs GDP Per Capita"
)

# Add Play and Pause Buttons
fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": "Play",
"method": "animate",
"args": [None, {"frame": {"duration": 500, "redraw": True}, "fromcurrent": True}]
},
{
"label": "Pause",
"method": "animate",
"args": [[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate"}]
}
],
"direction": "left",
"pad": {"r": 10, "t": 87},
"showactive": False,
"type": "buttons",
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top"
}
]
)

# Save the plot
fig.write_html("enhanced_animated_scatter_plot.html")

# Show the plot
fig.show()

Final Thoughts

Creating visualizations like this is not only fun but also incredibly insightful. It helps bring data to life and makes complex patterns easier to understand. By adding practical functionality such as filtering, labels, and play/pause buttons, this plot becomes more interactive and useful for storytelling.

Related blog posts