Choropleth Map with Regional Coloring — plotnine

A choropleth map visualizes data by shading geographic regions (countries, states, or counties) according to a measured variable. This technique is ideal for showing regional patterns and spatial distributions, making it easy to identify areas with high or low values at a glance. The color intensity represents the data magnitude, creating an intuitive way to understand geographic variation.

Choropleth Map with Regional Coloring rendered with plotnine

Python source (plotnine)

""" anyplot.ai
choropleth-basic: Choropleth Map with Regional Coloring
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 81/100 | Updated: 2026-05-15
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    coord_fixed,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_polygon,
    geom_text,
    ggplot,
    labs,
    scale_fill_cmap,
    theme,
)


THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
MISSING_DATA_COLOR = "#A9A9A9" if THEME == "light" else "#696969"

np.random.seed(42)

# Simplified European country boundaries (approximate polygon coordinates)
countries = {
    "France": [(0, 0), (3, 0), (4, 2), (3, 4), (1, 4), (0, 2)],
    "Germany": [(4, 2), (7, 1), (8, 3), (7, 5), (4, 5), (3, 4)],
    "Spain": [(-3, -3), (1, -3), (2, -1), (0, 0), (-2, 0), (-3, -1)],
    "Italy": [(5, -2), (7, -3), (9, -1), (8, 2), (6, 1), (5, 0)],
    "Poland": [(8, 3), (12, 2), (13, 5), (11, 6), (8, 5), (7, 5)],
    "UK": [(-2, 4), (1, 4), (2, 6), (1, 8), (-1, 8), (-2, 6)],
    "Sweden": [(7, 7), (10, 6), (11, 9), (10, 12), (8, 11), (7, 9)],
    "Norway": [(5, 9), (7, 7), (8, 11), (7, 14), (5, 13), (4, 11)],
    "Finland": [(11, 9), (14, 8), (15, 12), (13, 14), (11, 12), (10, 12)],
    "Austria": [(6, 1), (8, 0), (10, 1), (9, 3), (7, 3), (6, 2)],
    "Netherlands": [(2, 5), (4, 5), (5, 6), (4, 7), (2, 7), (1, 6)],
    "Belgium": [(1, 4), (3, 4), (4, 5), (2, 5), (1, 5)],
    "Switzerland": [(3, 1), (5, 0), (6, 1), (5, 2), (4, 2), (3, 2)],
    "Portugal": [(-4, -2), (-3, -3), (-2, -1), (-3, 0), (-4, 0)],
    "Denmark": [(5, 6), (7, 5), (8, 6), (7, 7), (5, 7)],
    "Czechia": [(7, 3), (9, 3), (10, 4), (9, 5), (7, 5), (6, 4)],
}

# Life expectancy at birth (years) - realistic values for 2023-2024
# Czechia has None to demonstrate missing data handling
life_expectancy = {
    "France": 82.4,
    "Germany": 81.8,
    "Spain": 83.1,
    "Italy": 83.5,
    "Poland": 78.0,
    "UK": 81.3,
    "Sweden": 84.2,
    "Norway": 84.6,
    "Finland": 82.5,
    "Austria": 81.9,
    "Netherlands": 82.1,
    "Belgium": 81.8,
    "Switzerland": 84.0,
    "Portugal": 82.2,
    "Denmark": 81.5,
    "Czechia": None,
}

# Build polygon dataframe
polygon_data = []
for country, coords in countries.items():
    closed_coords = coords + [coords[0]]
    for i, (x, y) in enumerate(closed_coords):
        polygon_data.append(
            {"country": country, "x": x, "y": y, "order": i, "life_expectancy": life_expectancy[country]}
        )

df = pd.DataFrame(polygon_data)

# Calculate centroids for country labels
label_offsets = {
    "Netherlands": (0.5, 1.0),
    "Belgium": (-0.8, -0.5),
    "Germany": (0.5, -0.5),
    "Denmark": (0.5, 0.5),
    "Czechia": (0, -0.5),
}

centroids = []
for country, coords in countries.items():
    cx = np.mean([c[0] for c in coords])
    cy = np.mean([c[1] for c in coords])
    if country in label_offsets:
        cx += label_offsets[country][0]
        cy += label_offsets[country][1]
    expectancy = life_expectancy[country]
    centroids.append({"country": country, "x": cx, "y": cy, "life_expectancy": expectancy})

df_centroids = pd.DataFrame(centroids)

# Separate data with and without values for missing data handling
df_with_data = df[df["life_expectancy"].notna()].copy()
df_missing = df[df["life_expectancy"].isna()].copy()

# Create the choropleth map
plot = (
    ggplot()
    + geom_polygon(
        df_missing, aes(x="x", y="y", group="country"), fill=MISSING_DATA_COLOR, color=INK_SOFT, size=0.6, alpha=0.7
    )
    + geom_polygon(
        df_with_data, aes(x="x", y="y", group="country", fill="life_expectancy"), color=INK_SOFT, size=0.6, alpha=0.95
    )
    + geom_text(df_centroids, aes(x="x", y="y", label="country"), size=7, color=INK)
    + scale_fill_cmap(cmap_name="BrBG", name="Life Expectancy\n(years)", limits=(77, 85))
    + coord_fixed(ratio=1.0)
    + labs(title="choropleth-basic · plotnine · anyplot.ai")
    + theme(
        figure_size=(16, 9),
        plot_title=element_text(size=24, ha="center", color=INK),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_grid=element_blank(),
        panel_border=element_line(color=INK_SOFT, size=0.3),
        axis_text=element_blank(),
        axis_title=element_blank(),
        axis_ticks=element_blank(),
        legend_title=element_text(size=16, color=INK),
        legend_text=element_text(size=14, color=INK_SOFT),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
        legend_position="right",
    )
)

# Save the plot
plot.save(f"plot-{THEME}.png", dpi=300, verbose=False)

Part of Choropleth Map with Regional Coloring on anyplot.ai.

Other implementations