UNIVERSIDAD DE EL SALVADOR
FACULTAD DE CIENCIAS ECONÓMICAS
ESCUELA DE ECONOMÍA
CICLO II-2025




Tema:

Análisis de Clúster (Conglomerados)


Asignatura:

Métodos para el Análisis Económico



Grupo Teórico:

02



Docente:

MSF. Carlos Ademir Pérez Alas



Integrante:

Yasira Yannel Medrano Carbajal (MC22122)


CIUDAD UNIVERSITARIA, 17 DE NOVIEMBRE 2025

1. ¿En qué consiste el análisis de conglomerados?

El Análisis Cluster, también conocido como Análisis de Conglomerados, es una técnica estadística multivariante utilizada para agrupar observaciones según su similitud, de manera que los elementos dentro de un mismo grupo sean lo más homogéneos posible y, al mismo tiempo, los distintos grupos sean lo más diferentes entre sí. Es un método de aprendizaje no supervisado, ya que no requiere una variable dependiente, sino que identifica patrones y estructuras internas en los datos. Este procedimiento se basa en medidas de distancia o similitud (como la distancia euclidiana), lo que permite detectar estructuras naturales en los datos y clasificar elementos según su cercanía geométrica. El análisis de conglomerados tiene amplia aplicación en áreas como marketing, ciencias sociales, biología, economía y análisis de datos en general. Sin embargo, presenta algunas limitaciones: es una técnica descriptiva y no inferencial, por lo que no permite realizar generalizaciones estadísticas sobre una población. Además, su resultado depende fuertemente de las variables seleccionadas, la escala de medición y el método utilizado para agrupar.

En resumen, el análisis de conglomerados es una herramienta exploratoria que permite identificar grupos naturales en los datos y facilitar la interpretación de estructuras complejas.

2. Elabore un cuadro comparativo.

Se presentara un cuadro comparativo para los diferentes tipos de análisis de cluster:

library(knitr)
library(kableExtra)

# Contenido (texto HTML para viñetas; escape = FALSE al imprimir)
tabla <- data.frame(
  `Análisis de Clúster` = c(
    paste0("*Jerárquico*<br>",
           "Método que construye una jerarquía de grupos (dendrograma). ",
           "No requiere especificar k de antemano; puede ser aglomerativo (bottom-up) o divisivo (top-down)."),
    paste0("*No jerárquico*<br>",
           "Métodos que particionan el conjunto en k grupos predefinidos por el analista; rápidos para grandes datos.")
  ),
  `Técnicas Disponibles` = c(
    paste0("<ul>",
           "<li>Aglomerativo (AGNES)</li>",
           "<li>Divisivo (DIANA)</li>",
           "</ul>"),
    paste0("<ul>",
           "<li>K-means</li>",
           "<li>K-medoids (PAM)</li>",
           "<li>CLARA (para conjuntos grandes)</li>",
           "</ul>")
  ),
  `Ventajas` = c(
    paste0("<ul>",
           "<li>Produce dendrogramas útiles para explorar la estructura jerárquica.</li>",
           "<li>No necesita k inicial.</li>",
           "<li>Útil para identificar clusters pequeños/estructurados.</li>",
           "</ul>"),
    paste0("<ul>",
           "<li>Rápidos y escalables (especialmente k-means).</li>",
           "<li>K-means: simple e interpretables por centroides.</li>",
           "<li>PAM/CLARA: más robustos a outliers (PAM) y adaptados a grandes datos (CLARA).</li>",
           "</ul>")
  ),
  `Desventajas` = c(
    paste0("<ul>",
           "<li>Decisiones irreversibles (aglomerativo): no se deshacen fusiones erróneas.</li>",
           "<li>Costoso computacionalmente en datasets grandes.</li>",
           "<li>Interpretación del corte del dendrograma puede ser subjetiva.</li>",
           "</ul>"),
    paste0("<ul>",
           "<li>Requieren especificar k a priori (salvo que se use criterios para elegirlo).</li>",
           "<li>Sensibles a semillas iniciales y a outliers (k-means especialmente).</li>",
           "<li>Resultados pueden variar si se reordena/transforma la data.</li>",
           "</ul>")
  ),
  stringsAsFactors = FALSE
)

# Imprimir la tabla con encabezado en rosa (fila 0)
kbl(tabla, escape = FALSE, caption = "Resumen de Análisis de Clúster") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, background = "#FFC0CB", color = "black", bold = TRUE) %>%   # header rosa
  column_spec(1, width = "32em") %>%
  column_spec(2, width = "18em") %>%
  column_spec(3, width = "22em") %>%
  column_spec(4, width = "22em") %>%
  add_footnote("Fuente: Elaboración propia (basado en Kassambara, 2017).",
               notation = "none", escape = FALSE)
Resumen de Análisis de Clúster
Análisis.de.Clúster Técnicas.Disponibles Ventajas Desventajas
Jerárquico
Método que construye una jerarquía de grupos (dendrograma). No requiere especificar k de antemano; puede ser aglomerativo (bottom-up) o divisivo (top-down).
  • Aglomerativo (AGNES)
  • Divisivo (DIANA)
  • Produce dendrogramas útiles para explorar la estructura jerárquica.
  • No necesita k inicial.
  • Útil para identificar clusters pequeños/estructurados.
  • Decisiones irreversibles (aglomerativo): no se deshacen fusiones erróneas.
  • Costoso computacionalmente en datasets grandes.
  • Interpretación del corte del dendrograma puede ser subjetiva.
No jerárquico
Métodos que particionan el conjunto en k grupos predefinidos por el analista; rápidos para grandes datos.
  • K-means
  • K-medoids (PAM)
  • CLARA (para conjuntos grandes)
  • Rápidos y escalables (especialmente k-means).
  • K-means: simple e interpretables por centroides.
  • PAM/CLARA: más robustos a outliers (PAM) y adaptados a grandes datos (CLARA).
  • Requieren especificar k a priori (salvo que se use criterios para elegirlo).
  • Sensibles a semillas iniciales y a outliers (k-means especialmente).
  • Resultados pueden variar si se reordena/transforma la data.
Fuente: Elaboración propia (basado en Kassambara, 2017).

3. Describa las técnicas disponibles para realizar el análisis de clúster, tanto jerárquicas como no jerárquicas, presentadas en el cuadro anterior, incluya una explicación de la librería y sintaxis para implementarla en R.

3.1 Descripción de las Técnicas de Análisis de Conglomerados

El análisis de clúster puede realizarse mediante dos enfoques principales: métodos jerárquicos y métodos no jerárquicos, cada uno con algoritmos especializados que permiten agrupar observaciones según su similitud. Toda la información presentada se basa exclusivamente en Kassambara (2017).

A. Métodos Jerárquicos (HCA – Hierarchical Cluster Analysis)

Los métodos jerárquicos construyen una estructura anidada de agrupamientos representada mediante un dendrograma, sin necesidad de definir de antemano cuántos grupos se formarán (Kassambara, 2017).

  1. Aglomerativo (AGNES)

Proceso ascendente (bottom-up):

Cada observación inicia como un clúster individual.

En cada iteración se fusionan los dos clústeres más similares.

Finaliza cuando todas las observaciones forman un solo clúster.

Es especialmente útil para detectar grupos pequeños (Kassambara, 2017).

Funciones en R:

hclust() del paquete stats

agnes() del paquete cluster

  1. Divisivo (DIANA)

Proceso descendente (top-down):

Todas las observaciones comienzan en un único clúster.

Se divide de manera sucesiva el grupo más heterogéneo.

Adecuado para identificar grupos grandes (Kassambara, 2017).

Función en R:

diana() del paquete cluster

B. Métodos No Jerárquicos (Particionales)

Estos métodos dividen los datos en k clústeres predefinidos, buscando maximizar la homogeneidad dentro de cada grupo (Kassambara, 2017).

  1. K-means

Es el método particional más popular. Busca:

Minimizar la suma de distancias al cuadrado entre observaciones y sus centroides.

Formar clústeres compactos y esféricos.

Función en R:

kmeans() (paquete base stats)

  1. K-Medoids (PAM)

Método más robusto que K-means porque usa medoides, que son observaciones reales del dataset. Es menos sensible a valores atípicos (Kassambara, 2017).

Función en R:

pam() del paquete cluster

  1. CLARA (Clustering Large Applications)

Adaptación de PAM para grandes volúmenes de datos. Trabaja con muestras del dataset para reducir el costo computacional (Kassambara, 2017).

Función en R:

clara() del paquete cluster

3.2 Librerías y Sintaxis para Implementación en R

A continuación se presenta el código esencial para implementar cada uno de los métodos, basado en Kassambara (2017). 1. Métodos Jerárquicos — AGNES:

# Método Jerárquico Aglomerativo
datos <- iris[, 1:4]  # Solo columnas numéricas

# Matriz de distancias Euclideana
matriz_distancia <- dist(datos, method = "euclidean")

# Clustering aglomerativo con Ward
resultado_agnes <- hclust(matriz_distancia, method = "ward.D2")

# Dendrograma
plot(resultado_agnes, main = "Dendrograma - Método Aglomerativo (Ward)")

# Cortar dendrograma en 3 grupos
clusters_agnes <- cutree(resultado_agnes, k = 3)
table(clusters_agnes)
## clusters_agnes
##  1  2  3 
## 50 64 36

Explicación:

dist(datos, method = “euclidean”): calcula la distancia entre observaciones usando la distancia Euclideana.

hclust(matriz_distancia, method = “ward.D2”): realiza clustering jerárquico aglomerativo usando el método de Ward para minimizar la varianza dentro de los clusters.

plot(): dibuja el dendrograma mostrando cómo se agrupan las observaciones.

cutree(…, k = 3): corta el dendrograma en 3 clusters y asigna cada observación a uno de ellos

  1. Método Jerárquico Divisivo (DIANA)
# Cargar librería
library(cluster)

datos <- iris[, 1:4]

# Clustering divisivo
resultado_diana <- diana(datos)

# Dendrograma
plot(resultado_diana, main = "Dendrograma - Método Divisivo (DIANA)")

# Cortar en 3 grupos
clusters_diana <- cutree(as.hclust(resultado_diana), k = 3)
table(clusters_diana)
## clusters_diana
##  1  2  3 
## 53 60 37

Explicación:

diana(datos): realiza clustering jerárquico divisivo, partiendo de un cluster grande que se va dividiendo.

plot(): dibuja el dendrograma del clustering divisivo.

as.hclust(resultado_diana): convierte el objeto DIANA a formato hclust para poder usar cutree.

cutree(…, k = 3): corta el dendrograma en 3 clusters y asigna cada observación.

  1. K-means
datos <- iris[, 1:4]

set.seed(123)  # Para reproducibilidad

# K-means con 3 clusters
resultado_kmeans <- kmeans(datos, centers = 3, nstart = 25)

# Clusters asignados
resultado_kmeans$cluster
##   [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [75] 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 3 3 3 3 2 3 3 3 3
## [112] 3 3 2 2 3 3 3 3 2 3 2 3 2 3 3 2 2 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 3 3 3 2 3
## [149] 3 2
# Centroides
resultado_kmeans$centers
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1     5.006000    3.428000     1.462000    0.246000
## 2     5.901613    2.748387     4.393548    1.433871
## 3     6.850000    3.073684     5.742105    2.071053

Explicación:

kmeans(datos, centers = 3, nstart = 25): realiza clustering particional en 3 clusters, probando 25 inicializaciones aleatorias y eligiendo la mejor.

$cluster: vector con el cluster asignado a cada observación.

$centers: coordenadas de los centroides de cada cluster.

  1. K-Medoids (PAM)
library(cluster)

datos <- iris[, 1:4]

# PAM con k = 3
resultado_pam <- pam(datos, k = 3)

# Clusters asignados
resultado_pam$clustering
##   [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [75] 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 3 3 3 3 2 3 3 3 3
## [112] 3 3 2 2 3 3 3 3 2 3 2 3 2 3 3 2 2 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 3 3 3 2 3
## [149] 3 2
# Medoids
resultado_pam$medoids
##      Sepal.Length Sepal.Width Petal.Length Petal.Width
## [1,]          5.0         3.4          1.5         0.2
## [2,]          6.0         2.9          4.5         1.5
## [3,]          6.8         3.0          5.5         2.1

Explicación:

pam(datos, k = 3): realiza clustering basado en medoids, más robusto frente a valores atípicos que K-means.

$clustering: cluster asignado a cada observación.

$medoids: observaciones centrales que representan cada cluster.

  1. CLARA
library(cluster)

datos <- iris[, 1:4]

# CLARA con k = 3 y 5 muestras
resultado_clara <- clara(datos, k = 3, samples = 5)

# Clusters asignados
resultado_clara$clustering
##   [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [75] 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 3 3 3 3 2 3 3 3 3
## [112] 3 3 2 3 3 3 3 3 2 3 2 3 2 3 3 2 2 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 3 3 3 2 3
## [149] 3 2
# Medoids
resultado_clara$medoids
##      Sepal.Length Sepal.Width Petal.Length Petal.Width
## [1,]          5.1         3.5          1.4         0.2
## [2,]          6.1         3.0          4.6         1.4
## [3,]          6.8         3.0          5.5         2.1

Explicación:

clara(datos, k = 3, samples = 5): versión de PAM para datasets grandes, toma 5 muestras aleatorias para mayor eficiencia y encuentra 3 clusters.

$clustering: cluster asignado a cada observación.

$medoids: medoi

4. Del texto: Kassambara, A. (2017). Practical Guide to Cluster Analysis in R: Unsupervised Machine Learning (Multivariate Analysis) (1st ed.). STHDA, disponible en: https://xsliulab.github.io/Workshop/week10/r-cluster-book.pdf, desarrolle los ejemplos presentados en los capítulos: 4,5,6,7,8,9.

Capitulo 4:

# ===========================
# Capítulo 4 – K-Means Clustering
# ===========================

# 1. Cargar librerías necesarias
library(ggplot2)
library(factoextra)
library(kableExtra)
library(ggrepel)
library(knitr)

# 2. Cargar dataset
data("USArrests")
df <- scale(USArrests)
df <- as.data.frame(df)  # Convertir a data frame
head(df, 3) %>%
  kable(caption = "Primeras 3 filas de USArrests escalado", digits = 2) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Primeras 3 filas de USArrests escalado
Murder Assault UrbanPop Rape
Alabama 1.24 0.78 -0.52 0.00
Alaska 0.51 1.11 -1.21 2.48
Arizona 0.07 1.48 1.00 1.04
# 3. Mostrar las primeras 3 filas antes de escalar
head(USArrests, 3) %>%
  kable(caption = "Primeras 3 filas de USArrests", digits = 2) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Primeras 3 filas de USArrests
Murder Assault UrbanPop Rape
Alabama 13.2 236 58 21.2
Alaska 10.0 263 48 44.5
Arizona 8.1 294 80 31.0
# 4. Selección del número óptimo de clusters (Método del codo)
fviz_nbclust(df, kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2) +
  labs(subtitle = "Método del codo para determinar k óptimo")

# 5. Ejecución de K-means con k = 4 clusters y 25 inicializaciones aleatorias
set.seed(123)
km.res <- kmeans(df, centers = 4, nstart = 25)

# 6. Resultados completos de K-means
print(km.res)
## K-means clustering with 4 clusters of sizes 8, 13, 16, 13
## 
## Cluster means:
##       Murder    Assault   UrbanPop        Rape
## 1  1.4118898  0.8743346 -0.8145211  0.01927104
## 2 -0.9615407 -1.1066010 -0.9301069 -0.96676331
## 3 -0.4894375 -0.3826001  0.5758298 -0.26165379
## 4  0.6950701  1.0394414  0.7226370  1.27693964
## 
## Clustering vector:
##        Alabama         Alaska        Arizona       Arkansas     California 
##              1              4              4              1              4 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              4              3              3              4              1 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              3              2              4              3              2 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              3              2              1              2              4 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              3              4              2              1              4 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              2              2              4              2              3 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              4              4              1              2              3 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              3              3              3              3              1 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              2              1              4              3              2 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              3              3              2              2              3 
## 
## Within cluster sum of squares by cluster:
## [1]  8.316061 11.952463 16.212213 19.922437
##  (between_SS / total_SS =  71.2 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
# 7. Media de cada variable por conglomerados
aggregate(USArrests, by = list(cluster = km.res$cluster), mean) %>%
  kable(caption = "Media de cada variable por conglomerados",
        align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Media de cada variable por conglomerados
cluster Murder Assault UrbanPop Rape
1 13.93750 243.62500 53.75000 21.41250
2 3.60000 78.53846 52.07692 12.17692
3 5.65625 138.87500 73.87500 18.78125
4 10.81538 257.38461 76.00000 33.19231
# 8. Agregar cluster a los datos originales
dd <- cbind(USArrests, cluster = km.res$cluster)
head(dd) %>%
  kable(caption = "Clasificaciones de Puntuaciones",
        align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Clasificaciones de Puntuaciones
Murder Assault UrbanPop Rape cluster
Alabama 13.2 236 58 21.2 1
Alaska 10.0 263 48 44.5 4
Arizona 8.1 294 80 31.0 4
Arkansas 8.8 190 50 19.5 1
California 9.0 276 91 40.6 4
Colorado 7.9 204 78 38.7 4
# 9. Número de cluster asignado a cada observación
km.res$cluster
##        Alabama         Alaska        Arizona       Arkansas     California 
##              1              4              4              1              4 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              4              3              3              4              1 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              3              2              4              3              2 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              3              2              1              2              4 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              3              4              2              1              4 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              2              2              4              2              3 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              4              4              1              2              3 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              3              3              3              3              1 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              2              1              4              3              2 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              3              3              2              2              3
# 10. Tamaño de cada cluster
km.res$size
## [1]  8 13 16 13
# 11. Medios de cada cluster
km.res$centers %>%
  kable(caption = "Medios de cluster",
        align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Medios de cluster
Murder Assault UrbanPop Rape
1.411890 0.874335 -0.814521 0.019271
-0.961541 -1.106601 -0.930107 -0.966763
-0.489438 -0.382600 0.575830 -0.261654
0.695070 1.039441 0.722637 1.276940
# 12. Visualización de clusters usando PCA
fviz_cluster(km.res, data = df,
             palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
             ellipse.type = "norm", 
             ggtheme = theme_minimal())

Capitulo 5:

# Capítulo 5: K-Medoids / PAM
# ===========================================

# 1. Cargar librerías y datos
library(cluster)
library(factoextra)
library(knitr)
library(kableExtra)

data("USArrests")
df <- scale(USArrests)

# Primeras 3 filas del dataset escalado
kable(head(df, 3), 
      caption = "Primeras 3 filas de USArrests escalado", 
      digits = 2) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, color = "black", background = "pink")
Primeras 3 filas de USArrests escalado
Murder Assault UrbanPop Rape
Alabama 1.24 0.78 -0.52 0.00
Alaska 0.51 1.11 -1.21 2.48
Arizona 0.07 1.48 1.00 1.04
# 2. Estimación del número óptimo de clusters con silhouette
fviz_nbclust(df, pam, method = "silhouette") +
  theme_classic()

# 3. Aplicación de PAM con k = 2 clusters
set.seed(123)
pam.res <- pam(df, k = 2)
print(pam.res)
## Medoids:
##            ID     Murder    Assault   UrbanPop       Rape
## New Mexico 31  0.8292944  1.3708088  0.3081225  1.1603196
## Nebraska   27 -0.8008247 -0.8250772 -0.2445636 -0.5052109
## Clustering vector:
##        Alabama         Alaska        Arizona       Arkansas     California 
##              1              1              1              2              1 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              1              2              2              1              1 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              2              2              1              2              2 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              2              2              1              2              1 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              2              1              2              1              1 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              2              2              1              2              2 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              1              1              1              2              2 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              2              2              2              2              1 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              2              1              1              2              2 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              2              2              2              2              2 
## Objective function:
##    build     swap 
## 1.441358 1.368969 
## 
## Available components:
##  [1] "medoids"    "id.med"     "clustering" "objective"  "isolation" 
##  [6] "clusinfo"   "silinfo"    "diss"       "call"       "data"
# 4. Agregar cluster a los datos originales
dd <- cbind(USArrests, cluster = pam.res$clustering)
kable(head(dd, 3),
      caption = "Clasificaciones de puntuaciones con PAM",
      digits = 2) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, color = "black", background = "pink")
Clasificaciones de puntuaciones con PAM
Murder Assault UrbanPop Rape cluster
Alabama 13.2 236 58 21.2 1
Alaska 10.0 263 48 44.5 1
Arizona 8.1 294 80 31.0 1
# 5. Resultados principales de PAM
# Medoids
pam.res$medoids %>% 
  kable(caption = "Medoids de cada cluster",
        digits = 3) %>%
  kable_classic_2(html_font = "Luminari",
                  lightable_options = c("basic","hover","striped")) %>%
  row_spec(0, bold = TRUE, color = "black", background = "pink")
Medoids de cada cluster
Murder Assault UrbanPop Rape
New Mexico 0.829 1.371 0.308 1.160
Nebraska -0.801 -0.825 -0.245 -0.505
# Número de cluster de las primeras observaciones
head(pam.res$clustering)
##    Alabama     Alaska    Arizona   Arkansas California   Colorado 
##          1          1          1          2          1          1
# Tamaño de clusters
pam.res$clusinfo[, "size"]
## [1] 20 30
# 6. Visualización de clusters PAM
fviz_cluster(pam.res,
             palette = c("#8B3626", "#CD6889"), # color palette
             ellipse.type = "t", # Concentration ellipse repel = TRUE, # Avoid label overplotting (slow)
             ggtheme = theme_classic()
             )

Capitulo 6:

# -------------------------------
# Capítulo 6: CLARA - Clustering Large Applications
# -------------------------------

# 1. Librerías necesarias
library(knitr)
library(kableExtra)
library(cluster)
library(factoextra)

# 2. Reproducir datos aleatorios
set.seed(1234)
df_CLARA <- rbind(
  cbind(rnorm(200, 0, 8), rnorm(200, 0, 8)),
  cbind(rnorm(300, 50, 8), rnorm(300, 50, 8))
)
colnames(df_CLARA) <- c("x", "y")
rownames(df_CLARA) <- paste0("S", 1:nrow(df_CLARA))

# 3. Vista previa de los datos
head(df_CLARA, 10) %>% 
  kable(caption = "Datos Aleatorios", align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari", lightable_options = c("basic","hover", "striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Datos Aleatorios
x y
S1 -9.656526 3.881815
S2 2.219434 5.574150
S3 8.675529 1.484111
S4 -18.765582 5.605868
S5 3.432998 2.493448
S6 4.048447 6.083699
S7 -4.597920 14.739709
S8 -4.373055 8.898903
S9 -4.515616 0.261312
S10 -7.120303 -8.915592
# 4. Estimación del número óptimo de clusters (usar PAM como proxy)
fviz_nbclust(df_CLARA, FUNcluster = function(x, k) pam(x, k), method = "silhouette") +
  theme_classic()

# 5. Calcular CLARA
clara.res <- clara(df_CLARA, k = 2, samples = 50, pamLike = TRUE)

# 6. Resultados de CLARA
print(clara.res)
## Call:     clara(x = df_CLARA, k = 2, samples = 50, pamLike = TRUE) 
## Medoids:
##              x         y
## S121 -1.531137  1.145057
## S455 48.357304 50.233499
## Objective function:   9.87862
## Clustering vector:    Named int [1:500] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ...
##  - attr(*, "names")= chr [1:500] "S1" "S2" "S3" "S4" "S5" "S6" "S7" ...
## Cluster sizes:            200 300 
## Best sample:
##  [1] S37  S49  S54  S63  S68  S71  S76  S80  S82  S101 S103 S108 S109 S118 S121
## [16] S128 S132 S138 S144 S162 S203 S210 S216 S231 S234 S249 S260 S261 S286 S299
## [31] S304 S305 S312 S315 S322 S350 S403 S450 S454 S455 S456 S465 S488 S497
## 
## Available components:
##  [1] "sample"     "medoids"    "i.med"      "clustering" "objective" 
##  [6] "clusinfo"   "diss"       "call"       "silinfo"    "data"
# 7. Clasificaciones de los puntos
dd_CLARA <- cbind(df_CLARA, cluster = clara.res$cluster)
head(dd_CLARA, 4) %>% 
  kable(caption = "Clasificaciones de Puntos", align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari", lightable_options = c("basic","hover", "striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Clasificaciones de Puntos
x y cluster
S1 -9.656526 3.881815 1
S2 2.219434 5.574150 1
S3 8.675529 1.484111 1
S4 -18.765582 5.605868 1
# 8. Medoids
clara.res$medoids %>% 
  kable(caption = "Medoides", align = "c", digits = 6) %>%
  kable_classic_2(html_font = "Luminari", lightable_options = c("basic","hover", "striped")) %>%
  row_spec(0, bold = TRUE, monospace = TRUE, color = "black", background = "pink")
Medoides
x y
S121 -1.531137 1.145057
S455 48.357304 50.233499
# 9. Primeros 10 elementos de clustering
head(clara.res$clustering, 10)
##  S1  S2  S3  S4  S5  S6  S7  S8  S9 S10 
##   1   1   1   1   1   1   1   1   1   1
# 10. Visualización de clústeres CLARA
fviz_cluster(clara.res,
             palette = c("pink", "turquoise"), # paleta de colores
             ellipse.type = "t",               # elipse de concentración
             geom = "point", pointsize = 1,
             ggtheme = theme_linedraw()
)

Capitulo 7:

# Capítulo 7: Clustering Aglomerativo (AGNES)
# -------------------------------------------

library(knitr)
data("USArrests")
df <- scale(USArrests)
kable(head(df, 6), digits = 4)
Murder Assault UrbanPop Rape
Alabama 1.2426 0.7828 -0.5209 -0.0034
Alaska 0.5079 1.1068 -1.2118 2.4842
Arizona 0.0716 1.4788 0.9990 1.0429
Arkansas 0.2323 0.2309 -1.0736 -0.1849
California 0.2783 1.2628 1.7589 2.0678
Colorado 0.0257 0.3989 0.8608 1.8650
library(knitr)
res.dist <- dist(df, method = "euclidean")
kable(as.matrix(res.dist)[1:6, 1:6], digits = 4)
Alabama Alaska Arizona Arkansas California Colorado
Alabama 0.0000 2.7038 2.2935 1.2898 3.2631 2.6511
Alaska 2.7038 0.0000 2.7006 2.8260 3.0125 2.3265
Arizona 2.2935 2.7006 0.0000 2.7178 1.3105 1.3650
Arkansas 1.2898 2.8260 2.7178 0.0000 3.7636 2.8311
California 3.2631 3.0125 1.3105 3.7636 0.0000 1.2876
Colorado 2.6511 2.3265 1.3650 2.8311 1.2876 0.0000
res.hc<-hclust(d=res.dist,method="ward.D2")
# cex:labelsize
library("factoextra")
fviz_dend(res.hc,cex=0.5)

# Compute cophentic distance
res.coph <- cophenetic(res.hc)

# Correlation between cophenetic distance and
# the original distance
cor(res.dist, res.coph)
## [1] 0.6975266
res.hc2 <- hclust(res.dist, method = "average")
cor(res.dist, cophenetic(res.hc2))
## [1] 0.7180382
# Cut tree into 4 groups
grp <- cutree(res.hc, k=4)
head(grp, n=4)
##  Alabama   Alaska  Arizona Arkansas 
##        1        2        2        3
# Number of members in each cluster
table(grp)
## grp
##  1  2  3  4 
##  7 12 19 12
# Get the names for the members of cluster 1
rownames(df)[grp == 1]
## [1] "Alabama"        "Georgia"        "Louisiana"      "Mississippi"   
## [5] "North Carolina" "South Carolina" "Tennessee"
# Cut in 4 groups and color by groups
fviz_dend(res.hc, k=4, # Cut in four groups
          cex = 0.5, # label size
          k_colors = c("#698B22", "#FF82AB", "#CD5555", "#9B30FF"),
          color_labels_by_k = TRUE, # color labels by groups
          rect = TRUE # Add rectangle around groups
          )

library(ggplot2)
library(ggrepel)
library(factoextra)
library(dplyr)

# Datos
df_numeric <- as.data.frame(scale(USArrests))
rownames(df_numeric)
##  [1] "Alabama"        "Alaska"         "Arizona"        "Arkansas"      
##  [5] "California"     "Colorado"       "Connecticut"    "Delaware"      
##  [9] "Florida"        "Georgia"        "Hawaii"         "Idaho"         
## [13] "Illinois"       "Indiana"        "Iowa"           "Kansas"        
## [17] "Kentucky"       "Louisiana"      "Maine"          "Maryland"      
## [21] "Massachusetts"  "Michigan"       "Minnesota"      "Mississippi"   
## [25] "Missouri"       "Montana"        "Nebraska"       "Nevada"        
## [29] "New Hampshire"  "New Jersey"     "New Mexico"     "New York"      
## [33] "North Carolina" "North Dakota"   "Ohio"           "Oklahoma"      
## [37] "Oregon"         "Pennsylvania"   "Rhode Island"   "South Carolina"
## [41] "South Dakota"   "Tennessee"      "Texas"          "Utah"          
## [45] "Vermont"        "Virginia"       "Washington"     "West Virginia" 
## [49] "Wisconsin"      "Wyoming"
# K-means
set.seed(123)
km <- kmeans(df_numeric, centers = 4)
df_plot <- as.data.frame(prcomp(df_numeric)$x[,1:2])
colnames(df_plot) <- c("Dim1","Dim2")
df_plot$cluster <- as.factor(km$cluster)
df_plot$country <- rownames(df_numeric)

# Asignar formas por cluster
shapes <- c(16, 17, 15, 3) # círculo, triángulo, cuadrado, cruz
names(shapes) <- levels(df_plot$cluster)

# Polígonos convexos por cluster
library(ggforce)
ggplot(df_plot, aes(x=Dim1, y=Dim2, color=cluster, shape=cluster)) +
  geom_point(size=3) +
  geom_text_repel(aes(label=country), size=3, max.overlaps=50) +
  geom_mark_hull(aes(fill=cluster), alpha=0.2, concavity = 5, expand = unit(2, "mm")) +
  scale_color_manual(values=c("#698B22","#FF82AB","#CD5555","#9B30FF")) +
  scale_fill_manual(values=c("#698B22","#FF82AB","#CD5555","#9B30FF")) +
  scale_shape_manual(values=shapes) +
  theme_minimal() +
  labs(title="Clusters")

library("cluster")
# Agglomerative Nesting (Hierarchical Clustering)
res.agnes <- agnes(x=USArrests, # data matrix
                   stand = TRUE, # Standardize the data
                   metric = "euclidean", # metric for distance matrix
                   method = "ward" # Linkage method
                   )

# DIvisive ANAlysis Clustering
res.diana <- diana(x=USArrests, # data matrix
                   stand = TRUE, # standardize the data
                   metric = "euclidean" # metric for distance matrix
                   )
 
fviz_dend(res.agnes, cex = 0.6, k=4)

Capitulo 8:

df <- scale(USArrests)
# Subset containing 10 rows
set.seed(123)
ss <- sample(1:50, 10)
df <- df[ss,]
library(dendextend)
## 
## ---------------------
## Welcome to dendextend version 1.19.1
## Type citation('dendextend') for how to cite the package.
## 
## Type browseVignettes(package = 'dendextend') for the package vignette.
## The github page is: https://github.com/talgalili/dendextend/
## 
## Suggestions and bug-reports can be submitted at: https://github.com/talgalili/dendextend/issues
## You may ask questions at stackoverflow, use the r and dendextend tags: 
##   https://stackoverflow.com/questions/tagged/dendextend
## 
##  To suppress this message use:  suppressPackageStartupMessages(library(dendextend))
## ---------------------
## 
## Adjuntando el paquete: 'dendextend'
## The following object is masked from 'package:stats':
## 
##     cutree
# Compute distance matrix
res.dist <- dist(df, method = "euclidean")
# Compute 2 hierarchical clusterings
hc1 <- hclust(res.dist, method = "average")
hc2 <- hclust(res.dist, method = "ward.D2")
# Create two dendrograms
dend1 <- as.dendrogram (hc1)
dend2 <- as.dendrogram (hc2)
# Create a list to hold dendrograms
dend_list <- dendlist(dend1, dend2)

tanglegram(dend1,dend2)
## Loading required namespace: colorspace

tanglegram(dend1,dend2,
           highlight_distinct_edges=FALSE,# Turn-off dashedlines
           common_subtrees_color_lines=FALSE,#Turn-off line colors
           common_subtrees_color_branches=TRUE,# Colorcommonbranches
           main=paste("entanglement=",round(entanglement(dend_list),2))
           )

# Cophenetic correlation matrix
cor.dendlist(dend_list, method = "cophenetic")
##           [,1]      [,2]
## [1,] 1.0000000 0.9925544
## [2,] 0.9925544 1.0000000
# Baker correlation matrix
cor.dendlist(dend_list, method = "baker")
##           [,1]      [,2]
## [1,] 1.0000000 0.9895528
## [2,] 0.9895528 1.0000000
# Cophenetic correlation coefficient
cor_cophenetic(dend1, dend2)
## [1] 0.9925544
# Baker correlation coefficient
cor_bakers_gamma(dend1, dend2)
## [1] 0.9895528
# Create multiple dendrograms by chaining
dend1 <- df %>% dist %>% hclust("complete")%>%as.dendrogram
dend2 <- df %>% dist %>% hclust("single")%>%as.dendrogram
dend3 <- df %>% dist %>% hclust("average")%>%as.dendrogram
dend4 <- df %>% dist %>% hclust("centroid")%>%as.dendrogram

# Compute correlation matrix
dend_list <- dendlist("Complete" = dend1, "Single" = dend2,
"Average" = dend3, "Centroid" = dend4)
cors <- cor.dendlist(dend_list)

# Print correlation matrix
round(cors, 2)
##          Complete Single Average Centroid
## Complete     1.00   0.46    0.45     0.30
## Single       0.46   1.00    0.23     0.17
## Average      0.45   0.23    1.00     0.31
## Centroid     0.30   0.17    0.31     1.00
# Visualize the correlation matrix using corrplot package
library(corrplot)
corrplot(cors, "pie", "lower")

Capitulo 9:

# Load data
data(USArrests)
# Compute distances and hierarchical clustering
dd <- dist(scale(USArrests), method = "euclidean")
hc <- hclust(dd, method = "ward.D2")
#visualizando dendrogramas
library(factoextra)
fviz_dend(hc, cex = 0.5)

#Puede utilizar los argumentos main, sub, xlab, ylab para cambiar los títulos de las tramas de la siguiente manera:
fviz_dend(hc, cex = 0.5,
main = "Dendrogram - ward.D2",
xlab = "Objects", ylab = "Distance", sub = "")

# Para mostrar un dendograma horizontal
fviz_dend(hc, cex = 0.5, horiz = TRUE)

#colorear ramas por grupos y agregar un rectángulo alrededor de cada grupo.

fviz_dend(hc, k = 4, 
cex = 0.5, 
k_colors = c("#8B0000", "#006400", "#8B008B", "#00008B"),
color_labels_by_k = TRUE, 
rect = TRUE, 
rect_fill = TRUE)

#Para cambiar el tema de la trama
fviz_dend(hc, k = 4, 
cex = 0.5, 
k_colors = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
color_labels_by_k = TRUE, 
ggtheme = theme_gray() # Cambiar tema
)

# cambiar los colores del grupo usando “jco”
fviz_dend(hc, cex = 0.5, k = 4, 
k_colors = "jco")

#dibujar un dendrograma horizontal con un rectángulo alrededor de los grupos
fviz_dend(hc, k = 4, cex = 0.4, horiz = TRUE, k_colors = "jco",
rect = TRUE, rect_border = "jco", rect_fill = TRUE)

#Forma circular
fviz_dend(hc, cex = 0.5, k = 4,
k_colors = "jco", type = "circular")

#Forma Phylogenic-like tree 
require("igraph")
library(igraph)
fviz_dend(hc, k = 4, k_colors = "jco",
type = "phylogenic", repel = TRUE)

#Forma Phylogenic trees 
require("igraph")
fviz_dend(hc, k = 4, # Cut in four groups

k_colors = "lancet",
type = "phylogenic", repel = TRUE,
phylo_layout = "layout.gem")

fviz_dend(hc, xlim = c(1, 20), ylim = c(1, 8))

# Crea un gráfico de todo el dendrograma, y extrae los datos del dendrograma
dend_plot <- fviz_dend(hc, k = 4, 
cex = 0.5, 
k_colors = "jco"
)
dend_data <- attr(dend_plot, "dendrogram") 
#Cortar el dendrograma a la altura h = 10
dend_cuts <- cut(dend_data, h = 10)
# Visualiza la versión truncada que contiene
# dos ramas
fviz_dend(dend_cuts$upper)

# Trazar todo el dendrograma
print(dend_plot)

# Trazar subárbol 1
fviz_dend(dend_cuts$lower[[1]], main = "Subtree 1")

# Trazar subárbol 2
fviz_dend(dend_cuts$lower[[2]], main = "Subtree 2")

#Trazar de forma circular
fviz_dend(dend_cuts$lower[[2]], type = "circular")

pdf("dendrogram.pdf", width=30, height=15) 
p <- fviz_dend(hc, k = 4, cex = 1, k_colors = "lancet" )
print(p)
dev.off()
## png 
##   2
data <- scale(USArrests)
dist.res <- dist(data)
hc <- hclust(dist.res, method = "ward.D2")
dend <- as.dendrogram(hc)
plot(dend)

library(dendextend)
dend <- USArrests[1:5,] %>% 
scale %>% 
dist %>% 
hclust(method = "ward.D2") %>% 
as.dendrogram 
plot(dend)

library(dendextend)
mycols <- c("#8B0000", "#006400", "#8B008B", "#00008B")
dend <- as.dendrogram(hc) %>%
set("branches_lwd", 1) %>% # 
set("branches_k_color", mycols, k = 4) %>% 
set("labels_colors", mycols, k = 4) %>% 
set("labels_cex", 0.5) 

fviz_dend(dend)

5.5. Presente un ejemplo de aplicación de Análisis de Cluster, con datos de un caso de los disponibles en https://www.kaggle.com/datasets (puede buscar dentro del sitio por la palabra clave “Cluster”.

Ejemplo de aplicación: Clustering de países según indicadores socioeconómicos:

  1. Objetivo:

Agrupar países según sus indicadores socioeconómicos para identificar patrones de desarrollo similares.

  1. Dataset

Fuente: Kaggle – Unsupervised Learning on Country Data

Variables seleccionadas para clustering:

child_mort → Mortalidad infantil

exports → Exportaciones

health → Gasto en salud

income → Ingreso

inflation → Inflación

life_expec → Esperanza de vida

total_fer → Fertilidad

gdpp → PIB per cápita

# 1 Cargar librerías
library(tidyverse)
library(cluster)      # Para clusplot
library(factoextra)   # Para visualización de clusters
library(readr)

# 2 Cargar dataset
Country_data_sv <- read_csv("C:/Users/MINEDUCYT/Downloads/Country-data.sv.csv")

# 3 Seleccionar variables para clustering y limpiar NAs
vars <- c("child_mort","exports","health","income","inflation","life_expec","total_fer","gdpp")
df_clust <- Country_data_sv[, vars] %>%
  mutate(across(everything(), as.numeric)) %>%
  mutate(across(everything(), ~ ifelse(is.na(.), mean(., na.rm=TRUE), .)))

# 4 Escalado de datos
df_scaled <- scale(df_clust)

# 5 Método del codo para determinar número óptimo de clusters
set.seed(123)
wcss <- vector()
for(i in 1:10){
  wcss[i] <- sum(kmeans(df_scaled, centers=i, nstart=25)$tot.withinss)
}
plot(1:10, wcss, type="o", xlab="Número de clusters k", ylab="WCSS", 
     main="Método del codo para seleccionar k óptimo")

# 6 Ejecutar K-means (suponiendo k = 3)
set.seed(123)
km_model <- kmeans(df_scaled, centers=3, nstart=25)

# 7 Agregar clusters al dataset original
Country_data_sv$cluster <- km_model$cluster

# 8 Ver medias de variables por cluster
aggregate(df_clust, by=list(Cluster=km_model$cluster), FUN=mean)
##   Cluster child_mort  exports    health    income   inflation life_expec
## 1       1   37.79632 57.57500 47.375000    60.850 5134.750000   11.83500
## 2       2   49.49748 33.67730  6.298319  8140.538    9.263227   67.57479
## 3       3    6.15000 60.63864  8.115000 42588.636    3.406864   79.18409
##   total_fer        gdpp
## 1 64.325000     4.10837
## 2  3.322437  4188.82353
## 3  1.830000 37637.95455
# 9 Visualización de clusters (2D con clusplot)
clusplot(df_scaled, km_model$cluster, lines=0, shade=TRUE, color=TRUE,
         main="Clusters de países según indicadores socioeconómicos")

# 10 Visualización moderna con factoextra
fviz_cluster(km_model, data=df_scaled,
             palette = c("#2E9FDF","#00AFBB","#E7B800"),
             geom = "point",
             ellipse.type = "convex",
             ggtheme = theme_minimal(),
             main = "Clusters de países según indicadores socioeconómicos")

Referencias Bibliograficas:

Dataset específico de Kaggle Rohan, R. (2021). Unsupervised Learning on Country Data [Data set]. Kaggle. https://www.kaggle.com/datasets/rohan0301/unsupervised-learning-on-country-data

Libro de Kassambara sobre análisis de clusters en R Kassambara, A. (2017). Practical guide to cluster analysis in R: Unsupervised machine learning (Multivariate Analysis) (1st ed.). STHDA. Disponible en: https://xsliulab.github.io/Workshop/week10/r-cluster-book.pdf