Cartograma de Dorling en Python con matplotlib

Librería

geopandas

Autor principal

GeoPandas developers

Un cartograma distorsiona la geografía de un mapa para que el tamaño de cada región represente una variable en lugar de su superficie. En un cartograma de Dorling cada región se sustituye por un círculo cuya área es proporcional a la variable, y los círculos se separan para que no se solapen. Los ejemplos representan la población de los países del mundo, leídos con geopandas.

Círculos proporcionales

Coloca un círculo sobre cada país con un área proporcional a su población. El radio es la raíz cuadrada del valor, de modo que el área (y no el radio) es proporcional a la población.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
world = world[world["POP_EST"] > 0].to_crs("+proj=robin")
pts = world.representative_point()
world["x"], world["y"] = pts.x, pts.y
world["r"] = np.sqrt(world["POP_EST"] / np.pi) * 100

fig, ax = plt.subplots()
world.plot(ax = ax, color = "lightgray", edgecolor = "white", linewidth = 0.3)

for x, y, r in zip(world["x"], world["y"], world["r"]):
    ax.add_patch(plt.Circle((x, y), r, color = "tab:red", alpha = 0.5))
ax.axis("off")
# plt.show()

Mapa de símbolos proporcionales de la población mundial en Python

Cartograma de Dorling

Los círculos grandes se solapan. La siguiente función separa los círculos paso a paso hasta que dejan de solaparse, manteniéndolos lo más cerca posible de su posición original. El mapa de los países ya no es necesario.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
world = world[world["POP_EST"] > 0].to_crs("+proj=robin")
pts = world.representative_point()
world["x"], world["y"] = pts.x, pts.y
world["r"] = np.sqrt(world["POP_EST"] / np.pi) * 100

def dorling(x, y, r, iterations = 200):
    """Separa los círculos hasta que no se solapen."""
    x, y = x.copy(), y.copy()
    for _ in range(iterations):
        dx = x[:, None] - x[None, :]
        dy = y[:, None] - y[None, :]
        dist = np.hypot(dx, dy) + 1e-9
        overlap = np.clip(r[:, None] + r[None, :] - dist, 0, None)
        np.fill_diagonal(overlap, 0)
        x += (overlap * dx / dist).sum(axis = 1) / 4
        y += (overlap * dy / dist).sum(axis = 1) / 4
    return x, y

x, y = dorling(world["x"].values, world["y"].values, world["r"].values)

fig, ax = plt.subplots()
for xi, yi, r in zip(x, y, world["r"]):
    ax.add_patch(plt.Circle((xi, yi), r, color = "tab:red", alpha = 0.7, ec = "white"))
ax.set_xlim(x.min() - 3e6, x.max() + 3e6)
ax.set_ylim(y.min() - 3e6, y.max() + 3e6)
ax.set_aspect("equal")
ax.axis("off")
# plt.show()

Cartograma de Dorling de la población mundial en Python

Color por continente y etiquetas

Colorea cada círculo según su continente y escribe el nombre de los países más grandes.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
world = world[world["POP_EST"] > 0].to_crs("+proj=robin")
pts = world.representative_point()
world["x"], world["y"] = pts.x, pts.y
world["r"] = np.sqrt(world["POP_EST"] / np.pi) * 100

def dorling(x, y, r, iterations = 200):
    """Separa los círculos hasta que no se solapen."""
    x, y = x.copy(), y.copy()
    for _ in range(iterations):
        dx = x[:, None] - x[None, :]
        dy = y[:, None] - y[None, :]
        dist = np.hypot(dx, dy) + 1e-9
        overlap = np.clip(r[:, None] + r[None, :] - dist, 0, None)
        np.fill_diagonal(overlap, 0)
        x += (overlap * dx / dist).sum(axis = 1) / 4
        y += (overlap * dy / dist).sum(axis = 1) / 4
    return x, y

x, y = dorling(world["x"].values, world["y"].values, world["r"].values)
continents = world["CONTINENT"].unique()
colors = dict(zip(continents, plt.cm.Set2.colors))

fig, ax = plt.subplots()
for xi, yi, r, cont, name in zip(x, y, world["r"], world["CONTINENT"], world["NAME"]):
    ax.add_patch(plt.Circle((xi, yi), r, color = colors[cont], ec = "white"))
    if r > 0.8e6:
        ax.text(xi, yi, name, ha = "center", va = "center", fontsize = 6)
ax.set_xlim(x.min() - 3e6, x.max() + 3e6)
ax.set_ylim(y.min() - 3e6, y.max() + 3e6)
ax.set_aspect("equal")
ax.axis("off")
# plt.show()

Cartograma de Dorling coloreado por continente en Python

Consulta también cómo crear un mapa de coropletas con geopandas y un mapa de burbujas en plotly.

Better Data Visualizations

A Guide for Scholars, Researchers, and Wonks

Comprar en Amazon

También te puede interesar