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:
- Artículo de Wikipedia sobre la ley de Zipf, que principalmente habla sobre la ley para lenguajes, junto con la versión en inglés, que tiene más detalles y gráficas.
- Un documento de trabajo de la OCDE sobre desarrollo regional con muchas gráficas y reflexiones interesantes, incluyendo consideraciones sobre la definición de lo que es una ciudad. Hay muchas referencias en el documento para profundizar más.