2026-09-28
El análisis de conglomerados (Cluster Analysis) es una técnica estadística multivariante de clasificación que tiene como objetivo agrupar observaciones, individuos, objetos o unidades territoriales en conjuntos homogéneos (conglomerados o clústeres), de modo que los elementos pertenecientes a un mismo grupo sean más similares entre sí que respecto a los elementos de otros grupos.
Según Hair et al. (2019), el análisis de conglomerados es una técnica exploratoria cuyo propósito es identificar estructuras naturales dentro de los datos cuando no existe una clasificación previa establecida.
El análisis de conglomerados, en términos técnicos, busca maximizar hasla lograr diferenciar los criterios:
La homogeneidad intragrupo, es decir, que los elementos dentro de cada conglomerado sean muy similares.
La heterogeneidad intergrupo, es decir, que los conglomerados sean claramente diferentes entre sí.
De acuerdo con Kaufman & Rousseeuw (2009) un buen agrupamiento produce grupos compactos internamente y bien separados externamente.
Es una técnica de interdependencia, ya que no distingue entre variables dependientes e independientes.
Se utiliza fundamentalmente con fines exploratorios.
Permite identificar patrones ocultos en grandes conjuntos de datos.
Puede emplearse con variables cuantitativas, cualitativas o mixtas (según el método utilizado).
En junio de 2024, el grupo de activistas hackers Ciberinteligencia SV, filtro y publico una base de datos en formato XLSX de aprox 104.8MB con la información de 974,428 cotizantes del ISSS, tanto del sector público como privado, En dicho archivo se detalla los nombres completos de los empleados, su DUI y NIT, la empresa a la que laboral, y el salario declarado al ISSS, y su numero telefónico.
Con el conocimiento de la realidad salvadoreña, podriamos considerar posibles escenarios de conglomerados, que podriamos descubrir con esta informaciòn, tal como:
Conglomerados de salarios relacionados a la edad del contribuyente.
Conglomerados de saslarios, relacionados a la ubicación geográfica de la empresa
Conglomerados de salarios, relacionados al domicilio del contribuyente.
Concentración salarial por municipio
Concentración salarial del sector publico.
Y con ello podríamos descubrir cuales son las mejores empresas para trabajar en base al salario. Cual seria nuestra expectativa salarial en base a la experiencia y edad, etc.
Este enfoque busca construir una jerarquía de grupos representada visualmente a través de un árbol binario o dendrograma. No requiere especificar el número de clusters k de antemano; el árbol se puede “cortar” a diferentes alturas para obtener distintas soluciones.
Existen dos estrategias para su construcción (Murtagh & Contreras, 2012) :
Aglomerativo (Bottom-up): Comienza con cada dato como un cluster individual. En cada paso, calcula la matriz de distancias y fusiona de par en par los dos grupos más cercanos (usando criterios de enlace o linkage como el método de Ward, enlace completo o enlace promedio) hasta que todos forman un único grupo raíz (Hastie et al., 2009).
Divisivo (Top-down): Comienza con todos los datos en un único gran grupo y lo va dividiendo sucesivamente de forma binaria.
A diferencia del jerárquico, estos métodos asignan cada dato a un grupo definitivo directamente, sin crear una estructura de árbol. Suelen ser mucho más eficientes computacionalmente para grandes volúmenes de datos.
Particional (K-Means / K-Medoids): Divide el conjunto de datos en exactamente k grupos predefinidos por el usuario. Funciona de manera iterativa: asigna cada punto al centroide más cercano y luego recalcula la posición de los centroides basándose en la media de los puntos asignados, repitiendo el proceso hasta la convergencia (MacQueen, 1967).
Basado en Densidad (DBSCAN / OPTICS): Define los clusters como regiones continuas donde la densidad de puntos es alta, separadas por regiones de baja densidad. Es ideal para identificar formas arbitrarias y gestionar eficazmente el ruido o valores atípicos (outliers), sin necesidad de predefinir el número de grupos (Ester et al., 1996) .
DBSCAN fue propuesto por Ester et al. (1996) como una solución a la dificultad de encontrar conglomerados de forma arbitraria en conjuntos de datos con diferentes densidades y formas.
No obstante, aunque el algoritmo DBSCAN puede encontrar formas no tradicionales en algunas ocasiones puede detectar los mismos clústeres que detectarían otros algoritmos como el k-means Por otro lado, cuando los individuos se encuentran relativamente concentrados es poco probable que DBSCAN pueda construir grupos, como sí lo hacía el algoritmo k-means (Una Introducción a Los Modelos de Clústering Empleando r, n.d.).
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) funciona bajo una premisa intuitiva:
Un grupo (conglomerado) es una región en el espacio donde hay una alta concentración de puntos, separada de otros grupos por zonas vacías o con muy pocos puntos (Sharma, 2025)
DBSA algoritmo de clústering particionado basado en densidad. En general, este tipo de algoritmos basado en densidad se caracteriza por identificar regiones de alta densidad de individuos separadas por regiones de baja densidad en el espacio de datos (Una Introducción a Los Modelos de Clústering Empleando r, n.d.).
A diferencia de K-Means, que asume que todos los grupos son círculos perfectos y requiere que se le indique cuántos buscar, DBSCAN descubre la cantidad de grupos automáticamente y puede encontrar formas geométricas complejas (como lunas entrelazadas o anillos) (Sharma, 2025)
La sintaxis básica para llamar al algoritmo es la siguiente(Hahsler et al., 2019):
En donde(Hahsler et al., 2019) :
x: La matriz o data frame con los datos numéricos. También puede recibir una matriz de distancias precalculada (dist).
eps (ε): El radio de la vecindad. En R, si las variables no están escaladas (con scale()), eps se medirá en las unidades originales de los datos, lo cual puede distorsionar las distancias.
minPts: El número mínimo de puntos requeridos en la vecindad para declarar un “Punto Núcleo” (por defecto es 5).
borderPoints: Un argumento lógico propio de R. Si es TRUE (por defecto), los puntos periféricos se asignan al grupo de su punto núcleo más cercano. Si se cambia a FALSE, R aplica una variante estricta llamada DBSCAN* donde los bordes se consideran directamente ruido.
Agruparemos a los países socios comerciales que participan en el comercio del café para 2023, basándonos en dos variables:
Valor de la exportación (Trade Value en USD).
Peso neto de la mercancía (Net Weight en kg).
Dado que el comercio global tiene economías “gigantes” bien conocidas (como EE. UU. o China) y muchos países pequeños, un enfoque como K-Means fallaría al verse arrastrado por esos extremos (outliers). DBSCAN es ideal aquí porque agrupará a los países con dinámicas comerciales densas y similares, aislando a las superpotencias o anomalías de registro como ruido.
Carga de librerias y seteo de nuestra key para acceder a UN COMTRADE
Extracción de los datos desde COMTRADE para el comercio del café, con datos de 2022-2025:
datos_comtrade <- ct_get_data(
reporter = "all_countries", # Países que reportan
partner = "World", # Destinado al resto del mundo
commodity_code = "0901", # Código HS para Café
flow_direction = "export", # Dirección del flujo comercial
start_date = 2025, # Año de inicio
end_date = 2025 # Año de fin
)
head(datos_comtrade) type_code freq_code ref_period_id ref_year ref_month period reporter_code
1 C A 20250101 2025 52 2025 20
2 C A 20250101 2025 52 2025 24
3 C A 20250101 2025 52 2025 31
4 C A 20250101 2025 52 2025 32
5 C A 20250101 2025 52 2025 36
6 C A 20250101 2025 52 2025 40
reporter_iso reporter_desc flow_code flow_desc partner_code partner_iso
1 AND Andorra X Export 0 W00
2 AGO Angola X Export 0 W00
3 AZE Azerbaijan X Export 0 W00
4 ARG Argentina X Export 0 W00
5 AUS Australia X Export 0 W00
6 AUT Austria X Export 0 W00
partner_desc partner2code partner2iso partner2desc classification_code
1 World 0 W00 World H6
2 World 0 W00 World H6
3 World 0 W00 World H6
4 World 0 W00 World H6
5 World 0 W00 World H6
6 World 0 W00 World H6
classification_search_code is_original_classification cmd_code
1 HS TRUE 0901
2 HS TRUE 0901
3 HS TRUE 0901
4 HS TRUE 0901
5 HS TRUE 0901
6 HS TRUE 0901
cmd_desc
1 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
2 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
3 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
4 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
5 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
6 Coffee, whether or not roasted or decaffeinated; husks and skins; coffee substitutes containing coffee in any proportion
aggr_level is_leaf customs_code customs_desc mos_code mot_code mot_desc
1 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
2 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
3 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
4 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
5 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
6 4 FALSE C00 TOTAL CPC 0 0 TOTAL MOT
qty_unit_code qty_unit_abbr qty is_qty_estimated alt_qty_unit_code
1 8 kg 842.95 FALSE 8
2 -1 N/A 0.00 FALSE -1
3 8 kg 260.90 FALSE 8
4 8 kg 53819.08 FALSE 8
5 8 kg 2515803.19 TRUE -1
6 8 kg 15216909.20 TRUE -1
alt_qty_unit_abbr alt_qty is_alt_qty_estimated net_wgt
1 kg 842.95 FALSE 842.95
2 N/A 0.00 FALSE NA
3 kg 260.90 FALSE 260.90
4 kg 53819.08 FALSE 53819.08
5 N/A 0.00 FALSE 2515803.19
6 N/A 0.00 FALSE 15216909.20
is_net_wgt_estimated gross_wgt is_gross_wgt_estimated cifvalue fobvalue
1 FALSE 0.000 FALSE NA 12195.68
2 FALSE 0.000 FALSE 9065070 9065019.79
3 FALSE 0.000 FALSE NA 7605.11
4 FALSE 0.000 FALSE NA 851339.76
5 TRUE 2656.152 FALSE NA 27708234.07
6 TRUE 0.000 FALSE NA 189286204.15
primary_value legacy_estimation_flag is_reported is_aggregate
1 12195.68 0 FALSE TRUE
2 9065019.79 0 FALSE TRUE
3 7605.11 0 FALSE TRUE
4 851339.76 0 FALSE TRUE
5 27708234.07 6 FALSE TRUE
6 189286204.15 6 FALSE TRUE
# Las columnas clave devueltas ahora son: 'primary_value_usd' y 'net_wgt_kg'.
datos_limpios <- datos_comtrade[
!is.na(datos_comtrade$primary_value) &
!is.na(datos_comtrade$net_wgt) &
datos_comtrade$primary_value > 0 &
datos_comtrade$net_wgt > 0,
]
# Seleccionamos las columnas para el agrupamiento
df_cluster <- datos_limpios[, c("primary_value", "net_wgt")]
head(df_cluster) primary_value net_wgt
1 12195.68 842.95
3 7605.11 260.90
4 851339.76 53819.08
5 27708234.07 2515803.19
6 189286204.15 15216909.20
7 36104304.98 4184959.13
primary_value net_wgt
1 -0.2960031 -0.2512729
3 -0.2960056 -0.2512751
4 -0.2955561 -0.2510737
5 -0.2812495 -0.2418124
6 -0.1951772 -0.1940346
7 -0.2767769 -0.2355335
ggplot(datos_limpios, aes(x = net_wgt, y = primary_value, color = cluster)) +
geom_point(size = 3, alpha = 0.8) +
scale_x_log10() +
scale_y_log10() +
scale_color_manual(
values = c("0" = "grey50", "1" = "#E41A1C", "2" = "#377EB8", "3" = "#4DAF4A"),
labels = c("0" = "Ruido / Potencias Extremas", "1" = "Grupo 1", "2" = "Grupo 2", "3" = "Grupo 3")
) +
labs(
title = "Clustering DBSCAN sobre Datos de Exportación de Café (UN Comtrade)",
subtitle = "Uso integrado de comtradr v1.0+ y dbscan",
x = "Peso Neto (Kg) - Escala Log",
y = "Valor del Comercio (USD) - Escala Log",
color = "Asignación"
) +
theme_minimal()# Ver la lista de países atípicos aislados por DBSCAN
paises_ruido <- datos_limpios[datos_limpios$cluster == 0, c("reporter_desc", "primary_value", "net_wgt")]
paises_grupo1 <- datos_limpios[datos_limpios$cluster == 1, c("reporter_desc", "primary_value", "net_wgt")]
paises_ruido <- paises_ruido[order(paises_ruido$primary_value), ]
paises_ruido$ValorAgregadoUnitario <-paises_ruido$primary_value/paises_ruido$net_wgt
paises_grupo1 <- paises_grupo1[order(paises_grupo1$primary_value), ]
paises_grupo1$ValorAgregadoUnitario <-paises_grupo1$primary_value/paises_grupo1$net_wgt
head(paises_ruido) reporter_desc primary_value net_wgt ValorAgregadoUnitario
88 Switzerland 4231831250 112246136 37.701353
34 Germany 5554082283 605293037 9.175857
20 Colombia 5964753544 752054025 7.931283
12 Brazil 14918395321 2274254323 6.559686
reporter_desc primary_value net_wgt ValorAgregadoUnitario
13 Belize 201.750 5.128 39.342824
38 Guyana 602.470 104.490 5.765815
86 South Sudan 1500.000 3000.000 0.500000
32 French Polynesia 2985.109 173.833 17.172280
52 Kyrgyzstan 4239.000 151.000 28.072848
73 Paraguay 4335.620 215.000 20.165674
# Ordenar de mayor a menor valor agregado antes de graficar
paises_ruido <- paises_ruido[order(-paises_ruido$ValorAgregadoUnitario), ]
# Gráfica de barras horizontales para el Ruido
ggplot(paises_ruido, aes(x = reorder(reporter_desc, ValorAgregadoUnitario), y = ValorAgregadoUnitario)) +
geom_col(fill = "grey40", width = 0.7) +
coord_flip() + # Voltea el gráfico para leer los nombres de los países fácilmente
labs(
title = "Valor Agregado Unitario: Potencias Comerciales (Ruido DBSCAN)",
subtitle = "Periodo multianual analizado",
x = "País",
y = "Valor Agregado por Kilo (USD / Kg)"
) +
theme_minimal()# 1. Ordenar de mayor a menor valor agregado
paises_grupo1 <- paises_grupo1[order(-paises_grupo1$ValorAgregadoUnitario), ]
# 2. Seleccionar únicamente los primeros 15 países (El Top 15)
top15_grupo1 <- head(paises_grupo1, 15)
# 3. Gráfica de barras horizontales para el Grupo 1
ggplot(top15_grupo1, aes(x = reorder(reporter_desc, ValorAgregadoUnitario), y = ValorAgregadoUnitario)) +
geom_col(fill = "#E41A1C", width = 0.7) +
coord_flip() +
labs(
title = "Top 15 Países con Mayor Valor Agregado (Grupo 1)",
subtitle = "Exportadores estándar con mayor precio promedio por kilogramo",
x = "País",
y = "Valor Agregado por Kilo (USD / Kg)"
) +
theme_minimal()Si estamos aplicando el algoritmo DBSCAN en R sobre datos de exportaciones y configuramos un radio de vecindad (eps) extremadamente pequeño, ¿qué impacto visual y matemático sufren los datos de los países clasificados? ¿Qué pasaría si el eps fuera demasiado grande?
Un eps muy pequeño fragmentará los grupos densos y enviará a la mayoría de los países al grupo 0 (ruido). Un eps muy grande fusionará todo el mapa de puntos en un único gran cluster masivo, perdiendo la capacidad de aislar outliers.
Al ejecutar la función kNNdistplot() en R para calibrar nuestro modelo de comercio, observamos una curva que tiene una llanura muy pegada a cero y luego un acantilado vertical. ¿Cómo se interpreta esa llanura plana desde la perspectiva de densidad de los países y en qué parte exacta debemos trazar la línea horizontal con abline()?
La llanura indica que la inmensa mayoría de los países están masificados a distancias muy cortas (alta densidad). La línea de abline(h = …) debe cruzarse justo en el punto de inflexión (“la rodilla”) donde la curva empieza a levantarse, que es el límite matemático de la densidad estable.
En nuestro script de R combinamos variables monetarias (USD) y de volumen físico (Kilogramos). Si elimináramos por completo la instrucción scale() del código antes de correr el modelo, ¿por qué fallaría el cálculo de las distancias euclidianas de DBSCAN?
Como las cifras de dólares y kilos difieren por millones de unidades, la variable con el rango numérico más grande dominaría de forma absoluta el cálculo de la distancia, dándonos una medición distorsionada a la vista.
Cuando revisamos los resultados de DBSCAN en R y vemos que algunos países tienen asignado el número 0, ¿qué nos está diciendo el algoritmo sobre el comportamiento de esos países? ¿Están agrupados o aislados
Significa que esos países son ruido o valores atípicos (outliers). No tienen suficientes vecinos cerca para formar un grupo, por lo que el algoritmo los dejó aislados
En el algoritmo DBSCAN configuramos el parámetro minPts = 5. ¿Qué significa este número en la vida real cuando el algoritmo dibuja un círculo alrededor de un país para decidir si puede formar un grupo
Significa que se necesitan como mínimo 5 países dentro de ese círculo (contando al del centro) para considerar que esa zona es lo suficientemente densa como para abrir o continuar un grupo comercial.