💡 OBJETIVOS:

💡 MATERIALES PARA LA ACTIVIDAD:

Los materiales para el desarrollo de esta actividad son los siguientes.

► Se utilizará la misma imagen utilizada en la actividad previa (clasificación manual).

script_laboratorio_7_no_supervisada

INTRODUCCIÓN

La clasificación no supervisada es la técnica de clasificación en la que el usuario no proporciona información previa. Las imágenes se clasifican en función de sus características espectrales, de modo que los píxeles que tienen propiedades espectrales similares se agrupan en la misma clase o categoría. Es decir, éstas se crean únicamente en función de la información numérica de los datos (es decir, los valores de píxeles para cada una de las bandas o índices).

Es un tipo de clasificación automatizada, si bien el usuario tiene control sobre ciertos aspectos del proceso de clasificación. Esto incluye el número de clases, el número máximo de iteraciones (que es la cantidad de veces que se ejecuta el algoritmo de clasificación) y el porcentaje de umbral de cambio, que especifica cuándo finalizar el procedimiento de clasificación.

Una vez clasificados los datos, el usuario debe interpretar, etiquetar y codificar con colores las clases en consecuencia.

Ventajas

  • Rápida y fácil de ejecutar.

  • No requiere trabajo de campo ni conocimiento previo del área (si bien cierto conocimiento es favorable). Por ejemplo, pueden ser útiles al estudiar zonas poco conocidas o con pocos datos.

  • Al crearse las clases a partir de la información espectral, por lo que no son tan subjetivas como la interpretación visual manual. El algorítmo agrupa automáticamente los píxeles según su similitud espectral.

  • Ofrecen una mayor consistencia y reproducibilidad (los mismos resultados) siempre que se usen los mismos parámetros iniciales. Este aspecto consituye una ventaja en estudios comparativos o en el análisis de series temporales. La clasificación manual puede variar entre analistas o incluso en distintos momentos.

  • Pueden detectar estructuras complejas o clases no evidentes, que el analista puede no haber considerado o percibido.

  • Constituyen métodos eficientes en el manejo de grandes volúmenes de datos, por ejemplo las imágenes multiespectrales con diferentes bandas. Ajustar umbrales manualmente en múltiples dimensiones es muy difícil.

Desventajas

  • Las clases espectrales no siempre corresponden a clases reales.

  • El usuario también tiene que dedicar tiempo a denominar, interpretar y asociar las categorías obtenidas a coberturas reales.

  • Las propiedades espectrales de las clases también pueden cambiar con el tiempo, por lo que no siempre se puede utilizar la misma información de clase al pasar de una imagen a otra.

  • Gran incertidumbre si hay muchas cubiertas con firmas similares.

Casos ideales de uso

  • Exploración preliminar del paisaje.

  • Estudios en zonas desconocidas.

  • Cuando no se cuenta con datos de referencia.

Los algorítmos de agrupamiento

La clasificación no supervisada utiliza algoritmos de agrupamiento. El propósito de estos algoritmos es minimizar la variabilidad dentro de cada categoría y maximizar la separación entre categorías. Es labor del usuario interpretar y etiquetar las categorías, convirtiendo las clases espectrales en clases de información. Por lo tanto, requiere un esfuerzo suplementario en la identificación de las categorías y su adscripción a clases de información.

K-means es el método más utilizado para realizar una clasificación no supervisada, al que se añadirán en este apartado otros métodos de uso cada vez más frecuentes, como CLARA o Random forest.

Algoritmo Tipo Idea básica Ventajas Limitaciones Uso típico en teledetección
K-means Particional Agrupa píxeles en k clusters minimizando la distancia a la media Rápido, fácil de implementar Hay que definir k, sensible a inicialización Clasificación preliminar de coberturas
ISODATA (Iterative Self-Organizing Data Analysis Technique) Particional dinámico Similar a K-means pero permite dividir y fusionar clusters Más flexible que K-means Más complejo, requiere más parámetros Análisis exploratorio en imágenes multiespectrales
Clustering jerárquico Jerárquico Forma una jerarquía de grupos (árbol o dendrograma) No requiere fijar k inicialmente Costoso computacionalmente Análisis detallado en áreas pequeñas
DBSCAN (Density-Based Spatial Clustering) Basado en densidad Agrupa puntos densos y detecta ruido Detecta formas arbitrarias, identifica outliers Difícil ajustar parámetros Detección de anomalías o cambios
Mean Shift Basado en densidad Encuentra máximos de densidad en el espacio de datos No necesita número de clusters Computacionalmente costoso Segmentación de imágenes
Self-Organizing Maps (SOM) Redes neuronales Proyecta datos en un mapa de menor dimensión preservando relaciones Visualización útil, capta patrones complejos Interpretación más compleja Análisis de firmas espectrales

PREPARACIÓN DE LOS DATOS

Para realizar una clasificación no supervisada de datos ráster en R, se requieren los siguientes paquetes.

library(terra)                                 # Manejo de datos en formato ráster.
## terra 1.8.93
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.2
## Warning: package 'readr' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.1.5
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.2     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::extract() masks terra::extract()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggplot2)                                # Visualización de datos ráster
library(RStoolbox)                              # Clasificación y visualización de imágenes siguiendo el método K-means
## This is version 1.0.2.2 of RStoolbox
library(cluster)                                # Clasificación según el según el método CLARA.
library(randomForest)                           # Clasificación siguiendo el método RandomForest.
## Warning: package 'randomForest' was built under R version 4.3.3
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin

Los datos que usaremos en este tema se encuentran en la carpeta /datos/no_supervisada/ que se accede mediante la siguiente ruta, que se comprueba

A continuación se importará el fichero ráster sobre la que se realizará una clasificación no supervisada

imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/no_supervisada/imagen.tif")
imagen
## class       : SpatRaster 
## size        : 1245, 1497, 7  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source      : imagen.tif 
## names       : coast~rosol,      blue,      green,        red,          NIR,        SWIR1, ... 
## min values  :  0.09641791, 0.0748399, 0.04259216, 0.02084067, 0.0008457669, -0.007872183, ... 
## max values  :  0.73462820, 0.7177562, 0.69246972, 0.78617686, 1.0124315023,  1.043204546, ...
plot(imagen)

Esa imagen contiene una escena de Landsat 8 con 7 bandas.

A continuación será representada en forma de imagen en color verdadero

ggRGB(imagen, r = 4, g= 3, b= 2, stretch ="lin") + 
  ggtitle("Imagen original") +                                                  # Título
  labs(x ="Longitud (m)", y ="Latitud (m)") +                                   # Etiquetas de los ejes
  theme(plot.title = element_text(hjust =0.5,                                   # Alineación del título 
                                  size =20),                                    # Tamaño del título
        axis.title = element_text(size =15))                                    # Tamaño de las etiquetas de los ejes

Las imágenes de satélite deben convertirse en una matriz de datos antes de aplicar cualquiera de los algoritmos disponibles. Este dataframe se crea con la función terra:as.data.frame(), teniendo la precaución de eliminar los valores ausentes (No data) con el argumento .

df <- as.data.frame(imagen,                                  # Nombre del ráster multibanda. 
                    na.rm=TRUE)                              # Elimina valores ausentes

Se va a eliminar la primera banda (coastal aerosol).

df <- df %>% 
  select(-1)

También es conveniente escalar datos, es decir, que todos los valores oscilen entre -1 y +1. Es fundamental que los datos estén escalados , ya que este algoritmo se basa en distancias euclidianas y las variables con rangos grandes podrían dominar el modelo.

df_escalado <- df %>%
  scale() %>%
  as.data.frame()

Por último, para evitar problemas de memoria, seleccionamos aleatoriamente el 10 % de todos los píxeles.

set.seed(123)
df_muestra <- df_escalado %>%
  slice_sample(prop = 0.1)

CLASIFICACIÓN USANDO EL ALGORITMO K-MEANS

Es el método más utilizado para la clasificación no supervisada de ráster. El número de grupos (\(k\)) debe ser predefinido por el usuario. El algorítmo agrupa los píxeles dentro de cada cateogría de acuerdo a su proximidad al centroide (punto medio de cada categoría o clase en el “space feature”) más cercano. Una vez asignados todos los píxeles a cada uno de los grupos, se recalculan de nuevo los centroides del grupo. Este procedimiento se itera hasta que no se encuentre una diferencia significativa en los centroides o se alcance el número predefinido de iteraciones. De esta forma, este algoritmo optimiza la posición de los centroides. El inconveniente de este algorítmo es su sensibilidad a los valores atípicos.

Con el paquete cluster

La agrupación de k-means en R se puede realizar utilizando la función cluster::kmeans(), que se acompaña de los siguientes argumentos:

  • El número de grupos se define en el argumento centers.

  • El número máximo de iteraciones permitidas se menciona en el argumento iter.max.

  • El algoritmo a utilizar se menciona en el argumento algorithm. El algoritmo de Lloyd es el más utilizado.

set.seed(999)                                                                   # Semillas aleatorias
cluster_kmeans <- kmeans(df_muestra,                                            # Bandas a clasificar
                 centers = 5,                                                   # Número de grupos o clusters
                 nstart = 10,                                                   # Número de puntos de partida.
                 iter.max = 500,                                                # número de iteraciones
                 algorithm = 'Lloyd')                                           # Algoritmo
                   
str(cluster_kmeans)
## List of 9
##  $ cluster     : Named int [1:186376] 2 5 1 3 2 1 5 5 5 2 ...
##   ..- attr(*, "names")= chr [1:186376] "1237518" "134058" "1172598" "685285" ...
##  $ centers     : num [1:5, 1:6] 0.51 -0.379 -0.228 1.5 -1.016 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:5] "1" "2" "3" "4" ...
##   .. ..$ : chr [1:6] "blue" "green" "red" "NIR" ...
##  $ totss       : num 1119709
##  $ withinss    : num [1:5] 69012 48202 28390 78219 54248
##  $ tot.withinss: num 278071
##  $ betweenss   : num 841639
##  $ size        : int [1:5] 53798 42493 23076 24469 42540
##  $ iter        : int 52
##  $ ifault      : NULL
##  - attr(*, "class")= chr "kmeans"

La salida de la función kmeans proporciona una gran cantidad de información:

# cluster_kmeans$cluster                                            # Número de casos
cluster_kmeans$centers                                              # Aparecen los valores de los centroides
cluster_kmeans$totss                                                # Total sum of squares
cluster_kmeans$withinss                                             # Within-cluster sum of squares
cluster_kmeans$totalwithins                                         # Total within-cluster sum of squares
cluster_kmeans$betweenss                                            # Between-cluster sum of squares
cluster_kmeans$size                                                 # Número de píxeles en cada cluster
cluster_kmeans$iter                                                 # Número de iteraciones
cluster_kmeans$ifault                                               # Indicador de un posible fallo del algorítmo 

Lo más relevante es lo siguiente:

  • Tamaño de cada grupo, clase o categoría.

  • Valor medio de las variables (bandas) que conforman cada grupo.

  • El vector de agrupación, es decir la identidad de grupo de cada celda o píxel clasificado, dentro del grupo suma de cuadrados por grupo.

  • withinss (Within-cluster sum of squares). Mide la variabilidad interna de cada cluster. Es la suma de las distancias cuadradas de los puntos al centroide de su cluster. Interpretación:

Bajo = bueno → los puntos están muy agrupados (clusters compactos).

Alto = malo → los clusters están dispersos.

  • totalwithinss. Es la suma total de la variabilidad interna de todos los clusters (básicamente totalwithinss=∑withinss de todos los clusters). Cuanto menor, mejor es la compactación global. Se usa mucho para comparar distintos valores de k.

  • betweenss (Between-cluster sum of squares). Mide la separación entre clusters. Representa cuánto varían los centroides entre sí respecto al centro global. Interpretación: Alto = bueno → clusters bien separados. Bajo = malo → clusters poco diferenciados.

Los últimos 3 términos están concetados \[Total SS = Within SS + Between SS\]

Donde:

\[Total SS\] = variabilidad total de los datos \[Within SS\] = variabilidad dentro de clusters \[Between SS\] = variabilidad entre clusters

¿Cómo saber si el el número de grupos es el idóneo?

  • Ratio de separación. Es una métrica muy usada: betweenss / total SS . Un valor cercano a 1 → excelente (clusters bien separados); un valor más bajo supone mala separación.

  • Método del “codo” (Elbow method). Se analiza totalwithinss para distintos valores de k. Se busca un punto donde la reducción deja de ser significativa. Si no hay “codo” claro → K-means puede no ser adecuado.

En resumen, una buena solución tiene:

  • withinss bajo (compactación)

  • betweenss alto (separación)

Parámetro Qué mide Ideal
withinss Compactación interna Bajo
totalwithinss Compactación global Bajo
betweenss Separación entre clusters Alto

Este algorítmo presenta limitaciones importantes, ya que asume clusters esféricos, no detecta bien estructuras complejas y es sensible tanto a la escala de los valores como a la presencia de “outliers” (casos extremos)

Extender la clasificación a todo el dataframe escalado

df_kmeans_completo <- kmeans(df_escalado,                                       # dataframe escalado (con todos los píxeles)
                             centers = cluster_kmeans$centers)                  # Se utilizan los centroides

Se traslada cada pixel clasificado en el dataframe al ráster

raster_kmeans <- imagen[[1]]
values(raster_kmeans) <- df_kmeans_completo$cluster
raster_kmeans
## class       : SpatRaster 
## size        : 1245, 1497, 1  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source(s)   : memory
## varname     : imagen 
## name        : coastal aerosol 
## min value   :               1 
## max value   :               5

Visualización de la información.

Una vez obtenida la clasificación, se puede intentar una interpretación basíca. Para ello, los píxeles clasificados (con una transparencia del 0.3, my alta) se superponen a la imagen en color verdadero

plotRGB(imagen, r=4, g=3, b=2, axes=TRUE, stretch="lin")
plot(raster_kmeans, col = rainbow(5),
     main = 'Visualización preliminar', add=TRUE, alpha = 0.4, legend = TRUE)

Otro opción es representar una única categorías sobre la que se pueden tener dudas. El proceso consiste en crear una máscara binaria donde solo la categoría 1 conserve su valor y el resto se convierta en NA (transparente), para luego dibujarla sobre el RGB.

Crear una capa donde solo los píxeles de categoría 1 existen

solo_cat1 <- ifel(raster_kmeans == 1, 1, NA)

Para visualizarlo, primero dibujamos la imagen RGB en color natural y luego superponemos la categoría seleccionada con un color llamativo (por ejemplo, rojo).

plotRGB(imagen, r=4, g=3, b=2, axes=TRUE, stretch="lin")           # Dibuja la imagen RGB
plot(solo_cat1, col = "red", add = TRUE, legend = FALSE)            # Superponer la categoría 1

Una vez realizada una interpretación preliminar, hay que identificar los grupos o clusters (categorías espectrales) en forma de coberturas reales del territorio (clases de información). Dado que el objeto ráster raster_kmeans es un ráster cuantitativo, se debe transformar los números en variables categóricas, convirtiéndolos en factor (variable categórica), Una vez conozcamos la relación con las clases de información, se dará nombre a estas categorías

raster_kmeans <- as.factor(raster_kmeans)

Si ya supiéramos a qué clases de información corresponden las clases espectrales, se deberían asignar etiquetas, por ejemplo

levels(raster_kmeans) <- data.frame(ID = 1:5,
                          clase = c("Suelo desnudo","Regadío","Agua", "Matorral", "Secano"))

Una vez creado este nuevo ráster con los resultados de la clasificación, se puede representar gráficamente, asignando etiquetas de color y clase según la interpretación visual (modo ‘classes’)

mis_colores <- c("Suelo desnudo" = "red",
                 "Regadío" = "darkgreen",
                "Agua" = "blue", 
                 "Matorral" = "tan", 
                 "Secano" = "lightgreen")

                # Verifica los niveles del raster como factor
levels(raster_kmeans)
## [[1]]
##   ID         clase
## 1  1 Suelo desnudo
## 2  2       Regadío
## 3  3          Agua
## 4  4      Matorral
## 5  5        Secano

Finalmente, representaríamos gráficamente el mapa con ggplot2

ggR(raster_kmeans, geom_raster = TRUE, stretch = "lin", forceCat = TRUE) + 
  ggtitle("Clasificación no supervisada. Método K-means") +                     # Título
  labs(x ="Longitud (m)", y ="Latitud (m)") +                                   # Etiquetas de los ejes
  theme(plot.title = element_text(hjust =0.5,                                   # Alineación del título 
                                  size =20),                                    # Tamaño del título
        axis.title = element_text(size =10),                                    # Tamaño de las etiquetas de los ejes
        legend.key.size =unit(1,"cm"),                                          # Tamaño de la leyenda
        legend.title = element_text(size = 20),                                 # Tamaño del título de la leyenda
        legend.text = element_text (size = 10)) +                               # Tamaño del texto de la leyenda
   # Dibuja los colores según un gradiente 
scale_fill_manual(values = mis_colores,  
                  name = "Clase") +
  theme_minimal()

Si todo está correcto, el ráster con las categorías obtenidas se graba como fichero *.tif

writeRaster(raster_kmeans, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/no_supervisada/imagen_cluster_kmeans.tif", overwrite = TRUE)

Con el paquete RStoolbox

Una posible alternativa es realizar directamente la clasificación con K-means sobre el objeto ráster mediante el argumento algorithm = "Lloyd"en la función RStoolbox::unsuperClass() del paquete RStoolbox.

kmeans_rstoolbox <- unsuperClass(imagen, 
                           nSamples = 10000, 
                           nClasses = 5, 
                           nStarts = 200, 
                           algorithm ="Lloyd")

kmeans_rstoolbox$model

kmeans_rstoolbox$map

📝 ACTIVIDAD DE EVALUACIÓN 1:

► Comprueba si los resultados de la clasificación utilizando ambos algoritmos son similares

CLASIFICACIÓN APLICANDO EL ALGORÍTMO “CLARA”.

CLARA es la abreviatura de Clustering Large Applications, un método de agrupamiento propuesto por Kaufman y Rousseeuw (1990). A diferencia de K-means, es más robusto frente a valores atípicos, ya que utiliza medoides en lugar de centroides. A diferencia de estos últimos, el medoide de una clase corresponde a una observación real del conjunto de datos cuya disimilitud total respecto al resto de los puntos del clúster es mínima; es decir, representa el punto más central del grupo.

El procedimiento para calcular los medoides consiste en extraer múltiples muestras del conjunto de datos original, sobre las cuales se aplica el algoritmo PAM (Partitioning Around Medoids) para encontrar el número de medoides especificado por el usuario (equivalente al número de grupos o clústeres). Este algoritmo comienza con un conjunto inicial de medoides seleccionados aleatoriamente y los reemplaza iterativamente con el objetivo de minimizar el coste de intercambio.

Una vez obtenidos los medoides óptimos, cada observación se asigna al medoide más cercano y se calcula la disimilitud correspondiente. Este proceso de muestreo y agrupamiento se repite varias veces, y finalmente se selecciona como resultado el agrupamiento que presenta la menor disimilitud global.

CLARA se aplica usando la función cluster:clara disponible en el paquete cluster. Esta función se acompaña de los siguientes argumentos:

cluster_clara <- clara(df_muestra,
                       k = 5, 
                       metric = "euclidean")
cluster_clara
## Call:     clara(x = df_muestra, k = 5, metric = "euclidean") 
## Medoids:
##                blue      green        red        NIR       SWIR1      SWIR2
## 626731  -0.44339608 -0.4679639 -0.6297139 -0.3520198 -0.04224666  0.1763737
## 696807  -1.20425681 -1.0992289 -1.0210409  0.5852803 -0.76658613 -1.1039912
## 62988    0.63334835  0.8050984  0.9391629  0.2945950  0.70766293  0.6413934
## 873048   0.06221418 -0.3499968 -0.5567613 -1.8031165 -1.92199894 -1.7425306
## 1855403  1.13309154  1.5817158  1.9593092  0.9701422  1.63384855  1.1685252
## Objective function:   1.095004
## Clustering vector:    Named int [1:186376] 1 2 3 4 1 3 2 2 2 1 2 3 1 1 3 5 4 5 ...
##  - attr(*, "names")= chr [1:186376] "1237518" "134058" "1172598" "685285" "1274894" "1413785" "1697371" ...
## Cluster sizes:            55579 36429 51868 21899 20601 
## Best sample:
##  [1] 1668835 812110  1067226 109947  1199368 946613  1206283 1512265 1131586
## [10] 1849201 1383146 1832605 830056  256836  696807  526862  479003  1220839
## [19] 382215  1238142 996009  407359  1668102 1305796 1780429 873048  1629544
## [28] 1058231 626731  270388  205419  365294  21814   521069  1492551 933172 
## [37] 122966  1223498 443919  1855403 914489  444293  1525222 62988   1856997
## [46] 437575  528966  577143  368884  68137  
## 
## Available components:
##  [1] "sample"     "medoids"    "i.med"      "clustering" "objective" 
##  [6] "clusinfo"   "diss"       "call"       "silinfo"    "data"

La salida proporcionada por la función incluye también una gran cantidad de información, entre los que cabe destacar:

cluster_clara$sample      # Número de los píxeles que corresponden a la mejor muestra usada para la partición final
##  [1] "1668835" "812110"  "1067226" "109947"  "1199368" "946613"  "1206283"
##  [8] "1512265" "1131586" "1849201" "1383146" "1832605" "830056"  "256836" 
## [15] "696807"  "526862"  "479003"  "1220839" "382215"  "1238142" "996009" 
## [22] "407359"  "1668102" "1305796" "1780429" "873048"  "1629544" "1058231"
## [29] "626731"  "270388"  "205419"  "365294"  "21814"   "521069"  "1492551"
## [36] "933172"  "122966"  "1223498" "443919"  "1855403" "914489"  "444293" 
## [43] "1525222" "62988"   "1856997" "437575"  "528966"  "577143"  "368884" 
## [50] "68137"
cluster_clara$medoids     # Medoids de los grupos
##                blue      green        red        NIR       SWIR1      SWIR2
## 626731  -0.44339608 -0.4679639 -0.6297139 -0.3520198 -0.04224666  0.1763737
## 696807  -1.20425681 -1.0992289 -1.0210409  0.5852803 -0.76658613 -1.1039912
## 62988    0.63334835  0.8050984  0.9391629  0.2945950  0.70766293  0.6413934
## 873048   0.06221418 -0.3499968 -0.5567613 -1.8031165 -1.92199894 -1.7425306
## 1855403  1.13309154  1.5817158  1.9593092  0.9701422  1.63384855  1.1685252
cluster_clara$i.med       # Índices de los medoids
## [1] 119088  66931 157972 114102 149483
cluster_clara$objective   # Funcion objetiva para la clasificación final de todo el conjunto de datos
## [1] 1.095004
cluster_clara$clusinfo    # Información de cada cluster
##       size  max_diss   av_diss isolation
## [1,] 55579  2.793793 1.1137020  1.367622
## [2,] 36429  3.818205 1.0809110  1.869094
## [3,] 51868  5.042190 1.0946160  2.700714
## [4,] 21899  3.604881 0.9754548  1.163699
## [5,] 20601 26.642860 1.1975371 14.270537

El componente objective es el más importante, pues mide cuán compactos son los clústeres. Si comparamos varias soluciones con diferente número de grupos, aquella solución con un valor más bajo corresponde a la mejor clasificación. Permite comparar entre distintas ejecuciones o distintos k

El componente clusinfo proporciona información sobre:

cluster_clara$diss

La lista con información acerca del “silouette width” para la mejor muestra es proporcionada por el argumento silinfo.

cluster_clara$silinfo

Extender la clasificación a todo el dataframe escalado

df_clara_completo <- clara(df_escalado,                                       # dataframe escalado (con todos los píxeles)
                            k = 5)

El resultado del procedimiento CLARA también es un dataframe, por lo que deberás ser convertido en un raster, siguiendo el mismo procedimiento mencionado en K-means.

raster_clara <- imagen[[1]]
values(raster_clara) <- df_clara_completo$clustering
raster_clara
## class       : SpatRaster 
## size        : 1245, 1497, 1  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source(s)   : memory
## varname     : imagen 
## name        : coastal aerosol 
## min value   :               1 
## max value   :               5

📝 ACTIVIDAD DE EVALUACIÓN 2:

► Muestra la distribución espacial de categorías obtenidas al aplicar el algoritmo CLARA, superpuesta a una imagen en color verdadero

► Representa superpuesta a una imagen en color natura la categoría 3.

► Transforma ese objeto ráster en un ráster categórico

► Identifica qué tipo de uso del territorio corresponde a cada una de las categorías espectrales y asigna las correspondientes etiquetas.

► Asigna a las etiquetas unos colores que se corresponda (aproximadamente) con el tipo de uso del territorio.

► Representa con la función ggr() el mapa temático correspondiente a la clasificación realizada con el algorimo CLARA.

Si todo está correcto, el ráster con las categorías obtenidas se graba como fichero *.tif

writeRaster(raster_clara, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/no_supervisada/imagen_cluster_clara.tif", overwrite = TRUE)

CLASIFICACIÓN CON RANDOM FOREST

El algoritmo Random Forest es una técnica de aprendizaje automático utilizada tanto para clasificación como para regresión. Este método construye múltiples árboles de decisión a partir de subconjuntos del conjunto de datos de entrenamiento, combinando sus resultados para mejorar la precisión y reducir el sobreajuste.

Normalmente, este algoritmo se emplea en procedimientos de clasificación supervisada. No obstante, también puede utilizarse en enfoques no supervisados de forma indirecta, por ejemplo, en combinación con algoritmos como K-means. En este caso, K-means genera agrupamientos iniciales a partir de los datos, y estas etiquetas se utilizan posteriormente para entrenar un modelo Random Forest. Una vez entrenado, el modelo puede predecir la distribución de clases en la imagen de satélite original.

El algorítmo necesita una gran cantidad de RAM, a diferencia de lo que ocurrió con K-means y CLARA. Por ello, se debe utilizar habitualmente una muestra reducida. Primero, se tomarán 1000 muestras aleatorias (sin reemplazo) del fichero ráster original (ìmagenpara obtener los valores de las bandas

raster_muestra_rf <- spatSample(imagen, 
                                size = 1000, 
                                method = "random", 
                                as.points = TRUE)

A continuación, se extraer los valores de las bandas en esos puntos y se convierte en dataframe

df_muestra_rf <- terra::extract(imagen, raster_muestra_rf)

Para que los resultados sean comparables a los de K-means y CLARA, eliminamos las columnas 1 (ID) y 2 (coastal aerosol).

df_muestra_rf <- df_muestra_rf[, -c(1,2)]

Finalmente, comprobamos el éxito de todo el procedimiento

names(df_muestra_rf)
## [1] "blue"  "green" "red"   "NIR"   "SWIR1" "SWIR2"

A continuación se aplica el algorítmo Random Forest a dicha muestra para calcular los valores de proximidad. Este valor se basa en la frecuencia con la que los pares de puntos se encuentran en los mismos nodos terminales, y se asigna un nombre de variable. Este procedimiento se aplicar a la anterior matriz utilizando la función randomForest del paquete randomForest.

set.seed(123)
rf_provisional <- randomForest(df_muestra_rf,                          
                   ntree = 500, 
                   proximity = TRUE, 
                   oob.prox = TRUE)

Aplicado a los datos, randomForest genera una matriz de proximidad basada en cómo los puntos caen juntos en los árboles, que responde a rf_provisional$proximity

Primero se debería aplicar el algoritmo randomForest a la muestra sin ningún entrenamiento previo. Obtenemos una lista

rf_provisional <- randomForest(df_muestra_rf)
names(rf_provisional)
##  [1] "call"            "type"            "predicted"       "err.rate"       
##  [5] "confusion"       "votes"           "oob.times"       "classes"        
##  [9] "importance"      "importanceSD"    "localImportance" "proximity"      
## [13] "ntree"           "mtry"            "forest"          "y"              
## [17] "test"            "inbag"

Posteriormente, de esa lista se extrae un elemento, denominado proximity y se convierte en un vector.

rf_proximity <- rf_provisional$proximity                                         # Asigna una variable a las medidas de la matriz de proximidad

Es sobre estos valores de proximidad sobre los que se aplica el algorítmo K-means para calcular el número de grupos (en este caso 5). Estos grupos se utilizan posteriormente para entrenar el algorítmo Random Forest, siendo el número de árbles a construir definido por el argumento ntree.

cluster_kmn_rf <- kmeans(rf_proximity, 
                 centers =5, 
                 iter.max =10000)
rf_train <- randomForest(df_muestra_rf, 
                         as.factor(cluster_kmn_rf$cluster), 
                         ntree =2000)

rf_train                   # Atributos del nuevo objeto
## 
## Call:
##  randomForest(x = df_muestra_rf, y = as.factor(cluster_kmn_rf$cluster),      ntree = 2000) 
##                Type of random forest: classification
##                      Number of trees: 2000
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 3.9%
## Confusion matrix:
##     1   2   3   4   5 class.error
## 1 101   0   1   0   0 0.009803922
## 2   0 178   0   1   8 0.048128342
## 3   2   0 166   0   6 0.045977011
## 4   0   5   0 164   0 0.029585799
## 5   1   9   6   0 352 0.043478261

La matriz de confusión y la estimación OOB (Out-of-Bag) de la tasa de error indican que el modelo desarrollado por randomForest tiene una buena precisión. Cuando esto ocurre, se puede usar el modelo para la predicción de clases en todo el ráster usando la función predict.

raster_rf <- predict(imagen, 
                     rf_train)
raster_rf                                                                 # Atributos
## class       : SpatRaster 
## size        : 1245, 1497, 1  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source(s)   : memory
## categories  : class 
## name        : class 
## min value   :     1 
## max value   :     5

Al igual que en el caso anterior, la interpretación básica consistirá en superponer los píxeles clasificados a la imagen en color verdadero

plotRGB(imagen, r=4, g=3, b=2, axes=TRUE, stretch="lin")
plot(raster_rf, col = rainbow(5),
     main = 'Visualización preliminar', add=TRUE, alpha = 0.4, legend = TRUE)

A continuación, se representa gráficamente el raster clasificado según el método randomForest, al igual que se realizó con kmeans y CLARA.

ggR(raster_rf, geom_raster = TRUE, stretch = "lin") + 
  ggtitle("Clasificación no supervisada según el método Random Forest") +       # Título
  labs(x ="Longitud (m)", y ="Latitud (m)") +                                   # Etiquetas de los ejes
  theme(plot.title = element_text(hjust =0.5,                                   # Alineación del título 
                                  size =20),                                    # Tamaño del título
        axis.title = element_text(size =10),                                    # Tamaño de las etiquetas de los ejes
        legend.key.size =unit(1,"cm"),                                          # size of the legend
        legend.title = element_text(size = 20),                                 # size of Legend tittle
        legend.text = element_text (size = 10)) +                               # size of legend text
  # Dibuja los colores según un gradiente
  scale_fill_manual(name = "Class",
                    values = c("black", "forestgreen", "burlywood", "red","yellow","blue"),
                    labels = c("Suelos salinos", "Vegetación", "Cultivos", "Edificaciones","Agua", "Suelos arenosos"))

Si todo está correcto, el ráster con las categorías obtenidas se graba como fichero *.tif

writeRaster(raster_rf, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/no_supervisada/imagen_cluster_rf.tif", overwrite = TRUE)

CLASIFICACIÓN CON Fuzzy C-means

El Fuzzy C-Means (FCM) es un algoritmo de agrupamiento (clustering) que pertenece a la denominada “lógica difusa”. A diferencia de los métodos tradicionales “duros” (hard clustering), donde cada punto pertenece a un solo grupo, en FCM los datos pueden pertenecer a varios grupos simultáneamente con distintos grados de pertenencia.

Sus principales características son las siguientes:

Ventajas

  • Manejo de la ambigüedad: Es ideal para datos que no tienen fronteras claras, como áreas de transición en vegetación (ecotonos) o imágenes médicas donde los tejidos se mezclan.

  • Mayor detalle: al obtener una matriz de probabilidad (membership), tienes mucha más información que una simple etiqueta. Puedes saber qué tan “seguro” está el modelo de su clasificación.

  • Robustez: suele ser menos sensible a ruidos o valores atípicos que el K-means tradicional, ya que estos puntos pueden repartir su pertenencia entre varios grupos en lugar de distorsionar un solo centro.

** Limitaciones

  • Al igual que K-means, todavía necesitas definir de antemano el número de centros (centers=5).

  • Costo Computacional: Es más lento que K-means porque tiene que calcular y actualizar una matriz de membresía completa en cada iteración.

  • Sensibilidad a la escala: Al basarse en distancias euclidianas, si no se escala las variables antes de procesarlas, las variables con números más grandes dominarán el agrupamiento.

  • Mínimos Locales: puede quedarse “atrapado” en una solución que no es la mejor globalmente, por lo que a veces es necesario ejecutarlo varias veces con semillas diferentes.

Desarrollo de la práctica

library(e1071)
## Warning: package 'e1071' was built under R version 4.3.3
## 
## Attaching package: 'e1071'
## The following object is masked from 'package:ggplot2':
## 
##     element
## The following object is masked from 'package:terra':
## 
##     interpolate

El argumento m = 2 es el parámetro (exponencial) que controla el grado de separación (difuminado o fuzziness) de las categorías, es decir, determina cuan “borrosas” son las fronteras entre grupos. Con un valor m = 1 el algoritmo se comporta casi como un K-means normal (pertenencia rígida), mientras que a partir de m > 1 aumenta el grado de solapamiento. El valor 2 es el estándar por defecto en la literatura científica y suele funcionar bien para la mayoría de los casos.

fuzzy_inicial <- cmeans(df_muestra, 
                    centers=5,                        # Número de grupo a automática partiendo de una semilla aleatoria.
                    m=2)

Generación de la clasificación definitiva a partir de los centros obtenidos previamente.

fuzzy_completo <- cmeans(df_escalado, 
                     centers= fuzzy_inicial$centers, 
                     m=2)

Creación del fichero ráster con los resultados

raster_fuzzy <- imagen[[1]]
values(raster_fuzzy) <- fuzzy_completo$cluster
raster_fuzzy
## class       : SpatRaster 
## size        : 1245, 1497, 1  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source(s)   : memory
## varname     : imagen 
## name        : coastal aerosol 
## min value   :               1 
## max value   :               5

Al igual que en el caso anterior, la interpretación básica consistirá en superponer los píxeles clasificados a la imagen en color verdadero

plotRGB(imagen, r=4, g=3, b=2, axes=TRUE, stretch="lin")
plot(raster_fuzzy, col = rainbow(5),
     main = 'Visualización preliminar', add=TRUE, alpha = 0.4, legend = TRUE)

Se puede representar también la probabilidad de pertenencia a un determinado cluster. Esto es muy útil para identificar zonas de transición o áreas donde la clasificación es ambigua.

matriz_pertenencia <- fuzzy_completo$membership
prob_cluster1 <- imagen[[1]]
values(prob_cluster1) <- fuzzy_completo$membership[, 1]
plot(prob_cluster1)

writeRaster(raster_fuzzy, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/no_supervisada/imagen_cluster_fuzzy.tif", overwrite = TRUE)

📝 ACTIVIDAD DE EVALUACIÓN 3:

Descarga el fichero landsat_calgary.zip.

► Realiza una clasificación no supervisada de la imagen utilizando para ello al menos dos algorítmos.

► Identifica qué tipo de supeficies corresponde a cada categoría extraida por los algorítmos. La imagen corresponde a la ciudad canadiense de Calgary. Puedes ayudarte de Google Earth para tener una información más detallada.

► Elabora sendos mapas temáticos con ambas clasificaciones.