Referencia rápida: aprendizaje no supervisado en R

Esta página es una referencia rápida de los comandos de tidyverse, cluster y factoextra que usamos para clustering y PCA en la Sesión 3.1 y en el Laboratorio 5. Todos los ejemplos son ejecutables con datos simulados. Pueden copiar y pegar el código en RStudio para practicar.

El flujo típico de un análisis no supervisado es:

  1. Seleccionar variables numéricas y escalarlas
  2. Explorar correlaciones
  3. Aplicar K-means (o clustering jerárquico) y elegir K
  4. Evaluar la calidad de los clusters con la silueta
  5. Reducir dimensionalidad con PCA
  6. Interpretar los componentes y visualizar con un biplot
  7. Caracterizar los grupos con variables sustantivas

1 Paquetes y datos simulados

1.1 Cargar paquetes

library(tidyverse)   # manipulación de datos y visualización
library(cluster)     # silhouette(), distancias
library(factoextra)  # fviz_cluster, fviz_pca, fviz_nbclust, fviz_eig
library(dendextend)  # dendrogramas coloreados
library(corrplot)    # matrices de correlación

set.seed(2026)       # fijar semilla para reproducibilidad

1.2 Crear datos simulados

Simulamos 90 “países” distribuidos en 3 niveles de desarrollo (alto, medio, bajo) con 5 indicadores correlacionados. Usaremos este mismo tibble en toda la página.

Sigma <- matrix(0.3, 5, 5); diag(Sigma) <- 1

sim_grupo <- function(n, mu, g) {
  MASS::mvrnorm(n, mu, Sigma) |>
    as_tibble(.name_repair = ~ c("pib", "educacion", "internet",
                                 "salud", "desigualdad")) |>
    mutate(grupo_real = g)
}

datos <- bind_rows(
  sim_grupo(30, c( 2.5,  2.5,  2.5,  2.0, -1.0), "alto"),
  sim_grupo(30, c( 0.0,  0.0,  0.0,  0.5,  2.0), "medio"),
  sim_grupo(30, c(-2.5, -2.5, -2.5, -1.5, -1.0), "bajo")
)

glimpse(datos)
Rows: 90
Columns: 6
$ pib         <dbl> 3.5811529, 4.8114772, 2.5808460, 3.2274287, 1.6410054, 4.5…
$ educacion   <dbl> 1.414789, 2.937925, 2.179774, 2.629537, 3.399682, 4.925100…
$ internet    <dbl> 0.5879008, 3.7838290, 2.1014524, 2.9783056, 1.9771634, 3.6…
$ salud       <dbl> 2.1784744, 1.6748550, 2.2056686, 1.6451850, 4.3726544, 3.5…
$ desigualdad <dbl> -0.9889160, -1.1271576, -1.0295412, -1.6993760, -0.6795119…
$ grupo_real  <chr> "alto", "alto", "alto", "alto", "alto", "alto", "alto", "a…

1.3 Preparar la matriz numérica

K-means y PCA requieren una matriz numérica escalada. Guardamos los nombres de grupo aparte para usarlos en la validación.

# Separar la etiqueta sustantiva (grupo_real) de las variables
grupo_real <- datos$grupo_real

datos_scaled <- datos |>
  select(where(is.numeric)) |>
  scale()

head(datos_scaled, 3)
          pib educacion  internet     salud desigualdad
[1,] 1.442561 0.6452436 0.2795019 1.0498135  -0.5833199
[2,] 1.951106 1.3189628 1.7146198 0.7655716  -0.6652105
[3,] 1.029093 0.9836143 0.9591557 1.0651618  -0.6073852

2 Exploración previa

2.1 Matriz de correlación

Antes de aplicar cualquier técnica, conviene ver qué variables están correlacionadas. Las correlaciones altas anticipan buenos resultados de PCA.

cor_matrix <- datos |>
  select(where(is.numeric)) |>
  cor()

corrplot(cor_matrix, method = "color", type = "upper",
         addCoef.col = "black", number.cex = 0.8,
         tl.col = "black", tl.srt = 45)

2.2 Distancias entre observaciones

La distancia euclidiana es la métrica por defecto de K-means y clustering jerárquico. También existen manhattan, maximum, canberra, binary y minkowski.

d <- dist(datos_scaled, method = "euclidean")

# Ver las primeras 5 observaciones
as.matrix(d)[1:5, 1:5] |> round(2)
     1    2    3    4    5
1 0.00 1.69 0.86 1.32 1.84
2 1.69 0.00 1.28 0.83 2.19
3 0.86 1.28 0.00 0.72 1.41
4 1.32 0.83 0.72 0.00 1.87
5 1.84 2.19 1.41 1.87 0.00

3 K-means

3.1 Ajustar K-means

nstart = 25 ejecuta 25 inicializaciones aleatorias y devuelve la mejor. Protege contra mínimos locales. Siempre escalar las variables antes de llamar a kmeans().

km <- kmeans(datos_scaled, centers = 3, nstart = 25)

# Estructura del objeto
names(km)
[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      
  • km$cluster: vector de asignación (1, 2, 3, …)
  • km$centers: centros de cada cluster (en escala escalada)
  • km$totss, km$withinss, km$tot.withinss, km$betweenss: descomposición de varianza
# Asignación de cada observación
head(km$cluster, 15)
 [1] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
# Centros de los clusters
round(km$centers, 2)
    pib educacion internet salud desigualdad
1 -0.07      0.03    -0.02 -0.04        1.08
2  1.14      1.10     1.10  1.03       -0.54
3 -1.14     -1.21    -1.16 -1.06       -0.78
# Tamaño de cada cluster
km$size
[1] 34 29 27

3.2 Elegir K: método del codo

El “codo” aparece donde la suma de distancias intra-cluster (wss) deja de caer bruscamente. Se busca el punto donde añadir un cluster más ya no mejora mucho.

fviz_nbclust(datos_scaled, kmeans, method = "wss",
             k.max = 8, nstart = 25) +
  labs(title = "Método del codo",
       subtitle = NULL,
       y = "Suma de distancias intra-cluster (WSS)")

3.3 Elegir K: método de la silueta

La silueta mide qué tan bien cada punto encaja en su cluster. El K óptimo maximiza la silueta promedio.

fviz_nbclust(datos_scaled, kmeans, method = "silhouette",
             k.max = 8, nstart = 25) +
  labs(title = "Método de la silueta", subtitle = NULL)

3.4 Silueta detallada por observación

Una vez elegido K, podemos inspeccionar la silueta de cada punto. Valores negativos indican puntos mal clasificados.

sil <- silhouette(km$cluster, dist(datos_scaled))

# Silueta promedio
round(mean(sil[, 3]), 3)
[1] 0.502
# Visualizar por observación
fviz_silhouette(sil) +
  theme(axis.text.x = element_blank())
  cluster size ave.sil.width
1       1   34          0.49
2       2   29          0.50
3       3   27          0.51

Valor Interpretación
0.71 – 1.00 Estructura fuerte
0.51 – 0.70 Estructura razonable
0.26 – 0.50 Estructura débil
< 0.25 Sin estructura clara

3.5 Visualizar los clusters con fviz_cluster()

factoextra proyecta automáticamente los datos en los dos primeros componentes principales para visualizar en 2D.

fviz_cluster(km, data = datos_scaled,
             geom = "point",
             ellipse.type = "convex",
             palette = c("#2d4563", "#b85450", "#6b8e23"),
             ggtheme = theme_minimal())

3.6 Validar contra una etiqueta conocida

Si tenemos una etiqueta sustantiva (como grupo_real), podemos cruzarla con la asignación del modelo. Los números de cluster son arbitrarios: lo que importa es si cada grupo real cae en un solo cluster.

table(real = grupo_real, cluster = km$cluster)
       cluster
real     1  2  3
  alto   1 29  0
  bajo   3  0 27
  medio 30  0  0

3.7 Perfil de cada cluster

Para dar sentido sustantivo a los clusters, calculamos la media de cada variable dentro de cada grupo.

datos |>
  mutate(cluster = km$cluster) |>
  group_by(cluster) |>
  summarise(across(where(is.numeric),
                   \(x) round(mean(x), 2)))
# A tibble: 3 × 6
  cluster   pib educacion internet salud desigualdad
    <int> <dbl>     <dbl>    <dbl> <dbl>       <dbl>
1       1 -0.08      0.02    -0.08  0.25        1.82
2       2  2.86      2.44     2.41  2.15       -0.91
3       3 -2.67     -2.79    -2.61 -1.56       -1.32

4 Clustering jerárquico

4.1 Ajustar con distintos métodos de enlace

Método Cómo mide la distancia entre clusters
single Puntos más cercanos (puede crear cadenas)
complete Puntos más lejanos (clusters compactos)
average Promedio de todas las distancias
ward.D2 Minimiza la varianza interna (muy usado)
hc <- hclust(d, method = "ward.D2")

4.2 Cortar el dendrograma

cutree() corta el dendrograma a una altura que produce K clusters.

grupos_hc <- cutree(hc, k = 3)

table(real = grupo_real, jerarquico = grupos_hc)
       jerarquico
real     1  2  3
  alto  28  2  0
  bajo   0  4 26
  medio  0 30  0

4.3 Visualizar el dendrograma

plot(hc, hang = -1, labels = FALSE,
     main = "", xlab = "", sub = "", ylab = "Altura")
rect.hclust(hc, k = 3,
            border = c("#2d4563", "#b85450", "#6b8e23"))

4.4 Dendrograma coloreado con dendextend

dend <- as.dendrogram(hc) |>
  dendextend::color_branches(k = 3,
                             col = c("#2d4563", "#b85450", "#6b8e23")) |>
  dendextend::set("labels_cex", 0.6)

plot(dend, main = "", ylab = "Altura")

4.5 K-means vs. jerárquico

Criterio K-means Jerárquico
Elegir K Antes de ejecutar Después, cortando
Forma de clusters Esféricos Cualquier forma
Escalabilidad Muy rápido Lento con n grande
Reproducibilidad Depende de la semilla Determinístico
Visualización Scatter plot Dendrograma

5 Análisis de componentes principales (PCA)

5.1 Ajustar PCA con prcomp()

prcomp() espera datos numéricos. Como ya escalamos antes, no necesitamos scale. = TRUE. Si los datos no estuvieran escalados, sí habría que pasarlo.

pca <- prcomp(datos_scaled)

# Resumen: varianza explicada y acumulada
summary(pca)
Importance of components:
                          PC1    PC2     PC3     PC4     PC5
Standard deviation     1.8906 0.9897 0.44368 0.36372 0.34200
Proportion of Variance 0.7149 0.1959 0.03937 0.02646 0.02339
Cumulative Proportion  0.7149 0.9108 0.95015 0.97661 1.00000

5.2 Elegir cuántos componentes conservar

Tres criterios comunes:

  • Varianza acumulada cerca del 70-80%
  • Método del codo en el scree plot
  • Regla de Kaiser: conservar componentes con eigenvalue > 1
fviz_eig(pca, addlabels = TRUE,
         barfill = "#2d4563", barcolor = "#2d4563") +
  labs(title = NULL, y = "Varianza explicada (%)") +
  theme_minimal()

5.3 Loadings: ¿qué mide cada componente?

Los loadings (matriz de rotación) indican cuánto pesa cada variable en cada componente. Un loading grande (positivo o negativo) significa que la variable contribuye mucho al componente.

round(pca$rotation[, 1:3], 2)
              PC1   PC2   PC3
pib         -0.50  0.06 -0.33
educacion   -0.50  0.01 -0.19
internet    -0.50  0.08 -0.32
salud       -0.49  0.05  0.87
desigualdad -0.09 -0.99  0.00

Para ordenar por valor absoluto del primer componente:

as.data.frame(pca$rotation[, 1:3]) |>
  rownames_to_column("variable") |>
  arrange(desc(abs(PC1)))
     variable         PC1          PC2          PC3
1   educacion -0.50452430  0.005298435 -0.186995940
2         pib -0.50010880  0.057165985 -0.334692681
3    internet -0.49909235  0.075955937 -0.320860879
4       salud -0.48730811  0.049274174  0.866059045
5 desigualdad -0.09372328 -0.994236757 -0.001831322

5.4 Contribución de variables

fviz_contrib() muestra qué variables contribuyen más a un componente específico. La línea roja es el umbral “esperado” bajo una contribución uniforme.

fviz_contrib(pca, choice = "var", axes = 1,
             fill = "#2d4563", color = "#2d4563") +
  labs(title = "Contribución de variables a PC1") +
  theme_minimal()

5.5 Scores: posición de cada observación

Los scores (pca$x) son las coordenadas de cada observación en el nuevo espacio de componentes. Sirven para ranking y para visualización.

pca$x[, 1:2] |>
  as.data.frame() |>
  mutate(PC1 = round(PC1, 2),
         PC2 = round(PC2, 2),
         grupo_real = grupo_real) |>
  slice_head(n = 6)
    PC1  PC2 grupo_real
1 -1.64 0.74       alto
2 -2.81 0.95       alto
3 -1.95 0.79       alto
4 -2.19 1.22       alto
5 -2.62 0.62       alto
6 -3.75 0.19       alto

5.6 Biplot: observaciones y variables juntos

Un biplot muestra las observaciones (puntos) y las variables (flechas) en el mismo plano. Flechas cercanas indican variables correlacionadas; observaciones cercanas a una flecha tienen valores altos en esa variable.

fviz_pca_biplot(pca,
                geom.ind = "point",
                habillage = grupo_real,
                addEllipses = TRUE,
                palette = c("#2d4563", "#b85450", "#6b8e23"),
                col.var = "black",
                repel = TRUE,
                legend.title = "Grupo real") +
  labs(title = NULL) +
  theme_minimal()

6 Combinar PCA y clustering

Una práctica común es ejecutar K-means en las variables originales y visualizar los clusters en el plano PC1-PC2.

pca_coords <- as.data.frame(pca$x[, 1:2]) |>
  mutate(cluster = factor(km$cluster),
         grupo_real = grupo_real)

ggplot(pca_coords, aes(x = PC1, y = PC2,
                       colour = cluster, shape = grupo_real)) +
  geom_point(size = 3) +
  scale_colour_manual(values = c("#2d4563", "#b85450", "#6b8e23")) +
  labs(title = "Clusters de K-means sobre PC1-PC2",
       colour = "Cluster", shape = "Grupo real") +
  theme_minimal()

Otra opción es ejecutar K-means directamente sobre los primeros componentes (útil cuando hay muchas variables correlacionadas).

km_pca <- kmeans(pca$x[, 1:2], centers = 3, nstart = 25)

table(real = grupo_real, cluster = km_pca$cluster)
       cluster
real     1  2  3
  alto   1  0 29
  bajo   3 27  0
  medio 30  0  0

7 Recomendaciones prácticas

  • Siempre escalar antes de K-means o PCA. Las variables con mayor varianza dominan el resultado si no lo hacemos.
  • Fijar la semilla con set.seed(). K-means depende de inicialización aleatoria.
  • Usar nstart = 25 o más en kmeans() para evitar mínimos locales.
  • Combinar métricas: codo, silueta y conocimiento del dominio. Si los tres coinciden, el K elegido es defendible.
  • Nombrar los clusters con etiquetas sustantivas (“alto desarrollo”) en lugar de dejar “1, 2, 3”.
  • No reificar: los clusters son un resumen útil, no categorías naturales. Dependen de K, de las variables elegidas y del escalado.
  • PCA sólo captura relaciones lineales. Si sospechan no linealidad, considerar UMAP o t-SNE.

8 Resumen de funciones

Función Paquete Para qué sirve
scale() base Estandariza variables (media 0, sd 1)
dist() base Matriz de distancias entre observaciones
kmeans() base Ajusta K-means
hclust() base Ajusta clustering jerárquico
cutree() base Corta un dendrograma en K grupos
rect.hclust() base Dibuja rectángulos sobre el dendrograma
prcomp() base Ajusta PCA
silhouette() cluster Calcula la silueta por observación
fviz_cluster() factoextra Visualiza clusters en 2D
fviz_nbclust() factoextra Elige K con codo o silueta
fviz_silhouette() factoextra Silueta por observación
fviz_eig() factoextra Scree plot de PCA
fviz_contrib() factoextra Contribución de variables a un PC
fviz_pca_biplot() factoextra Biplot de PCA
color_branches() dendextend Colorea ramas del dendrograma
corrplot() corrplot Matriz de correlaciones visual

Para la referencia de aprendizaje supervisado (clasificación y regresión con tidymodels), ver Referencia: tidymodels. Para análisis de texto, ver Referencia: análisis de texto.

Volver arriba