Two choropleth maps with different granularities

logo of a chart:Choropleth

How to Create Dual Maps with Varying Granularities? This guide demonstrates how to create two US maps: one displaying data at the county level and the other at the state level. By combining these maps, you can easily compare metrics across different levels of detail.

We'll walk through the process step by step, providing clear instructions and reproducible code examples to help you recreate the chart seamlessly.

Libraries

First, we need to install the following libraries:

import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
from pypalettes import load_cmap
from pyfonts import load_font

Dataset

First of all, we need two sets of data: one for the counties and one for the states.

path = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/us-counties-employmentrate.geojson"
path = "../../static/data/us-counties-employmentrate.geojson"
uscounties = gpd.read_file(path)
uscounties.head()
NAME_ALT ADM2_CODE state county rate quartile geometry
0 Whatcom County USA-53073 Washington Whatcom County 6.3 3 MULTIPOLYGON (((-122.75302 48.99251, -122.6532...
1 Okanogan County USA-53047 Washington Okanogan County 6.1 3 POLYGON ((-120.85196 48.99251, -120.67495 48.9...
2 Ferry County USA-53019 Washington Ferry County 9.8 4 POLYGON ((-118.83688 48.99251, -118.69668 48.9...
3 Stevens County USA-53065 Washington Stevens County 7.9 4 POLYGON ((-118.21996 48.99251, -118.03723 48.9...
4 Pend Oreille County USA-53051 Washington Pend Oreille County 8.9 4 POLYGON ((-117.42951 48.99251, -117.37787 48.9...
path = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/us-states-employmentrate.geojson"
path = "../../static/data/us-states-employmentrate.geojson"
usstates = gpd.read_file(path)
usstates.head()
state rate quartile geometry
0 Alabama 6.449254 4 MULTIPOLYGON (((-87.41958 30.47960, -87.42683 ...
1 Alaska 7.036000 4 MULTIPOLYGON (((-141.00556 69.65095, -141.0054...
2 Arizona 8.533333 4 POLYGON ((-111.00627 31.32718, -111.06712 31.3...
3 Arkansas 4.496000 1 POLYGON ((-90.30422 35.00008, -90.30124 34.995...
4 California 6.527586 4 MULTIPOLYGON (((-114.72428 32.71284, -114.7645...

Simple double map

We start by creating a figure with 2 subplots.

The first subplot will contain the map with counties, and the second subplot will contain the map with states.

fig, axs = plt.subplots(ncols=2, dpi=300, figsize=(15, 10))

uscounties.plot(ax=axs[0])
usstates.plot(ax=axs[1])

for i in range(2):
    ax = axs[i]

    ax.set_xlim(-126, -68)
    ax.set_ylim(24, 50)
    ax.set_axis_off()

plt.show()

Custom color map

Customizing colors is super easy thanks to pypalettes!

Here, we want to use the ‘Coconut’ palette and specify the column from our previous data images to map to the palette (here "quartile").

cmap = load_cmap("Coconut", cmap_type="continuous")
cmap
fig, axs = plt.subplots(ncols=2, dpi=300, figsize=(15, 10))

map_args = dict(column="quartile", cmap=cmap)
uscounties.plot(ax=axs[0], **map_args)
usstates.plot(ax=axs[1], **map_args)

for i in range(2):
    ax = axs[i]

    ax.set_xlim(-126, -68)
    ax.set_ylim(24, 50)
    ax.set_axis_off()

plt.show()

Title and description

The title and description are added using the text() function. We also add a short label for each map (‘counties’ and ‘states’) in the for loop.

cmap = load_cmap("Coconut", cmap_type="continuous")

fig, axs = plt.subplots(ncols=2, dpi=300, figsize=(15, 10))

map_args = dict(column="quartile", cmap=cmap)
uscounties.plot(ax=axs[0], **map_args)
usstates.plot(ax=axs[1], **map_args)

metrics = ["counties", "states (average across counties)"]
for i in range(2):
    ax = axs[i]

    ax.set_xlim(-126, -68)
    ax.set_ylim(24, 50)
    ax.set_axis_off()

    ax.text(
        x=0.06,
        y=0.98,
        s=f"{metrics[i]}",
        transform=ax.transAxes,
        size=13,
    )

fig.text(
    x=0.02,
    y=0.78,
    s="By county VS by state",
    ha="left",
    size=60,
)
fig.text(
    x=0.02,
    y=0.86,
    s="Unemployment rates (in %) in the United States",
    ha="left",
    size=20,
)

plt.tight_layout()
plt.show()

Better annotations with custom font

The default matplotlib font isn't the best, so we need other fonts using pyfonts! You don't need to install anything, all you need is an internet connection.

We load 3 differents fonts: a light one, a bold one and an italic one.

fontlight = load_font(
    "https://github.com/coreyhu/Urbanist/blob/main/fonts/ttf/Urbanist-Light.ttf?raw=true"
)
fontbold = load_font(
    "https://github.com/coreyhu/Urbanist/blob/main/fonts/ttf/Urbanist-Bold.ttf?raw=true"
)
fontitalic = load_font(
    "https://github.com/coreyhu/Urbanist/blob/main/fonts/ttf/Urbanist-LightItalic.ttf?raw=true"
)
cmap = load_cmap("Coconut", cmap_type="continuous")

fig, axs = plt.subplots(ncols=2, dpi=300, figsize=(15, 10))

map_args = dict(column="quartile", cmap=cmap)
test = uscounties.plot(ax=axs[0], **map_args)
usstates.plot(ax=axs[1], **map_args)

metrics = ["counties", "states (average across counties)"]
for i in range(2):
    ax = axs[i]

    ax.set_xlim(-126, -68)
    ax.set_ylim(24, 50)
    ax.set_axis_off()

    ax.text(
        x=0.06,
        y=0.98,
        s=f"{metrics[i]}",
        transform=ax.transAxes,
        size=13,
        font=fontitalic,
        color="#949494",
    )

fig.text(x=0.02, y=0.81, s="By county VS by state", ha="left", size=60, font=fontbold)
fig.text(
    x=0.02,
    y=0.89,
    s="Unemployment rates (in %) in the United States",
    ha="left",
    size=20,
    font=fontlight,
    color="#282828",
)

plt.tight_layout()
plt.show()

Legend

This step is a little more complex than the previous one, as it requires the use of matplotlib's Patch class.

First, we define the extent of each block in the legend:

quartile_ranges = [
    (uscounties["rate"].quantile(0), uscounties["rate"].quantile(0.2)),
    (uscounties["rate"].quantile(0.2), uscounties["rate"].quantile(0.4)),
    (uscounties["rate"].quantile(0.4), uscounties["rate"].quantile(0.6)),
    (uscounties["rate"].quantile(0.6), uscounties["rate"].quantile(0.8)),
    (uscounties["rate"].quantile(0.8), uscounties["rate"].quantile(1)),
]
quartile_ranges
[(1.6, 3.7), (3.7, 4.6), (4.6, 5.4), (5.4, 6.6), (6.6, 26.4)]

We then create a list of legend elements (a list of squares of different colours):

from matplotlib.patches import Patch

legend_elements = [
    Patch(
        facecolor=cmap.colors[i],
        edgecolor="none",
    )
    for i in range(5)
]
legend_elements
[<matplotlib.patches.Patch at 0x14753f310>,
 <matplotlib.patches.Patch at 0x16fda8f40>,
 <matplotlib.patches.Patch at 0x16fda9e70>,
 <matplotlib.patches.Patch at 0x16fda9390>,
 <matplotlib.patches.Patch at 0x16fda9fc0>]

Finally, we add the legend elements using the legend() function, and the labels that go with them using the ax.text() function:

cmap = load_cmap("Coconut", cmap_type="continuous")

fig, axs = plt.subplots(ncols=2, dpi=300, figsize=(15, 10))

map_args = dict(column="quartile", cmap=cmap)
test = uscounties.plot(ax=axs[0], **map_args)
usstates.plot(ax=axs[1], **map_args)

metrics = ["counties", "states (average across counties)"]
for i in range(2):
    ax = axs[i]

    ax.set_xlim(-126, -68)
    ax.set_ylim(24, 50)
    ax.set_axis_off()

    ax.text(
        x=0.06,
        y=0.98,
        s=f"{metrics[i]}",
        transform=ax.transAxes,
        size=13,
        font=fontitalic,
        color="#949494",
    )

fig.text(x=0.02, y=0.81, s="By county VS by state", ha="left", size=60, font=fontbold)
fig.text(
    x=0.02,
    y=0.89,
    s="Unemployment rates (in %) in the United States",
    ha="left",
    size=20,
    font=fontlight,
    color="#282828",
)

for i, element in enumerate(legend_elements):
    axs[1].text(
        0.325 + i * 0.13,
        1.18,  # Adjust the x, y positions
        f"{quartile_ranges[i][0]:.1f} - {quartile_ranges[i][1]:.1f}",
        horizontalalignment="center",
        verticalalignment="top",
        transform=axs[1].transAxes,
        font=fontbold,
    )

fig.legend(
    handles=legend_elements,
    bbox_to_anchor=(0.95, 0.86),
    prop=fontbold,
    handlelength=4,
    fancybox=False,
    frameon=False,
    handleheight=3,
    labelspacing=0.8,
    ncol=len(legend_elements),
    labels=[""] * len(legend_elements),
)

plt.tight_layout()
plt.savefig(
    "../../static/graph/web-two-maps-with-different-granulaties.png",
    dpi=300,
    bbox_inches="tight",
)
plt.show()

Going further

This post shows how to create a double maps with different granularities.

You might be interested in:

Contact & Edit


👋 This document is a work by Yan Holtz. You can contribute on github, send me a feedback on twitter or subscribe to the newsletter to know when new examples are published! 🔥

This page is just a jupyter notebook, you can edit it here. Please help me making this website better 🙏!