domingo, 6 de julio de 2025

Cluster. K-mean

Análisis de cluster. Códigos

K-means

En este ejemplo trabajaremos con el conjunto de datos “USArrest” que está precargado en R.

# Cargar la base de datos USArrests
data(USArrests)

Inspecciono el conjunto de datos

# Mostrar las primeras filas de la base de datos

head(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
## Arkansas      8.8     190       50 19.5
## California    9.0     276       91 40.6
## Colorado      7.9     204       78 38.7

También inspecciono el contenido completo

glimpse(USArrests)
## Rows: 50
## Columns: 4
## $ Murder   <dbl> 13.2, 10.0, 8.1, 8.8, 9.0, 7.9, 3.3, 5.9, 15.4, 17.4, 5.3, 2.~
## $ Assault  <int> 236, 263, 294, 190, 276, 204, 110, 238, 335, 211, 46, 120, 24~
## $ UrbanPop <int> 58, 48, 80, 50, 91, 78, 77, 72, 80, 60, 83, 54, 83, 65, 57, 6~
## $ Rape     <dbl> 21.2, 44.5, 31.0, 19.5, 40.6, 38.7, 11.1, 15.8, 31.9, 25.8, 2~

Puede observarse que la magnitud en que están expresadas las variables no es homogénea. En ese caso es necesario aplicar cualquiera de los métodos conocidos para estandarizar las variables. Voy a emplear puntuaciones z. En R existe una función que ayuda con este procedimiento:

# Escalar los datos es crucial para que todas las variables tengan la misma importancia
# Esto evita que variables con rangos más grandes dominen el cálculo de distancias

# guardo el resultado en un objeto que llamare "df"
df <- scale(USArrests)

Inspecciono nuevamente

glimpse(df)
##  num [1:50, 1:4] 1.2426 0.5079 0.0716 0.2323 0.2783 ...
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:50] "Alabama" "Alaska" "Arizona" "Arkansas" ...
##   ..$ : chr [1:4] "Murder" "Assault" "UrbanPop" "Rape"
##  - attr(*, "scaled:center")= Named num [1:4] 7.79 170.76 65.54 21.23
##   ..- attr(*, "names")= chr [1:4] "Murder" "Assault" "UrbanPop" "Rape"
##  - attr(*, "scaled:scale")= Named num [1:4] 4.36 83.34 14.47 9.37
##   ..- attr(*, "names")= chr [1:4] "Murder" "Assault" "UrbanPop" "Rape"

El siguiente paso consiste en determinar el número óptimo de conglomerados (K). Para ello emplearé el “Método del codo (Elbow method)” para encontrar el K óptimo. Se calcula la suma total de cuadrados dentro de los conglomerados (tot.withinss) para diferentes valores de K.

set.seed(123) # Para reproducibilidad de los resultados aleatorios
wss <- (nrow(df)-1)*sum(apply(df,2,var))
for (i in 2:15) {
  wss[i] <- sum(kmeans(df, centers=i, nstart=25)$withinss)
}

Grafico el método del codo:

plot(1:15, wss, type="b", xlab="Número de Clusters (K)",
     ylab="Suma de Cuadrados Intra-Cluster",
     main="Método del Codo para K-means en USArrests")

En este gráfico, se busca un “codo” donde la disminución de la suma de cuadrados intra-cluster se ralentiza significativamente. Esto sugiere un K apropiado. Para USArrests, K=4 es un punto de inflexión común.

Asumiendo K=4 por el método del codo, nstart=25 significa que el algoritmo se ejecutará 25 veces con diferentes centroides iniciales y se elegirá la mejor solución (la que tenga la menor suma de cuadrados intra-cluster total), lo que ayuda a evitar óptimos locales.

km.res <- kmeans(df, centers = 4, nstart = 25)

El siguiente paso consiste en interpretar los resultados

km.res
## K-means clustering with 4 clusters of sizes 13, 13, 8, 16
## 
## Cluster means:
##       Murder    Assault   UrbanPop        Rape
## 1 -0.9615407 -1.1066010 -0.9301069 -0.96676331
## 2  0.6950701  1.0394414  0.7226370  1.27693964
## 3  1.4118898  0.8743346 -0.8145211  0.01927104
## 4 -0.4894375 -0.3826001  0.5758298 -0.26165379
## 
## Clustering vector:
##        Alabama         Alaska        Arizona       Arkansas     California 
##              3              2              2              3              2 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              2              4              4              2              3 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              4              1              2              4              1 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              4              1              3              1              2 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              4              2              1              3              2 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              1              1              2              1              4 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              2              2              3              1              4 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              4              4              4              4              3 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              1              3              2              4              1 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              4              4              1              1              4 
## 
## Within cluster sum of squares by cluster:
## [1] 11.952463 19.922437  8.316061 16.212213
##  (between_SS / total_SS =  71.2 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Para evitar tener de un “solo golpe” todo el resultado se puede proceder de la siguiente manera:

#centroides
km.res$centers
##       Murder    Assault   UrbanPop        Rape
## 1 -0.9615407 -1.1066010 -0.9301069 -0.96676331
## 2  0.6950701  1.0394414  0.7226370  1.27693964
## 3  1.4118898  0.8743346 -0.8145211  0.01927104
## 4 -0.4894375 -0.3826001  0.5758298 -0.26165379

los centroides de los clusters (medias para cada variable en cada cluster) representan las características promedio de cada conglomerado.

Veamos ahora el tamaño de cada cluster:

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

El tamaño de cada cluster es el número de individuos, en esta caso de estados, en cada conglomerado

Ahora, observamos a qué cluster pertenece cada estado:

km.res$cluster
##        Alabama         Alaska        Arizona       Arkansas     California 
##              3              2              2              3              2 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              2              4              4              2              3 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              4              1              2              4              1 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              4              1              3              1              2 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              4              2              1              3              2 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              1              1              2              1              4 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              2              2              3              1              4 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              4              4              4              4              3 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              1              3              2              4              1 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              4              4              1              1              4

Lo ideal es ver esos clusters en el conjunto de datos inicial. Para ello procedo de la siguiente forma:

# Añadir la asignación de cluster al data frame original para un análisis más fácil
USArrests_clustered <- cbind(USArrests, cluster = km.res$cluster)

Inspecciono:

glimpse(USArrests_clustered)
## Rows: 50
## Columns: 5
## $ Murder   <dbl> 13.2, 10.0, 8.1, 8.8, 9.0, 7.9, 3.3, 5.9, 15.4, 17.4, 5.3, 2.~
## $ Assault  <int> 236, 263, 294, 190, 276, 204, 110, 238, 335, 211, 46, 120, 24~
## $ UrbanPop <int> 58, 48, 80, 50, 91, 78, 77, 72, 80, 60, 83, 54, 83, 65, 57, 6~
## $ Rape     <dbl> 21.2, 44.5, 31.0, 19.5, 40.6, 38.7, 11.1, 15.8, 31.9, 25.8, 2~
## $ cluster  <int> 3, 2, 2, 3, 2, 2, 4, 4, 2, 3, 4, 1, 2, 4, 1, 4, 1, 3, 1, 2, 4~

Ahora lo importante es visualizar este resultado:

# Se utiliza el paquete 'factoextra' para una visualización más rica. Si no está instalada, descomentar la siguiente línea:
# install.packages("factoextra")
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
fviz_cluster(km.res, data = df,
             palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"), # Colores para los clusters
             ellipse.type = "euclid", # Dibuja elipses alrededor de los clusters basadas en la distancia euclidiana
             star.plot = TRUE, # Dibuja líneas desde cada punto a su centroide
             repel = TRUE, # Evita la superposición de etiquetas de texto
             ggtheme = theme_minimal(), # Tema del gráfico
             main = "Clusters de Estados de EE. UU. (USArrests) segun criminalidad")

Discusión de resultados en contexto sociológico:

Analizar las características de los estados en cada conglomerado basándose en los centroides (tasas de arresto). Por ejemplo, si se observa el output de km.res$centers:

  • Un cluster podría tener altas tasas en todas las categorías (estados con alta criminalidad general).
  • Otro cluster podría tener bajas tasas en todas las categorías (estados con baja criminalidad).
  • Un tercer cluster podría tener tasas altas de asalto pero bajas de asesinato y violación (patrón específico).
  • Un cuarto cluster podría mostrar otro patrón distintivo.

Implicaciones para políticas públicas: ¿Cómo diseñar políticas de seguridad o programas de intervención social diferenciados para cada tipo de estado, basándose en sus patrones de criminalidad?

No hay comentarios.: