Universidad de El Salvador

Facultad de Ciencias Económicas

Métodos para el análisis Económico

Lic. Jefrry Isaac Bonilla Melara

Métodos de Conglomerados / Clustering

Integrantes:

  • Laura Lissete Flores Ayala
  • Miguel Ángel López Ayala
  • Stephany lourdes García Cárcamo
  • Victor Daniel Genovez Recinos
  • Katherine Gabriela Miranda Mancia
  • Christopher Eduardo Portillo Portillo

1 Clasificación Lógica Económica

Método K-Medianas (PAM): técnica de agrupamiento no supervisado utilizada para formar grupos de observaciones con características relativamente similares.

1.1 ¿En qué consiste el método K-Medianas (PAM)?

El método K-Medianas, denominado técnicamente K-Medoids mediante el algoritmo PAM (Partitioning Around Medoids), es una técnica de aprendizaje no supervisado que pertenece al campo del Clustering o agrupamiento de datos.

Su objetivo consiste en dividir un conjunto de observaciones en un número determinado de grupos, llamados clusters, de manera que las observaciones que pertenecen a un mismo grupo presenten características relativamente similares.

A diferencia de los métodos supervisados, el algoritmo no necesita una variable dependiente previamente clasificada. En lugar de predecir una categoría conocida, busca descubrir agrupaciones dentro de los datos a partir de las variables y la medida de distancia seleccionadas.

En el contexto económico, el método puede utilizarse para agrupar países, regiones o períodos según sus indicadores económicos. Por ejemplo, se pueden analizar variables relacionadas con:

  • Inflación.
  • Crecimiento económico.
  • Desempleo
  • Deuda pública.
  • PIB per cápita.
  • Balanza de pagos.

La particularidad de PAM es que selecciona observaciones reales del conjunto de datos como representantes de los grupos. Estos representantes se conocen como medoids. El algoritmo busca seleccionar aquellos medoids que permitan reducir la distancia total entre los datos y sus respectivos representantes.

1.2 ¿Qué es un medoid?

Un medoid es una observación real que representa a un grupo. Se selecciona porque, de acuerdo con la medida de distancia utilizada, permite minimizar la disimilitud total de las observaciones que pertenecen a ese conglomerado.

Por ejemplo, si se agrupan países según varios indicadores económicos, el medoid puede ser un país que represente relativamente bien las características del resto de países de su grupo.

Esto no significa que el país seleccionado sea el más desarrollado, el más importante o el que tenga mejores resultados económicos. Su función es representar al grupo dentro del criterio matemático del algoritmo.

1.3 ¿A qué categoría de Clustering pertenece?

El método K-Medianas (PAM) pertenece a la categoría de:

NO JERÁRQUICO — Clustering particional

PAM divide las observaciones en K grupos, según sus distancias respecto a los medoids seleccionados.

1.4 ¿Por qué es no jerárquico?

El clustering no jerárquico particional busca formar grupos directamente, sin construir una secuencia de fusiones o divisiones representada mediante un dendrograma.

En el caso de PAM:

  1. Se establece el número de grupos K.
  2. Se seleccionan medoids iniciales.
  3. Las observaciones se asignan a los medoids más cercanos.
  4. Se evalúan posibles sustituciones de medoids.
  5. Se busca mejorar el costo total de la agrupación.

El método no produce como resultado principal un árbol jerárquico. Su resultado es una partición de las observaciones en grupos.

1.5 Diferencia con otras categorías

Categoría Método Característica
Jerárquico Ward Construye agrupaciones sucesivas y permite visualizar un dendrograma.
No jerárquico, particional PAM Forma K grupos utilizando medoids reales.
No jerárquico, basado en densidad DBSCAN Forma grupos según la densidad y puede identificar ruido.
No jerárquico, particional K-Means Forma K grupos alrededor de centroides calculados mediante promedios.

Aclaración: Ward es un método jerárquico aglomerativo, mientras que PAM y K-Means son métodos particionales. DBSCAN pertenece a una lógica de agrupamiento basada en densidad.

1.6 Justificación económica: ¿por qué PAM puede ser idóneo para el FMI?

El Fondo Monetario Internacional proporciona información económica y financiera de diferentes países y períodos. Estos datos pueden presentar diferencias importantes entre economías, variaciones temporales y valores extremos relacionados con acontecimientos económicos.

PAM puede ser una alternativa adecuada cuando se busca clasificar observaciones según sus similitudes y utilizar representantes reales, especialmente si determinados valores atípicos podrían afectar a los centros calculados mediante promedios.

Sin embargo, es importante formular la justificación de manera rigurosa: PAM no es automáticamente el método más adecuado para todos los datos del FMI. Su idoneidad depende de las variables, la escala de los datos, la medida de distancia, la unidad de análisis y el objetivo de la investigación.

1.6.1 Heterogeneidad de los datos económicos

Las economías presentan diferencias en sus estructuras productivas, niveles de ingreso, inflación, crecimiento, endeudamiento y relaciones con el sector externo.

Si se utilizan indicadores del FMI, es posible que los datos presenten grupos de países con características diferentes. El agrupamiento puede ayudar a organizar esas observaciones y estudiar sus similitudes.

Por ejemplo, se podría analizar un conjunto de países mediante:

  • Crecimiento del PIB.
  • Inflación.
  • El desempleo

El algoritmo buscaría agrupar las observaciones según las diferencias de los indicadores seleccionados.

Interpretación económica: los grupos obtenidos pueden servir para realizar comparaciones descriptivas entre economías, pero no demuestran por sí mismos que los países compartan las mismas causas económicas ni que deban recibir una política idéntica.

1.6.2 Resistencia relativa a valores atípicos

Una característica importante de PAM es el uso de medoids reales en lugar de centroides calculados a través de promedios.

En K-Means, un valor extremo puede desplazar el centroide porque el promedio es sensible a los valores atípicos. PAM puede presentar una resistencia relativa mayor frente a determinados valores extremos, dependiendo de la medida de distancia y de la estructura de los datos.

Por ejemplo, si se estudia la inflación de varios países y uno de ellos registra un valor extraordinariamente alto, ese dato puede afectar a los resultados del agrupamiento.

PAM puede ser útil en este escenario, pero no elimina los valores atípicos ni garantiza que estos no influyan en las distancias o en la asignación de grupos.

1.6.3 Aplicación a datos relacionados con la volatilidad

La volatilidad se refiere a las fluctuaciones de una variable a través del tiempo. Para analizarla, primero se deben construir indicadores que representen esas variaciones, como:

  • Desviación estándar del crecimiento económico.
  • Variación de la inflación.
  • Fluctuación del tipo de cambio.

PAM puede agrupar países según sus características de volatilidad, siempre que los indicadores estén correctamente construidos.

Por ejemplo, se podría calcular para cada país:

  • Promedio del crecimiento del PIB durante un período.
  • Desviación estándar del crecimiento.
  • Promedio de inflación.
  • Desviación estándar de inflación.

Después, esas variables podrían utilizarse para clasificar los países en grupos.

Precisión metodológica: PAM no es un modelo especializado de volatilidad ni un método específico de series temporales. El algoritmo agrupa las observaciones según las variables que se le proporcionen.

1.6.4 Representantes económicos reales

Otra razón para utilizar PAM es que los medoids son observaciones reales.

Si se agrupan países, cada medoid puede corresponder a un país existente dentro del dataset. Esto puede facilitar la descripción del grupo, porque se puede identificar una observación concreta que tenga características relativamente representativas.

No obstante, un medoid no debe interpretarse automáticamente como un país promedio ni como un modelo de política económica para los demás países.

2 Explicación Matemática

2.1 Datos

Se tienen 10 puntos con coordenadas (x, y):

datos <- data.frame(
  id = paste0("X", 1:10),
  x  = c(2, 3, 3, 4, 6, 6, 7, 7, 8, 7),
  y  = c(6, 4, 8, 7, 2, 4, 3, 4, 5, 6)
)
kable(datos, caption = "Tabla de datos originales")
Tabla de datos originales
id x y
X1 2 6
X2 3 4
X3 3 8
X4 4 7
X5 6 2
X6 6 4
X7 7 3
X8 7 4
X9 8 5
X10 7 6

2.2 Paso 1: Seleccionar los medoides iniciales

Explicación: se eligen al azar (o de forma manual) dos puntos del propio conjunto de datos para que actúen como los primeros representantes (medoides) de los clústeres C1 y C2. En este ejemplo se toma C1 = (3,4) (el punto X2) y C2 = (7,4) (el punto X8).

medoide_C1 <- c(3, 4)
medoide_C2 <- c(7, 4)

medoide_C1
## [1] 3 4
medoide_C2
## [1] 7 4

2.3 Paso 2: Calcular distancias (Manhattan) y asignar clústeres

Explicación: se calcula la distancia de cada punto a cada medoide usando la Distancia Manhattan:

\[Mdist = |x_1-x_2| + |y_1-y_2|\]

Cada punto se asigna al clúster cuyo medoide le quede más cerca (menor distancia).

2.3.1 Cálculo manual (ejemplo con los primeros puntos)

Con \(C1=(3,4)\):

  • \(Mdist[(2,6),(3,4)] = |2-3|+|6-4| = 1+2 = 3\)
  • \(Mdist[(3,4),(3,4)] = |3-3|+|4-4| = 0+0 = 0\)
  • \(Mdist[(3,8),(3,4)] = |3-3|+|8-4| = 0+4 = 4\)
  • \(Mdist[(4,7),(3,4)] = |4-3|+|7-4| = 1+3 = 4\)

Con \(C2=(7,4)\):

  • \(Mdist[(2,6),(7,4)] = |2-7|+|6-4| = 5+2 = 7\)
  • \(Mdist[(3,4),(7,4)] = |3-7|+|4-4| = 4+0 = 4\)
  • \(Mdist[(3,8),(7,4)] = |3-7|+|8-4| = 4+4 = 8\)
  • \(Mdist[(4,7),(7,4)] = |4-7|+|7-4| = 3+3 = 6\)

El mismo procedimiento se repite para los puntos X5 a X10, obteniendo la columna C1 = (3,0,4,4,5,3,5,4,6,6) y la columna C2 = (7,4,8,6,3,1,1,0,2,2). Cada punto se asigna al clúster con la distancia más pequeña (por ejemplo, X1 → C1 porque 3 < 7).

2.3.2 Cálculo en R

dist_manhattan <- function(p1, p2) sum(abs(p1 - p2))

datos$C1 <- apply(datos[, c("x","y")], 1, function(p) dist_manhattan(p, medoide_C1))
datos$C2 <- apply(datos[, c("x","y")], 1, function(p) dist_manhattan(p, medoide_C2))

datos$Cluster <- ifelse(datos$C1 <= datos$C2, "C1", "C2")

kable(datos, caption = "Distancias a cada medoide y clúster asignado")
Distancias a cada medoide y clúster asignado
id x y C1 C2 Cluster
X1 2 6 3 7 C1
X2 3 4 0 4 C1
X3 3 8 4 8 C1
X4 4 7 4 6 C1
X5 6 2 5 3 C2
X6 6 4 3 1 C2
X7 7 3 5 1 C2
X8 7 4 4 0 C2
X9 8 5 6 2 C2
X10 7 6 6 2 C2

2.4 Paso 3: Calcular el costo total

Explicación: el “costo” de una configuración de medoides es la suma de las distancias de cada punto a su medoide asignado (la más pequeña de las dos). Este valor sirve como referencia para comparar configuraciones de medoides.

2.4.1 Cálculo manual

Se toma, para cada punto, la distancia mínima entre C1 y C2 (la del clúster al que fue asignado) y se suman todas:

\[Cost(C,X) = \sum_i |c_i - x_i|\]

\[\text{Total Cost} = 3+0+4+4+3+1+1+0+2+2 = 20\]

(Donde cada término corresponde, en orden, a X1, X2, X3, …, X10 tomando el mínimo entre su distancia a C1 y a C2.)

2.4.2 Cálculo en R

costo_total_inicial <- sum(pmin(datos$C1, datos$C2))
costo_total_inicial
## [1] 20

2.5 Paso 4: Elegir un punto no-medoide y recalcular

Explicación: se selecciona aleatoriamente un punto que no sea medoide (en el ejemplo, O = (7,3), el punto X7) y se intercambia (swap) con uno de los medoides actuales (C2). Se recalculan las distancias con este nuevo medoide candidato.

2.5.1 Cálculo manual (ejemplo)

Con \(O=(7,3)\):

  • \(Mdist[(2,6),(7,3)] = |2-7|+|6-3| = 5+3 = 8\)
  • \(Mdist[(3,4),(7,3)] = |3-7|+|4-3| = 4+1 = 5\)
  • \(Mdist[(3,8),(7,3)] = |3-7|+|8-3| = 4+5 = 9\)
  • \(Mdist[(4,7),(7,3)] = |4-7|+|7-3| = 3+4 = 7\)
  • \(Mdist[(7,3),(7,3)] = |7-7|+|3-3| = 0\)

Repitiendo el cálculo para todos los puntos se obtiene la columna O = (8,5,9,7,2,2,0,1,3,3). El punto X1 sigue más cerca de C1 (3 < 8), por lo que se mantiene en C1; lo mismo ocurre con X2, X3 y X4. Los puntos X5 a X10 quedan más cerca de O.

2.5.2 Cálculo en R

O <- c(7, 3)

datos$O <- apply(datos[, c("x","y")], 1, function(p) dist_manhattan(p, O))

datos$Cluster_nuevo <- ifelse(datos$C1 <= datos$O, "C1", "O")

kable(datos[, c("id","x","y","C1","O","Cluster_nuevo")],
      caption = "Distancias con el nuevo medoide candidato O")
Distancias con el nuevo medoide candidato O
id x y C1 O Cluster_nuevo
X1 2 6 3 8 C1
X2 3 4 0 5 C1
X3 3 8 4 9 C1
X4 4 7 4 7 C1
X5 6 2 5 2 O
X6 6 4 3 2 O
X7 7 3 5 0 O
X8 7 4 4 1 O
X9 8 5 6 3 O
X10 7 6 6 3 O

2.6 Paso 5: Calcular el nuevo costo total (con el candidato)

Explicación: de la misma manera que en el Paso 3, se suman las distancias mínimas de cada punto, pero ahora usando el medoide candidato O en lugar de C2.

2.6.1 Cálculo manual

Tomando el mínimo entre la columna C1 y la columna O para cada punto y sumando:

\[\text{Current Total Cost} = 3+0+4+4+2+2+0+1+3+3 = 22\]

2.6.2 Cálculo en R

costo_total_actual <- sum(pmin(datos$C1, datos$O))
costo_total_actual
## [1] 22

2.7 Paso 6: Calcular el costo de intercambio (S) y decidir

Explicación: se compara el costo actual (con el candidato) contra el costo previo (con los medoides originales), mediante:

\[S = \text{Costo actual} - \text{Costo anterior}\]

  • Si S < 0: el intercambio mejora la solución → se acepta el nuevo medoide.
  • Si S > 0: el intercambio empeora la solución → se descarta y se mantienen los medoides originales.

2.7.1 Cálculo manual

\[S = 22 - 20 = 2 > 0\]

Como \(S>0\), intercambiar C2 por O no conviene: el costo total aumentaría. Por lo tanto, se descarta el cambio y los medoides originales C1=(3,4) y C2=(7,4) se conservan como definitivos.

2.7.2 Cálculo en R

S <- costo_total_actual - costo_total_inicial
S
## [1] 2
if (S > 0) {
  decision <- "El intercambio NO es una buena idea. Se mantienen los medoides originales."
} else {
  decision <- "El intercambio SÍ mejora la solución. Se actualizan los medoides."
}
decision
## [1] "El intercambio NO es una buena idea. Se mantienen los medoides originales."

2.8 Paso 7: Medoides finales y clústeres resultantes

Explicación: dado que en este ejemplo el intercambio no mejora el costo (S > 0), los medoides finales siguen siendo C1 = (3,4) y C2 = (7,4). A continuación se listan los puntos que pertenecen a cada clúster final.

cluster_C1 <- datos[datos$Cluster == "C1", c("id","x","y")]
cluster_C2 <- datos[datos$Cluster == "C2", c("id","x","y")]

kable(cluster_C1, caption = "Puntos del Clúster C1")
Puntos del Clúster C1
id x y
X1 2 6
X2 3 4
X3 3 8
X4 4 7
kable(cluster_C2, caption = "Puntos del Clúster C2")
Puntos del Clúster C2
id x y
5 X5 6 2
6 X6 6 4
7 X7 7 3
8 X8 7 4
9 X9 8 5
10 X10 7 6

2.9 Paso 8 (verificación): aplicar K-Medoides con la función pam() de R

Explicación: como comprobación, se puede aplicar el algoritmo de forma automática con la función pam() del paquete cluster, usando también distancia Manhattan y k = 2 clústeres, para contrastar el resultado con el cálculo manual.

matriz <- as.matrix(datos[, c("x","y")])
rownames(matriz) <- datos$id

resultado_pam <- pam(matriz, k = 2, metric = "manhattan")

resultado_pam$medoids   # Medoides encontrados por el algoritmo
##    x y
## X4 4 7
## X8 7 4
resultado_pam$clustering # Asignación de cada punto a su clúster
##  X1  X2  X3  X4  X5  X6  X7  X8  X9 X10 
##   1   1   1   1   2   2   2   2   2   2

El proceso de K-Medoides consiste en: (1) elegir medoides iniciales, (2) asignar cada punto al medoide más cercano, (3) calcular el costo total, (4) probar el intercambio de un medoide por un punto no-medoide, (5-6) comparar costos y decidir si se acepta o rechaza el cambio, repitiendo el proceso hasta que ningún intercambio mejore el costo. El resultado final son los medoides y clústeres que minimizan la suma de distancias dentro de cada grupo.


3 Ventajas y Desventajas Comparativas

3.1 Ventajas de PAM

3.1.1 Ventaja 1. Utiliza representantes reales

PAM selecciona medoids que pertenecen al conjunto de observaciones. Esto facilita la interpretación cuando se desea que cada grupo tenga un representante concreto.

Comparación con K-Means: el centroide de K-Means puede no corresponder a ningún país real, mientras que el medoid sí puede representar una observación existente.

Aplicación económica: si se agrupan países, el medoid puede ser un país que se utilice para describir las características del grupo.

3.1.2 Ventaja 2. Mayor resistencia relativa frente a determinados valores atípicos que K-Means

Al utilizar medoids en lugar de promedios, PAM puede ser menos sensible a ciertos valores extremos que K-Means.

Esto puede resultar útil cuando se analizan datos económicos que presentan observaciones muy alejadas de las demás.

Comparación con K-Means: el centroide puede desplazarse debido a valores extremos, mientras que un medoid es una observación real que se elige para reducir la distancia total.

Limitación: PAM no elimina los valores atípicos y también puede verse afectado por ellos.

3.1.3 Permite utilizar diferentes medidas de disimilitud

PAM puede trabajar con diferentes medidas de distancia, según la implementación y el tipo de datos.

Esto permite adaptar el criterio de similitud al problema de investigación. Por ejemplo, se puede utilizar la distancia Manhattan cuando se desea sumar diferencias absolutas entre variables.

Aplicación económica: permite analizar las diferencias entre países de acuerdo con una medida de disimilitud que debe ser justificada.

3.1.4 Puede ser interpretable cuando se establece un número de grupos

PAM permite definir un número K de grupos y obtener un medoid para cada uno.

Esto puede ser útil si el investigador tiene una justificación para comparar un número determinado de grupos económicos.

Comparación con DBSCAN: DBSCAN no necesita establecer el número de grupos de antemano, porque forma agrupaciones a partir de la densidad.

3.2 Desventajas de PAM

3.2.1 Necesita definir el número de grupos

El método requiere establecer K. Si no se tiene una justificación clara para elegir el número de conglomerados, puede ser necesario comparar diferentes valores de K.

Se pueden utilizar herramientas como el análisis de silueta para evaluar la calidad de diferentes particiones.

Método Necesita K
PAM Sí
K-Means Sí
Ward No directamente; permite construir una jerarquía y elegir un corte
DBSCAN No

3.2.2 Puede ser costoso computacionalmente

El PAM clásico evalúa posibles intercambios de medoids. Cuando aumenta el número de observaciones, el costo computacional puede incrementarse.

Esto es relevante si se utilizan muchos países, períodos y variables de una API del FMI.

Comparación con K-Means: K-Means suele ser más eficiente en grandes conjuntos de datos numéricos, aunque su eficiencia real depende de la implementación, el número de iteraciones y el tamaño de la muestra.

3.2.3 No está diseñado principalmente para detectar ruido como DBSCAN

PAM no clasifica automáticamente las observaciones como ruido mediante un criterio de densidad.

DBSCAN, en cambio, puede identificar observaciones que no pertenecen a regiones suficientemente densas, de acuerdo con sus parámetros.

Aplicación económica: si la investigación busca detectar países o períodos con comportamientos aislados, DBSCAN podría ser una alternativa que se debe evaluar.

Sin embargo, un país con un indicador extremo no debe considerarse automáticamente ruido, porque puede representar un fenómeno económico relevante.

3.2.4 Los resultados dependen de las variables y la medida de distancia

PAM agrupa de acuerdo con las variables que se proporcionan y la distancia utilizada.

Si las variables tienen escalas muy diferentes, los resultados pueden estar dominados por aquellas con mayor magnitud numérica.

Por eso, es necesario revisar:

  • Unidades de medición.
  • Estandarización.
  • Valores faltantes.
  • Variables seleccionadas.
  • Interpretación de las distancias.

Esta limitación también existe en los demás métodos, aunque cada uno presenta sensibilidades distintas.

3.3 Consideraciones finales para una aplicación con datos del FMI

Antes de aplicar PAM a una base real del FMI, se recomienda revisar cuidadosamente:

  1. La unidad de análisis: país, año, país-año u otra.
  2. Las variables económicas seleccionadas.
  3. Las unidades de medición.
  4. Los valores faltantes.
  5. La presencia de valores extremos.
  6. La necesidad de estandarizar las variables.
  7. La medida de distancia utilizada.
  8. La justificación del número de grupos K.
  9. La estabilidad de los grupos obtenidos.
  10. La interpretación económica de los medoids.

4 Selección del Número Óptimo de Clústeres (o Parámetros)

Para determinar en cuántos grupos conviene dividir a los países existen varias métricas generales:

Método del Codo: evalúa qué tan compactos quedan los grupos a medida que aumenta k, buscando el punto donde ya no mejora mucho más. Se usa principalmente con K-Means. Una técnica que se emplea para determinar el número óptimo de clústeres para el algoritmo de K-means. Se debe identificar el punto en el que la disminución de la varianza intra-clúster se desacelera, lo que indica que aumentar el número de clústeres no mejorará la compactación de estos. Permitiendo encontrar el equilibrio entre la cantidad de clústeres y la compactación de los datos.

Coeficiente de Silueta (Silhouette): mide qué tan bien encaja cada elemento dentro de su propio grupo en comparación con los demás grupos. Es aplicable tanto a K-Means como a K-Medoides.

Este es un coeficiente que mide la calidad del agrupamiento, en el que valores más altos indican clusters mejor definidos. Su cálculo se realiza usando dos métricas:

a: El promedio de la distancia entre una muestra y todos los demás puntos que pertenecen a la misma clase. b: La distancia entre la muestra y todos los puntos del siguiente cluster más cercano.

La métrica se calcula como:

\(s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}\)

y toma valores entre -1 y +1.

La interpretación es la siguiente:

Valores cercanos a -1 indican un agrupamiento incorrecto.

Valores cercanos a cero indican clusters traslapados

Valores cercanos a +1 indican clusters altamente densos.

Las métricas generales para determinar el número de agrupaciones (o los parámetros de un algoritmo de clustering) son las siguientes:

Método Algoritmo al que corresponde
Método del Codo (WSS) K-means
Coeficiente de Silueta PAM / K-medianas (y K-means)
Dendrograma con corte Jerárquico Aglomerativo (Ward)
Gráfico de K-vecinos (k-NN) DBSCAN (estimación de épsilon)

Dendogramas: Gráfico en forma de árbol utilizado en clustering jerárquico, un tipo de algoritmo distinto al utilizado en este trabajo. Gráfico de K-Vecinos: se usa para el algoritmo DBSCAN, que agrupa por densidad de puntos y no por distancia a un centro.

Dado que el algoritmo asignado para este trabajo es K-Medoides (PAM), la métrica que le corresponde es el Coeficiente de Silueta, por ser la medida estándar para algoritmos de partición basados en distancias como PAM. El Dendrograma y el Gráfico de K-Vecinos no aplican, ya que corresponden a otros tipos de algoritmos (jerárquico y basado en densidad, respectivamente).

Al aplicar esta métrica sobre los datos de los ocho países (WEO-FMI), se evaluaron distintos valores de k, obteniendo el mejor desempeño en k = 2 (índice de silueta ≈ 0.42), el valor más alto entre todas las opciones evaluadas. Esto indica que, según su comportamiento en desempleo, crecimiento e inflación (2015-2024), los países se agrupan mejor en dos perfiles macroeconómicos diferenciados en lugar de subdividirse en más grupos.

5 Ejemplo Práctico y Representación Gráfica en R

library(dplyr)
library(tidyr)
library(tibble)
library(cluster)     
library(factoextra)   
library(ggplot2)
library(knitr)
library(kableExtra)
library(ggrepel)      

5.1 Datos: Balanza de Pagos (BOP/FMI)

Para el algoritmo asignado a este grupo (PAM), el método analíticamente correcto es el Ancho de Silueta Promedio (Average Silhouette Width, ASW): se ajusta PAM para un rango de valores de k (2 a 6, dado que la muestra tiene 8 países), se calcula la silueta promedio de cada partición, y se elige el k que la maximiza. Este método es preferible al codo porque PAM no minimiza varianza intragrupo alrededor de un centroide artificial, sino disimilitud (aquí, distancia Manhattan) respecto a un medoide real — un país efectivamente observado —, por lo que la evaluación debe basarse en cohesión/separación (silueta) y no en varianza.

usar_api <- requireNamespace("imf.data", quietly = TRUE)

paises <- c("SLV", "GTM", "HND", "NIC", "CRI", "PAN", "MEX", "USA")

consultar_pais_CAB <- function(pais) {
  resultado <- tryCatch(
    imf.data::get_data("BOP", agency_id = "IMF.STA",
                        filters = list(COUNTRY = pais, FREQUENCY = "A",
                                       INDICATOR = "CAB",
                                       BOP_ACCOUNTING_ENTRY = "NETCD_T",
                                       UNIT = "USD")),
    error = function(e) tibble()
  )
  if (nrow(resultado) == 0) return(tibble())
  resultado %>%
    mutate(TIME_PERIOD = as.numeric(TIME_PERIOD),
           OBS_VALUE   = as.numeric(OBS_VALUE)) %>%
    filter(TIME_PERIOD >= 2015, TIME_PERIOD <= 2024)
}

datos_comparacion <- tibble()
if (usar_api) {
  datos_comparacion <- tryCatch(
    bind_rows(lapply(paises, consultar_pais_CAB)),
    error = function(e) tibble()
  )
}

if (nrow(datos_comparacion) == 0) {
  
  datos_comparacion <- tribble(
    ~COUNTRY, ~TIME_PERIOD, ~OBS_VALUE,
    "SLV", 2015, -753757240,  "SLV", 2016, -550121069,  "SLV", 2017, -464589787,
    "SLV", 2018, -859154621,  "SLV", 2019, -113356041,  "SLV", 2020, 276701043,
    "SLV", 2021, -1250461218, "SLV", 2022, -2143721477, "SLV", 2023, -584682906,
    "SLV", 2024, -964417195,
    "GTM", 2015, -773787480,  "GTM", 2016, 637267670,   "GTM", 2017, 857080020,
    "GTM", 2018, 649564060,   "GTM", 2019, 1821490950,  "GTM", 2020, 3918080170,
    "GTM", 2021, 1873410490,  "GTM", 2022, 1116174170,  "GTM", 2023, 3212019060,
    "GTM", 2024, 3300417910,
    "HND", 2015, -979945387,  "HND", 2016, -682674236,  "HND", 2017, -288253383,
    "HND", 2018, -1581815977, "HND", 2019, -660874047,  "HND", 2020, 665854482,
    "HND", 2021, -1537902787, "HND", 2022, -2156562609, "HND", 2023, -1449262852,
    "HND", 2024, -1595401881,
    "NIC", 2015, -1259500000, "NIC", 2016, -1127100000, "NIC", 2017, -987100000,
    "NIC", 2018, -234100000,  "NIC", 2019, 754100000,   "NIC", 2020, 480200000,
    "NIC", 2021, -391178858,  "NIC", 2022, -459600000,  "NIC", 2023, 1465400000,
    "NIC", 2024, 817618290,
    "CRI", 2015, -1921262983, "CRI", 2016, -1257445124, "CRI", 2017, -2188723948,
    "CRI", 2018, -1867318664, "CRI", 2019, -750921207,  "CRI", 2020, -625175269,
    "CRI", 2021, -2178238613, "CRI", 2022, -1850873043, "CRI", 2023, -1244166276,
    "CRI", 2024, -839431794,
    "PAN", 2015, -4848400000, "PAN", 2016, -4507669628, "PAN", 2017, -3745435583,
    "PAN", 2018, -5307360501, "PAN", 2019, -3589766446, "PAN", 2020, 122856978,
    "PAN", 2021, -818191315,  "PAN", 2022, -276704272,  "PAN", 2023, -2676789962,
    "PAN", 2024, 568132122,
    "MEX", 2015, -31535662037,"MEX", 2016, -25282312907,"MEX", 2017, -21402776864,
    "MEX", 2018, -25931803949,"MEX", 2019, -3871166263, "MEX", 2020, 26873079471,
    "MEX", 2021, -4485665790, "MEX", 2022, -15007569273,"MEX", 2023, -10232313218,
    "MEX", 2024, -16790722578,
    "USA", 2015, -390780000000,"USA", 2016, -380011000000,"USA", 2017, -348862000000,
    "USA", 2018, -419443000000,"USA", 2019, -404302000000,"USA", 2020, -589622000000,
    "USA", 2021, -855203000000,"USA", 2022, -972359000000,"USA", 2023, -931394000000,
    "USA", 2024, -1198628000000
  )
}

5.1.1 Países incluidos

El ejercicio utiliza los 8 países definidos en los datos de BOP/FMI: El Salvador, Guatemala, Honduras, Nicaragua, Costa Rica, Panamá, México y Estados Unidos.

5.1.2 Construcción de variables y estandarización

# 1. Perfil por país a partir del panel: nivel y volatilidad de la cuenta
#    corriente (2015-2024).
resumen_pais <- datos_comparacion %>%
  group_by(COUNTRY) %>%
  summarise(
    promedio_CAB    = mean(OBS_VALUE, na.rm = TRUE),
    volatilidad_CAB = sd(OBS_VALUE,   na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    # Transformación log-modulus (conserva signo y orden, comprime la
    # escala): necesaria porque el panel mezcla economías de tamaños muy
    # distintos (EE.UU./México frente a Centroamérica); sin esto, la
    # estandarización quedaría dominada por el país de mayor tamaño.
    nivel_relativo  = sign(promedio_CAB) * log1p(abs(promedio_CAB)),
    # Volatilidad relativa al nivel (coeficiente de variación): compara la
    # inestabilidad de cada país en sus propios términos, no en dólares
    # brutos.
    volatilidad_rel = volatilidad_CAB / abs(promedio_CAB)
  )

# 2. Tratamiento de NA (permitido: imputación por la media). En este panel
#    no hay valores faltantes, pero se deja el tratamiento por consistencia
#    con lo solicitado.
resumen_pais <- resumen_pais %>%
  mutate(across(c(nivel_relativo, volatilidad_rel),
                ~ ifelse(is.na(.), mean(., na.rm = TRUE), .)))

# 3. Estandarización obligatoria antes de correr la matriz de distancias
datos_pam <- resumen_pais %>%
  select(COUNTRY, nivel_relativo, volatilidad_rel) %>%
  column_to_rownames("COUNTRY")
datos_escalados <- scale(datos_pam)

5.1.3 Coeficiente de silueta y elección de k

fviz_nbclust(datos_escalados, cluster::pam, method = "silhouette", k.max = 6,
             linecolor = "#4A6FA5") +
  geom_point(size = 3.2, color = "#17365D") +
  labs(title = "Número óptimo de clústeres (k) — Coeficiente de Silueta",
       subtitle = "PAM sobre perfil de cuenta corriente — BOP/FMI, 8 países, 2015–2024",
       x = "Número de clústeres (k)", y = "Ancho de silueta promedio") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", color = "#17365D", size = 14),
    plot.subtitle = element_text(color = "#315B7D", size = 11),
    axis.title = element_text(face = "bold", color = "#17365D"),
    axis.text = element_text(color = "#334455"),
    panel.grid.major = element_line(color = "#DCE6EF", linewidth = 0.35),
    panel.grid.minor = element_blank(),
    plot.background = element_rect(fill = "#FFFFFF", color = NA),
    panel.background = element_rect(fill = "#FFFFFF", color = NA)
  )

El ancho de silueta promedio se maximiza en k = 3, con un valor bastante por encima del resto de alternativas (k = 2 o k = 4), por lo que ese es el número de clústeres que se usa en la partición final.

5.1.4 Ejemplo práctico y representación gráfica en R

Variables: saldo de la cuenta corriente (indicador CAB, dataset BOP, agencia IMF.STA) para 8 países (El Salvador, Guatemala, Honduras, Nicaragua, Costa Rica, Panamá, México y Estados Unidos), 2015–2024 — el mismo panel ya extraído en la sección teórica del equipo. De esa serie se derivan dos variables por país: su nivel relativo (transformación log-modulus del promedio) y su volatilidad relativa (coeficiente de variación), que son las que efectivamente entran al algoritmo.

Justificación de PAM para estos datos: el panel mezcla economías muy pequeñas (Centroamérica) con economías de escala mucho mayor (México, Estados Unidos) y con perfiles de volatilidad muy distintos entre sí. Un algoritmo basado en centroides (K-means) se vería distorsionado por estos casos extremos; PAM, al usar medoides reales (un país concreto como representante de cada grupo) y distancia Manhattan, es más robusto ante esa heterogeneidad — de ahí que sea el método asignado para la naturaleza de los datos del FMI.

5.1.5 Estimación del modelo PAM

k_optimo <- 3  # obtenido del gráfico de silueta anterior

set.seed(123)
modelo_pam <- pam(datos_escalados, k = k_optimo, metric = "manhattan")

# País que actúa como medoide (representante real) de cada clúster
rownames(datos_pam)[modelo_pam$id.med]
## [1] "PAN" "GTM" "NIC"
resumen_pais %>%
  mutate(cluster = factor(modelo_pam$clustering)) %>%
  select(COUNTRY, promedio_CAB, volatilidad_CAB, cluster) %>%
  kable(caption = "Asignación de países a clústeres — PAM sobre BOP/CAB (2015–2024)",
        digits = 0) %>%
  kable_classic(html_font = "Times New Roman", font_size = 14) %>%
  kable_styling(full_width = FALSE)
Asignación de países a clústeres — PAM sobre BOP/CAB (2015–2024)
COUNTRY promedio_CAB volatilidad_CAB cluster
CRI -1472355692 600858189 1
GTM 1661171702 1460630781 2
HND -1026683868 817344288 1
MEX -12766691341 16678235391 1
NIC -94126057 929556374 3
PAN -2507932861 2217622770 1
SLV -740756051 654738365 1
USA -649060400000 311650220088 1

5.1.6 Visualización de los clústeres

# PREPARACIÓN DE DATOS PARA LA INFOGRAFÍA

# PAM ya fue estimado arriba. Aquí solo preparamos las coordenadas
# en 2 dimensiones (PCA) y las etiquetas para graficar.

pca_pam <- prcomp(datos_escalados, scale. = FALSE)

coord_pam <- as.data.frame(pca_pam$x[, 1:2])
names(coord_pam) <- c("Componente1", "Componente2")

coord_pam$COUNTRY <- rownames(coord_pam)

coord_pam$cluster <- factor(
  modelo_pam$clustering[
    match(coord_pam$COUNTRY, names(modelo_pam$clustering))
  ],
  levels = c("1", "2", "3")
)

nombres_paises <- c(
  SLV = "El Salvador",
  GTM = "Guatemala",
  HND = "Honduras",
  NIC = "Nicaragua",
  CRI = "Costa Rica",
  PAN = "Panamá",
  MEX = "México",
  USA = "Estados Unidos"
)

coord_pam$Pais <- ifelse(
  coord_pam$COUNTRY %in% names(nombres_paises),
  nombres_paises[coord_pam$COUNTRY],
  coord_pam$COUNTRY
)

# Medoides reales obtenidos por PAM
medoides <- coord_pam[
  match(rownames(datos_pam)[modelo_pam$id.med], coord_pam$COUNTRY),
]

medoides$cluster <- factor(
  modelo_pam$clustering[modelo_pam$id.med],
  levels = c("1", "2", "3")
)

n_cl <- table(coord_pam$cluster)

colores_pam <- c(
  "1" = "#4A9BD8",
  "2" = "#59A96A",
  "3" = "#9567D7"
)

etiquetas_cl <- c(
  "1" = paste0("Clúster 1 (", n_cl["1"], " países)"),
  "2" = paste0("Clúster 2 (", n_cl["2"], " países)"),
  "3" = paste0("Clúster 3 (", n_cl["3"], " países)")
)
# GRÁFICO PRINCIPAL PAM 

rango_x <- range(coord_pam$Componente1)
rango_y <- range(coord_pam$Componente2)
margen_x <- diff(rango_x) * 0.18
margen_y <- diff(rango_y) * 0.18

grafico_base <- ggplot(
  coord_pam,
  aes(x = Componente1, y = Componente2, color = cluster, fill = cluster)
) +

  
  stat_ellipse(geom = "polygon", type = "norm", level = 0.80,
               alpha = 0.20, linewidth = 1) +
  stat_ellipse(type = "norm", level = 0.80, linewidth = 1.1) +

  # Países
  geom_point(shape = 21, size = 7, stroke = 1.3, color = "#FFFFFF") +

  
  geom_text_repel(aes(label = Pais), color = "#18324B", size = 5.3,
                   fontface = "bold", seed = 123, max.overlaps = Inf,
                   box.padding = 0.55, point.padding = 0.35,
                   min.segment.length = 0.15, segment.color = "#9AA9BC") +

  
  geom_point(
    data = medoides, aes(x = Componente1, y = Componente2),
    inherit.aes = FALSE, shape = 8, size = 6.5,
    color = "#17324D", stroke = 1.3
  ) +

  scale_color_manual(values = colores_pam, guide = "none") +
  scale_fill_manual(values = colores_pam, guide = "none") +

  coord_cartesian(
    xlim = c(rango_x[1] - margen_x, rango_x[2] + margen_x),
    ylim = c(rango_y[1] - margen_y, rango_y[2] + margen_y)
  ) +

  labs(x = "Componente 1 (PCA)", y = "Componente 2 (PCA)") +

  theme_minimal(base_size = 18) +
  theme(
    legend.position = "none",
    axis.title = element_text(size = 16, face = "bold", color = "#17365D"),
    axis.text = element_text(size = 13, color = "#334455"),
    panel.grid.major = element_line(color = "#DCE6EF", linewidth = 0.4),
    panel.grid.minor = element_blank(),
    plot.background = element_rect(fill = "#FFFFFF", color = NA),
    panel.background = element_rect(fill = "#FFFFFF", color = "#D8E6F4"),
    plot.margin = margin(20, 20, 20, 20)
  )


ggsave("pam_grafico_principal.png", grafico_base,
       width = 14, height = 10, dpi = 200, bg = "white")

Clustering de países según Balanza de Pagos (Método PAM)

Análisis de 8 países según nivel y volatilidad de la cuenta corriente — BOP/FMI, 2015–2024

Clústeres

Clúster 1 (6 países)
Clúster 2 (1 países)
Clúster 3 (1 países)
★Medoide de cada clúster

Países incluidos

  1. Costa Rica
  2. Guatemala
  3. Honduras
  4. México
  5. Nicaragua
  6. Panamá
  7. El Salvador
  8. Estados Unidos

5.1.7 Interpretación del gráfico

Clúster 1 (azul)

Está formado por Costa Rica, Honduras, México, Panamá, El Salvador, Estados Unidos.

Su medoide es Panamá, es decir, un país real que representa al grupo según las variables utilizadas en el método PAM.

Clúster 2 (verde)

Está formado por Guatemala.

Su medoide es Guatemala. Es un grupo que PAM aísla por presentar un perfil propio dentro de la muestra.

Clúster 3 (morado)

Está formado por Nicaragua.

Su medoide es Nicaragua. Al igual que el Clúster 2, PAM lo trata como un perfil atípico y le asigna un medoide propio en lugar de forzarlo dentro del grupo mayoritario.

Nota: cada punto representa un país y las elipses muestran visualmente los grupos formados por PAM (no se dibuja elipse en los clústeres de un solo país, ya que no puede estimarse una región de dispersión a partir de un único punto). La estrella representa el medoide de cada clúster, es decir, un país real que funciona como representante del grupo. La partición no es balanceada por diseño: PAM aísla a Guatemala y Nicaragua como clústeres propios por ser casos atípicos en el panel BOP/FMI (ver sección 5.5).

5.1.8 Validación: gráfico de silueta

fviz_silhouette(modelo_pam, palette = unname(colores_pam),
                 print.summary = FALSE) +
  labs(title = "Validación de la partición PAM: ancho de silueta por país",
       subtitle = paste0("Silueta promedio global = ",
                          round(modelo_pam$silinfo$avg.width, 2)),
       x = "País", y = "Ancho de silueta") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", color = "#17365D", size = 14),
    plot.subtitle = element_text(color = "#315B7D", size = 11.5),
    axis.title = element_text(face = "bold", color = "#17365D"),
    axis.text.x = element_text(angle = 40, hjust = 1, color = "#334455"),
    axis.text.y = element_text(color = "#334455"),
    panel.grid.major = element_line(color = "#DCE6EF", linewidth = 0.35),
    panel.grid.minor = element_blank(),
    legend.position = "none",
    plot.background = element_rect(fill = "#FFFFFF", color = NA),
    panel.background = element_rect(fill = "#FFFFFF", color = NA)
  )

5.1.9 Lectura económica

La partición separa un bloque mayoritario y muy cohesivo (silueta promedio ≈ 0.9, medoide Panamá) formado por El Salvador, Honduras, Costa Rica, Panamá, México y Estados Unidos — economías con déficits de cuenta corriente persistentes y moderados en términos relativos —, frente a dos países que PAM aísla como perfiles atípicos, cada uno como medoide de su propio clúster: Guatemala, la única economía con un saldo promedio positivo (superavitario) en el período, y Nicaragua, cuyo promedio cercano a cero infla su volatilidad relativa muy por encima del resto.

Esto ilustra directamente la ventaja de PAM frente a K-means: en lugar de forzar a estos dos países atípicos dentro de un centroide promedio que no representaría a nadie, el algoritmo los conserva como medoides — casos reales, interpretables — de clústeres propios.

5.1.10 Resultados

El coeficiente de silueta identifica k = 3 como el número óptimo de clústeres para el perfil de cuenta corriente (BOP/FMI, 2015–2024) de los 8 países analizados. El algoritmo PAM, al basarse en medoides reales y distancia Manhattan, resulta más apropiado que K-means para este panel, que mezcla economías de tamaño y volatilidad muy distintos: en vez de diluir a los países atípicos (Guatemala, Nicaragua) en un promedio artificial, los preserva como representantes legítimos de sus propios grupos, mientras que el resto de la muestra conforma un clúster amplio y cohesivo de economías con déficits moderados y persistentes.

5.2 Aplicación de Dataset-WEO (API FMI)

library(imf.data)   # Extracción de datos desde la API del FMI
library(dplyr)      # Manipulación de datos
library(tidyr)      # Transformación de formatos (pivot_wider)
library(cluster)    # Algoritmo PAM (K-Medianas)
library(factoextra) # Visualización de clústeres y siluetas
library(knitr)
library(kableExtra)

paises <- c("SLV", "GTM", "HND", "NIC", "CRI", "PAN")
datos_weo <- get_data(
  "WEO",
  agency_id = "IMF.RES",
  filters   = list(
    COUNTRY   = paises, 
    INDICATOR = c("NGDP_RPCH", "PCPIPCH"),                   
    FREQUENCY = "A"                           
  )
)
datos_macro <- datos_weo %>%
  mutate(
    TIME_PERIOD = as.numeric(TIME_PERIOD),
    OBS_VALUE   = as.numeric(OBS_VALUE)
  ) %>%
  filter(TIME_PERIOD == 2023) %>%
  select(COUNTRY, INDICATOR, OBS_VALUE) %>%
  pivot_wider(names_from = INDICATOR, values_from = OBS_VALUE)

datos_macro %>% 
  kable(caption = "Proyección de indicadores macroeconómicos en la región Centroamericana para el año 2023") %>% 
  kable_classic(html_font = "Times New Roman", font_size = 13) %>% 
  kable_styling(full_width = FALSE)
Proyección de indicadores macroeconómicos en la región Centroamericana para el año 2023
COUNTRY NGDP_RPCH PCPIPCH
CRI 4.791877 0.525186
GTM 3.533157 6.209335
HND 3.575892 6.663167
NIC 4.427794 8.445432
PAN 7.166329 1.486421
SLV 3.539157 4.046867

5.2.1 Recomenaciones Técincas: Tratamiento de Na y Estandraización.

datos <- datos_macro %>%
  mutate(across(where(is.numeric), ~ ifelse(is.na(.), mean(., na.rm = TRUE), .)))

matriz_datos <- as.data.frame(datos[, -1])
rownames(matriz_datos) <- datos$COUNTRY

datos_escalados <- scale(matriz_datos)

n_paises <- nrow(datos_escalados)
print(paste("Cantidad de países en el análisis:", n_paises))
## [1] "Cantidad de países en el análisis: 6"
as.data.frame(datos_escalados) %>% 
  kable(caption = "Matriz de Datos Estandarizados (Z-Scores)") %>% 
  kable_classic(html_font = "Times New Roman", font_size = 12) %>% 
  kable_styling(full_width = FALSE)
Matriz de Datos Estandarizados (Z-Scores)
NGDP_RPCH PCPIPCH
CRI 0.2032584 -1.3000014
GTM -0.6907558 0.5301689
HND -0.6604030 0.6762928
NIC -0.0553340 1.2501427
PAN 1.8897286 -0.9905050
SLV -0.6864943 -0.1660980

5.2.2 Selección del número óptimo de Clústeres

max_k <- min(10, n_paises - 1)

grafico_k_optimo <- fviz_nbclust(
  x = datos_escalados, 
  FUNcluster = pam, 
  method = "silhouette",
  k.max = max_k 
) +
  labs(
    title = "Número Óptimo de Clústeres - Algoritmo PAM (K-Medianas)",
    subtitle = "Criterio de Silueta Media con datos de la API del FMI",
    x = "Número de clústeres (k)",
    y = "Ancho promedio de silueta"
  ) +
  theme_minimal()

print(grafico_k_optimo)

5.2.3 Ejecución del Algoritmo PAM

# Asumiendo k = 3 según el resultado óptimo de la silueta
k_opt <- 3
resultado_pam <- pam(x = datos_escalados, k = k_opt, metric = "euclidean")
medoides_reales <- rownames(datos_escalados)[resultado_pam$id.med]
cat("Los países seleccionados como medoides son:", paste(medoides_reales, collapse = ", "), "\n")
## Los países seleccionados como medoides son: CRI, GTM, PAN
library(factoextra)
fviz_cluster(
  object = resultado_pam,
  data = datos_escalados,
  ellipse.type = "convex",
  star.plot = TRUE,       
  repel = TRUE,           
  show.clust.cent = TRUE, 
  palette = "jco",        
  ggtheme = theme_minimal()
) +
  labs(
    title = "Grupos Macroeconómicos y sus Países Medoides",
    subtitle = "Las líneas conectan cada país con su medoide asignado (CRI, GTM, PAN)",
    caption = "Fuente: Elaboración propia con datos del FMI"
  )

5.2.4 Representación gráfica

# A) Gráfico de Elipsoides de Concentración y Medoides
grafico_clusters <- fviz_cluster(
  object = resultado_pam,
  data = datos_escalados,
  ellipse.type = "convex", 
  repel = TRUE,            
  show.clust.cent = TRUE,  
  star.plot = TRUE        
) +
  labs(
    title = "Agrupación mediante K-Medianas (PAM)",
    subtitle = "Clustering Particional Robusto basado en Medoides Reales (API FMI)",
    caption = "Fuente: Elaboración propia con datos de la API del FMI"
  ) +
  theme_minimal()

print(grafico_clusters)

# B) Gráfico de Silueta de la Partición
grafico_silueta <- fviz_silhouette(resultado_pam) +
  labs(
    title = "Gráfico de Silueta para Clustering PAM (k = 3)",
    subtitle = "Evaluación del ajuste individual y promedio de los clústeres"
  ) +
  theme_minimal()
##   cluster size ave.sil.width
## 1       1    1          0.00
## 2       2    4          0.57
## 3       3    1          0.00
print(grafico_silueta)

5.3 Aplicación del Dataset-GFS_SOO

library(imf.data)
library(dplyr)

## --- Ingresos y gastos (GFS_SOO) para El Salvador ---
datos_soo <- get_data(
  "GFS_SOO",
  filters = list(
    COUNTRY = "SLV",
    SECTOR = "S13",
    GFS_GRP = c("G1", "G2M"),
    INDICATOR = c("G1_T", "G2M_T"),
    TYPE_OF_TRANSFORMATION = "XDC",
    FREQUENCY = "A"
  ),
  last_n_obs = 40
) %>%
  mutate(
    TIME_PERIOD = as.integer(TIME_PERIOD),
    indicador = recode(INDICATOR, "G1_T" = "Ingresos", "G2M_T" = "Gastos")
  ) %>%
  filter(!is.na(OBS_VALUE))

resultado_operativo <- datos_soo %>%
  select(TIME_PERIOD, indicador, OBS_VALUE) %>%
  tidyr::pivot_wider(names_from = indicador, values_from = OBS_VALUE) %>%
  mutate(resultado = Ingresos - Gastos)

## --- Deuda pública (GFS_BS) para El Salvador ---
grp_bs <- list_dimension_values("GFS_BS", "GFS_GRP", filters = list(COUNTRY = "SLV", SECTOR = "S13"))
grp_deuda <- grp_bs %>% filter(stringr::str_detect(name, stringr::regex("Liabilit|Balance Sheet", ignore_case = TRUE)))

indicadores_bs <- list_dimension_values("GFS_BS", "INDICATOR",
  filters = list(COUNTRY = "SLV", SECTOR = "S13", GFS_GRP = grp_deuda$code))
ind_deuda <- indicadores_bs %>%
  filter(stringr::str_detect(name, stringr::regex("Gross debt|Total liabilities|Debt securities", ignore_case = TRUE)))

datos_bs <- get_data(
  "GFS_BS",
  filters = list(
    COUNTRY = "SLV",
    SECTOR = "S13",
    GFS_GRP = grp_deuda$code[1],
    INDICATOR = ind_deuda$code[1],
    TYPE_OF_TRANSFORMATION = "XDC",
    FREQUENCY = "A"
  ),
  last_n_obs = 40
) %>%
  mutate(TIME_PERIOD = as.integer(TIME_PERIOD)) %>%
  filter(!is.na(OBS_VALUE))
library(imf.data)
library(dplyr)
library(tidyr)
library(cluster)
library(factoextra)
library(knitr)

set.seed(123)  # reproducibilidad del build/swap de PAM en ambas opciones


## ============================================================
## OPCIÓN A — Clusterizar AÑOS (un solo país: El Salvador)
## Cada fila = un año; columnas = Ingresos, Gastos, Deuda (USD nominales)
## ============================================================

## --- Paso A1: consolidar la tabla desde los objetos ya descargados ---
tabla_pam_A <- resultado_operativo %>%
  left_join(
    datos_bs %>% select(TIME_PERIOD, Deuda = OBS_VALUE),
    by = "TIME_PERIOD"
  ) %>%
  filter(!is.na(Deuda)) %>%              # se pierden 2002-2004: sin dato de Deuda en GFS_BS
  select(TIME_PERIOD, Ingresos, Gastos, Deuda) %>%
  tibble::column_to_rownames("TIME_PERIOD")

## --- Paso A2: estandarizar (evita que Deuda domine por escala) ---
tabla_std_A <- scale(tabla_pam_A)

## --- Paso A3: elegir k con índice de silueta (no a ojo) ---
fviz_nbclust(tabla_std_A, pam, method = "silhouette") +
  labs(title = "Opción A — k óptimo por año fiscal (El Salvador)")

## --- Paso A4: correr PAM ---
modelo_pam_A <- pam(tabla_std_A, k = 3, metric = "euclidean", stand = FALSE)

rownames(tabla_pam_A)[modelo_pam_A$id.med]      # años medoide de cada cluster
## [1] "2010" "2018" "2023"
tabla_pam_A$cluster <- factor(modelo_pam_A$clustering)

## --- Paso A5: visualizar ---
fviz_cluster(modelo_pam_A, data = tabla_std_A,
             palette = c("#2c7fb8", "#d7301f", "#31a354"),
             ellipse.type = "t", repel = TRUE, labelsize = 8,
             ggtheme = theme_minimal(),
             main = "Opción A — Clustering PAM de años fiscales, El Salvador")

## --- Paso A6: interpretar medoides ---
## Se seleccionan solo las columnas numéricas antes de round(): 'cluster' es
## un factor y round() no puede operar sobre él (Error: non-numeric-alike
## variable(s) in data frame).
kable(round(tabla_pam_A[modelo_pam_A$id.med, c("Ingresos", "Gastos", "Deuda")], 0),
      caption = "Opción A — Años medoide (año real más representativo de cada cluster)")
Opción A — Años medoide (año real más representativo de cada cluster)
Ingresos Gastos Deuda
2010 4311300000 4862600000 79300000
2018 6816100000 6977500000 332800000
2023 9505638793 10427400000 2226700000
## ============================================================
## OPCIÓN B — Clusterizar PAÍSES (Centroamérica)
## Cada fila = un país; columnas = Ingresos, Gastos y Déficit (% del PIB)
## ============================================================

paises <- c("SLV", "GTM", "HND", "NIC", "CRI", "PAN")

## --- Paso B1: verificar qué países reportan bajo Sector S13 ---
verificacion_sector <- lapply(paises, function(p) {
  list_dimension_values("GFS_SOO", "SECTOR", filters = list(COUNTRY = p)) %>%
    mutate(pais = p)
}) %>% bind_rows()

paises_validos <- verificacion_sector %>%
  filter(code == "S13") %>%
  pull(pais) %>%
  unique()

paises_validos   # revisar antes de continuar: puede ser menor a 'paises'
## [1] "SLV" "GTM" "HND" "NIC" "CRI" "PAN"
## NOTA (diagnóstico cerrado): en una corrida anterior, 'tabla_pam_B' quedó
## con solo 5 países porque el Paso B2 se ejecutó con un 'paises_validos'
## desactualizado (fuera de orden en la consola, sin incluir "NIC" todavía).
## Nicaragua sí tiene series completas de Ingresos/Gastos en POGDP_PT — no
## era una limitación de los datos. Lección: correr siempre B1 -> B7 en
## secuencia y verificar unique(datos_multi_soo$COUNTRY) antes de continuar,
## no confiar en objetos que pudieron quedar de una sesión anterior.

## --- Paso B2: descargar Ingresos/Gastos en % del PIB (POGDP_PT, no XDC) ---
## XDC no es comparable entre países porque cada uno reporta en su propia
## moneda local; para comparar países es obligatorio usar % del PIB.
datos_multi_soo <- get_data(
  "GFS_SOO",
  filters = list(
    COUNTRY = paises_validos,
    SECTOR = "S13",
    GFS_GRP = c("G1", "G2M"),
    INDICATOR = c("G1_T", "G2M_T"),
    TYPE_OF_TRANSFORMATION = "POGDP_PT",
    FREQUENCY = "A"
  ),
  last_n_obs = 10
) %>%
  mutate(
    TIME_PERIOD = as.integer(TIME_PERIOD),
    indicador = recode(INDICATOR, "G1_T" = "Ingresos_PIB", "G2M_T" = "Gastos_PIB")
  ) %>%
  filter(!is.na(OBS_VALUE))

## --- Paso B3: agregar por país (promedio de los últimos 10 años) ---
tabla_pam_B <- datos_multi_soo %>%
  group_by(COUNTRY, indicador) %>%
  summarise(promedio = mean(OBS_VALUE, na.rm = TRUE), .groups = "drop") %>%
  pivot_wider(names_from = indicador, values_from = promedio) %>%
  mutate(Deficit_PIB = Ingresos_PIB - Gastos_PIB) %>%
  filter(!is.na(Ingresos_PIB), !is.na(Gastos_PIB)) %>%
  tibble::column_to_rownames("COUNTRY")

## --- Paso B4: estandarizar y elegir k ---
## nrow(tabla_pam_B) debería ser 6 ahora (con Nicaragua corregida). El límite
## k.max = min(10, nrow(tabla_std_B) - 1) sigue vigente para cualquier n que
## resulte, y evita el error "Number of clusters 'k' must be in {1,...,n-1}".
tabla_std_B <- scale(tabla_pam_B)

fviz_nbclust(tabla_std_B, pam, method = "silhouette",
             k.max = min(10, nrow(tabla_std_B) - 1)) +
  labs(title = paste0("Opción B — k óptimo por perfil fiscal centroamericano (n = ", nrow(tabla_std_B), ")"))

## --- Paso B5: correr PAM ---
## IMPORTANTE: revisar el gráfico del Paso B4 antes de fijar k aquí. Con
## n = 6 ya es razonable dejar que la silueta sugiera k = 2 o k = 3 (2 países
## por cluster), en vez de forzar k = 2 como se hizo cuando n = 5. Ajustar el
## valor de k abajo según lo que muestre el gráfico, y documentar por qué se
## elige ese k y no otro.
modelo_pam_B <- pam(tabla_std_B, k = 3, metric = "euclidean", stand = FALSE)

rownames(tabla_pam_B)[modelo_pam_B$id.med]      # país medoide de cada cluster
## [1] "CRI" "GTM" "PAN"
tabla_pam_B$cluster <- factor(modelo_pam_B$clustering)

## --- Paso B6: visualizar ---
fviz_cluster(modelo_pam_B, data = tabla_std_B,
             palette = "jco", ellipse.type = "t", repel = TRUE,
             ggtheme = theme_minimal(),
             main = "Opción B — PAM: perfiles fiscales de Centroamérica (% PIB)")

## --- Paso B7: interpretar medoides ---
## Mismo ajuste que en el Paso A6: solo columnas numéricas antes de round().
kable(round(tabla_pam_B[modelo_pam_B$id.med, c("Ingresos_PIB", "Gastos_PIB", "Deficit_PIB")], 2),
      caption = paste0("Opción B — País medoide de cada cluster (perfil fiscal representativo, n = ", nrow(tabla_pam_B), ")"))
Opción B — País medoide de cada cluster (perfil fiscal representativo, n = 5)
Ingresos_PIB Gastos_PIB Deficit_PIB
CRI 27.62 31.04 -3.42
GTM 15.74 16.72 -0.98
PAN 12.30 18.77 -6.48
## ============================================================
## Limitaciones a declarar en la exposición (ambas opciones)
## ============================================================
## - Opción A: ~20 observaciones y 3 variables; la partición en k
##   clusters puede no ser estable ante pequeños cambios en los datos.
##   Es un ejercicio ilustrativo del pipeline API -> clustering, no
##   un hallazgo estadísticamente robusto.
## - Opción B: la lista 'paises_validos' puede reducirse si algún país
##   no reporta bajo el mismo sector/indicador que El Salvador; eso debe
##   mostrarse como parte del resultado, no ocultarse. En una corrida se
##   perdió Nicaragua por ejecutar B1-B7 fuera de orden en la consola, no
##   por una limitación real de los datos: lección para no confiar en el
##   estado de objetos de sesiones anteriores y siempre correr en secuencia.
## - En ambas opciones, el promedio de años recientes suaviza choques
##   puntuales (crisis, pandemia) pero esconde volatilidad; una extensión
##   razonable sería agregar la desviación estándar como variable adicional.

6 Conclusiones

En conclusión, el análisis de conglomerados constituye una herramienta útil para identificar patrones y similitudes entre observaciones sin establecer previamente grupos determinados. En este sentido, el método PAM permite realizar una clasificación más robusta al utilizar medoides, es decir, observaciones reales del conjunto de datos que representan a cada grupo, reduciendo la influencia de valores extremos.

Su aplicación mediante la distancia euclidiana permite determinar qué observaciones presentan características fiscales similares a partir de las variables seleccionadas. La aplicación práctica en R permitió trasladar estos fundamentos teóricos al análisis de las finanzas públicas.

En el caso de El Salvador, se identificaron los años 2010, 2018 y 2023 como medoides representativos de los conglomerados obtenidos al considerar ingresos, gastos y deuda. Asimismo, en la comparación centroamericana, Costa Rica, Guatemala y Panamá fueron identificados como medoides de los grupos formados. Estos resultados muestran cómo las técnicas de clustering pueden facilitar la identificación exploratoria de diferentes perfiles fiscales, aunque deben interpretarse con cautela debido al número limitado de observaciones y a las características de los datos utilizados.

7 Referencias Bibliograficas

7.1 Videos explicativos

  1. Video complementario sobre clustering. Disponible en: https://youtu.be/OFELCn-6r2o

  2. Video complementario sobre clustering. Disponible en: https://youtu.be/dbzYw9i1KYY

  3. Video complementario sobre clustering. Disponible en: https://youtu.be/B0zDQ3jNtSM

7.2 Fuentes académicas y técnicas

  1. ScienceDirect. Fuente académica sobre K-Medoids, PAM y la comparación con K-Means, incluida la sensibilidad relativa a valores atípicos. Disponible en: https://www.sciencedirect.com/science/article/abs/pii/S095741740800081X

  2. Scikit-learn-extra (documentación oficial, GitHub). Explica la diferencia entre K-Medoids y K-Medians, complejidad computacional y variantes de implementación. Disponible en: https://github.com/scikit-learn-contrib/scikit-learn-extra/blob/main/doc/modules/cluster.rst

  3. Fondo Monetario Internacional (FMI), IMF eLibrary. Ejemplo de investigación del FMI que utiliza un enfoque de agrupamiento para estudiar características económicas entre países. Disponible en: https://www.elibrary.imf.org/view/journals/001/2015/155/article-A001-en.xml

  4. Fondo Monetario Internacional (FMI). IMF Data API — página oficial de recursos de la API SDMX 3.0 utilizada para acceder a los datos de Balanza de Pagos (BOP, agencia IMF.STA). Disponible en: https://data.imf.org/en/Resource-Pages/IMF-API

  5. RPubs. Ejemplo aplicado de clustering en R. Disponible en: https://rpubs.com/maribiua/1042001

  6. AnalyticsLane (2023). Método del codo (Elbow Method) para seleccionar el número óptimo de clústeres en K-Means. Disponible en: https://www.analyticslane.com/2023/06/09/metodo-del-codo-elbow-method-para-seleccionar-el-numero-optimo-de-clusteres-en-k-means/

  7. Velasquez, J. D. Método de la silueta (Silhouette Method). Disponible en: https://jdvelasq.github.io/curso_ml_con_sklearn/46_clustering/03_metodo_de_la_silueta.html

  8. RStudio Pubs (RPubs static). Ejemplo aplicado de clustering en R. Disponible en: https://rstudio-pubs-static.s3.amazonaws.com/1186600_3bd61f62725d425488649f1045ab3626.html