Inteligencia Artificial

Agrupamiento de áreas en cordillera Cantábrica

Author

Dr. Jorge Párraga Álava

Maestria en Geomática

Introducción

Una agencia de planificación territorial en una región montañosa de España, específicamente en la cordillera Cantábrica, desea desarrollar un plan de manejo sostenible del territorio que equilibre la conservación del medio ambiente con el desarrollo económico local. Como experto de machine learning se le solicita lo siguiente:

  1. Implementar técnicas de clustering (k-means, agrupamiento jerárquico) para identificar grupos naturales de áreas dentro de la región que compartan características similares.

  2. Caracterizar los grupos identificados, describiendo sus principales características en términos de las variables cualitativas.

Etapa 1: Dominio del problema

El dataset proporcionado incluye la siguiente descripción de variables:

  • latitud y longitud: Coordenadas geográficas en grados decimales.

  • elevacion: Altura sobre el nivel del mar en metros.

  • pendiente: Inclinación del terreno en grados.

  • orientacion: Dirección de la pendiente (puntos cardinales).

  • tipo_suelo: Clasificación del suelo según su composición.

  • cobertura_vegetal: Tipo de vegetación o uso del suelo predominante.

  • temperatura_media: Temperatura promedio anual en grados Celsius.

  • indice_ndvi: Índice de Vegetación de Diferencia Normalizada, que mide la salud de la vegetación.

  • riesgo_erosion: Clasificación del riesgo de erosión del suelo.

A partir de la información proporcionada, se responde lo siguiente:

  1. ¿Qué exactamente deseamos hacer?

Identificar grupos de áreas dentro de la región de la coordillera.

  1. ¿Es factible alcanzar lo que buscamos con los datos disponibles?

Si, las variables proporcionadas aparentemente parecen ser suficientes para alcanzar el agrupamiento.

  1. ¿Cómo podemos lograrlo?

Aplicar técnicas no supervisadas de agrupamiento tanto jerárquico como particional.

  1. ¿Qué tipo de problema se va a resolver?

Problema de agrupamiento.

  1. ¿El objetivo es?

Agrupar: zonas geográficas

Configuración Inicial

Code
# Bibliotecas -------------------------------------------------------------
if(!require(tidyverse)) {install.packages("tidyverse")} 
if(!require(tidyr)) {install.packages("tidyr")} 
if(!require(leaflet)) {install.packages("leaflet")} 
if(!require(ggplot2)) {install.packages("ggplot2")} 
if(!require(cluster)) {install.packages("cluster")} 
if(!require(readr)) {install.packages("readr")}  
if(!require(dplyr)) {install.packages("dplyr")}  
if(!require(factoextra)) {install.packages("factoextra")}  
if(!require(dendextend)) {install.packages("dendextend")}  
if(!require(colorspace)) {install.packages("colorspace")} 
if(!require(ggdendro)) {install.packages("ggdendro")}   

# Config -------------------------------------------------------------
# Para notación no numérica.
options(scipen=999) 
# Para reproducibilidad.
set.seed(2024) 

Etapa 2 y 3: Adquisición y preprocesamiento

Se adquieren los datos desde un archivo externo. Luego, para el proceso de agrupamiento, se seleccionan las variables numéricas elevacion, pendiente, precipitacion_anual, temperatura_media, indice_ndvi. Para análisis posterior al proceso de agrupamiento se usarán latitud, longitud y las variables cualitativas orientacion, tipo_suelo, cobertura_vegetal, riesgo_erosion. Las variables seleccionadas se transforman mediante escalado de variables.

Code
#Cargar datos
dataset_original <- read_csv("Datos/datos_cantabrico_clusters.csv")

#Preparar datos para clustering seleccionando variables numéricas y escalándolas 
  datos_coordillera <- dataset_original %>%
      dplyr::select(elevacion, pendiente, precipitacion_anual, temperatura_media, indice_ndvi) %>%
  scale()

Etapa 4: Modelado

Agrupamiento particional

Para el modelado se considerarán varios valores de k para el algoritmo k-means. Esta es una forma para encontrar el agrupamiento óptimo de los datos.

Code
# Función para evaluar diferentes valores de k y distancias
evalua_kmeans <- function(data, k_rango) {
  
  results <- expand.grid(k = k_rango) 
  results$silhouette <- NA
  
  for(i in 1:nrow(results)) {
      k <- results$k[i] 
      km <- kmeans(data, centers = k, nstart = 25) 
      results$silhouette[i] <- mean(silhouette(km$cluster, dist(data))[, 3])
  }
return(results)
}

Agrupamiento jerárquico

Ahora aplicamos agrupamiento jerárquico con los tres criterios de enlace: simple, completo, promedio.

Code
# Calculamos la matriz de distancia euclidea
dist_matrix <- dist(datos_coordillera, method = "euclidean")

# Aplicamos agrupamiento jerárquico con los tres criterios de enlace estudiados
hc_simple <- hclust(dist_matrix, method = "single")
hc_completo<- hclust(dist_matrix, method = "complete")
hc_promedio <- hclust(dist_matrix, method = "average")

Etapa 5: Validación

Validación de método k-means

Se evalúan diversos valores de k:

Code
# Definir rangos de k y distancias a evaluar
k_rango <- 2:50 
#Evaluar combinaciones
eval_results <- evalua_kmeans(datos_coordillera, k_rango)
eval_results
    k silhouette
1   2  0.7080919
2   3  0.7045614
3   4  0.6949079
4   5  0.7270429
5   6  0.6140980
6   7  0.5264596
7   8  0.4133476
8   9  0.3528784
9  10  0.2417459
10 11  0.2450212
11 12  0.2374133
12 13  0.2429678
13 14  0.2410220
14 15  0.2387805
15 16  0.2309613
16 17  0.2299858
17 18  0.2297218
18 19  0.2164076
19 20  0.2171612
20 21  0.2255639
21 22  0.2023871
22 23  0.2165377
23 24  0.2018630
24 25  0.2010984
25 26  0.2133195
26 27  0.1930601
27 28  0.1972603
28 29  0.1977786
29 30  0.1875753
30 31  0.1985370
31 32  0.1952455
32 33  0.1965598
33 34  0.1963880
34 35  0.1973889
35 36  0.1948518
36 37  0.1977862
37 38  0.1924531
38 39  0.1958264
39 40  0.2012937
40 41  0.1952994
41 42  0.1957448
42 43  0.1960877
43 44  0.1985176
44 45  0.1973843
45 46  0.2026829
46 47  0.1954363
47 48  0.1947494
48 49  0.1989149
49 50  0.2010796

La tabla muestra los valores del valor de la Silueta. A partir de eso, aplicamos el método elbows (codo) para observar el comportamiento de los diversos parámetros considerando el valor de esta métrica de calidad del agrupamiento.

Code
# Visualizar resultados
ggplot(eval_results, aes(x = k, y = silhouette)) +
geom_line() +
geom_point() +
labs(title = "Silueta para cada valor de k (grupos)",
x = "Número de grupos k",
y = "Promedio de Silueta")

Una vez explorado y visualizado el rendimiento del algoritmo con diversos valores de k (el codo parece producirse en 5 y 10), seleccionamos el mejor basado en el método del codo.

Code
mejor_k <- eval_results %>% 
slice(which.max(silhouette)) %>%
ungroup() %>%
slice(which.max(silhouette))
print(paste("El mejor k es", mejor_k$k, "con distancia", mejor_k$distance, "con silueta", mejor_k$silhouette))
[1] "El mejor k es 5 con distancia  con silueta 0.727042855601676"

Ahora, se aplica k-means con los mejores parámetros:

Code
# Para reproducibilidad.
set.seed(1987) 
# ejecutamos k means con mejor k
mejor_kmeans <- kmeans(datos_coordillera, centers = mejor_k$k)
#Mostrar resumen del mejor resultado de kmeans
mejor_kmeans
K-means clustering with 5 clusters of sizes 191, 202, 204, 200, 203

Cluster means:
   elevacion  pendiente precipitacion_anual temperatura_media indice_ndvi
1  1.6049007  1.5871543           1.6121983        -1.6021427   1.6093155
2 -0.7205987 -0.7336381          -0.7297630         0.6121287  -0.7147875
3 -0.2577072 -0.2539548          -0.2595383         0.2950225  -0.2599726
4  0.6713916  0.6663928           0.6746585        -0.6404972   0.6645509
5 -1.1954736 -1.1646471          -1.1945992         1.2328773  -1.1963938

Clustering vector:
   [1] 4 4 3 3 4 2 1 5 3 4 2 4 4 5 1 5 5 2 4 3 3 5 4 1 5 4 2 1 3 2 5 5 3 4 1 2 2
  [38] 4 5 3 2 2 1 2 3 5 5 4 5 2 5 3 1 1 4 5 3 5 3 1 2 2 4 5 1 5 5 4 1 5 4 2 4 3
  [75] 2 2 4 3 3 3 1 3 3 1 1 5 4 4 5 4 2 3 4 3 2 2 4 1 1 1 2 4 5 3 5 3 2 4 1 1 5
 [112] 1 5 4 1 4 2 1 1 1 5 3 4 1 4 5 2 2 3 4 2 5 1 3 1 2 2 2 2 5 3 5 3 2 2 5 3 2
 [149] 1 3 3 4 5 5 2 2 4 3 2 2 4 4 5 1 3 5 3 1 2 5 5 5 2 4 5 3 3 5 3 5 2 3 4 2 1
 [186] 2 3 5 1 3 2 5 2 5 1 4 5 5 3 1 3 5 2 2 5 5 2 2 3 3 2 5 5 2 2 1 2 2 1 3 2 5
 [223] 5 3 5 3 2 5 4 3 3 2 1 5 2 3 2 3 3 1 4 1 4 4 4 2 4 3 4 5 2 5 1 3 3 1 5 1 2
 [260] 2 4 2 2 4 5 4 1 3 2 5 1 4 5 3 4 4 5 3 5 3 3 3 2 1 1 2 1 5 3 5 3 4 3 4 5 1
 [297] 3 4 2 2 4 3 2 1 3 5 1 5 1 2 1 3 3 1 4 2 3 4 4 4 5 5 4 1 1 2 3 3 1 3 5 1 4
 [334] 5 2 4 1 3 2 2 3 3 3 5 4 1 4 4 4 1 1 4 1 3 4 1 3 5 4 1 5 5 1 1 2 5 1 2 4 3
 [371] 2 1 1 5 4 4 4 2 3 2 3 5 1 3 3 4 2 5 4 4 3 2 5 1 1 5 4 1 1 5 2 4 1 3 4 3 4
 [408] 4 5 2 1 1 2 4 3 1 2 3 4 4 5 4 3 4 4 5 3 3 3 1 5 2 2 4 4 2 5 1 3 2 1 2 5 3
 [445] 4 5 5 4 3 1 2 4 1 2 4 5 2 3 1 3 5 2 3 2 5 5 3 1 3 5 5 5 1 5 4 3 5 5 2 2 1
 [482] 1 1 2 1 5 4 2 1 3 5 1 5 4 5 4 4 3 4 1 3 5 2 4 1 2 2 1 5 5 1 4 2 3 5 1 1 1
 [519] 4 3 3 4 2 1 4 4 5 2 2 5 5 5 2 2 1 3 3 1 3 5 4 2 4 5 3 1 1 5 2 5 1 4 1 2 2
 [556] 1 4 4 3 2 2 5 4 3 4 1 3 2 4 4 3 3 1 3 5 1 5 2 3 2 3 3 4 3 4 4 5 5 2 5 2 5
 [593] 3 5 4 4 1 3 1 4 5 4 1 3 2 3 5 3 4 4 3 3 1 2 3 4 5 2 4 3 5 1 1 4 5 3 4 5 5
 [630] 3 3 1 4 3 3 2 5 4 3 3 1 1 1 2 2 1 1 3 2 3 3 1 2 4 4 3 3 5 4 2 4 2 5 3 1 4
 [667] 2 2 2 1 4 4 3 3 3 3 3 4 5 5 5 5 4 5 3 4 1 1 4 1 3 4 1 4 1 2 5 2 1 2 5 5 5
 [704] 3 3 1 5 3 5 4 4 5 1 4 2 3 1 3 3 5 5 4 2 2 5 1 3 4 4 3 2 1 5 1 4 2 2 1 2 5
 [741] 2 1 2 2 2 4 1 2 2 1 1 4 4 1 5 1 3 3 1 5 5 3 1 2 4 5 4 4 3 1 5 4 2 2 2 3 2
 [778] 2 5 2 5 3 5 5 3 1 2 3 3 3 1 2 3 4 5 1 4 4 4 3 1 1 3 4 5 1 5 5 1 4 2 3 2 3
 [815] 1 3 3 1 1 5 4 5 4 4 4 2 3 5 2 4 4 4 4 5 4 4 3 2 2 3 2 3 4 5 4 4 2 2 3 1 4
 [852] 2 5 5 2 4 3 2 1 5 2 2 5 5 2 1 2 1 2 2 5 3 2 5 2 1 4 1 4 5 5 5 2 5 1 2 3 5
 [889] 1 2 4 5 1 1 5 5 4 1 2 5 5 2 4 1 4 3 3 1 4 3 1 1 2 2 5 2 4 3 5 5 4 3 3 4 2
 [926] 2 2 1 5 3 1 2 3 1 5 2 1 2 3 2 1 5 3 3 3 2 2 4 3 3 4 4 4 1 5 2 3 5 5 5 2 5
 [963] 3 1 3 2 3 3 2 1 4 2 4 1 5 1 3 5 2 4 1 1 5 1 1 4 5 2 1 4 1 1 1 4 4 4 1 4 4
[1000] 3

Within cluster sum of squares by cluster:
[1] 13.90882 14.22065 15.78727 14.91477 14.94863
 (between_SS / total_SS =  98.5 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      
Code
#Añadir cluster al dataset original
dataset_original$cluster <- as.factor(mejor_kmeans$cluster)

Se observa que hay 191, 202, 204, 200, 203 puntos de datos que pertenecen a cada uno de los cinco grupos. De igual modo se observan los valores de cada uno de los cinco centroides.

Ahora mostramos los grupos de forma espacial en un mapa:

Code
# Crear una paleta de colores para los clusters
pal <- colorFactor(palette = "Set1", domain = dataset_original$cluster)

# Crear el mapa Leaflet
mapa_leaflet <- leaflet(dataset_original) %>%
  addProviderTiles(providers$OpenStreetMap) %>%  # Añadir el mapa base de OpenStreetMap
  addCircleMarkers(
    ~longitud, ~latitud,
    color = ~pal(cluster),
    radius = 5,
    stroke = FALSE,
    fillOpacity = 0.7,
    popup = ~paste("Grupo:", cluster, "<br>",
                   "Elevación:", round(elevacion, 2), "m<br>",
                   "Pendiente:", round(pendiente, 2), "m<br>",
                   "Precipitación:", round(precipitacion_anual, 2), "m<br>",
                   "Temp. media:", round(temperatura_media, 2), "m<br>", 
                   "NDVI:", round(indice_ndvi, 2))
  ) %>%
  addLegend(
    "bottomright",
    pal = pal,
    values = ~cluster,
    title = "Grupos",
    opacity = 1
  ) %>%
  setView(lng = -5, lat = 43.1, zoom = 7)  # Centrar en la cordillera Cantábrica

# Mostrar el mapa
mapa_leaflet

Caracterizar los grupos identificados.

Finalmente, caracterizamos los grupos considerando las variables cualitativas orientacion, tipo_suelo, cobertura_vegetal, riesgo_erosion.

Code
# Caracterizar los grupos
caracterizacion <- dataset_original %>%
  group_by(cluster) %>%
  summarise(
    count = n(),
    orientacion = list(table(orientacion)),
    tipo_suelo = list(table(tipo_suelo)),
    cobertura_vegetal = list(table(cobertura_vegetal)),
    riesgo_erosion = list(table(riesgo_erosion))
  )

# Mostrar la caracterización de cada grupo
print(caracterizacion)
# A tibble: 5 × 6
  cluster count orientacion tipo_suelo  cobertura_vegetal riesgo_erosion
  <fct>   <int> <list>      <list>      <list>            <list>        
1 1         191 <table [8]> <table [5]> <table [5]>       <table [3]>   
2 2         202 <table [8]> <table [5]> <table [5]>       <table [3]>   
3 3         204 <table [8]> <table [5]> <table [5]>       <table [3]>   
4 4         200 <table [8]> <table [5]> <table [5]>       <table [3]>   
5 5         203 <table [8]> <table [5]> <table [5]>       <table [3]>   
Code
# Función para imprimir tablas de distribución de variables categóricas
print_distribucion <- function(distribution, cluster, variable) {
  cat(paste("\nCluster:", cluster, "- Variable:", variable, "\n"))
  print(distribution)
}

# Imprimir las tablas de distribución de cada variable categórica por cluster
for (i in 1:nrow(caracterizacion)) {
  cluster <- caracterizacion$cluster[i]
  print_distribucion(caracterizacion$orientacion[[i]], cluster, "Orientacion")
  print_distribucion(caracterizacion$tipo_suelo[[i]], cluster, "Tipo de Suelo")
  print_distribucion(caracterizacion$cobertura_vegetal[[i]], cluster, "Cobertura Vegetal")
  print_distribucion(caracterizacion$riesgo_erosion[[i]], cluster, "Riesgo de Erosion")
}

Cluster: 1 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
27 27 23 29 20 20 27 18 

Cluster: 1 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       39        41        33        36        42 

Cluster: 1 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      39       38       24       44       46 

Cluster: 1 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   72    59    60 

Cluster: 2 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
34 20 26 25 15 30 26 26 

Cluster: 2 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       44        39        45        46        28 

Cluster: 2 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      47       25       46       36       48 

Cluster: 2 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   78    65    59 

Cluster: 3 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
28 34 24 21 21 27 26 23 

Cluster: 3 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       34        53        47        40        30 

Cluster: 3 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      46       31       46       42       39 

Cluster: 3 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   69    67    68 

Cluster: 4 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
20 30 22 25 37 22 24 20 

Cluster: 4 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       41        41        39        59        20 

Cluster: 4 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      45       33       43       35       44 

Cluster: 4 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   67    65    68 

Cluster: 5 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
21 25 30 26 29 23 22 27 

Cluster: 5 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       48        34        45        40        36 

Cluster: 5 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      46       34       49       37       37 

Cluster: 5 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   67    59    77 

Observamos que:

  • Grupo 1: Se caracteriza por una mayor presencia de suelos orgánicos y una distribución equilibrada en términos de orientación. La cobertura vegetal muestra una mezcla de Bosque, Urbano y Matorral, con un riesgo de erosión que no varía mucho entre Bajo, Medio y Alto.

  • Grupo 2: Se destaca por su alta proporción de suelos Arcillosos, Limosos y Orgánicos, además de una alta incidencia de riesgo de erosión Alto. La cobertura vegetal está dominada por Bosque, Urbano y Matorral, y la orientación varía pero con predominancia en Este.

  • Grupo 3: Se diferencia por una mayor proporción de suelos Arenosos y una cobertura vegetal diversa con una buena representación de Bosque y Matorral. El riesgo de erosión está bien equilibrado.

  • Grupo 4: Se destaca por su alta proporción de suelos Rocosos y Arenosos, con una cobertura vegetal dominada por Urbano y Pradera. El riesgo de erosión es alto en este grupo, indicando áreas potencialmente más vulnerables a la erosión.

  • Grupo 5: Se caracteriza por su alta proporción de suelos Arcillosos y Limosos, y una cobertura vegetal que incluye una buena cantidad de Bosque y Matorral. El riesgo de erosión es predominantemente Medio, con una distribución más equilibrada comparado con otros grupos.

Validación de método jerárquico

Ahora evaluamos el rendimiento de los tres criterios considerando puntos de corte que generen entre 2 y 50 grupos.

Code
# Funicón para calcular le promedio del silueta para el rango de k grupos.
calc_avg_sil <- function(k, hc_method) {
  cut <- cutree(hc_method, k = k)
  sil <- silhouette(cut, dist_matrix)
  return(mean(sil[, 3]))
}

# Calcula el promedio de siuelta para cada criterio de enlace
k_rango <- 2:50
sil_simple <- sapply(k_rango, calc_avg_sil, hc_method = hc_simple)
sil_completo <- sapply(k_rango, calc_avg_sil, hc_method = hc_completo)
sil_promedio <- sapply(k_rango, calc_avg_sil, hc_method = hc_promedio)

# Combina resultados results
sil_df <- data.frame(
  k = k_rango,
  Simple = sil_simple,
  Completo = sil_completo,
  Promedio = sil_promedio
)

Mostramos el rendimiento:

Code
# Plot de Silueta 
ggplot(sil_df, aes(x = k)) +
  geom_line(aes(y = Simple, color = "Simple")) +
  geom_line(aes(y = Completo, color = "Completo")) +
  geom_line(aes(y = Promedio, color = "Promedio")) +
  labs(x = "Número de grupos (k)", y = "Promedio de Silueta",
       title = "Análisis de rendimiento de métodos jerárquicos",
       color = "Criterio de enlace") +
  theme_minimal() +
  theme(legend.position = "bottom")

Ahora determinamos cuál de los tres criterios es que le mejor agrupamiento ofrece.

Code
# Encontrar el mejor k
mejor_enlace <- c("Simple", "Completo", "Promedio")[which.max(c(max(sil_simple), max(sil_completo), max(sil_promedio)))]
mejor_k <- k_rango[which.max(sil_df[[mejor_enlace]])]

cat("Mejor criterio de enlace:", mejor_enlace, "\n")
Mejor criterio de enlace: Simple 
Code
cat("Mejor k:", mejor_k, "\n")
Mejor k: 5 
Code
cat("Mejor silueta:", max(sil_df[[mejor_enlace]]), "\n")
Mejor silueta: 0.7270429 

El mejor rendimiento se alcanza con agrupamiento con k=5 y criterio de enlace simple, lo que proporciona una silueta de 0.72, indicando que las estructuras grupales encontradas son sólidas.

Code
# Definir la altura de punto de corte.
cut_simple <- cutree(hc_simple, k = mejor_k)

#Añadir cluster al dataset original
dataset_original$cluster_dendro <- as.factor(cut_simple)


# Función para crear y plotear un dendrograma coloreado

plot_dendrograma_coloreado <- function(hc, cut, title) {
  # Convertir el objeto hclust a un objeto dendrogram
  dend <- as.dendrogram(hc)
  # Colorear las ramas
  dend <- color_branches(dend, k = mejor_k)
  # Crear una paleta de colores distintivos
  col_palette <- rainbow_hcl(mejor_k)
  # Colorear las etiquetas de las hojas
  dend <- color_labels(dend, col = col_palette[cut])
  
  # Crear el plot
  plot(dend, main = title, 
       ylab = "Altura/Distancia", 
       xlab = "Observaciones",
       sub = paste("Número de clusters:", mejor_k))
  # Añadir rectángulos para los clusters
  rect.dendrogram(dend, k = mejor_k, border = col_palette, lwd = 2)
  # Añadir una leyenda
  legend("topright", 
         legend = paste("Grupo", 1:mejor_k), 
         fill = col_palette, 
         border = col_palette,
         title = "Grupos")
}

# Plotear el dendrograma coloreado
plot_dendrograma_coloreado(hc_simple, cut_simple, "Dendrograma con enlace simple")

Caracterizar los grupos identificados.

Finalmente, caracterizamos los grupos considerando las variables orientacion, tipo_suelo, cobertura_vegetal, riesgo_erosion.

Code
# Caracterizar los grupos
caracterizacion <- dataset_original %>%
  group_by(cluster_dendro) %>%
  summarise(
    count = n(),
    orientacion = list(table(orientacion)),
    tipo_suelo = list(table(tipo_suelo)),
    cobertura_vegetal = list(table(cobertura_vegetal)),
    riesgo_erosion = list(table(riesgo_erosion))
  )

# Mostrar la caracterización de cada grupo
print(caracterizacion)
# A tibble: 5 × 6
  cluster_dendro count orientacion tipo_suelo  cobertura_vegetal riesgo_erosion
  <fct>          <int> <list>      <list>      <list>            <list>        
1 1                200 <table [8]> <table [5]> <table [5]>       <table [3]>   
2 2                204 <table [8]> <table [5]> <table [5]>       <table [3]>   
3 3                202 <table [8]> <table [5]> <table [5]>       <table [3]>   
4 4                191 <table [8]> <table [5]> <table [5]>       <table [3]>   
5 5                203 <table [8]> <table [5]> <table [5]>       <table [3]>   
Code
# Función para imprimir tablas de distribución de variables categóricas
print_distribucion <- function(distribution, cluster_dendro, variable) {
  cat(paste("\nCluster:", cluster_dendro, "- Variable:", variable, "\n"))
  print(distribution)
}

# Imprimir las tablas de distribución de cada variable categórica por cluster
for (i in 1:nrow(caracterizacion)) {
  cluster <- caracterizacion$cluster_dendro[i]
  print_distribucion(caracterizacion$orientacion[[i]], cluster, "Orientacion")
  print_distribucion(caracterizacion$tipo_suelo[[i]], cluster, "Tipo de Suelo")
  print_distribucion(caracterizacion$cobertura_vegetal[[i]], cluster, "Cobertura Vegetal")
  print_distribucion(caracterizacion$riesgo_erosion[[i]], cluster, "Riesgo de Erosion")
}

Cluster: 1 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
20 30 22 25 37 22 24 20 

Cluster: 1 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       41        41        39        59        20 

Cluster: 1 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      45       33       43       35       44 

Cluster: 1 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   67    65    68 

Cluster: 2 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
28 34 24 21 21 27 26 23 

Cluster: 2 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       34        53        47        40        30 

Cluster: 2 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      46       31       46       42       39 

Cluster: 2 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   69    67    68 

Cluster: 3 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
34 20 26 25 15 30 26 26 

Cluster: 3 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       44        39        45        46        28 

Cluster: 3 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      47       25       46       36       48 

Cluster: 3 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   78    65    59 

Cluster: 4 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
27 27 23 29 20 20 27 18 

Cluster: 4 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       39        41        33        36        42 

Cluster: 4 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      39       38       24       44       46 

Cluster: 4 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   72    59    60 

Cluster: 5 - Variable: Orientacion 
orientacion
 E  N NE NW  S SE SW  W 
21 25 30 26 29 23 22 27 

Cluster: 5 - Variable: Tipo de Suelo 
tipo_suelo
Arcilloso   Arenoso    Limoso  Orgánico    Rocoso 
       48        34        45        40        36 

Cluster: 5 - Variable: Cobertura Vegetal 
cobertura_vegetal
  Bosque  Cultivo Matorral  Pradera   Urbano 
      46       34       49       37       37 

Cluster: 5 - Variable: Riesgo de Erosion 
riesgo_erosion
 Alto  Bajo Medio 
   67    59    77 

Observamos que:

  • Grupo 1: Se caracteriza por una distribución mayoritariamente dirigida al sur, con una mayor presencia de suelos orgánicos. La cobertura vegetal muestra una mezcla de Bosque y Urbano, con un riesgo de erosión que no varía mucho entre Bajo, Medio y Alto..

  • Grupo 2: Se destaca por su alta proporción de suelos Arenosos y Limosos, además de un riesgo de erosión equilibrado. La cobertura vegetal está dominada por Bosque y Matorral, y la orientación varía pero con predominancia en Norte..

  • Grupo 3: Se diferencia por una mayor proporción de suelos Orgánicos, Limosos y Arcillosos. Una cobertura vegetal diversa con una buena representación de Bosque , Urbano y Matorral. El riesgo de erosión es Alto.

  • Grupo 4: Se destaca por su alta proporción de suelos Rocosos y Arcilloso, con una cobertura vegetal dominada por Urbano y Pradera. El riesgo de erosión es alto en este grupo, indicando áreas potencialmente más vulnerables a la erosión..

  • Grupo 5: Se caracteriza por su alta proporción de suelos Arcillosos con una cobertura vegetal que incluye una buena cantidad de Bosque y Matorral. El riesgo de erosión es predominantemente Medio, con una distribución más equilibrada comparado con otros grupos.