1. Clasificación lógica económica

El problema del clustering como aprendizaje no supervisado

El análisis de conglomerados (clustering) pertenece a la familia de técnicas de aprendizaje no supervisado: a diferencia de un modelo de regresión o de clasificación, no existe una variable dependiente ni una “respuesta correcta” previamente etiquetada. El objetivo es, exclusivamente a partir de las distancias o similitudes entre observaciones, descubrir una estructura de agrupamiento latente en los datos. En este trabajo, las observaciones son economías (países) y las variables son indicadores macroeconómicos extraídos de la API del Banco Mundial; el algoritmo no conoce de antemano ninguna clasificación oficial de “países desarrollados” o “en desarrollo”, y toda agrupación que emerja debe justificarse únicamente por la estructura estadística de los datos.

Dentro de esta familia de técnicas, se distinguen dos grandes enfoques taxonómicos: los métodos jerárquicos y los métodos no jerárquicos.

Métodos jerárquicos

Un método jerárquico no produce una única partición de los datos, sino una secuencia completa de particiones anidadas, representada gráficamente mediante un dendrograma. Dentro de esta familia existen dos estrategias de construcción.

Aglomerativa: se inicia considerando cada observación como un grupo unitario y, en cada iteración, se fusionan los dos grupos más similares entre sí, hasta que todas las observaciones quedan contenidas en un único grupo. Es la estrategia dominante en la práctica aplicada, tanto por su menor costo computacional relativo como por la mayor disponibilidad de criterios de fusión bien estudiados (enlace simple, completo, promedio, Ward, entre otros).

Divisiva: parte del proceso inverso, dividiendo sucesivamente un grupo inicial que contiene a todas las observaciones. Es computacionalmente más costosa —evaluar la mejor forma de dividir un grupo es un problema combinatorio más difícil que evaluar la mejor fusión— y por ello mucho menos utilizada en aplicaciones económicas.

El método asignado a nuestro equipo, el método de Ward, pertenece a la clasificación jerárquico aglomerativo. Formalmente, en cada paso \(t\) el algoritmo evalúa todas las fusiones posibles entre pares de grupos existentes y ejecuta aquella que produce el menor incremento en la suma de cuadrados intragrupo (varianza dentro de los conglomerados). Es, por construcción, un procedimiento secuencial y ávido (greedy): la decisión tomada en un paso no se revisa en pasos posteriores, aunque deje de ser óptima a la luz de fusiones futuras. El resultado final es un árbol binario donde la altura de cada fusión es proporcional al costo, en términos de homogeneidad perdida, de haber unido esos dos grupos.

Esta propiedad —que el algoritmo entregue toda la jerarquía y no una partición aislada— es la que distingue de raíz al método de Ward de los tres métodos restantes trabajados en la clase.

Métodos no jerárquicos

Los métodos no jerárquicos, en contraste, entregan directamente una única partición de las observaciones en grupos, sin construir una estructura de árbol. Dentro de esta familia coexisten dos lógicas distintas, representadas por los otros tres equipos.

Particionales basados en prototipos, que requieren fijar de antemano el número de grupos \(k\) y asignan cada observación al grupo cuyo prototipo (centroide en K-means, medoide en K-medianas/PAM) resulte más cercano, iterando hasta la convergencia.

Basados en densidad, como DBSCAN, que no requieren fijar \(k\) pero sí parámetros de vecindad (\(\varepsilon\), MinPts), y que agrupan observaciones en regiones de alta concentración, tratando explícitamente como ruido a las que no encajan en ninguna región densa.

Lo que comparten estos tres métodos, frente al nuestro, es que exigen una decisión ex ante (el número de grupos o un parámetro de densidad equivalente) y devuelven una sola solución; explorar otra configuración implica volver a ejecutar el algoritmo completo.

Justificación económica de la elección metodológica

La pregunta relevante no es sólo “¿qué es Ward?”, sino por qué un método jerárquico es idóneo para la naturaleza específica de los datos del Banco Mundial, y no simplemente un método más.

Primero, los indicadores del Banco Mundial son variables continuas de gradación, no categorías discretas. El PIB per cápita, la esperanza de vida, la mortalidad infantil o la tasa de urbanización no dividen a los países en cajas cerradas, sino que los ubican en un espectro continuo de desarrollo. Un método jerárquico es más adecuado para describir este tipo de estructura porque no fuerza de entrada un número fijo de categorías: permite observar, en el propio dendrograma, si existen quiebres naturales (saltos grandes en la altura de fusión) o si, por el contrario, el continuo se corta de forma más o menos arbitraria en cualquier \(k\) que se elija.

La jerarquía permite análisis a múltiples niveles de agregación simultáneamente. Un dendrograma completo contiene, en una sola estructura, la solución de 2, 3, 4 o 10 grupos. Para un análisis exploratorio de desarrollo económico regional, esto es valioso porque permite comparar, por ejemplo, la partición gruesa (economías más y menos desarrolladas) con particiones más finas (subgrupos dentro de cada bloque), sin tener que volver a ejecutar el algoritmo para cada nivel.

El criterio de mínima varianza de Ward es coherente con la multidimensionalidad del desarrollo. El “nivel de desarrollo” de una economía no se resume en una sola variable, sino que se manifiesta simultáneamente en producto, salud, demografía y estructura productiva. Al minimizar el incremento de la varianza intragrupo considerando todas las variables estandarizadas a la vez, Ward agrupa países que son globalmente parecidos en ese conjunto multidimensional, en lugar de privilegiar una sola dimensión (como ocurriría, por ejemplo, con un ordenamiento simple por PIB per cápita).

No exige comprometerse de entrada con un número de grupos. A diferencia de K-means o K-medianas, donde \(k\) debe fijarse antes de correr el algoritmo (usualmente mediante un criterio externo como el método del codo o la silueta), Ward permite primero construir el árbol completo y decidir el número de grupos después, con base en la estructura observada: una ventaja relevante cuando, como en este caso, no existe un número “correcto” de categorías de desarrollo económico conocido a priori.

En síntesis: el método de Ward se clasifica como jerárquico aglomerativo porque construye progresivamente, de abajo hacia arriba, una estructura de árbol binario mediante fusiones sucesivas que minimizan el incremento de varianza intragrupo, y resulta idóneo para los datos del Banco Mundial porque éstos describen un fenómeno —el desarrollo económico— de naturaleza continua y multidimensional, mejor capturado por una jerarquía completa que por una partición única y fija.

2. Explicación matemática

Esta sección construye la matemática del método de Ward de forma progresiva, con datos ideales: se parte de dos países y se agrega uno a la vez, de modo que cada idea nueva aparece cuando el ejemplo la necesita. Los países son ficticios y se describen con dos indicadores que ya están en escalas comparables (con datos reales, antes habría que estandarizarlos con scale()).

2.1 Dos países: la distancia euclidiana

Empecemos con sólo dos países, A y B:

X2 <- rbind(A = c(1, 2), B = c(4, 6))
colnames(X2) <- c("indicador_1", "indicador_2")
X2
##   indicador_1 indicador_2
## A           1           2
## B           4           6

¿Qué tan distintos son? Cada país es un punto en el plano, y la distancia entre ellos es la distancia euclidiana: el teorema de Pitágoras. Las diferencias en cada indicador son los catetos de un triángulo rectángulo y la distancia es la hipotenusa.

\[ d(A,B) = \sqrt{(4-1)^2 + (6-2)^2} = \sqrt{9 + 16} = \sqrt{25} = 5 \]

Con más indicadores la idea es la misma: se suma un cateto al cuadrado por cada indicador y se saca la raíz.

\[ d(i,j) = \sqrt{\sum_{v=1}^{p} (x_{iv} - x_{jv})^2} \]

¿Por qué el método de Ward trabaja con la distancia al cuadrado? Porque el cuadrado se reparte entre los indicadores: \(d^2 = 25 = 9 + 16\), es decir, 9 unidades de separación vienen del indicador 1 y 16 del indicador 2. La distancia sin elevar al cuadrado no se descompone así (\(5 \neq 3 + 4\)). Esa propiedad es la que permite hablar de sumas de cuadrados, como en el análisis de varianza. También explica por qué hay que estandarizar: un indicador medido en unidades grandes (por ejemplo, dólares) aportaría casi toda la separación.

2.2 El error cuadrático de un grupo

Supongamos que A y B forman un grupo. Su centroide es el promedio de cada indicador:

\[ \bar{x}_{AB} = \left(\tfrac{1+4}{2},\ \tfrac{2+6}{2}\right) = (2{,}5;\ 4) \]

El error cuadrático del grupo, o suma de cuadrados intragrupo (SCE), mide qué tan dispersos están sus miembros: es la suma de las distancias al cuadrado de cada país al centroide.

\[ \mathrm{SCE}(G) = \sum_{i \in G} \lVert x_i - \bar{x}_G \rVert^2 \]

Es la misma idea que la suma de cuadrados de los residuos en una regresión: allí el residuo es la distancia de cada observación a la recta; aquí, la distancia de cada país al centro de su grupo.

País Diferencia indicador 1 Diferencia indicador 2 Distancia² al centroide
A = (1, 2) 1 − 2,5 = −1,5 → 2,25 2 − 4 = −2 → 4 6,25
B = (4, 6) 4 − 2,5 = 1,5 → 2,25 6 − 4 = 2 → 4 6,25
SCE del grupo 12,5

Cuando A y B estaban separados, cada uno era su propio centro y su error era 0. Al unirlos, el error sube de 0 a 12,5. Ese aumento es el costo de fusión: cuánta homogeneidad se pierde al tratar a dos países como si fueran uno.

\[ \Delta(G_1, G_2) = \mathrm{SCE}(G_1 \cup G_2) - \mathrm{SCE}(G_1) - \mathrm{SCE}(G_2) \]

Fíjese en que 12,5 es exactamente la mitad de \(d^2 = 25\). No es casualidad: para dos países sueltos, el costo de unirlos es siempre la mitad de su distancia al cuadrado.

sce(X2)            # error del grupo {A, B}
## [1] 12.5
sce(X2) / sum((X2["A", ] - X2["B", ])^2)   # proporción respecto a la distancia al cuadrado
## [1] 0.5

2.3 Llega un tercer país: Ward une el par más barato

Agregamos un tercer país, C = (1, 3). Ahora hay tres fusiones posibles, y el método de Ward elige la que menos aumenta el error:

X3 <- rbind(X2, C = c(1, 3))
pares <- combn(rownames(X3), 2)
costos3 <- data.frame(
  fusion = apply(pares, 2, paste, collapse = "-"),
  d2     = apply(pares, 2, function(p) sum((X3[p[1], ] - X3[p[2], ])^2)),
  costo  = apply(pares, 2, function(p) sce(X3[p, ]))
)
knitr::kable(costos3, col.names = c("Fusión", "Distancia²", "Costo = aumento del error"),
             caption = "Las tres fusiones posibles con tres países")
Las tres fusiones posibles con tres países
Fusión Distancia² Costo = aumento del error
A-B 25 12.5
A-C 1 0.5
B-C 18 9.0

La fusión más barata es A-C, con un costo de 0,5, así que Ward forma el grupo {A, C}. B queda solo por ahora.

Este es el criterio de Ward completo: en cada paso, fusionar el par de grupos cuyo costo de fusión sea el menor. Por eso se dice que Ward “minimiza la varianza”: con más precisión, minimiza el aumento de la suma de cuadrados intragrupo en cada paso, y no revisa después las decisiones ya tomadas.

2.4 Unir un país a un grupo: el tamaño también cuesta

¿Cuánto costaría ahora unir a B con el grupo {A, C}? La definición no cambia: el error del grupo nuevo menos el que ya había.

Paso 1: el error del grupo nuevo {A, B, C}. Su centroide es el promedio de los tres países en cada indicador:

\[ \bar{x}_{ABC} = \left(\tfrac{1+4+1}{3},\ \tfrac{2+6+3}{3}\right) = \left(2,\ \tfrac{11}{3}\right) \approx (2;\ 3{,}667) \]

Con tercios conviene trabajar en novenos para no arrastrar redondeos:

País Diferencia indicador 1 Diferencia indicador 2 Distancia² al centroide
A = (1, 2) 1 − 2 = −1 → 1 2 − 11/3 = −5/3 → 25/9 34/9 ≈ 3,778
B = (4, 6) 4 − 2 = 2 → 4 6 − 11/3 = 7/3 → 49/9 85/9 ≈ 9,444
C = (1, 3) 1 − 2 = −1 → 1 3 − 11/3 = −2/3 → 4/9 13/9 ≈ 1,444
SCE de {A, B, C} 132/9 ≈ 14,667

Paso 2: el costo es cuánto sube el error.

Momento Grupos Error cuadrático (SCE)
Antes de la fusión {A, C} y {B} 0,5 + 0 = 0,5
Después de la fusión {A, B, C} 14,667
Costo de fusión 14,667 − 0,5 = 14,167
sce(X3)                                   # error de {A, B, C}
## [1] 14.66667
sce(X3) - sce(X3[c("A", "C"), ]) - 0      # costo de unir B con {A, C}
## [1] 14.16667

Si la regla de “la mitad” siguiera valiendo, el costo sería la mitad de la distancia al cuadrado entre B y el centroide de {A, C}, que está en (1; 2,5): \(\tfrac{1}{2}(3^2 + 3{,}5^2) = \tfrac{21{,}25}{2} = 10{,}625\). Pero el costo real es mayor. La razón es que {A, C} “pesa” el doble que B: al unirlos, el centro nuevo apenas se desplaza hacia B (un tercio del camino), B queda lejos del centro y el error sube más. El costo real es \(\tfrac{2}{3} \times 21{,}25 = 14{,}167\).

En general, el costo de fusionar dos grupos es:

\[ \Delta(G_1, G_2) = \frac{n_1\, n_2}{n_1 + n_2}\, \lVert \bar{x}_{G_1} - \bar{x}_{G_2} \rVert^2 \]

con dos componentes:

  • Un factor geométrico, la distancia al cuadrado entre los centroides: cuanto más lejos están los grupos, más cuesta unirlos.
  • Un factor de tamaño, \(n_1 n_2 / (n_1 + n_2)\): para una misma distancia, unir grupos grandes cuesta más.
Qué se une Factor de tamaño Costo con una distancia² de 21,25
País suelto + país suelto (1 × 1)/(1 + 1) = 1/2 10,625
Grupo de 2 + país suelto (2 × 1)/(2 + 1) = 2/3 14,167
Grupo de 2 + grupo de 2 (2 × 2)/(2 + 2) = 1 21,25

De aquí salen dos rasgos característicos de Ward: tiende a formar grupos de tamaño parecido, porque unir bloques grandes es caro, y deja a los países atípicos para el final, porque su distancia a cualquier grupo es grande.

2.5 Llega un cuarto país: dos grupos y la última fusión

Agregamos D = (4, 8), cercano a B. Antes de unir B con {A, C}, que costaría 14,167, a Ward le resulta más barato unir B con D:

X4 <- rbind(X3, D = c(4, 8))
c(B_con_D = sce(X4[c("B", "D"), ]),                              # dos países sueltos
  B_con_AC = sce(X4[c("A", "B", "C"), ]) - sce(X4[c("A", "C"), ]),
  D_con_AC = sce(X4[c("A", "C", "D"), ]) - sce(X4[c("A", "C"), ]))
##  B_con_D B_con_AC D_con_AC 
##  2.00000 14.16667 26.16667

Ward forma el grupo {B, D}, con un costo de 2. Quedan dos grupos de dos países, y la última fusión los une: sus centroides son (1; 2,5) y (4; 7), a una distancia al cuadrado de \(3^2 + 4{,}5^2 = 29{,}25\), y el factor de tamaño es \(2 \times 2/(2+2) = 1\), así que el costo es 29,25.

El resumen del proceso completo:

Paso Fusión Factor de tamaño Distancia² entre centroides Costo
1 A + C 1/2 1 0,5
2 B + D 1/2 4 2
3 {A, C} + {B, D} 1 29,25 29,25
Suma de los costos 31,75

La suma de los costos, 31,75, es exactamente la suma de cuadrados total \(T\) de los cuatro países, es decir, su dispersión respecto a la media general. Al empezar, el error dentro de los grupos es 0; al final, con todos en un grupo, es \(T\); cada fusión aporta su costo. Por eso el árbol completo reparte la variabilidad total entre las fusiones.

sce(X4)          # T: suma de cuadrados total de los cuatro países
## [1] 31.75

Esa descomposición es la del análisis de varianza: \(T = W + B\), donde \(W\) es la suma de cuadrados dentro de los grupos y \(B\) entre grupos. Como \(T\) no depende de cómo se agrupe, minimizar \(W\) equivale a maximizar \(B\): buscar grupos internamente parecidos es lo mismo que buscar grupos bien separados.

2.6 La recurrencia de Lance-Williams

Con cuatro países se pueden calcular los centroides a mano, pero con cientos no. Lance y Williams (1967) mostraron que el cálculo se puede hacer actualizando sólo una tabla de disimilitudes: cuando se fusionan los grupos \(i\) y \(j\), la disimilitud entre el grupo nuevo y otro grupo \(k\) se obtiene de las que ya se conocían:

\[ D(i \cup j,\, k) = \alpha_i\, D(i,k) + \alpha_j\, D(j,k) + \beta\, D(i,j) + \gamma\, \lvert D(i,k) - D(j,k) \rvert \]

Para Ward, sobre distancias al cuadrado, los coeficientes son:

\[ \alpha_i = \frac{n_i + n_k}{n_i + n_j + n_k}, \qquad \alpha_j = \frac{n_j + n_k}{n_i + n_j + n_k}, \qquad \beta = \frac{-\,n_k}{n_i + n_j + n_k}, \qquad \gamma = 0 \]

Los tamaños de los grupos aparecen en los coeficientes: es el mismo factor de tamaño de la sección 2.4, escrito de otra forma. La cantidad \(D\) que actualiza la recurrencia es el doble del costo de fusión, \(D = 2\Delta\); para dos países sueltos coincide con su distancia al cuadrado.

Ejemplo: después de unir A y C, ¿cuál es la disimilitud entre {A, C} y B? Con \(D(A,B) = 25\), \(D(C,B) = 18\), \(D(A,C) = 1\) y todos los tamaños iguales a 1:

\[ D(AC, B) = \frac{2}{3}(25) + \frac{2}{3}(18) - \frac{1}{3}(1) = \frac{50 + 36 - 1}{3} = \frac{85}{3} \approx 28{,}33 \]

Es exactamente \(2 \times 14{,}167\), el doble del costo que se calculó en la sección 2.4, pero sin calcular ningún centroide.

Ejemplo: después de unir B y D, ¿cuál es la disimilitud entre {B, D} y {A, C}? Ahora \(i = B\), \(j = D\) y \(k = \{A, C\}\), con \(n_k = 2\). Usando \(D(AC, B) = 85/3\), \(D(AC, D) = 157/3\) (que sale de la misma forma) y \(D(B,D) = 4\):

\[ D(BD, AC) = \frac{3}{4}\cdot\frac{85}{3} + \frac{3}{4}\cdot\frac{157}{3} - \frac{2}{4}(4) = \frac{85 + 157 - 8}{4} = 58{,}5 \]

Es \(2 \times 29{,}25\): el doble del costo de la última fusión.

lw_ward <- function(D_ik, D_jk, D_ij, n_i, n_j, n_k) {
  ((n_i + n_k) * D_ik + (n_j + n_k) * D_jk - n_k * D_ij) / (n_i + n_j + n_k)
}
D2 <- as.matrix(dist(X4))^2                                    # distancias al cuadrado
D_AC_B  <- lw_ward(D2["A", "B"], D2["C", "B"], D2["A", "C"], 1, 1, 1)
D_AC_D  <- lw_ward(D2["A", "D"], D2["C", "D"], D2["A", "C"], 1, 1, 1)
D_BD_AC <- lw_ward(D_AC_B, D_AC_D, D2["B", "D"], 1, 1, 2)
c(D_AC_B = D_AC_B, D_AC_D = D_AC_D, D_BD_AC = D_BD_AC)
##   D_AC_B   D_AC_D  D_BD_AC 
## 28.33333 52.33333 58.50000

2.7 Cómo se lee la altura del dendrograma

Todo lo anterior es lo que hace hclust() con method = "ward.D2". El dendrograma dibuja las tres fusiones:

modelo_ideal <- hclust(dist(X4), method = "ward.D2")
par(mar = c(2, 4, 3, 5))
plot(modelo_ideal, hang = -1, main = "Dendrograma de Ward: cuatro países",
     xlab = "", sub = "", ylab = "Altura de fusión h")
abline(h = modelo_ideal$height, lty = 3, col = "grey60")
text(4.15, modelo_ideal$height, paste0("h = ", round(modelo_ideal$height, 2)),
     pos = 4, cex = 0.8, col = "grey30", xpd = TRUE)

round(modelo_ideal$height, 3)
## [1] 1.000 2.000 7.649

La altura de cada fusión es la raíz de la cantidad \(D\) de Lance-Williams:

\[ h = \sqrt{D} = \sqrt{2\,\Delta} \qquad\Longleftrightarrow\qquad \Delta = \frac{h^2}{2} \]

data.frame(fusion     = c("A + C", "B + D", "{A,C} + {B,D}"),
           altura_h   = round(modelo_ideal$height, 3),
           costo_h2_2 = modelo_ideal$height^2 / 2)
##          fusion altura_h costo_h2_2
## 1         A + C    1.000       0.50
## 2         B + D    2.000       2.00
## 3 {A,C} + {B,D}    7.649      29.25

De ahí salen las lecturas del eje vertical:

  1. La altura no es la distancia entre dos países; es el precio de unirlos. Las alturas 1, 2 y 7,65 corresponden a costos de 0,5, 2 y 29,25. El dendrograma es una tabla de precios: cada nudo dice cuánta homogeneidad se sacrifica al tratar como iguales a dos grupos.
  2. Las alturas suman la variabilidad total: \(\sum h^2/2 = 0{,}5 + 2 + 29{,}25 = 31{,}75 = T\). Por eso cada corte tiene un \(R^2\): cortar en dos grupos deja un error de \(0{,}5 + 2 = 2{,}5\) y explica \(1 - 2{,}5/31{,}75 = 92\,\%\) de la variabilidad.
  3. Las alturas nunca bajan, así que el árbol no tiene cruces y se puede cortar horizontalmente.
  4. Un salto grande señala dónde cortar. Pasar de la altura 2 a la 7,65 es un salto grande: unir los dos grupos cuesta mucho más que formarlos. El criterio del salto de altura (sección 4) indica cortar en dos grupos, {A, C} y {B, D}, que es lo que se ve en el gráfico de la sección 2.5.

Lectura para decisiones económicas. Si A, B, C y D fueran economías y los indicadores midieran su desarrollo, las alturas dirían cuánto se pierde al tratarlas como un solo bloque para, por ejemplo, diseñar una política común: tratar a A y C como iguales cuesta casi nada (0,5), mientras que tratar a los cuatro países como un bloque único cuesta 29,25 de las 31,75 unidades de variabilidad.

Advertencia de implementación: ward.D frente a ward.D2. La recurrencia de Lance-Williams para Ward es válida sobre distancias al cuadrado. ward.D2 eleva al cuadrado internamente las distancias que produce dist(); ward.D aplica la recurrencia a lo que recibe, así que sólo es el criterio de Ward si se le pasan las distancias ya elevadas al cuadrado. hclust(dist(X), method = "ward.D") corre sin error pero no minimiza la suma de cuadrados; por eso en todo el informe se usa ward.D2.

3. Ventajas y desventajas frente a DBSCAN, PAM y K-means

Método a comparar frente a Ward Fortalezas de Ward Limitaciones de Ward
DBSCAN (densidad) • Determinístico: Ward siempre produce los mismos resultados. DBSCAN depende de parámetros eps y minPts que son difíciles de ajustar.
• Dendrograma intuitivo: Ward proporciona una visualización jerárquica clara. DBSCAN no ofrece estructura de niveles.
• Mejor para datos numéricos: Ward optimiza varianza, ideal para datos continuos bien distribuidos.
• No detecta formas arbitrarias: Ward asume clusters esféricos. DBSCAN encuentra clusters de cualquier forma (alargados, curvos, etc.).
• Sensible a outliers: DBSCAN clasifica outliers como ruido, es más robusto. Ward es muy sensible.
• Requiere exploración: para DBSCAN, el número de clusters emerge automáticamente. Ward requiere decidir dónde cortar el dendrograma.
PAM (medoides) • No requiere especificar K: Ward determina el número de clusters. PAM obliga a predefinir K.
• Más eficiente: aunque ambos son O(n²), Ward tiene algoritmos optimizados de vecinos cercanos. PAM requiere cálculos más extensos.
• Análisis jerárquico: Ward explora múltiples niveles. PAM sólo da una partición única.
• Muy sensible a outliers: los valores atípicos distorsionan la varianza. PAM usa medoides reales, mucho más robusto.
• Centro abstracto: Ward optimiza varianza (concepto matemático). PAM usa observaciones reales, más interpretables.
• Sin segundas oportunidades: si los outliers ya distorsionaron el árbol, no hay forma de recuperarse. PAM es más flexible ante datos ruidosos.
K-means (centroides) • No requiere especificar K: Ward lo determina en el dendrograma. K-means obliga a definir K de antemano.
• Resultados exactos: Ward siempre da el mismo resultado. K-means varía según inicialización aleatoria.
• Análisis exploratorio: el dendrograma permite explorar múltiples niveles de agrupamiento sin repetir el análisis.
• Muy lento en datos grandes: Ward es O(n²) aunque optimizado. K-means es O(n·k·i), mucho más rápido en millones de registros.
• Alto consumo de memoria: Ward requiere la matriz de distancias completa. K-means es mucho más eficiente en uso de memoria.
• Sensible a outliers: los valores atípicos afectan la varianza. K-means es más práctico y rápido de reajustar si es necesario.

Síntesis: fortalezas y limitaciones de Ward

Fortalezas de Ward: dendrograma intuitivo para análisis exploratorio, no requiere especificar K previamente, determinístico (sin variabilidad), excelente para entender estructura jerárquica.

Limitaciones de Ward: muy sensible a outliers (pierde frente a DBSCAN y PAM), lento en datos grandes (pierde frente a K-means), asume clusters esféricos, alto consumo de memoria.

4. Selección del número óptimo de clústeres

4.1 Panorama general: las cuatro métricas disponibles

Antes de aplicar el método que nos corresponde, vale la pena repasar brevemente las cuatro métricas generales, porque cada una está pensada para una lógica de agrupamiento distinta:

Método del Codo (Elbow): se usa principalmente en K-means. Se corre el algoritmo para distintos valores de \(k\) y se grafica la Suma de Cuadrados Intra-clúster (WSS) contra \(k\). El “codo” de la curva —el punto donde agregar un clúster más deja de reducir significativamente el WSS— marca el \(k\) óptimo. Funciona bien porque K-means necesita el número de centroides definido desde el inicio.

Coeficiente de Silueta: se usa sobre todo en K-medianas/PAM. Mide, para cada observación, qué tan bien encaja en su propio clúster frente a qué tan lejos está del clúster vecino más cercano (valores cercanos a 1 = buena asignación). Se elige el \(k\) que maximiza la silueta promedio. Es especialmente útil con medoides porque no depende de promedios sensibles a outliers.

Gráfico de K-vecinos (k-NN distance plot): se usa para DBSCAN, no para elegir un número de clústeres sino para calibrar el parámetro Épsilon (radio de vecindad). Se grafican las distancias al k-ésimo vecino más cercano de cada punto, ordenadas de menor a mayor, y se busca el “codo” de esa curva.

Dendrograma: es la métrica propia de los métodos jerárquicos, como Ward. A diferencia de las anteriores, no exige fijar \(k\) antes de correr el algoritmo: primero se construye el árbol completo de fusiones y luego se analiza visual y analíticamente en qué altura conviene “cortarlo”.

4.2 El método que corresponde a Ward: criterio del salto de altura

Para nuestro algoritmo asignado, Ward, la métrica que le corresponde analíticamente es el Dendrograma, aplicando el criterio del salto de altura (“height jump”), que es la forma cuantitativa —no solo visual— de decidir dónde cortar el árbol.

Recordemos que Ward fusiona en cada paso el par de clústeres cuya unión produce el menor incremento posible en la ESS (Suma de Cuadrados Intragrupo). La altura a la que ocurre cada fusión en el dendrograma es, precisamente, ese incremento de ESS que costó unir esos dos grupos. Por lo tanto:

  • Si dos fusiones consecutivas ocurren a alturas muy parecidas, significa que seguir fusionando “no duele mucho” en términos de homogeneidad perdida: esos grupos eran razonablemente similares.
  • Si de una fusión a la siguiente la altura da un salto grande, significa que esa fusión unió grupos que en realidad eran bastante distintos entre sí: forzarla ya no se justifica.

El criterio del salto de altura formaliza esto: se toman las alturas de fusión ordenadas de mayor a menor (alturas_lac), se calcula la diferencia entre cada altura y la siguiente (saltos_lac <- -diff(alturas_lac)), y se localiza el salto más grande con which.max(saltos_lac). El número de clústeres óptimo k_optimo_lac es, entonces, la posición de ese salto más grande + 1 (porque justo antes de ese salto es el último punto donde todavía tenía sentido seguir uniendo grupos). Esta es una forma analítica y reproducible del criterio, no una simple “lectura a ojo” del gráfico.

Preparación previa (necesaria para poder aplicar el criterio)

El criterio del salto de altura se calcula sobre un modelo de Ward ya ajustado, así que antes de aplicarlo necesitamos los datos de América Latina, limpios y estandarizados. A continuación se explica qué hace cada bloque de código.

paquetes <- c("httr", "jsonlite", "dplyr", "stringr", "knitr", "kableExtra",
              "cluster", "factoextra", "RColorBrewer")

instalar_faltantes <- paquetes[!(paquetes %in% installed.packages()[, "Package"])]
if (length(instalar_faltantes) > 0) install.packages(instalar_faltantes)

library(httr)
library(jsonlite)
library(dplyr)
library(stringr)
library(knitr)
library(kableExtra)
library(cluster)
library(factoextra)
library(RColorBrewer)

Selección de países

Definimos el universo de análisis: los 19 países de América Latina (código ISO3) para los que consultaremos la API del Banco Mundial. codigos_paises_lac los concatena en el formato "ARG;BOL;BRA;..." que exige la URL de la API (los países se separan con punto y coma en una sola consulta).

paises_lac <- c(
  "ARG", "BOL", "BRA", "CHL", "COL", "CRI", "CUB", "DOM", "ECU", "SLV",
  "GTM", "HND", "MEX", "NIC", "PAN", "PRY", "PER", "URY", "VEN"
)
codigos_paises_lac <- str_c(paises_lac, collapse = ";")

Descarga de los indicadores

Elegimos cinco indicadores que en conjunto describen el nivel de desarrollo de un país: PIB per cápita, esperanza de vida, acceso a internet, acceso a electricidad y gasto en salud. Queremos comparar a los países en igualdad de condiciones, así que no basta con que cada país aporte “su” año más reciente (un país podría llegar a reportar 2024 y otro solo hasta 2021, y eso distorsionaría la comparación).

indicadores_cluster <- c(
  pib_percapita = "NY.GDP.PCAP.CD",
  esp_vida      = "SP.DYN.LE00.IN",
  internet      = "IT.NET.USER.ZS",
  electricidad  = "EG.ELC.ACCS.ZS",
  gasto_salud   = "SH.XPD.CHEX.GD.ZS"
)

# Rango dinámico con 5 años de margen: termina 2 años antes del año actual
# (rezago de publicación de la API) y empieza 5 años antes de ese año final.
anio_fin    <- as.integer(format(Sys.Date(), "%Y")) - 2
anio_inicio <- anio_fin - 5

# Función que descarga UN indicador para todos los países de América Latina,
# dentro del rango anio_inicio:anio_fin, y elige un único año COMÚN para
# todos los países: el más reciente con mayor cobertura de datos.
descargar_indicador_lac <- function(codigo_indicador, nombre_variable) {
  url <- str_c(
    "https://api.worldbank.org/v2/country/", codigos_paises_lac,
    "/indicator/", codigo_indicador,
    "?date=", anio_inicio, ":", anio_fin,
    "&lang=es&format=json&per_page=2000"
  )

  json <- fromJSON(url)
  df <- json[[2]]

  if (is.null(df) || length(df) == 0) {
    vacio <- data.frame(iso3c = character(0), pais = character(0))
    vacio[[nombre_variable]] <- numeric(0)
    return(vacio)
  }

  df <- flatten(df) %>%
    filter(!is.na(value)) %>%
    mutate(date = as.integer(date))

  # Cobertura por año: cuántos países tienen dato en cada año
  cobertura_por_anio <- df %>%
    count(date, name = "n_paises") %>%
    arrange(desc(n_paises), desc(date))

  anio_comun <- cobertura_por_anio$date[1]

  df %>%
    filter(date == anio_comun) %>%
    transmute(
      iso3c = countryiso3code,
      pais  = country.value,
      valor = as.numeric(value)
    ) %>%
    rename(!!nombre_variable := valor)
}

lista_indicadores <- Map(
  descargar_indicador_lac,
  indicadores_cluster,
  names(indicadores_cluster)
)

datos_lac <- Reduce(
  function(x, y) full_join(x, y, by = c("iso3c", "pais")),
  lista_indicadores
)

Tratamiento de datos faltantes (NA)

No todos los países tienen dato disponible para los cinco indicadores. Para no perder observaciones, mutate(across(...)) recorre cada columna numérica y reemplaza los valores faltantes (NA) por la media de esa columna. El na.omit() final es una salvaguarda que elimina cualquier fila que aun así quedara incompleta (por ejemplo, si un país no tuviera dato en absolutamente ningún año para un indicador).

datos_lac_limpios <- datos_lac %>%
  mutate(across(
    pib_percapita:gasto_salud,
    ~ ifelse(is.na(.), mean(., na.rm = TRUE), .)
  )) %>%
  na.omit()

Estandarización de variables

Las cinco variables están en escalas muy distintas (dólares, años, porcentajes), así que si no se corrige, el PIB per cápita —al tener los números más grandes— dominaría por completo el cálculo de distancias, aunque no sea “más importante” que los demás indicadores. scale() convierte cada variable a puntuación Z (media 0, desviación estándar 1), dejando a los cinco indicadores en pie de igualdad antes de calcular distancias.

matriz_lac <- datos_lac_limpios %>%
  select(pib_percapita, esp_vida, internet, electricidad, gasto_salud) %>%
  as.data.frame()

rownames(matriz_lac) <- datos_lac_limpios$pais
matriz_lac_escalada <- scale(matriz_lac)

Matriz de distancias y modelo de Ward

Con las variables ya estandarizadas, dist(..., method = "euclidean") calcula la distancia euclidiana entre cada par de países (qué tan “lejos” están en términos de sus cinco indicadores). Sobre esa matriz de distancias, hclust(..., method = "ward.D2") construye el modelo de Ward: empieza con 19 clústeres (uno por país) y va fusionando el par que menos incrementa la ESS total, hasta que todos quedan en un solo grupo. El resultado (modelo_ward_lac) contiene, entre otras cosas, el historial completo de fusiones y sus alturas, que es justo lo que necesitamos para aplicar el criterio.

distancias_lac <- dist(matriz_lac_escalada, method = "euclidean")
modelo_ward_lac <- hclust(distancias_lac, method = "ward.D2")

Aplicación del criterio del salto de altura

Con el modelo ya ajustado, extraemos y ordenamos las alturas de fusión (alturas_lac), calculamos la diferencia entre alturas consecutivas (saltos_lac) y localizamos automáticamente el salto más grande con which.max(). El gráfico muestra las primeras 10 alturas de fusión y marca en rojo el punto donde se ubicó ese salto máximo: es la representación visual del mismo criterio analítico explicado en la sección 4.2.

alturas_lac <- rev(modelo_ward_lac$height)
saltos_lac  <- -diff(alturas_lac)
n_candidatos_lac <- min(10, length(alturas_lac))

plot(1:n_candidatos_lac, alturas_lac[1:n_candidatos_lac], type = "b", pch = 19, col = "steelblue",
     xlab = "Número de fusiones desde el final (≈ número de clústeres)",
     ylab = "Altura de fusión (incremento de ESS)",
     main = "Identificación del salto más grande - América Latina")

k_optimo_lac <- which.max(saltos_lac[1:(n_candidatos_lac - 1)]) + 1
abline(v = k_optimo_lac, col = "red", lty = 2)
text(k_optimo_lac, max(alturas_lac[1:n_candidatos_lac]) * 0.9,
     labels = paste0("k óptimo ≈ ", k_optimo_lac), col = "red", pos = 4)

k_optimo_lac
## [1] 3

La siguiente tabla muestra, para cada fusión contada desde el final del árbol, la altura a la que ocurrió esa fusión y cuánto “saltó” la altura respecto a la fusión siguiente: es la versión numérica exacta de lo que acabamos de ver en el gráfico.

tabla_saltos_lac <- data.frame(
  k_clusteres     = 1:n_candidatos_lac,
  altura_fusion   = round(alturas_lac[1:n_candidatos_lac], 2),
  salto_siguiente = c(round(saltos_lac[1:(n_candidatos_lac - 1)], 2), NA)
)

tabla_saltos_lac %>%
  kable(
    col.names = c("Fusión j (pasa de j + 1 a j grupos)", "Altura de fusión (ESS)", "Salto a la fusión siguiente"),
    caption = "Alturas de fusión y saltos - América Latina"
  ) %>%
  kable_classic(html_font = "Times New Roman", font_size = 14) %>%
  kable_styling()
Alturas de fusión y saltos - América Latina
Fusión j (pasa de j + 1 a j grupos) Altura de fusión (ESS) Salto a la fusión siguiente
1 7.91 1.73
2 6.19 2.01
3 4.18 0.78
4 3.40 0.28
5 3.12 0.13
6 2.99 0.58
7 2.40 0.23
8 2.18 0.21
9 1.97 0.12
10 1.84 NA

Confirmación visual: dendrograma con el k seleccionado

Por último, fviz_dend() dibuja el dendrograma completo y, con k = k_optimo_lac, traza automáticamente los rectángulos de color que delimitan los clústeres resultantes de cortar el árbol justo en el número óptimo que identificamos analíticamente. Esto permite verificar visualmente que el corte propuesto por el criterio del salto de altura efectivamente separa grupos de países que se ven razonablemente compactos y distintos entre sí.

fviz_dend(
  modelo_ward_lac,
  k = k_optimo_lac,
  cex = 0.8,
  k_colors = "Set1",
  rect = TRUE,
  rect_fill = TRUE,
  main = paste0("Dendrograma - Método de Ward (América Latina, k = ", k_optimo_lac, ")")
)

Los rectángulos de color del dendrograma coinciden exactamente con el k_optimo_lac identificado mediante el salto de altura, confirmando visualmente que ese es el punto donde agrupar más países dejaría de ser conveniente.

4.3 Lectura del resultado

La tabla de alturas y saltos (tabla_saltos_lac) es la evidencia numérica detrás de la decisión: mientras se avanza de \(k\) alto hacia \(k\) bajo, la columna de saltos se mantiene relativamente estable —indicando fusiones “baratas” entre países parecidos— hasta que aparece un salto notoriamente mayor que los anteriores. Ese salto es justamente el que identifica which.max(saltos_lac), y marca el punto de corte k_optimo_lac.

Esto es lo que hace objetivo el proceso: no estamos decidiendo el número de clústeres “porque el dendrograma se ve bien” en ese punto, sino porque es matemáticamente donde el costo de seguir fusionando (en términos de varianza intragrupo perdida) se dispara. Es la respuesta directa y verificable a “especificar analíticamente el método exacto que le corresponde utilizar según el algoritmo asignado”: para Ward, ese método es el dendrograma con el criterio del salto de altura, y el resultado obtenido (k_optimo_lac = 3) es reproducible por cualquiera que corra este mismo código.

5. Ejemplo práctico de clustering jerárquico aglomerativo (método de Ward)

Data y variables a utilizar

Remesas personales: son transferencias de dinero realizadas por personas residentes en el extranjero (generalmente trabajadores migrantes) a favor de sus familiares o allegados en su país de origen. Se destinan mayoritariamente al consumo básico de los hogares receptores, cubriendo necesidades inmediatas como alimentación, vivienda, educación, salud y pago de servicios.

Ingresos tributarios: son los recursos financieros que el Estado recauda de forma obligatoria de los contribuyentes (personas naturales y jurídicas) en ejercicio de su poder de imperio (potestad tributaria), con el objetivo de financiar el gasto público y satisfacer las necesidades de la sociedad.

Gasto público: es el total de recursos financieros que el Estado (gobierno central, gobiernos locales y entidades descentralizadas) destina a la adquisición de bienes, la contratación de servicios, la realización de inversiones públicas y la transferencia de ingresos para cumplir con sus funciones fundamentales y satisfacer las necesidades colectivas de la sociedad.

Los datos a utilizar son obtenidos de la API del Banco Mundial.

library(wbstats)
indicadores <- wb_indicators(lang = "es")

Presentación de indicadores

library(readr)
library(dplyr)
library(tidyr)
library(kableExtra)

mis_indicadores <- c(Gasto_Publico = "GC.XPN.TOTL.GD.ZS",
                     Ingresos_Tributarios = "GC.TAX.TOTL.GD.ZS",
                     Remesas_Personales = "BX.TRF.PWKR.DT.GD.ZS")

# Descargar datos para los países
datos_wb <- wb_data(indicator = mis_indicadores,
                    start_date = 2000,
                    end_date = 2000,
                    return_wide = TRUE)

# Visualizar los indicadores
datos_wb %>%
  head(10) %>%
  kable(format = "html",
        caption = "Indicadores para el año 2000",
        align = "c") %>%
  add_footnote(label = "Tomado de la API del BM",
               notation = "symbol") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
                full_width = TRUE,
                position = "center")
Indicadores para el año 2000
iso2c iso3c country date Remesas_Personales Ingresos_Tributarios Gasto_Publico
AW ABW Aruba 2000 0.0578504 NA NA
AF AFG Afghanistan 2000 NA NA NA
AO AGO Angola 2000 NA 28.703382 21.83002
AL ALB Albania 2000 16.6770344 NA NA
AD AND Andorra 2000 NA NA NA
AE ARE United Arab Emirates 2000 NA NA NA
AR ARG Argentina 2000 0.0303809 9.622604 16.83289
AM ARM Armenia 2000 9.5293713 NA NA
AS ASM American Samoa 2000 NA NA NA
AG ATG Antigua and Barbuda 2000 1.9248492 NA NA
* Tomado de la API del BM

Preparación de datos

Para el análisis, las filas deben contener sólo observaciones numéricas y las columnas deben ser variables, así que se seleccionan exactamente sólo las variables que se utilizarán, que son “países” e “indicadores”, dejando de lado los “iso” de los países y el año (date).

ejemplo <- datos_wb %>%
  select(country, Gasto_Publico, Remesas_Personales, Ingresos_Tributarios)

ejemplo %>%
  head(10) %>%
  kable(format = "html",
        caption = "Indicadores",
        align = "c") %>%
  add_footnote(label = "Tomado de la API del BM",
               notation = "symbol") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
                full_width = TRUE,
                position = "center")
Indicadores
country Gasto_Publico Remesas_Personales Ingresos_Tributarios
Aruba NA 0.0578504 NA
Afghanistan NA NA NA
Angola 21.83002 NA 28.703382
Albania NA 16.6770344 NA
Andorra NA NA NA
United Arab Emirates NA NA NA
Argentina 16.83289 0.0303809 9.622604
Armenia NA 9.5293713 NA
American Samoa NA NA NA
Antigua and Barbuda NA 1.9248492 NA
* Tomado de la API del BM

Función hclust

Para esta función se necesitan valores de distancia que se pueden calcular utilizando la función dist; la medida predeterminada para esta función es euclidiana.

Es importante verificar que los datos no tengan valores faltantes; si es así, es necesario eliminarlos. También hay que tener en cuenta que los datos en las columnas deben estar estandarizados o escalados, para que las variables sean comparables, es decir, transformar todas las variables para que estén en la misma escala de medida, logrando que tengan una media de \(0\) y una desviación estándar de \(1\).

Se utilizará tibble::column_to_rownames para transformar los valores de una columna normal (en este caso, los nombres de los países) en los nombres oficiales de las filas de la matriz de datos.

library(cluster)

datos <- ejemplo %>%
  tibble::column_to_rownames(var = "country")

# Para eliminar cualquier valor faltante que pueda estar presente en los datos
datos_limpios <- na.omit(datos)

# Para escalar todas las variables
datos_escalados <- scale(datos_limpios)

# Matriz de distancias (disimilitud): cuanto mayor es su valor,
# menos se parecen las dos unidades comparadas
matriz <- dist(datos_escalados, method = "euclidean")

# Agrupamiento jerárquico por el método de Ward
grafico <- hclust(matriz, method = "ward.D2")

# Graficar el dendrograma obtenido
plot(grafico, cex = 0.6, hang = -1, main = "Dendrograma - Método de Ward")

Función agnes

Otra opción para trabajar con el método de Ward es la función agnes; con esta se puede obtener el coeficiente de aglomeración, que mide la cantidad de estructura de agrupamiento encontrada, es decir, los valores más cercanos a 1 sugieren una estructura de agrupación fuerte.

library(cluster)
grafico_2 <- agnes(datos_escalados, method = "complete")

# Coeficiente de aglomeración
cof_aglo <- grafico_2$ac
print(cof_aglo)
## [1] 0.9416853
# Resultados
grafico_22 <- agnes(datos_escalados, method = "ward")
grafico_22$ac          # coeficiente de aglomeración con el método de Ward
## [1] 0.9623115
pltree(grafico_22, cex = 0.6, hang = -1, main = "Dendrograma - función agnes")

Corte del dendrograma con rect.hclust

El número de grupos se elige con el criterio del salto de altura de la sección 4.2, aplicado al árbol de este ejemplo. rect.hclust() marca el corte sobre el dendrograma y cutree() asigna cada país a su grupo.

alturas <- rev(grafico$height)
k_ejemplo <- which.max(-diff(alturas)[1:9]) + 1      # criterio del salto de altura
k_ejemplo
## [1] 3
plot(grafico, cex = 0.6, hang = -1, main = paste0("Dendrograma - Método de Ward (k = ", k_ejemplo, ")"))
grupos <- cutree(grafico, k = k_ejemplo)
rect.hclust(grafico, k = k_ejemplo, border = unique(grupos[grafico$order]) + 1)  # mismos colores que el cruce

table(grupos)
## grupos
##  1  2  3 
## 43 30  1

Cruce de variables

Los grupos se estiman con las tres variables estandarizadas, pero se interpretan mejor al cruzar cada par de indicadores en sus unidades originales (% del PIB), coloreando cada país según su grupo.

pairs(datos_limpios, col = grupos + 1, pch = 19,
      main = "Cruce de variables por grupo de Ward (% del PIB, año 2000)")

aggregate(datos_limpios, by = list(grupo = grupos), FUN = median)   # mediana por grupo
##   grupo Gasto_Publico Remesas_Personales Ingresos_Tributarios
## 1     1      18.42129          1.5107428             13.61538
## 2     2      36.76052          0.3987383             23.42024
## 3     3      33.80813         53.8263953             22.93956