La ley de Zipf para las ciudades de Colombia en 2025

Etiquetas: Ley de Zipf

(English: Zipf’s Law for Colombian City Sizes in 2025)

La ley de Zipf dice que el tamaño de las ciudades en una región sigue, a menudo sorprendentemente bien, una ley de potencias sencilla: $$ P(r) = \frac{C}{r^{\alpha}} $$ En esta fórmula, $P(r)$ es la población de la ciudad en el puesto $r$ (ordenadas de mayor a menor población), y $C$ y $\alpha$ son constantes que dependen de la región.

Lo que esto nos dice es que la población de la ciudad más grande (puesto $r=1$) es aproximadamente $P(1)=C/1^\alpha=C$, la población de la segunda ciudad más grande (puesto $r=2$) es $P(2)=C/2^\alpha$, la de la tercera es $P(3)=C/3^\alpha$, y así sucesivamente. Los datos de muchos países sugieren que $\alpha \approx 1$, aunque en la práctica este valor puede variar según el país o la región.

Busqué algún gráfico reciente que mostrara cómo se cumple esto para Colombia, pero no encontré ninguno. Así que, ¡decidí hacer uno yo mismo! Siempre es bueno encontrar un pretexto para crear una figura en TikZ y aprender algo nuevo en el camino.

El resultado: la ley de potencias se ajusta bastante bien a los datos de las ciudades colombianas.

(Imagen creada con TikZ. Código fuente acá.)

Algo que salta a la vista (además del buen ajuste) es que Bogotá parece ser un caso atípico: está claramente por encima de la curva de ajuste. Sin embargo, esto no es raro y es un fenómeno bien documentado: las capitales suelen atraer población de manera distinta al resto de las ciudades.

Los datos de población que usé para esa gráfica son las proyecciones oficiales del gobierno para 2025, que tomé de Wikipedia. La tabla completa se puede consultar allí o en el código fuente de la figura.

Gráfica log-log

Es mejor revisar el ajuste a los datos en una gráfica log-log, pues este tipo de gráfico convierte funciones de potencia (cómo $P(r)$) en líneas rectas, y nuestro cerebro es muy bueno para detectar rectas. Este es el resultado:

(Imagen creada con TikZ. Código fuente acá.)

Para encontrar la función que se ajusta a los datos colombianos, en realidad empecé transformando los datos a escala logarítmica: hice una regresión lineal de $\log P(r)$ y $\log (r)$ (se puede ver el código Python al final de esta entrada de blog). El ajuste da un $R^2=0.96$. ¡Bastante alto!

Errores porcentuales

Pero a veces es más convincente ver los errores porcentuales entre la predicción y los datos reales, en vez de solo mirar el valor de $R^2$ o las gráficas. Los errores son sorprendentemente bajos (ignorando el caso particular de Bogotá, y a Soacha, que en realidad es un municipio vecino de Bogotá).

Ciudad Rango Población real Población del modelo Error (%)
Bogotá 1 7,937,898 5,544,989 30%
Medellín 2 2,634,570 2,924,988 -11%
Cali 3 2,285,099 2,012,034 12%
Barranquilla 4 1,342,818 1,542,935 -15%
Cartagena 5 1,065,881 1,255,809 -18%
Soacha 6 828,947 1,061,350 -28%
Cúcuta 7 815,891 920,626 -13%
Soledad 8 686,339 813,900 -19%
Bucaramanga 9 623,881 730,079 -17%
Villavicencio 10 593,273 662,440 -12%
Valledupar 11 575,225 606,669 -5%
Bello 12 570,329 559,863 2%
Santa Marta 13 566,650 520,002 8%
Ibagué 14 546,003 485,631 11%
Montería 15 531,424 455,678 14%
Pereira 16 482,824 429,333 11%
Manizales 17 459,262 405,975 12%
Pasto 18 415,937 385,117 7%
Neiva 19 388,229 366,375 6%
Palmira 20 359,888 349,438 3%

¿Qué significa esto?

Es muy sorprendente saber que Cartagena debería tener una población cercana a 1 millón solo por ser la quinta ciudad más grande del país, y que Ibagué debería tener alrededor de 500 mil habitantes simplemente por estar en el puesto 14. Todo esto de una fórmula muy simple:

$$ P(r)=\frac{5544989}{r^{0.92}} $$

$P(r):$ Población en 2025 de la ciudad Colombiana en el puesto $r$ en orden de población.

Código Python

Este es el código Python que usé para encontrar $P(r)$ y para generar algunos gráficos. El código da la función que se ajusta a los datos y el $R^2$ de la regresión. Para probar con otro país, tan solo hay que cambiar los datos al inicio.

# setup
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
import pandas as pd


colombia_cities = [
    ("Bogotá",        7937898),
    ("Medellín",      2634570),
    ("Cali",          2285099),
    ("Barranquilla",  1342818),
    ("Cartagena",      1065881),
    ("Soacha",         828947),
    ("Cúcuta",         815891),
    ("Soledad",        686339),
    ("Bucaramanga",    623881),
    ("Villavicencio",  593273),
    ("Valledupar",     575225),
    ("Bello",          570329),
    ("Santa Marta",    566650),
    ("Ibagué",         546003),
    ("Montería",       531424),
    ("Pereira",        482824),
    ("Manizales",      459262),
    ("Pasto",          415937),
    ("Neiva",          388229),
    ("Palmira",        359888)
]

ranks = np.arange(1, 21)
city_names = [name for name, pop in colombia_cities]
city_pops = np.array([pop for name, pop in colombia_cities])

# Power law fit: log(Pop) = log(a) + b*log(rank)
log_ranks = np.log(ranks)
log_pops = np.log(city_pops)
b, log_a = np.polyfit(log_ranks, log_pops, 1)
a = np.exp(log_a)
fit_pops = a * ranks ** b

# Fit quality
r2 = r2_score(log_pops, log_a + b * log_ranks)

print(f"Power law fit: Population = {a:.0f} × Rank^{b:.2f} (R² = {r2:.2f})")

Y el siguiente código sirve para crear los gráficos (no tan bonitos como los de TikZ, pero sí más fáciles de generar):

import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# Linear
ax1.scatter(ranks, city_pops, s=60)
ax1.plot(ranks, fit_pops, '--r', label=f"Fit: $y={a:.0f}x^{{{b:.2f}}}$")
ax1.set_xlabel("Rank")
ax1.set_ylabel("Population")
ax1.set_title("Colombia City Populations: Linear Scale")
ax1.set_xticks(ranks)
for i, name in enumerate(city_names):
    ax1.annotate(name, (ranks[i], city_pops[i]), textcoords="offset points", xytext=(0, 7), ha='center', fontsize=9)
ax1.legend()

# Log-log
ax2.scatter(ranks, city_pops, s=60)
ax2.plot(ranks, fit_pops, '--r', label=f"Fit: $y={a:.0f}x^{{{b:.2f}}}$")
ax2.set_xscale("log")
ax2.set_yscale("log")
ax2.set_xlabel("Rank (log)")
ax2.set_ylabel("Population (log)")
ax2.set_title("Colombia City Populations: Log-Log")
for i, name in enumerate(city_names):
    ax2.annotate(name, (ranks[i], city_pops[i]), textcoords="offset points", xytext=(0, 7), ha='center', fontsize=9)
ax2.legend()

plt.tight_layout()
plt.show()

Más información

Se ha escrito mucho sobre la ley de Zipf, tanto para el estudio de idiomas como para poblaciones urbanas. Para los que quieran profundizar, recomiendo:

Suscríbase

¿Quiere recibir un email cuando haya una nueva entrada de blog? Suscríbase acá.