Método K-Medianas (PAM): técnica de agrupamiento no supervisado utilizada para formar grupos de observaciones con características relativamente similares.
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:
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.
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.
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.
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:
El método no produce como resultado principal un árbol jerárquico. Su resultado es una partición de las observaciones en grupos.
| 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.
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.
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:
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.
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.
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:
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:
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.
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.
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")| 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 |
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).
## [1] 3 4
## [1] 7 4
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).
Con \(C1=(3,4)\):
Con \(C2=(7,4)\):
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).
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")| 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 |
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.
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.)
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.
Con \(O=(7,3)\):
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.
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")| 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 |
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.
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\]
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}\]
\[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.
## [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."
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")| id | x | y |
|---|---|---|
| X1 | 2 | 6 |
| X2 | 3 | 4 |
| X3 | 3 | 8 |
| X4 | 4 | 7 |
| 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 |
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
## 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.
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.
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.
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.
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.
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 |
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.
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.
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:
Esta limitación también existe en los demás métodos, aunque cada uno presenta sensibilidades distintas.
Antes de aplicar PAM a una base real del FMI, se recomienda revisar cuidadosamente:
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.
library(dplyr)
library(tidyr)
library(tibble)
library(cluster)
library(factoextra)
library(ggplot2)
library(knitr)
library(kableExtra)
library(ggrepel) 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
)
}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.
# 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)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.
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.
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)| 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 |
# 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")Análisis de 8 países según nivel y volatilidad de la cuenta corriente — BOP/FMI, 2015–2024
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.
Está formado por Guatemala.
Su medoide es Guatemala. Es un grupo que PAM aísla por presentar un perfil propio dentro de la muestra.
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.
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)
)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.
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.
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)| 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 |
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)| 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 |
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)# 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"
)# 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
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)")| 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), ")"))| 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.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.
Video complementario sobre clustering. Disponible en: https://youtu.be/OFELCn-6r2o
Video complementario sobre clustering. Disponible en: https://youtu.be/dbzYw9i1KYY
Video complementario sobre clustering. Disponible en: https://youtu.be/B0zDQ3jNtSM
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
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
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
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
RPubs. Ejemplo aplicado de clustering en R. Disponible en: https://rpubs.com/maribiua/1042001
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/
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
RStudio Pubs (RPubs static). Ejemplo aplicado de clustering en R. Disponible en: https://rstudio-pubs-static.s3.amazonaws.com/1186600_3bd61f62725d425488649f1045ab3626.html