A31-Análisis de Clúster (Conglomerados)

UNIVERSIDAD DE EL SALVADOR

FACULTAD DE CIENCIAS ECONÓMICAS

ESCUELA DE ECONOMÍA

Ciclo II - 2025


“Análisis de Clúster (Conglomerados)”

Asignatura:

Métodos para el Análisis Económico

Grupo teórico:

GT-01

Docente:

MSF Carlos Ademir Pérez Alas


Integrantes:

Rosa Audelia Hernández Herrera — HH23026

Fátima Carolina Guillén Aguilar — GA22013

José Ricardo Vides Hernández — VH22011

Ciudad Universitaria, San Salvador – 17 de noviembre de 2025

1. Análisis de conglomerados

Definición de análisis de conglomerados

“El análisis de conglomerados es uno de los métodos importantes de minería de datos para descubrir conocimiento en datos multidimensionales. El objetivo del análisis de conglomerados es identificar patrones o grupos de objetos similares dentro de un conjunto de datos de interés.” (Kassambara, 2017, p. 3)

El análisis de conglomerados es una herramienta estadística que se utiliza para agrupar observaciones, personas, empresas o cualquier tipo de objeto en categorías llamadas “conglomerados” o “clusters”, de modo que los elementos dentro de un mismo grupo sean lo más parecidos posibles entre sí y, al mismo tiempo, diferentes de los pertenecientes a otros grupos.

Lo interesante del análisis de clúster es que no requiere una variable dependiente ni hipótesis previas, sino que deja que los datos “hablen por sí mismos”, revelando patrones ocultos o estructuras naturales en la información. Por eso se le considera parte de las técnicas de aprendizaje automático no supervisado

2. Cuadro comparativo

Cuadro Comparativo

Análisis de Clúster

Técnicas disponibles

Ventajas

Desventajas

Jerárquico: Este tipo de análisis agrupa los datos de manera progresiva o jerárquica, como si construyéramos un “árbol de relaciones”. Comienza tomando cada observación como un grupo individual y luego las va uniendo según su similitud, hasta formar grupos más grandes. El resultado se representa visualmente con un dendrograma, que muestra cómo los grupos se combinan paso a paso. Estos métodos permiten visualizar las relaciones entre observaciones sin necesidad de definir previamente el número de conglomerados.

Métodos aglomerativos:

Single Linkage (Vecino más próximo)

Complete Linkage (Vecino más lejano)

Average Linkage (Promedio entre grupos)

Método del Centroide

Método de la Mediana

Método de Ward

Métodos divisivos (disociativos):

DIANA (Divisive Analysis Clustering)*

No necesito saber cuántos grupos hay desde el inicio.

• Permite ver la formación de los grupos paso a paso.

• Es bueno para explorar los datos por primera vez.

• Ayuda a identificar observaciones raras o atípicas.

 No funciona bien cuando hay demasiados datos.

• Es muy sensible a valores extremos.

• Una vez une dos grupos, ya no hay marcha atrás.

• A veces crea grupos muy desbalanceados.

No jerárquico: El análisis de conglomerados no jerárquico parte de un número predeterminado de grupos (k), y busca la mejor partición de los datos asignando cada observación al conglomerado más cercano según una medida de distancia. Kassambara (2017) indica que estos métodos son más eficientes computacionalmente y adecuados para grandes conjuntos de datos.

Métodos de reasignación:

K-Means (centroides basados en medias)

K-Medoids (PAM) (centroides basados en objetos reales)

CLARA (Cluster Large Applications)

Métodos basados en densidad o forma:

DBSCAN (Density-Based Spatial Clustering)

Fuzzy C-Means (Clustering difuso)

Otros métodos directos o reductivos:

Block Clustering

Análisis factorial tipo Q

• Es mucho más rápido y eficiente.

Cuando trabajo con muchos datos, los métodos no jerárquicos (especialmente K-means) pueden procesarlos sin problema. Es ideal para bases grandes.

• Los grupos pueden reajustarse en cada iteración.

Los algoritmos mueven los puntos entre grupos hasta encontrar la mejor combinación posible. Esto ayuda a obtener grupos más “limpios”.

• Algunos métodos son muy robustos a valores atípicos.

• Si ya sé cuántos grupos quiero, es la mejor opción.

 Tengo que decir cuántos grupos quiero antes de empezar.

• La solución depende mucho de cómo inicia el algoritmo.

Por ejemplo, K-means puede encontrar grupos diferentes dependiendo de los valores iniciales. Hay que fijar una semilla y probar varias veces.

• Si no estandarizo las variables, el resultado puede ser engañoso.

• Algunos métodos requieren parámetros adicionales difíciles de elegir.

Fuente: Elaboración propia con base en Kassambara (2017) y De la Fuente Fernández (2014).

3. Librería y sintaxis en R

Métodos Jerarquicos

Los métodos jerárquicos construyen una estructura llamada dendrograma, que representa cómo las observaciones se van juntando (o separando) según su similitud. La característica esencial es que no necesitas definir cuántos clústeres quieres antes de comenzar, ya que el dendrograma muestra todas las posibles agrupaciones.

Métodos Jerárquicos Aglomerativos

En estos métodos, cada observación comienza siendo su propio grupo. Luego, poco a poco, se fusionan los grupos más similares. La forma en que se decide cuáles grupos se unen depende de la técnica o criterio de enlace (linkage). Ese criterio determina cómo se mide la distancia entre dos grupos.

Estrutura General en R

# Preparar datos
df <- USArrests
df <- na.omit(df)
df <- scale(df)

# Matriz de distancias
d <- dist(df)

# Métodos jerárquicos aglomerativos
hc_single   <- hclust(d, method = "single")
hc_complete <- hclust(d, method = "complete")
hc_average  <- hclust(d, method = "average")
hc_centroid <- hclust(d, method = "centroid")
hc_median   <- hclust(d, method = "median")
hc_ward     <- hclust(d, method = "ward.D2")

Antes de usar hclust(), debemos convertir el df estandarizado en una matriz de distancias: d <- dist(df)

Donde: hclust() es la función universal de clustering jerárquico aglomerativo en R:

hclust(d, …) = Primer argumento: la matriz de distancias hclust(d, method = “single”) = Segundo argumento: method = “single”, este parámetro le dice a R: “¿Cómo quieres que mida la distancia entre clústeres cuando tenga que unirlos?”

“single” → une los clústeres si tienen puntos muy cercanos

“complete” → une los clústeres si todos los puntos están cerca

“average” → promedio de distancias

“centroid” → distancia entre centroides

“median” → distancia entre medianas

“ward.D” o “ward.D2” → método de mínima varianza

Formas especificas

• Single Linkage (Vecino más próximo)

Esta técnica une los clústeres que tienen al menos dos puntos muy cercanos entre sí. Es decir, si existe un par de observaciones —una en cada grupo— que están muy cerca, entonces los grupos se consideran similares.

library(cluster)
hc_single <- hclust(d, method = "single")

• Complete Linkage (Vecino más lejano)

Considera que dos clústeres son similares sólo si todos sus puntos están relativamente cerca entre sí. Busca que los grupos formados sean compactos y de forma relativamente redonda.

hc_complete <- hclust(d, method = "complete")

• Average Linkage (Promedio)

Mide la similitud entre grupos usando el promedio de todas las distancias entre sus observaciones. Es una técnica intermedia entre single y complete.

hc_average <- hclust(d, method = "average")

• Centroid Linkage (Método del centroide)

Cada clúster se representa por el punto promedio de sus observaciones (el centroide). Se unen los clústeres cuyos centroides están más cerca.

hc_centroid <- hclust(d, method = "centroid")

• Median Linkage (Método de la mediana)

Parecido al centroide, pero los clústeres se representan por su mediana en cada variable, lo que lo hace más robusto a valores extremos.

hc_median <- hclust(d, method = "median")

• Ward’s Method (Mínima varianza interna)

Ward une los grupos de tal forma que el aumento en la variación interna del nuevo clúster sea el más pequeño posible. Es decir, siempre busca crear clústeres compactos y homogéneos.

hc_ward <- hclust(d, method = "ward.D2")

Métodos Jerárquicos Divisivos

• DIANA (Divisive Analysis Clustering)

DIANA es el inverso del método aglomerativo:

Empieza con un solo clúster que contiene todos los datos. Busca los puntos más distintos entre sí para separar un subgrupo y en cada paso divide el clúster más heterogéneo.

library(cluster)
res.diana <- diana(df, stand = FALSE)

#Si queremos ser más explicito y apegarnos más a la forma de Kassambara podemos usar esta forma: diana(x, stand = FALSE, metric = "euclidean")

Entonces:

diana(df) recibe datos ya estandarizados.

stand = TRUE aquí es redundante (doble estandarización), por eso usamos el argumento FALSE

Como no especificas metric, DIANA usa por defecto “euclidean”.

Métodos No Jérarquicos

Los métodos no jerárquicos de cluster son técnicas de particionamiento que optan por dividir directamente los datos en grupos, sin construir el “árbol” (dendrograma) característico de los métodos jerárquicos. Su funcionamiento se basa en la reasignación iterativa de cada punto de dato: comienzan con una división inicial y luego mueven y ajustan continuamente los puntos hasta que se alcanza una solución estable (convergencia), donde los grupos están bien definidos. La mayoría de estos algoritmos requiere que el analista defina primero la cantidad de grupos deseada, (exceptuando métodos avanzados basados en densidad como DBSCAN). Al centrarse en esta optimización directa e iterativa, son intrínsecamente más rápidos y eficientes para procesar grandes volúmenes de datos, lo que los convierte en la herramienta predilecta para el análisis a gran escala.

Métodos de reasignación

Minimizar la variación interna dentro de cada grupo y maximizar la separación entre grupos.

Es decir, estos métodos funcionan tal que así:

  1. Se elige aleatoriamente un número de k clústeres.

  2. Cada observación se asigna provisionalmente a uno de ellos.

  3. Se mide qué tan buena es la agrupación (homogeneidad interna).

  4. Se reasignan observaciones si eso mejora la calidad.

5 .Continúa hasta que las asignaciones ya no cambian.

Estrutura General en R

#df <- scale(USArrests), si fuese necesario prepar los datos 

km <- kmeans(df, centers = 4)
pam_res <- pam(df, k = 4)
clara_res <- clara(df, k = 4)

Donde: Función(df, parámetro del número de clústeres): df → es el dataset numérico (preferiblemente estandarizado) centers / k → número de clústeres que quieres formar función → indica el algoritmo usado: kmeans, pam o clara.

Formas especificas

•K-MEANS

K-means crea clústeres representados por un centroide, que no es una observación real sino un punto promedio. Cada observación se asigna al clúster cuyo centroide esté más cerca. Luego, los centroides se recalculan y se vuelve a asignar.

set.seed(123)
km <- kmeans(df, centers = 4, nstart = 25)

# km$cluster (categoría asignada a cada observación)
# km$centers (centroides de cada clúster)

• K-MEDOIDS (PAM)

Igual que K-means, pero aquí cada clúster se representa con un medoid, que es una observación real, no un promedio.

library(cluster)

pam_res <- pam(df, k = 2)

# pam_res$clustering (asignación de cada punto)
# pam_res$medoids (observaciones representativas)

• CLARA (Clustering Large Applications)

CLARA es una versión de PAM diseñada para bases muy grandes. En lugar de aplicar PAM a toda la base, toma submuestras aleatorias, ejecuta PAM, y conserva la mejor solución.

library(cluster)
clara_res <- clara(df, k = 2, samples = 50, pamLike = TRUE)

Métodos de densidad o forma

Aquí, un clúster se define como:

“Una región donde los puntos están más densamente concentrados que en otras partes del espacio.”

Estos métodos detectan:

  1. clústeres de formas arbitrarias (curvas, anillos, figuras no esféricas),

  2. outliers de forma natural

  3. regiones dispersas como “ruido”.

No requieren especificar k.

Estrutura General en R

db <- dbscan::dbscan(df, eps = 0.15, minPts = 5)
res.fanny <- fanny(df, 2) 

Donde:

dbscan::dbscan(df, eps, minPts)

df→ Dataset numérico estandarizado.

eps→ Radio máximo dentro del cual los puntos se consideran vecinos.

minPts→ Número mínimo de vecinos dentro de eps para formar un clúster.

fanny(df, k)

df→ Dataset numérico.

k→ Número de clústeres difusos a generar.

Formas especificas

• DBSCAN

DBSCAN agrupa puntos que están suficientemente cerca unos de otros en términos de densidad.

library(dbscan)
db <- dbscan::dbscan(df, eps = 0.15, minPts = 5)

Se sigue esta forma de obtener los resultados siguiendo los dichos de Kassambara: Here, we’ll use the R package fpc to compute DBSCAN. It’s also possible to use the package dbscan, which provides a faster re-implementation of DBSCAN algorithm compared to the fpc package (Kassambara, 2017, p. 182).

library(fpc)
set.seed(123)
db_fpc <- fpc::dbscan(df, eps = 0.15, MinPts = 5)
#Metodo tradicional

•FUZZY C-MEANS

Parecido a K-means, pero en vez de asignar cada punto a un único clúster, asigna porcentajes de pertenencia.

library(cluster)
res.fanny <- fanny(df, 2) 

4. Ejemplos desarrollados

Replica de ejemplos de capitulo 4

Cargar y estandarizar los datos (USArrests)

data("USArrests")
df <- scale(USArrests)
head(df, n = 3)
##             Murder   Assault   UrbanPop         Rape
## Alabama 1.24256408 0.7828393 -0.5209066 -0.003416473
## Alaska  0.50786248 1.1068225 -1.2117642  2.484202941
## Arizona 0.07163341 1.4788032  0.9989801  1.042878388

Determinar el número óptimo de clusters (método del codo – WSS)

library(factoextra)
library(ggplot2)
library(ggrepel)
fviz_nbclust(df, kmeans, method = "wss") +
geom_vline(xintercept = 4, linetype = 2)

Ejecutar K-means con k = 4

set.seed(123)
km.res <- kmeans(df, 4, nstart = 25)
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"

Promedios por cluster y unión con el dataset original

aggregate(USArrests, by=list(cluster=km.res$cluster), mean)
##   cluster   Murder   Assault UrbanPop     Rape
## 1       1 13.93750 243.62500 53.75000 21.41250
## 2       2  3.60000  78.53846 52.07692 12.17692
## 3       3  5.65625 138.87500 73.87500 18.78125
## 4       4 10.81538 257.38462 76.00000 33.19231
dd <- cbind(USArrests, cluster = km.res$cluster)
head(dd)
##            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

Asignación de cluster para cada observación

head(km.res$cluster, 4)
##  Alabama   Alaska  Arizona Arkansas 
##        1        4        4        1

Tamaño de cada cluster

km.res$size
## [1]  8 13 16 13

Centros (centroides) de los clusters

km.res$centers
##       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

Visualización final del resultado K-means

fviz_cluster(km.res, 
             data = df,
palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
ellipse.type = "euclid", 
star.plot = TRUE, 
repel = TRUE,
ggtheme = theme_minimal()
)

Replica de ejemplos capitulo 5

Cargar y estandarizar datos

data("USArrests")
df <- scale(USArrests)
head(df, n = 3)
##             Murder   Assault   UrbanPop         Rape
## Alabama 1.24256408 0.7828393 -0.5209066 -0.003416473
## Alaska  0.50786248 1.1068225 -1.2117642  2.484202941
## Arizona 0.07163341 1.4788032  0.9989801  1.042878388

Determinar número óptimo de clusters (silhouette)

library(cluster)
library(factoextra)
fviz_nbclust(df, pam, method = "silhouette")+
theme_classic()

Ejecutar PAM con k = 2

pam.res <- pam(df, 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"

Unir datos originales con su asignación de cluster

dd <- cbind(USArrests, cluster = pam.res$cluster)
head(dd, n = 3)
##         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

Medoids del clustering

pam.res$medoids
##                Murder    Assault   UrbanPop       Rape
## New Mexico  0.8292944  1.3708088  0.3081225  1.1603196
## Nebraska   -0.8008247 -0.8250772 -0.2445636 -0.5052109

Asignación de cada observación al cluster

head(pam.res$clustering)
##    Alabama     Alaska    Arizona   Arkansas California   Colorado 
##          1          1          1          2          1          1

Visualización del resultado PAM

fviz_cluster(pam.res,
palette = c("#00AFBB", "#FC4E07"), 
ellipse.type = "t",
repel = TRUE,
ggtheme = theme_classic()
)

Replica de ejemplos capitulo 6

Generar datos artificiales para aplicar CLARA

set.seed(1234)
df <- rbind(cbind(rnorm(200,0,8), rnorm(200,0,8)),
cbind(rnorm(300,50,8), rnorm(300,50,8)))
colnames(df) <- c("x", "y")
rownames(df) <- paste0("S", 1:nrow(df))
head(df, nrow = 6)
##             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

Determinar el número óptimo de clusters (silhouette)

library(cluster)
library(factoextra)
fviz_nbclust(df, clara, method = "silhouette")+
theme_classic()

Ejecutar CLARA

clara.res <- clara(df, 2, samples = 50, pamLike = TRUE)
print(clara.res)
## Call:     clara(x = df, 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"

Unir clusters con datos originales

dd <- cbind(df, cluster = clara.res$cluster)
head(dd, n = 4)
##             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

Medoids

clara.res$medoids
##              x         y
## S121 -1.531137  1.145057
## S455 48.357304 50.233499

Asignación a clusters

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

Visualización de CLARA

fviz_cluster(clara.res,
palette = c("#00AFBB", "#FC4E07"), # color palette
ellipse.type = "t", # Concentration ellipse
geom = "point", pointsize = 1,
ggtheme = theme_classic()
)

Replica de ejemplos capitulo 7

Cargar y estandarizar datos

data("USArrests")
df <- scale(USArrests)
head(df, nrow = 6)
##                Murder   Assault   UrbanPop         Rape
## Alabama    1.24256408 0.7828393 -0.5209066 -0.003416473
## Alaska     0.50786248 1.1068225 -1.2117642  2.484202941
## Arizona    0.07163341 1.4788032  0.9989801  1.042878388
## Arkansas   0.23234938 0.2308680 -1.0735927 -0.184916602
## California 0.27826823 1.2628144  1.7589234  2.067820292
## Colorado   0.02571456 0.3988593  0.8608085  1.864967207

Calcular la matriz de distancias

res.dist <- dist(df, method = "euclidean")

Mostrar parte de la matriz de distancias

as.matrix(res.dist)[1:6, 1:6]
##             Alabama   Alaska  Arizona Arkansas California Colorado
## Alabama    0.000000 2.703754 2.293520 1.289810   3.263110 2.651067
## Alaska     2.703754 0.000000 2.700643 2.826039   3.012541 2.326519
## Arizona    2.293520 2.700643 0.000000 2.717758   1.310484 1.365031
## Arkansas   1.289810 2.826039 2.717758 0.000000   3.763641 2.831051
## California 3.263110 3.012541 1.310484 3.763641   0.000000 1.287619
## Colorado   2.651067 2.326519 1.365031 2.831051   1.287619 0.000000

Ejecutar hclust con método Ward.D2

res.hc <- hclust(d = res.dist, method = "ward.D2")

Distancia cophenética y correlación

res.coph <- cophenetic(res.hc)
cor(res.dist, res.coph)
## [1] 0.6975266

Comparación con método average

res.hc2 <- hclust(res.dist, method = "average")
cor(res.dist, cophenetic(res.hc2))
## [1] 0.7180382

Cortar dendrograma en 4 grupos

grp <- cutree(res.hc, k = 4)
head(grp, n = 4)
##  Alabama   Alaska  Arizona Arkansas 
##        1        2        2        3

Tamaño de cada cluster

table(grp)
## grp
##  1  2  3  4 
##  7 12 19 12

Miembros del cluster 1

rownames(df)[grp == 1]
## [1] "Alabama"        "Georgia"        "Louisiana"      "Mississippi"   
## [5] "North Carolina" "South Carolina" "Tennessee"

Visualización del dendrograma con colores

fviz_dend(res.hc, k = 4, 
cex = 0.5, 
k_colors = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
color_labels_by_k = TRUE, 
rect = TRUE 
)

Visualización de clusters en el espacio original

fviz_cluster(list(data = df, cluster = grp),
palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
ellipse.type = "convex", 
repel = TRUE,
show.clust.cent = FALSE, ggtheme = theme_minimal())

Métodos aglomerativo (agnes) y divisivo (diana)

library("cluster")

res.agnes <- agnes(x = USArrests, 
stand = TRUE,
metric = "euclidean",
method = "ward" 
)

res.diana <- diana(x = USArrests, 
stand = TRUE, 
metric = "euclidean" 
)

Dendrograma AGNES

fviz_dend(res.agnes, cex = 0.6, k = 4)

Replica de ejemplo capitulo 8

Selección aleatoria de 10 observaciones

df <- scale(USArrests)
set.seed(123)
ss <- sample(1:50, 10)
df <- df[ss,]

Construcción de dos dendrogramas con métodos diferentes

library(dendextend)
res.dist <- dist(df, method = "euclidean")
hc1 <- hclust(res.dist, method = "average")
hc2 <- hclust(res.dist, method = "ward.D2")
dend1 <- as.dendrogram (hc1)
dend2 <- as.dendrogram (hc2)
dend_list <- dendlist(dend1, dend2)

Tanglegram básico

tanglegram(dend1, dend2)

Tanglegram mejorado

tanglegram(dend1, dend2,
highlight_distinct_edges = FALSE,
common_subtrees_color_lines = FALSE,
common_subtrees_color_branches = TRUE, 
main = paste("entanglement =", round(entanglement(dend_list), 2))
)

Correlación cophenética

cor.dendlist(dend_list, method = "cophenetic")
##           [,1]      [,2]
## [1,] 1.0000000 0.9925544
## [2,] 0.9925544 1.0000000

Correlación Baker

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

Comparación de múltiples métodos jerárquicos

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
dend_list <- dendlist("Complete" = dend1, "Single" = dend2,
"Average" = dend3, "Centroid" = dend4)
cors <- cor.dendlist(dend_list)
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

Visualizar matriz de correlación

library(corrplot)
corrplot(cors, "pie", "lower")

Los resultados no son identicos a los del libro ya que se usan datos seleccionados aleatoriamente.

Replica de ejemplos capitulo 9

Cargar datos y crear dendrograma

data(USArrests)
dd <- dist(scale(USArrests), method = "euclidean")
hc <- hclust(dd, method = "ward.D2")

Visualización básica del dendrograma

library(factoextra)
fviz_dend(hc, cex = 0.5)

Dendrograma con títulos personalizados

library(factoextra)
fviz_dend(hc, cex = 0.5,
main = "Dendrogram - ward.D2",
xlab = "Objects", ylab = "Distance", sub = "")

Dendrograma horizontal

fviz_dend(hc, cex = 0.5, horiz = TRUE)

Dendrograma con colores y rectángulos

fviz_dend(hc, k = 4,
          cex = 0.5,
          k_colors = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
          color_labels_by_k = TRUE,
          rect = TRUE,
          rect_border = "jco",
          rect_fill = TRUE)

Cambiar tema del gráfico

fviz_dend(hc, k = 4,
cex = 0.5, 
k_colors = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
color_labels_by_k = TRUE, 
ggtheme = theme_gray() 
)

Dendrograma con colores jco

fviz_dend(hc, cex = 0.5, k = 4,
k_colors = "jco")

Dendrograma horizontal con rectángulos

fviz_dend(hc, k = 4, cex = 0.4, horiz = TRUE, k_colors = "jco",
rect = TRUE, rect_border = "jco", rect_fill = TRUE)

Visualización circular

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

Visualización tipo phylogenic

require("igraph")
fviz_dend(hc, k = 4, k_colors = "jco",
type = "phylogenic", repel = TRUE)

Visualización phylogenic con layout gem

require("igraph")
fviz_dend(hc, k = 4, 
k_colors = "jco",
type = "phylogenic", repel = TRUE,
phylo_layout = "layout.gem")

Ajustar límites del gráfico

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

Extraer datos del dendrograma generado

dend_plot <- fviz_dend(hc, k = 4, 
cex = 0.5, # 
k_colors = "jco"
)
dend_data <- attr(dend_plot, "dendrogram") 

Cortar dendrograma a una altura específica

dend_cuts <- cut(dend_data, h = 10)
fviz_dend(dend_cuts$upper)

Mostrar dendrograma completo

print(dend_plot)

Visualizar subárbol 1

fviz_dend(dend_cuts$lower[[1]], main = "Subtree 1")

Visualizar subárbol 2

fviz_dend(dend_cuts$lower[[2]], main = "Subtree 2")

Subárbol en formato circular

fviz_dend(dend_cuts$lower[[2]], type = "circular")

Crear dendrograma básico

data <- scale(USArrests)
dist.res <- dist(data)
hc <- hclust(dist.res, method = "ward.D2")
dend <- as.dendrogram(hc)
plot(dend)

Ejemplo con primeras 5 observaciones

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

Personalización avanzada del dendrograma

mycols <- c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07")
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. Ejemplo de aplicación

Indicación: 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”.

El DataSet a desarrollar para este ejemplo es el de: Unsupervised Learning on Country Data.

Esta data clasifica los países utilizando factores socioeconómicos y sanitarios que determinan el desarrollo general del país.

El DataFrame está compuesto por 167 observaciones (paises) y 10 variables que representan factores socieconómicos de los paises:

Variables incluidas

  1. country – Nombre del país (única variable no numérica)

  2. child_mort – Tasa de mortalidad infantil (por cada 1,000 nacidos vivos)

  3. exports – Exportaciones (% del PIB)

  4. health – Gasto en salud (% del PIB)

  5. imports – Importaciones (% del PIB)income

  6. Ingreso per cápita (en dólares)

  7. inflation – Tasa de inflación (% anual)

  8. life_expec – Esperanza de vida

  9. total_fer – Tasa total de fertilidad

  10. gdpp – PIB per cápita (en dólares)

Importación de datos

library(dplyr)
library(readr)
Country_data <- read_csv("C:/Users/PC/Downloads/archive (1)/Country-data.csv")
Datos5 <- Country_data %>% select(-country)
head(Datos5, 10)
## # A tibble: 10 × 9
##    child_mort exports health imports income inflation life_expec total_fer  gdpp
##         <dbl>   <dbl>  <dbl>   <dbl>  <dbl>     <dbl>      <dbl>     <dbl> <dbl>
##  1       90.2    10     7.58    44.9   1610     9.44        56.2      5.82   553
##  2       16.6    28     6.55    48.6   9930     4.49        76.3      1.65  4090
##  3       27.3    38.4   4.17    31.4  12900    16.1         76.5      2.89  4460
##  4      119      62.3   2.85    42.9   5900    22.4         60.1      6.16  3530
##  5       10.3    45.5   6.03    58.9  19100     1.44        76.8      2.13 12200
##  6       14.5    18.9   8.1     16    18700    20.9         75.8      2.37 10300
##  7       18.1    20.8   4.4     45.3   6700     7.77        73.3      1.69  3220
##  8        4.8    19.8   8.73    20.9  41400     1.16        82        1.93 51900
##  9        4.3    51.3  11       47.8  43200     0.873       80.5      1.44 46900
## 10       39.2    54.3   5.88    20.7  16000    13.8         69.1      1.92  5840

Normalización de datos

Datos_normalizados <-  scale(Datos5)
head(Datos_normalizados,25)
##        child_mort      exports      health     imports      income    inflation
##  [1,]  1.28765971 -1.134866649  0.27825140 -0.08220771 -0.80582187  0.156864451
##  [2,] -0.53733286 -0.478220167 -0.09672528  0.07062429 -0.37424335 -0.311410892
##  [3,] -0.27201464 -0.098824422 -0.96317624 -0.63983800 -0.22018227  0.786907640
##  [4,]  2.00178723  0.773056185 -1.44372888 -0.16481961 -0.58328920  1.382894441
##  [5,] -0.69354825  0.160186135 -0.28603389  0.49607554  0.10142673 -0.599944185
##  [6,] -0.58940465 -0.810191444  0.46756001 -1.27594958  0.08067776  1.240992822
##  [7,] -0.50013871 -0.740878760 -0.87944359 -0.06568534 -0.54179126 -0.001119352
##  [8,] -0.82992677 -0.777359120  0.69691468 -1.07355044  1.25818167 -0.626432487
##  [9,] -0.84232482  0.371772224  1.52331959  0.03757953  1.35155202 -0.653582997
## [10,]  0.02305888  0.481213304 -0.34064215 -1.08181163 -0.05937777  0.569325158
## [11,] -0.60676192 -0.222857646  0.39110846 -0.13177485  0.29854192 -0.773347964
## [12,] -0.73570161  1.035714777 -0.67193222  0.16562797  1.24261994 -0.032337708
## [13,]  0.27597905 -0.915984488 -1.19981201 -1.03637509 -0.76276777 -0.060718032
## [14,] -0.59684348 -0.058696026  0.42023286  0.07475488 -0.09568846 -0.705802793
## [15,] -0.81256951  0.375420260 -0.43893700  0.72738885 -0.04900328  0.692306561
## [16,] -0.83736560  1.287429262  1.41410308  1.14870951  1.24261994 -0.558319710
## [17,] -0.48278145  0.623486708 -0.58819957  0.43824722 -0.48058181 -0.628324509
## [18,]  1.80341848 -0.631437679 -0.98866010 -0.40026351 -0.79492867 -0.652447784
## [19,]  0.10984521  0.050745055 -0.58819957  0.98348572 -0.55631554 -0.169509273
## [20,]  0.20654998  0.003320587 -0.71925938 -0.52005075 -0.60870668  0.094427739
## [21,] -0.77785497 -0.416203555  1.55972509  0.18215035 -0.38513656 -0.603728228
## [22,]  0.35284694  0.090873451  0.54037102  0.18215035 -0.19943330  0.107671890
## [23,] -0.45798535 -1.109330397  0.79885009 -1.44943456 -0.13718640  0.059425339
## [24,] -0.68858903  0.959106021 -1.44736943 -0.78027822  3.29158048  0.843668288
## [25,] -0.68115020  0.331643827  0.01977233  0.25237046 -0.09568846 -0.631162541
##        life_expec   total_fer        gdpp
##  [1,] -1.61423717  1.89717646 -0.67714308
##  [2,]  0.64592380 -0.85739418 -0.48416709
##  [3,]  0.66841296 -0.03828924 -0.46398018
##  [4,] -1.17569847  2.12176975 -0.51472026
##  [5,]  0.70214671 -0.54032130 -0.04169175
##  [6,]  0.58970089 -0.38178486 -0.14535428
##  [7,]  0.30858634 -0.83097144 -0.53163362
##  [8,]  1.28686497 -0.67243500  2.12430964
##  [9,]  1.11819624 -0.99611356  1.85151350
## [10,] -0.16368610 -0.67904068 -0.38868844
## [11,]  0.36480925 -0.71867479  0.82034407
## [12,]  0.61219005 -0.52050424  0.42206170
## [13,] -0.01750653 -0.40820760 -0.66595844
## [14,]  0.69090213 -0.77152027  0.16563332
## [15,] -0.01750653 -0.96308514 -0.37832219
## [16,]  1.06197333 -0.71867479  1.71511542
## [17,]  0.09493928 -0.15719157 -0.47052728
## [18,] -0.98454058  1.59331495 -0.66595844
## [19,]  0.17365136 -0.37517917 -0.58837522
## [20,]  0.11742845  0.16648699 -0.59928706
## [21,]  0.70214671 -1.08198747 -0.45579629
## [22,] -1.51303593 -0.04489492 -0.36086323
## [23,]  0.40978758 -0.75830890 -0.09625097
## [24,]  0.73588045 -0.73188616  1.21862644
## [25,]  0.37605383 -0.91023966 -0.33412921

Validación del Cluster

La validación de clústeres consiste en medir la calidad de los resultados de la agrupación. Por ello, antes de aplicar cualquier algoritmo de agrupación de datos, se debe evaluar la tendencia de estos. (Kassambara, 2017, p. 118)

Metodo Hopkins statistic

library(clustertend)
set.seed(123)
hopkins(Datos_normalizados, n = nrow(Datos_normalizados)-1)
## $H
## [1] 0.1470677

El valor obtenidos (0.1470) es mucho menor que 0.5, lo que indica que estos datos sí tienen una fuerte tendencia a formar clusters naturales. Es decir, sí tiene sentido aplicar un análisis de clúster (como K-means o PAM) sobre el dataset presentado.

Metodo Visual

library(factoextra)
fviz_dist(dist(Datos_normalizados), show_labels = FALSE)+
labs(title = "Country Dataset")

Desarollo de ejemplo

Ya que se validó el dataset se procede a realizar el Analisis de Cluster, para ello se utilizará el metodo no jerarquico K-means dado que contiene valores continuos, numéricos y sin grandes outliers extremos.

Determinación de K

K es el número optimo de cluster en un conjunto de datos, en este ejemplo utilizaremos el método de la silueta y del codo para Kmeans

Metodo del codo

fviz_nbclust(Datos_normalizados, kmeans, method = "wss") +
labs(subtitle = "Metodo del codo para K-means")

Metodo de la silueta

library(NbClust)
fviz_nbclust(Datos_normalizados, kmeans, method = "silhouette") +
labs(subtitle = "Método de la silueta con K-means")

El método del codo muestra una tendencia visual ( quiebre cerca de 4 o 5) y el método de la silueta confirma objetivamente que el número óptimo de grupos es 5. Es decir, con k = 5, los países son lo suficientemente distintos entre grupos, pero internamente similares dentro de cada grupo.

K-means

set.seed(123)
# K-means clustering
kmean <- kmeans(Datos_normalizados,5, nstart = 25)
print(kmean)
## K-means clustering with 5 clusters of sizes 47, 86, 3, 1, 30
## 
## Cluster means:
##   child_mort      exports       health     imports     income   inflation
## 1  1.3023791 -0.417592913 -0.128560729 -0.13256582 -0.6885538  0.20528209
## 2 -0.4205029  0.003231082 -0.220884976  0.03151135 -0.2098741 -0.03418463
## 3 -0.8464575  4.920873128 -0.008138555  4.53442030  2.4322274 -0.50269428
## 4  2.2745443 -0.576717139 -0.635526719 -1.21812126 -0.6221935  9.10234253
## 5 -0.8261247  0.172103053  0.856613486 -0.29548409  1.4578905 -0.47675466
##   life_expec  total_fer       gdpp
## 1 -1.2754642  1.3383636 -0.6036379
## 2  0.2823054 -0.4519127 -0.3262422
## 3  1.2231457 -1.0357477  2.4334786
## 4 -1.1307201  1.9103878 -0.5801913
## 5  1.1043279 -0.7613916  1.6569189
## 
## Clustering vector:
##   [1] 1 2 2 1 2 2 2 5 5 2 2 2 2 2 2 5 2 1 2 2 2 1 2 5 2 1 1 2 1 5 2 1 1 2 2 2 1
##  [38] 1 1 2 1 2 5 2 5 2 2 2 2 1 1 2 2 5 5 1 1 2 5 1 5 2 2 1 1 2 1 2 5 2 2 2 1 5
##  [75] 5 5 2 5 2 2 1 1 5 2 1 2 2 1 1 2 2 3 2 1 1 2 2 1 3 1 2 2 2 2 2 2 1 2 1 2 5
## [112] 5 1 4 5 2 1 2 2 2 2 2 5 5 2 2 1 2 2 1 2 2 1 3 2 5 1 1 5 5 2 2 1 2 5 5 2 1
## [149] 2 1 1 2 2 2 2 1 2 5 5 5 2 2 2 2 2 1 1
## 
## Within cluster sum of squares by cluster:
## [1] 196.22502 267.44392  20.87409   0.00000 131.68189
##  (between_SS / total_SS =  58.8 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Tabla resumen de clusters

tabla_clusters <- aggregate(Datos_normalizados, by = list(Cluster = kmean$cluster), mean)
print(tabla_clusters)
##   Cluster child_mort      exports       health     imports     income
## 1       1  1.3023791 -0.417592913 -0.128560729 -0.13256582 -0.6885538
## 2       2 -0.4205029  0.003231082 -0.220884976  0.03151135 -0.2098741
## 3       3 -0.8464575  4.920873128 -0.008138555  4.53442030  2.4322274
## 4       4  2.2745443 -0.576717139 -0.635526719 -1.21812126 -0.6221935
## 5       5 -0.8261247  0.172103053  0.856613486 -0.29548409  1.4578905
##     inflation life_expec  total_fer       gdpp
## 1  0.20528209 -1.2754642  1.3383636 -0.6036379
## 2 -0.03418463  0.2823054 -0.4519127 -0.3262422
## 3 -0.50269428  1.2231457 -1.0357477  2.4334786
## 4  9.10234253 -1.1307201  1.9103878 -0.5801913
## 5 -0.47675466  1.1043279 -0.7613916  1.6569189

Análisis de resultado

Cluster 1: Alta mortalidad infantil (child_mort = 1.3), bajo ingreso y PIB (income = -0.68, gdpp = -0.60), baja esperanza de vida (life_expec = -1.27) Es decir, son países en desarrollo.

Cluster 2: Variables cercanas al promedio, con ligera mejora en salud (life_expec = 0.28). Es decir, países con desarrollo intermedio.

Cluster 3: Muy alto ingreso y PIB (income = 2.43, gdpp = 2.43), alto comercio (exports = 4.92, imports = 4.53), baja mortalidad (child_mort = -0.85). Es decir, paises con economías avanzadas.

Cluster 4: Mortalidad e inflación extremadamente altas (child_mort = 2.27, inflation = 9.10), ingresos bajos. Es decir, países con inestabilidad económica.

Cluster 5: Alta salud y PIB (health = 0.85, gdpp = 1.65), buena esperanza de vida (life_expec = 1.10). Es decir, países en crecimiento sólido.

Adicionalmente, el modelo de K-means logró explicar el 58.8% de la variabilidad total de los datos mediante los 5 clústeres. Es un valor razonablemente bueno para datos socioeconómicos (no se espera un 100% porque las variables tienen mucha dispersión).

Componentes

# Cluster a los que pertenecen los países 
kmean$cluster
##   [1] 1 2 2 1 2 2 2 5 5 2 2 2 2 2 2 5 2 1 2 2 2 1 2 5 2 1 1 2 1 5 2 1 1 2 2 2 1
##  [38] 1 1 2 1 2 5 2 5 2 2 2 2 1 1 2 2 5 5 1 1 2 5 1 5 2 2 1 1 2 1 2 5 2 2 2 1 5
##  [75] 5 5 2 5 2 2 1 1 5 2 1 2 2 1 1 2 2 3 2 1 1 2 2 1 3 1 2 2 2 2 2 2 1 2 1 2 5
## [112] 5 1 4 5 2 1 2 2 2 2 2 5 5 2 2 1 2 2 1 2 2 1 3 2 5 1 1 5 5 2 2 1 2 5 5 2 1
## [149] 2 1 1 2 2 2 2 1 2 5 5 5 2 2 2 2 2 1 1
# Cuántas observaciones (países) hay en cada uno de los 5 clusters.
kmean$size
## [1] 47 86  3  1 30

El cluster 1 tiene 47 países

El cluster 2 tiene 86 países

El cluster 3 tiene 3 países

El cluster 4 tiene 1 país (posible outlier)

El cluster 5 tiene 30 países

# Perfil socioeconómico típico de cada grupo
kmean$centers
##   child_mort      exports       health     imports     income   inflation
## 1  1.3023791 -0.417592913 -0.128560729 -0.13256582 -0.6885538  0.20528209
## 2 -0.4205029  0.003231082 -0.220884976  0.03151135 -0.2098741 -0.03418463
## 3 -0.8464575  4.920873128 -0.008138555  4.53442030  2.4322274 -0.50269428
## 4  2.2745443 -0.576717139 -0.635526719 -1.21812126 -0.6221935  9.10234253
## 5 -0.8261247  0.172103053  0.856613486 -0.29548409  1.4578905 -0.47675466
##   life_expec  total_fer       gdpp
## 1 -1.2754642  1.3383636 -0.6036379
## 2  0.2823054 -0.4519127 -0.3262422
## 3  1.2231457 -1.0357477  2.4334786
## 4 -1.1307201  1.9103878 -0.5801913
## 5  1.1043279 -0.7613916  1.6569189
# Resultados del K-means con la data original
df_final <- cbind(Country_data, cluster = kmean$cluster)
head(df_final)
##               country child_mort exports health imports income inflation
## 1         Afghanistan       90.2    10.0   7.58    44.9   1610      9.44
## 2             Albania       16.6    28.0   6.55    48.6   9930      4.49
## 3             Algeria       27.3    38.4   4.17    31.4  12900     16.10
## 4              Angola      119.0    62.3   2.85    42.9   5900     22.40
## 5 Antigua and Barbuda       10.3    45.5   6.03    58.9  19100      1.44
## 6           Argentina       14.5    18.9   8.10    16.0  18700     20.90
##   life_expec total_fer  gdpp cluster
## 1       56.2      5.82   553       1
## 2       76.3      1.65  4090       2
## 3       76.5      2.89  4460       2
## 4       60.1      6.16  3530       1
## 5       76.8      2.13 12200       2
## 6       75.8      2.37 10300       2

Grafico de Clusters

# Visualize k-means clusters
country_names <- Country_data$country 
rownames(Datos_normalizados) <- country_names


fviz_cluster(kmean,
             data = Datos_normalizados,
             palette = c("#2E9FDF", "#F164FA", "#E7B800", "#FC4E07", "#99DE99"),
             ellipse.type = "euclid",
             star.plot = TRUE,
             repel = TRUE,
             ggtheme = theme_minimal())

Bibliografía

De la Fuente Fernández, S. (2014). Análisis de conglomerados.
Kassambara, A. (2017). Practical guide to cluster analysis in r: Unsupervised machine learning. STHDA. https://books.google.com.sv/books?id=-q3snAAACAAJ