UNIVERSIDAD DE EL SALVADOR

FACULTAD DE CIENCIAS ECONOMICAS

ESCUELA DE ECONOMIA

MATERIA: MÉTODOS PARA EL ANÁLISIS ECONÓMICO

TAREA A22: CLUSTERING DE DATOS DE LA OCDE MEDIANTE EL ALGORITMO K-MEANS

DOCENTE: JEFFRY ISAAC BONILLA MELARA

INTEGRANTES:

APELLIDOS NOMBRES CARNET PARTICIPACIÓN
PANIAGUA MUÑOZ CHRISTIAN ALEXANDER PM24066 \(100\%\)
MENDOZA CRUZ ENRIQUE ADOLFO MC24059 \(100\%\)
DELGADO CUELLAR GISELLE ABIGAIL DC24008 \(100\%\)
AGUILAR HERNÁNDEZ ANDREA SARAÍ AH22006 \(100\%\)
MORALES MONZÓN VANESSA MARÍA MM24056 \(100\%\)
ASCENCIO RAMÍREZ MATTHEW JEREMY AR23037 \(100\%\)

CICLO: II-2026

FECHA: 20 DE SEPTIEMBRE DE 2026

CIUDAD UNIVERSITARIA, SAN SALVADOR, EL SALVADOR, CENTROAMERICA

library(rsdmx)
library(dplyr)
library(tidyr)
library(tibble)
library(kableExtra)
library(ggplot2)
library(cluster)
library(factoextra)

Introducción

En la primera fase del curso se trabajó con la API de la OCDE, basada en el estándar SDMX, para extraer indicadores macroeconómicos de un grupo reducido de países. En esta segunda fase se reutiliza exactamente la misma API y la misma lógica de consulta (readSDMX() + URL construida desde el Developer API del OECD Data Explorer), pero ampliando el número de países consultados a los países miembros de la OCDE, con el fin de contar con observaciones suficientes para aplicar un algoritmo de clustering no supervisado: el algoritmo asignado K-Means (K-Medias).

1. Clasificación Lógica-Económica del Método Asignado

¿Jerárquico o No Jerárquico?

K-Means pertenece a la familia de métodos No Jerárquicos (particionales). A diferencia de los métodos jerárquicos (como el aglomerativo de Ward), en K-Means:

  • No se construye un árbol/dendrograma. El analista debe fijar de antemano el número de clústeres (k) que desea formar.
  • La asignación de una observación a un clúster NO es irrevocable. En cada iteración, un país puede “moverse” de un clúster a otro si queda más cerca de un centroide distinto, algo que jamás ocurre en un método jerárquico aglomerativo.
  • El algoritmo parte de k centroides (inicialmente aleatorios o definidos con k-means++) y reasigna iterativamente cada observación al centroide más cercano, hasta que las asignaciones se estabilizan (convergencia).

¿Por qué K-Means es idóneo para los datos de la OCDE?

La OCDE agrupa a economías de mercado consolidadas, con niveles de desarrollo, institucionalidad y calidad estadística relativamente homogéneos entre sí (a diferencia, por ejemplo, de la base de UN COMTRADE, que mezcla socios comerciales de tamaños y niveles de desarrollo muy dispares, con outliers naturales que favorecen un método basado en densidad como DBSCAN). Esta relativa homogeneidad estructural de los países OCDE genera nubes de datos razonablemente compactas y sin una gran cantidad de valores atípicos extremos una vez depurados, lo cual favorece precisamente el supuesto central de K-Means: que los clústeres tienden a ser de forma esférica/convexa alrededor de un centroide (la media). Además, K-Means es computacionalmente eficiente y escala bien, lo cual es conveniente cuando se trabaja con los 38 países miembros de la organización y múltiples indicadores simultáneamente.

2. Explicación Matemática

2.1 Origen del algoritmo

El agrupamiento K-Means fue propuesto originalmente por MacQueen (1967) como el algoritmo de aprendizaje automático no supervisado más utilizado para dividir un conjunto de datos en un número predefinido de grupos (k clústeres). Este método clasifica los objetos en varios grupos asegurando que los objetos dentro del mismo grupo sean lo más similares posible, mientras que los objetos de diferentes grupos sean lo más diferentes posible. Cada grupo se caracteriza por su centroide, que es la media de los puntos asignados a ese grupo.

La esencia del clustering K-Means radica en definir los grupos de manera que se minimice la variación total dentro de cada grupo (variación intra-clúster total). La implementación estándar en R (kmeans()) utiliza por defecto el algoritmo de Hartigan-Wong (1979), una variante eficiente del esquema iterativo asignación-actualización, que define la variación total dentro del clúster como la suma de las distancias euclidianas al cuadrado entre los elementos y sus centroides respectivos.

2.2 Pasos del algoritmo (Hartigan-Wong)

  1. Inicialización. Se especifica la cantidad de grupos k a formar (decisión del analista) y se seleccionan aleatoriamente k observaciones del conjunto de datos como centroides iniciales. El centroide de un grupo es un vector de longitud p que contiene las medias de todas las variables para las observaciones de ese grupo, donde p es el número de variables (en este trabajo, p = 5: Manufactura, Desempleo, Gasto_Salud, Exportaciones y Gini).

  2. Asignación de clústeres (expectation). Cada observación se asigna a su centroide más cercano según la distancia euclidiana. Por esta razón es indispensable estandarizar previamente las variables con scale().

  3. Actualización de centroides (maximization). Para cada uno de los k grupos se recalcula el centroide como el promedio de todos los países asignados a ese grupo.

  4. Regla de Hartigan. En cada iteración se evalúa si mover una observación individual de su clúster actual a otro reduce la suma total de cuadrados intra-clúster; si es así, se realiza el movimiento y se actualizan los centroides afectados.

  5. Convergencia. Los pasos de asignación y actualización se repiten hasta que las asignaciones dejan de cambiar entre iteraciones, o hasta alcanzar el número máximo de iteraciones (por defecto, R usa 10).

2.3 Formalización matemática

El objetivo de K-Means es encontrar una partición de las n observaciones (países) en k grupos \(C_1, C_2, \dots, C_k\) que minimice la Suma de Cuadrados Intra-Clúster (Within-Cluster Sum of Squares, WCSS):

\[ \text{WCSS} = \sum_{j=1}^{k} \sum_{x_i \in C_j} \lVert x_i - \mu_j \rVert^2 \]

Donde \(x_i\) es el vector de variables estandarizadas del país \(i\), y \(\mu_j\) es el centroide del clúster \(j\).

De forma intuitiva para la toma de decisiones económicas: K-Means busca “países representativos promedio” (los centroides) y agrupa al resto de países alrededor de aquel que estadísticamente se les parece más, minimizando qué tan dispersos están los países dentro de cada grupo.

2.4 Descomposición de la varianza que reporta kmeans() en R

Los objetos que devuelve kmeans() en R (withinss, tot.withinss, betweenss, totss) corresponden a la siguiente descomposición formal:

1. Suma de cuadrados dentro del clúster \(k\) (withinss[k]): para cada clúster \(C_k\), con centroide \(\bar{x}_k\) y distancias \(d_i = x_i - \bar{x}_k\) de cada observación a ese centroide:

\[ \text{Withinss}_k = W(C_k) = \sum_{x_i \in C_k} \lVert x_i - \bar{x}_k \rVert^2 = \sum_{x_i \in C_k} d_i^2 \] Withinss\(_k\)(vector de suma de cuadrados dentro del clúster k)

Fuente: Llinás Solano, H. (2024).

2. Suma total de cuadrados intra-clúster (tot.withinss): mide qué tan compacto es el agrupamiento en conjunto — representa la variabilidad total dentro de todos los clústeres. Un valor mayor indica que los puntos dentro de cada clúster están más dispersos (menor calidad del agrupamiento):

\[ \text{Tot.withinss} = \sum_{k=1}^{K} W(C_k) = \sum_{k=1}^{K} \sum_{x_i \in C_k} d_i^2 \]

Tot.withinss (variabilidad total dentro de todos los clúster)

Fuente: Llinás Solano, H. (2024).

3. Suma total de cuadrados de todos los datos (totss): mide la variabilidad total en los datos, como si no se hubiera particionado en ningún grupo (un solo “clúster” con toda la muestra):

\[ \text{Totss} = \sum_{i=1}^{n} \lVert x_i - \bar{x} \rVert^2 \]

4. Suma de cuadrados entre clústeres (betweenss): mide la variación explicada por las diferencias entre los centroides de los clústeres:

\[ \text{Betweenss} = \text{Totss} - \text{Tot.withinss} \]

5. Proporción de varianza explicada (between_SS / total_SS): es exactamente el valor que R reporta al imprimir el objeto kmeans():

\[ \text{Prop.Var} = \frac{\text{Betweenss}}{\text{Totss}} = 1 - \frac{\text{Tot.withinss}}{\text{Totss}} \]

Interpretación: un valor de Prop.Var cercano a 100% indica que los clústeres formados explican bien la variación total en los datos (grupos bien definidos y separados); un valor más bajo sugiere que los clústeres no explican del todo bien la estructura de los datos, o que la variabilidad remanente dentro de cada grupo sigue siendo considerable. Este es precisamente el indicador que se reporta y discute en la sección 5.7 de este trabajo.

Objetivo final del algoritmo:

\[ \min(\text{Tot.withinss}) = \min \sum_{k=1}^{K} W(C_k) \]

3. Ventajas y Desventajas Comparativas

comparativa <- tribble(
  ~Método, ~Grupo, ~Clasificación, ~Ventajas, ~Desventajas,
  "K-Means", "Grupo 4 (OCDE)", "No jerárquico particional",
  "Simple, rápido y escalable; funciona bien con economías de tamaño y estructura similares",
  "Requiere definir k de antemano; sensible a outliers y a la inicialización de centroides; asume clústeres esféricos y de tamaño similar",

  "Ward (jerárquico aglomerativo)", "Grupo 1 (Banco Mundial)", "Jerárquico aglomerativo",
  "No requiere definir k de antemano; produce un dendrograma interpretable; útil para ver gradaciones continuas de desarrollo",
  "Computacionalmente más costoso con muestras grandes; una vez asignada una observación a un grupo, no puede reasignarse",

  "DBSCAN", "Grupo 2 (UN Comtrade)", "No jerárquico basado en densidad",
  "Detecta automáticamente outliers/ruido; no asume forma esférica de los clústeres; no requiere definir k",
  "Sensible a los parámetros Epsilon y MinPts; le cuesta identificar clústeres de densidad muy distinta entre sí",

  "K-Medianas / PAM", "Grupo 3 (FMI)", "No jerárquico particional",
  "Usa medoides reales (observaciones existentes) en vez de promedios; más robusto ante outliers que K-means",
  "Más costoso computacionalmente que K-means; sigue requiriendo definir k de antemano"
)

comparativa %>%
  kable(caption = "Comparación de los cuatro métodos de clustering asignados en clase") %>%
  kable_classic(html_font = "Times New Roman", font_size = 15) %>%
  kable_styling()
Comparación de los cuatro métodos de clustering asignados en clase
Método Grupo Clasificación Ventajas Desventajas
K-Means Grupo 4 (OCDE) No jerárquico particional Simple, rápido y escalable; funciona bien con economías de tamaño y estructura similares Requiere definir k de antemano; sensible a outliers y a la inicialización de centroides; asume clústeres esféricos y de tamaño similar
Ward (jerárquico aglomerativo) Grupo 1 (Banco Mundial) Jerárquico aglomerativo No requiere definir k de antemano; produce un dendrograma interpretable; útil para ver gradaciones continuas de desarrollo Computacionalmente más costoso con muestras grandes; una vez asignada una observación a un grupo, no puede reasignarse
DBSCAN Grupo 2 (UN Comtrade) No jerárquico basado en densidad Detecta automáticamente outliers/ruido; no asume forma esférica de los clústeres; no requiere definir k Sensible a los parámetros Epsilon y MinPts; le cuesta identificar clústeres de densidad muy distinta entre sí
K-Medianas / PAM Grupo 3 (FMI) No jerárquico particional Usa medoides reales (observaciones existentes) en vez de promedios; más robusto ante outliers que K-means Más costoso computacionalmente que K-means; sigue requiriendo definir k de antemano

Fortaleza principal de K-Means frente a los demás: simplicidad, velocidad y facilidad de interpretación económica de los centroides (cada centroide es directamente un “perfil promedio” de país). Su principal debilidad frente a DBSCAN y PAM: al basarse en la media, es sensible a valores atípicos (un país con un indicador extremo puede desplazar el centroide de todo su grupo), y a diferencia de Ward, no ofrece una jerarquía interpretable ni un dendrograma que muestre en qué orden se van fusionando los países.

4. Selección del Número Óptimo de Clústeres

Existen varias métricas generales para decidir el número de agrupaciones o parámetros: el Método del Codo (Elbow Method), el Coeficiente de Silueta, el Dendrograma (solo aplicable a métodos jerárquicos) y el Gráfico de K-Vecinos (usado específicamente para calibrar el parámetro Épsilon en DBSCAN).

Para K-Means, el método que corresponde analíticamente es el Método del Codo: se ejecuta el algoritmo para distintos valores de k (por ejemplo, de 1 a 10), se calcula la WCSS (tot.withinss, formalizada en la sección 2.4) para cada uno, y se grafica WCSS contra k. A medida que k aumenta, la WCSS disminuye monótonamente (más clústeres siempre reducen la dispersión interna), pero el punto en el que la reducción de la WCSS deja de ser marcada y la curva se “aplana” (formando un codo) señala el número de clústeres donde agregar un grupo adicional ya no aporta una mejora sustancial. Este es el método que se aplica en el ejemplo práctico a continuación.

5. Ejemplo Práctico (datos reales de la API de la OCDE)

5.1 Ampliación del grupo de países consultados

En el trabajo anterior se consultó únicamente un grupo reducido de países (Costa Rica, México, Colombia, España, Estados Unidos y Alemania). Para que el K-Means tenga sentido estadístico se necesita más de un puñado de observaciones, por lo que se reutiliza la misma API, pero ampliando la consulta a los países miembros de la OCDE:

paises_ocde <- c(
  "AUS","AUT","BEL","CAN","CHL","COL","CRI","CZE","DNK","EST",
  "FIN","FRA","DEU","GRC","HUN","ISL","IRL","ISR","ITA","JPN",
  "KOR","LVA","LTU","LUX","MEX","NLD","NZL","NOR","POL","PRT",
  "SVK","SVN","ESP","SWE","CHE","TUR","GBR","USA"
)

lista_paises <- paste(paises_ocde, collapse = "+")

5.2 Descarga de indicadores macroeconómicos

Se reutilizan las mismas cinco bases/dimensiones ya usadas en el trabajo anterior (producción manufacturera, desempleo, gasto en salud, comercio internacional y Gini), pero ahora con el vector lista_paises ampliado:

# Producción manufacturera
url_manuf <- paste0(
  "https://sdmx.oecd.org/public/rest/data/OECD.SDD.STES,DSD_KEI@DF_KEI,/",
  lista_paises, ".M.PRVM.IX.C..?startPeriod=2023-01&dimensionAtObservation=AllDimensions"
)
manufactura <- readSDMX(url_manuf) %>% as_tibble()

# Tasa de desempleo
url_desempleo <- paste0(
  "https://sdmx.oecd.org/public/rest/data/OECD.SDD.TPS,DSD_LFS@DF_IALFS_UNE_M,1.0/",
  lista_paises, "..PT_LF_SUB._Z.Y._T.Y_GE15..M?startPeriod=2023-01&dimensionAtObservation=AllDimensions"
)
desempleo <- readSDMX(url_desempleo) %>% as_tibble()

# Gasto en salud (% del PIB) - se descarga todo el dataset y se filtra después
url_salud <- "https://sdmx.oecd.org/public/rest/data/OECD.ELS.HD,DSD_SHA@DF_SHA,1.1/.A.EXP_HEALTH.PT_B1GQ._T.._T.._T...?startPeriod=2021&dimensionAtObservation=AllDimensions"
gasto_salud <- readSDMX(url_salud) %>% as_tibble()

# Comercio internacional de mercancías (exportaciones) - se descarga todo y se filtra
url_comercio <- "https://sdmx.oecd.org/public/rest/data/OECD.SDD.TPS,DSD_IMTS@DF_IMTS,1.0/....M.USD_EXC.Y.?startPeriod=2023-01&dimensionAtObservation=AllDimensions"
comercio <- readSDMX(url_comercio) %>% as_tibble()

# Índice de Gini del ingreso disponible
url_gini <- paste0(
  "https://sdmx.oecd.org/public/rest/data/OECD.WISE.INE,DSD_WISE_IDD@DF_IDD,1.0/",
  lista_paises, ".A.INC_DISP_GINI._Z.0_TO_1._T.METH2012.D_CUR._Z?startPeriod=2021&dimensionAtObservation=AllDimensions"
)
gini <- readSDMX(url_gini) %>% as_tibble()

Nota: al igual que en el trabajo anterior, si alguna de estas URLs devuelve error 404, es porque la OCDE reordenó las dimensiones de ese dataflow; hay que volver a generar la URL desde el botón Developer API del OECD Data Explorer para el dataset correspondiente.

5.3 Construcción de la matriz de datos para clustering (último dato disponible por país)

manuf_latest <- manufactura %>%
  mutate(Anio = as.integer(substr(TIME_PERIOD, 1, 4))) %>%
  group_by(REF_AREA) %>%
  filter(Anio == max(Anio)) %>%
  summarise(Manufactura = mean(obsValue, na.rm = TRUE), .groups = "drop")

desem_latest <- desempleo %>%
  mutate(Anio = as.integer(substr(TIME_PERIOD, 1, 4))) %>%
  group_by(REF_AREA) %>%
  filter(Anio == max(Anio)) %>%
  summarise(Desempleo = mean(obsValue, na.rm = TRUE), .groups = "drop")

salud_latest <- gasto_salud %>%
  filter(REF_AREA %in% paises_ocde) %>%
  group_by(REF_AREA) %>%
  filter(TIME_PERIOD == max(TIME_PERIOD)) %>%
  summarise(Gasto_Salud = mean(obsValue, na.rm = TRUE), .groups = "drop")

comercio_latest <- comercio %>%
  filter(REF_AREA %in% paises_ocde) %>%
  mutate(Anio = as.integer(substr(TIME_PERIOD, 1, 4))) %>%
  group_by(REF_AREA) %>%
  filter(Anio == max(Anio)) %>%
  summarise(Exportaciones = sum(obsValue, na.rm = TRUE), .groups = "drop")

gini_latest <- gini %>%
  filter(REF_AREA %in% paises_ocde) %>%
  group_by(REF_AREA) %>%
  filter(TIME_PERIOD == max(TIME_PERIOD)) %>%
  summarise(Gini = mean(obsValue, na.rm = TRUE), .groups = "drop")

# Unión de las 5 variables por país
data_cluster <- manuf_latest %>%
  full_join(desem_latest, by = "REF_AREA") %>%
  full_join(salud_latest, by = "REF_AREA") %>%
  full_join(comercio_latest, by = "REF_AREA") %>%
  full_join(gini_latest, by = "REF_AREA") %>%
  filter(REF_AREA %in% paises_ocde)

data_cluster %>%
  kable(caption = "Matriz de variables macroeconómicas por país (último dato disponible)") %>%
  kable_classic(html_font = "Times New Roman", font_size = 13) %>%
  add_footnote(label = "Elaboración propia con datos de la API de la OCDE", notation = "symbol") %>%
  kable_styling()
Matriz de variables macroeconómicas por país (último dato disponible)
REF_AREA Manufactura Desempleo Gasto_Salud Exportaciones Gini
AUT 119.10037 5.871429 11.926 279.299922 0.2882049
BEL 127.10602 6.228571 11.194 713.695316 0.2540810
CAN 96.95769 6.587500 11.484 714.398121 0.3060000
CHE 142.53828 NA 11.611 480.790551 0.3146433
CHL 100.99903 9.012396 10.300 167.134902 0.4481475
COL 122.77184 8.403465 8.519 65.427599 NA
CRI 160.17708 6.786876 6.776 28.483198 0.4576662
CZE 121.00744 3.157143 8.941 357.844867 0.2424243
DEU 91.60000 3.928571 12.735 2199.831240 0.3070000
DNK 172.73812 6.914286 9.576 245.065407 0.2764000
ESP 108.20583 10.200000 8.855 550.088704 0.3124321
EST 121.40565 6.528571 8.058 27.053169 0.3111607
FIN 118.82069 10.457143 10.590 111.706145 0.2794500
FRA 101.80240 8.257143 11.375 857.638576 0.2990000
GBR 111.44439 4.916667 11.363 578.134420 0.3670304
GRC 139.21473 8.842857 8.234 75.462138 0.3195594
HUN 115.15695 4.500000 6.524 220.786767 0.2823346
IRL 176.78710 5.014286 6.079 280.290262 0.2806353
ISR 179.45575 2.895981 7.023 83.185399 0.3415500
ITA 99.81030 5.585714 8.420 910.759874 0.3247440
JPN 93.59913 2.557143 10.095 1059.377918 0.3380000
KOR 125.94245 2.800000 8.448 1405.570700 0.3230000
LTU 170.00558 6.628571 7.639 54.520762 0.3548257
LUX 84.50881 7.042857 5.906 23.062535 0.2962761
LVA 132.54786 6.900000 8.526 29.116396 0.3399463
MEX 110.74751 2.710088 6.171 947.834375 0.4001119
NLD 119.86738 3.957143 10.191 1265.751425 0.3110000
NOR 100.01656 4.571429 9.336 288.974682 0.2590000
POL 160.38024 3.228571 8.894 527.753462 0.2568486
PRT 94.27112 5.771429 10.392 109.112977 0.3198702
SVK 88.63062 5.742857 8.089 154.734241 0.2133524
SVN 137.86633 3.928571 9.675 112.542928 0.2471946
SWE 129.36062 8.657143 10.911 265.973765 0.2890000
TUR 151.22054 8.085714 4.703 329.811271 0.4271480
USA 97.25478 4.250000 17.598 2890.516000 0.3944025
AUS NA 4.394310 11.148 441.595298 NA
ISL NA 5.628571 9.187 9.449087 NA
NZL NA NA 9.786 67.816187 0.3260000
* Elaboración propia con datos de la API de la OCDE

5.4 Tratamiento de datos faltantes (NA)

Siguiendo la recomendación técnica de la guía de la tarea, los valores faltantes se sustituyen por la media de la columna (imputación simple permitida para efectos de esta exposición):

data_cluster_imputado <- data_cluster %>%
  mutate(across(
    .cols = c(Manufactura, Desempleo, Gasto_Salud, Exportaciones, Gini),
    .fns = ~ ifelse(is.na(.x), mean(.x, na.rm = TRUE), .x)
  ))

# Verificación: ya no deberían quedar NA
data_cluster_imputado %>%
  summarise(across(c(Manufactura, Desempleo, Gasto_Salud, Exportaciones, Gini), ~ sum(is.na(.x))))
## # A tibble: 1 × 5
##   Manufactura Desempleo Gasto_Salud Exportaciones  Gini
##         <int>     <int>       <int>         <int> <int>
## 1           0         0           0             0     0

5.5 Estandarización de variables

Como las variables están en escalas muy distintas (porcentajes, índices, millones de USD), es obligatorio aplicar scale() antes de calcular distancias:

matriz_paises <- data_cluster_imputado %>%
  column_to_rownames(var = "REF_AREA")

matriz_tipificada <- scale(matriz_paises)

5.6 Selección de k: Método del Codo

Se utiliza fviz_nbclust() del paquete factoextra, que automatiza el cálculo de la WCSS (tot.withinss, sección 2.4) para distintos valores de k y grafica directamente el método del codo:

set.seed(123)

fviz_nbclust(matriz_tipificada, kmeans, method = "wss", k.max = 10) +
  labs(title = "Método del Codo para K-Means",
       subtitle = "Países OCDE - Manufactura, Desempleo, Gasto en Salud, Exportaciones, Gini",
       x = "Número de clústeres (k)", y = "WCSS (Suma de cuadrados intra-clúster)") +
  theme_minimal()

El método del codo muestra una reducción importante de la WCSS al aumentar el número de clústeres, especialmente entre \(k=1\) y \(k=3\). A partir de este punto, las mejoras son más moderadas, por lo que el codo se encuentra aproximadamente entre \(k=3\) y \(k=4\). Como criterio adicional, el coeficiente de silueta alcanza su valor máximo en \(k=3\), con un valor aproximado de 0.27. En conjunto, ambos criterios respaldan la selección de 3 clústeres para el análisis.

fviz_nbclust(matriz_tipificada, kmeans, method = "silhouette", k.max = 10) +
  labs(title = "Coeficiente de Silueta promedio por número de clústeres (k)") +
  theme_minimal()

El gráfico muestra el coeficiente de silueta promedio para diferentes valores de \(k\). Se observa que el valor aumenta desde \(k=2\) y alcanza su máximo en \(k=3\), con un valor aproximado de 0.27. A partir de \(k=4\), el coeficiente disminuye y, aunque presenta algunas mejoras posteriores, no alcanza nuevamente el valor obtenido con tres clústeres. Por lo tanto, según el criterio de silueta, \(k=3\) proporciona la mayor separación y cohesión relativa entre los grupos identificados.

5.7 Aplicación de K-Means con el k seleccionado

k_optimo <- 3  # AJUSTAR según el codo observado en el gráfico anterior

set.seed(123)
resultado_kmeans <- kmeans(matriz_tipificada, centers = k_optimo, nstart = 25)

resultado_kmeans
## K-means clustering with 3 clusters of sizes 25, 7, 6
## 
## Cluster means:
##   Manufactura   Desempleo Gasto_Salud Exportaciones       Gini
## 1  -0.3161314  0.31166875   0.1015864    -0.3141601 -0.2535385
## 2   1.6943554 -0.09915621  -0.9099886    -0.4491268  0.4582652
## 3  -0.6595338 -1.18293755   0.6383766     1.8329817  0.5217677
## 
## Clustering vector:
## AUT BEL CAN CHE CHL COL CRI CZE DEU DNK ESP EST FIN FRA GBR GRC HUN IRL ISR ITA 
##   1   1   1   1   1   1   2   1   3   2   1   1   1   1   1   1   1   2   2   1 
## JPN KOR LTU LUX LVA MEX NLD NOR POL PRT SVK SVN SWE TUR USA AUS ISL NZL 
##   3   3   2   1   1   3   3   1   2   1   1   1   1   2   3   1   1   1 
## 
## Within cluster sum of squares by cluster:
## [1] 62.63291 22.12474 26.89619
##  (between_SS / total_SS =  39.6 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
data_cluster_imputado %>%
  mutate(Cluster = resultado_kmeans$cluster) %>%
  arrange(Cluster, REF_AREA) %>%
  kable(caption = paste("Asignación de países OCDE a", k_optimo, "clústeres mediante K-Means")) %>%
  kable_classic(html_font = "Times New Roman", font_size = 13) %>%
  add_footnote(label = "Elaboración propia con datos de la API de la OCDE", notation = "symbol") %>%
  kable_styling()
Asignación de países OCDE a 3 clústeres mediante K-Means
REF_AREA Manufactura Desempleo Gasto_Salud Exportaciones Gini Cluster
AUS 123.52340 4.394310 11.148 441.595298 0.3173840 1
AUT 119.10037 5.871429 11.926 279.299922 0.2882049 1
BEL 127.10602 6.228571 11.194 713.695316 0.2540810 1
CAN 96.95769 6.587500 11.484 714.398121 0.3060000 1
CHE 142.53828 5.859528 11.611 480.790551 0.3146433 1
CHL 100.99903 9.012396 10.300 167.134902 0.4481475 1
COL 122.77184 8.403465 8.519 65.427599 0.3173840 1
CZE 121.00744 3.157143 8.941 357.844867 0.2424243 1
ESP 108.20583 10.200000 8.855 550.088704 0.3124321 1
EST 121.40565 6.528571 8.058 27.053169 0.3111607 1
FIN 118.82069 10.457143 10.590 111.706145 0.2794500 1
FRA 101.80240 8.257143 11.375 857.638576 0.2990000 1
GBR 111.44439 4.916667 11.363 578.134420 0.3670304 1
GRC 139.21473 8.842857 8.234 75.462138 0.3195594 1
HUN 115.15695 4.500000 6.524 220.786767 0.2823346 1
ISL 123.52340 5.628571 9.187 9.449087 0.3173840 1
ITA 99.81030 5.585714 8.420 910.759874 0.3247440 1
LUX 84.50881 7.042857 5.906 23.062535 0.2962761 1
LVA 132.54786 6.900000 8.526 29.116396 0.3399463 1
NOR 100.01656 4.571429 9.336 288.974682 0.2590000 1
NZL 123.52340 5.859528 9.786 67.816187 0.3260000 1
PRT 94.27112 5.771429 10.392 109.112977 0.3198702 1
SVK 88.63062 5.742857 8.089 154.734241 0.2133524 1
SVN 137.86633 3.928571 9.675 112.542928 0.2471946 1
SWE 129.36062 8.657143 10.911 265.973765 0.2890000 1
CRI 160.17708 6.786876 6.776 28.483198 0.4576662 2
DNK 172.73812 6.914286 9.576 245.065407 0.2764000 2
IRL 176.78710 5.014286 6.079 280.290262 0.2806353 2
ISR 179.45575 2.895981 7.023 83.185399 0.3415500 2
LTU 170.00558 6.628571 7.639 54.520762 0.3548257 2
POL 160.38024 3.228571 8.894 527.753462 0.2568486 2
TUR 151.22054 8.085714 4.703 329.811271 0.4271480 2
DEU 91.60000 3.928571 12.735 2199.831240 0.3070000 3
JPN 93.59913 2.557143 10.095 1059.377918 0.3380000 3
KOR 125.94245 2.800000 8.448 1405.570700 0.3230000 3
MEX 110.74751 2.710088 6.171 947.834375 0.4001119 3
NLD 119.86738 3.957143 10.191 1265.751425 0.3110000 3
USA 97.25478 4.250000 17.598 2890.516000 0.3944025 3
* Elaboración propia con datos de la API de la OCDE

El objeto resultado_kmeans reporta, entre otros elementos, withinss (la \(\text{Withinss}_k\) de cada uno de los 3 clústeres, sección 2.4), tot.withinss (la WCSS total que el algoritmo minimizó) y la razón between_SS / total_SS, que corresponde exactamente a la \(\text{Prop.Var}\) formalizada en la sección 2.4. Este último valor se retoma e interpreta en la sección 5.9.

5.8 Representación gráfica

5.8.1 Elipsoides de concentración sobre componentes principales

Dado que se tienen 5 variables (5 dimensiones), para poder visualizar los clústeres en un plano fviz_cluster() proyecta automáticamente las observaciones sobre sus dos primeros componentes principales (PCA) y dibuja los elipsoides de concentración por clúster, junto con un star.plot que traza una línea de cada país hacia el centroide de su grupo (útil para identificar visualmente qué tan lejos queda cada país de su propio centroide):

fviz_cluster(
  resultado_kmeans,
  data = matriz_tipificada,
  palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07", "#8E44AD"),
  ellipse.type = "norm",
  star.plot = TRUE,
  repel = TRUE,
  labelsize = 8,
  main = paste("K-Means: Clústeres de países OCDE (k =", k_optimo, ")"),
  ggtheme = theme_minimal()
)

5.8.2 Diagrama de distancias al centroide (Withinss_k)

Como complemento visual del concepto matemático formalizado en la sección 2.4 (cada país es un punto que aporta una distancia \(d_i\) a su centroide \(\bar{x}_k\), y la suma de esas distancias al cuadrado es \(\text{Withinss}_k\)), se construye el mismo esquema pero con los datos reales de los países OCDE, usando la proyección sobre los dos primeros componentes principales ya calculada:

scores_pca <- as.data.frame(prcomp(matriz_tipificada)$x[, 1:2])
scores_pca$Cluster <- factor(resultado_kmeans$cluster)
scores_pca$Pais <- rownames(matriz_tipificada)

centroides_pca <- scores_pca %>%
  group_by(Cluster) %>%
  summarise(PC1 = mean(PC1), PC2 = mean(PC2), .groups = "drop")

scores_pca_lineas <- scores_pca %>%
  left_join(centroides_pca, by = "Cluster", suffix = c("", "_centroide"))

ggplot() +
  geom_segment(data = scores_pca_lineas,
               aes(x = PC1_centroide, y = PC2_centroide, xend = PC1, yend = PC2, color = Cluster),
               alpha = 0.4) +
  geom_point(data = scores_pca_lineas, aes(x = PC1, y = PC2, color = Cluster), size = 2) +
  geom_point(data = centroides_pca, aes(x = PC1, y = PC2), color = "black", size = 5, shape = 17) +
  geom_text(data = centroides_pca, aes(x = PC1, y = PC2, label = paste0("x\u0304", Cluster)),
            vjust = -1, fontface = "bold") +
  labs(title = "Withinss_k: distancias de cada país a su centroide",
       subtitle = "Triángulos negros = centroides (x̄_k) de cada clúster",
       x = "Componente Principal 1", y = "Componente Principal 2") +
  theme_minimal()

Cada línea de este gráfico representa una distancia \(d_i\) (país → centroide de su clúster); la suma de esas distancias al cuadrado dentro de cada color es, por definición, \(\text{Withinss}_k\), y la suma de las tres \(\text{Withinss}_k\) es el tot.withinss que reporta el objeto resultado_kmeans en la sección 5.7.

5.9 Interpretación económica

data_cluster_imputado %>%
  mutate(Cluster = resultado_kmeans$cluster) %>%
  group_by(Cluster) %>%
  summarise(across(c(Manufactura, Desempleo, Gasto_Salud, Exportaciones, Gini), ~ round(mean(.x), 2)), .groups = "drop") %>%
  kable(caption = "Perfil promedio (centroide) de cada clúster") %>%
  kable_classic(html_font = "Times New Roman", font_size = 13) %>%
  add_footnote(label = "Elaboración propia con datos de la API de la OCDE", notation = "symbol") %>%
  kable_styling()
Perfil promedio (centroide) de cada clúster
Cluster Manufactura Desempleo Gasto_Salud Exportaciones Gini
1 115.36 6.52 9.61 304.50 0.30
2 167.25 5.65 7.24 221.30 0.34
3 106.50 3.37 10.87 1628.15 0.35
* Elaboración propia con datos de la API de la OCDE

La tabla anterior muestra que los países se agrupan en tres perfiles económicos diferenciados:

  • Clúster 1 presenta el menor desempleo (3.37%), el mayor gasto en salud como % del PIB (10.87%) y, sobre todo, exportaciones muchísimo más altas que los otros dos grupos (1,628.15, varias veces el valor de los Clústeres 2 y 3). También registra el Gini más alto (0.35) de los tres. Es el perfil de economías pequeñas y muy abiertas comercialmente, con bajo desempleo y fuerte gasto social, pero también con mayor desigualdad de ingreso relativa.

  • Clúster 2 destaca por tener el mayor nivel de producción manufacturera de los tres grupos (166.58), aunque con un desempleo intermedio (5.65%, mayor que el del Clúster 1 pero menor que el del Clúster 3) y el menor gasto en salud (7.24%) y las menores exportaciones (209.95) del grupo.

  • Clúster 3 es el que presenta el mayor desempleo del grupo OCDE (6.51%), un gasto en salud relativamente alto (9.61%, segundo lugar), y el Gini más bajo (0.30), es decir, la menor desigualdad relativa de ingreso.

En conjunto, los resultados muestran diferencias importantes entre los grupos en cuanto a estructura productiva, empleo, gasto social, comercio exterior y desigualdad. Cabe señalar que esta partición explica cerca del 39.5% de la variabilidad total entre países (between_SS / total_SS, es decir, la \(\text{Prop.Var}\) formalizada en la sección 2.4). Al no ser un porcentaje cercano al 100%, esto indica que, aun dentro de un bloque relativamente homogéneo como la OCDE, persiste heterogeneidad considerable dentro de cada clúster — especialmente en el Clúster 1, compuesto por solo 6 países con una dispersión interna alta, principalmente explicada por la variable Exportaciones.

6. Preguntas

Instrucciones: responda las siguientes preguntas después de la exposición del Grupo 4.

  1. (Teórica) ¿Por qué se dice que K-Means es un método de clustering no jerárquico y particional? Mencione al menos una diferencia clave frente al método de Ward.
  2. (Teórica) ¿Qué mide exactamente la WCSS (tot.withinss) y por qué el algoritmo K-Means busca minimizarla? ¿Qué representa, en cambio, el betweenss?
  3. (Teórica) ¿Por qué es obligatorio aplicar scale() a las variables antes de correr kmeans() en R?
  4. (Práctica) En el gráfico del Método del Codo presentado, ¿en qué valor de k identifica usted el “codo”, y qué significa económicamente elegir ese número de clústeres en lugar de uno mayor?
  5. (Práctica) El objeto resultado_kmeans reportó between_SS / total_SS = 39.5%. ¿Qué significa este porcentaje y por qué no ser cercano al 100% no invalida necesariamente el análisis?

Conclusiones

K-Means resultó un método adecuado para explorar agrupaciones de países OCDE a partir de indicadores de producción, empleo, gasto social, comercio exterior y desigualdad, precisamente porque este bloque de países comparte un nivel de desarrollo relativamente homogéneo, lo que favorece clústeres compactos y bien definidos alrededor de un centroide. A diferencia del método de Ward (jerárquico), K-Means permitió reasignar iterativamente a cada país hasta minimizar la dispersión interna de cada grupo (tot.withinss), siguiendo el algoritmo de Hartigan-Wong implementado por defecto en R, y el Método del Codo brindó un criterio objetivo y replicable para decidir cuántos grupos formar. Como toda técnica basada en la media, su principal limitación es la sensibilidad a valores atípicos, por lo que siempre es recomendable revisar visualmente (como se hizo con los elipsoides de concentración y el diagrama de distancias al centroide) si algún país se comporta como un outlier relativo dentro de su propio clúster.

Bibliografía

IBM. (2024). What is k-means clustering? https://www.ibm.com/think/topics/k-means-clustering

MacQueen, J. (1967). Some methods for classification and analysis of multivariate observations. Proceedings of the Fifth Berkeley Symposium on Mathematical Statistics and Probability.

Hartigan, J. A., & Wong, M. A. (1979). Algorithm AS 136: A K-Means Clustering Algorithm. Journal of the Royal Statistical Society. Series C (Applied Statistics), 28(1), 100–108.

OCDE. (n.d.). OECD Data Explorer. https://data-explorer.oecd.org/

OCDE. (2024). OECD Data API documentation. https://www.oecd.org/en/data/insights/data-explainers/2024/09/api.html

Summary, R. (2024). rsdmx: R package for SDMX data. https://cran.r-project.org/web/packages/rsdmx/rsdmx.pdf

Kassambara, A. (2017). Practical Guide to Cluster Analysis in R: Unsupervised Machine Learning (Multivariate Analysis) (1st ed.). STHDA.

Llinás Solano, H. (2024, 4 de junio). Análisis de Conglomerados: Algoritmo k-means. Departamento de Matemáticas y Estadística, Universidad del Norte, Barranquilla, Colombia. https://rpubs.com/hllinas/Bio_Sketch