INTRODUCCIÓN A RANDOM FOREST

Random forest es otra técnica de aprendizaje automático que construye múltiples árboles de decisión aleatorios no correlacionados a partir del subconjunto de datos de entrenamiento y agrupa sus resultados para proporcionar una decisión final o resultado, evitando el problema del sobreajuste. Este algorítmo también se utiliza para la clasificación y la regresión; el resultado final en caso de clasificación utiliza la predicción de la moda, mientras que utiliza la media en el caso de la regresión.

Relación entre CART y Random Forest. Es importante saber que Random Forest es simplemente una “bosque” compuesto por cientos de estos árboles CART. Mientras que un solo árbol CART puede ser inestable o cometer errores de precisión (overfitting), el Random Forest promedia muchos árboles para ser mucho más robusto.

En R se puede implementar este procedimiento de dos maneras:

Ambos métodos serán analizados en esta sección. En primer lugar, deben activarse los paquetes necesarios para realizar este ejercicio.

library(randomForest)                                            # Para ejecutar el primer procedimiento
## 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.
library(caret)                                                   # Para la creación de la matriz de confusión
## Warning: package 'caret' was built under R version 4.3.3
## Loading required package: ggplot2
## 
## Attaching package: 'ggplot2'
## The following object is masked from 'package:randomForest':
## 
##     margin
## Loading required package: lattice
## Warning: package 'lattice' was built under R version 4.3.2

PROCEDIMIENTOS INICIALES

Se comienza la actividad activando, si fuera necesario, los restantes paquetes

library(terra)             # Importación y análisis de imágenes
## terra 1.8.93
library(RStoolbox)          # Visualización de imágenes
## This is version 1.0.2.2 of RStoolbox
library(ggplot2)            # Visualización de imágenes
library(sf)                 # Importación de datos formato vectorial
## Linking to GEOS 3.11.2, GDAL 3.8.2, PROJ 9.3.1; sf_use_s2() is TRUE
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
## ✔ lubridate 1.9.5     ✔ tibble    3.3.1
## ✔ purrr     1.2.1     ✔ tidyr     1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::combine()  masks randomForest::combine()
## ✖ tidyr::extract()  masks terra::extract()
## ✖ dplyr::filter()   masks stats::filter()
## ✖ dplyr::lag()      masks stats::lag()
## ✖ purrr::lift()     masks caret::lift()
## ✖ ggplot2::margin() masks randomForest::margin()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

A continuación se importa el fichero que contiene los valores de reflectividad según coberturas de suelo con la función read.csv2, a la que se añade el argumento rownames = 1 para convertir la primera columna en nombres de fila.

df_muestra <- read.csv2("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/df_muestra.csv", row.names = 1)

Puede verficarse a continuación

df_muestra

También se importará la escena Landsat.

imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/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, ...

RANDOM FOREST CON EL PAQUETE randomForest

Para aplicar el paquete randomForest a la clasificación de una imagen, se deben convertir los valores de cada banda de esta última en una matriz.

imagen_matriz <- values(imagen, dataframe = TRUE)  # Extraer los valores como una matriz (o data.frame)
imagen_matriz <- na.omit(imagen_matriz)            # Eliminar los valores NA

A continuación, el dataframe df_muestra se dividirá en dos subconjuntos, uno de entrenamiento (con el 70 % de los casos) y otro de verificación (con el 30% restante).

# Crear el set de entrenamiento (70%)
df_entrenamiento <- df_muestra %>% 
  sample_frac(0.7)

# Crear el set de verificación con el resto (30%). Usamos anti_join para asegurar que no se repitan filas
df_verificacion <- df_muestra %>% 
  anti_join(df_entrenamiento)
## Joining with `by = join_by(categoria, blue, green, red, NIR, SWIR1, SWIR2)`

Ahora aplicaremos el modelo randomForest al conjunto de datos de entrenamiento con, digamos, 2000 árboles de decisión (argumento ntree) de la función randomForest(). Como el propósito es la clasificación, el método de "Class" se asigna en el argumento strata. La columna “Class” es la variable dependiente (que debermos transformar en factor) mientras que las columnas restantes son las independientes.

rf_mod_inicial <- randomForest(as.factor(categoria)~., 
                               data = df_entrenamiento, 
                               ntree = 2000, 
                               strata = "Class", 
                               importance = T)
rf_mod_inicial
## 
## Call:
##  randomForest(formula = as.factor(categoria) ~ ., data = df_entrenamiento,      ntree = 2000, strata = "Class", importance = T) 
##                Type of random forest: classification
##                      Number of trees: 2000
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 1.43%
## Confusion matrix:
##          abierto agua barbecho cultivos urbano class.error
## abierto       42    0        0        0      0   0.0000000
## agua           0   57        0        0      0   0.0000000
## barbecho       0    0       13        0      0   0.0000000
## cultivos       0    0        0        9      0   0.0000000
## urbano         2    0        0        0     17   0.1052632

una de las mayores ventajas de Random Forest es su capacidad de autoevaluarse sin necesidad de un conjunto de datos externo. Cuando Random Forest construye cada uno de sus cientos de árboles, no utiliza todos tus datos de entrenamiento para cada árbol. En su lugar, toma una muestra aleatoria (técnica llamada bootstrapping). Los datos que se quedan fuera de ese árbol específico se llaman “Out-Of-Bag” (fuera de la bolsa). El algoritmo usa esos datos “sobrantes” para probar el árbol inmediatamente. Como consecuencia, la tasa de error OOB es una validación interna muy buena: si el error OOB es del 5%, significa que el modelo tiene una precisión esperada del 95% con datos nuevos.

A diferencia de otros modelos donde el analista genera la matriz al final, Random Forest proporciona una durante el entrenamiento. Esta matriz compara lo que el modelo predijo para esos datos “fuera de la bolsa” frente a su clase real, indicando qué clases se están confundiendo (por ejemplo, si está confundiendo “Urbano” con “Suelo desnudo”).

Para optimizar el número de árboles (ntree) se representa una gráfica de error. Al principio el error es alto y, a medida que añades árboles, el error cae drásticamente hasta que se estabiliza (se vuelve plano). El punto óptimo es aquel donde añadir más árboles ya no reduce el error: usar más árboles de los necesarios solo hace que el proceso sea más lento sin ganar precisión.

plot(rf_mod_inicial, lwd=rep(2, 3))
legend("right", 
       legend = c("OOB Error", "FPR", "FNR"), 
       lwd=rep(2, 3), 
       lty = c(1,2,3), 
       col = c("black", "red", "green"))

La gráfica muestra que la tasa de error es constante a partir de unos 700 árboles. Por lo tanto, es posible mejorar el modelo alterando el número de árboles.

rf_mod_final <- randomForest(as.factor(categoria)~., 
                   data = df_entrenamiento, 
                   ntree = 700, 
                   strata = "Class", 
                   importance = T)
rf_mod_final
## 
## Call:
##  randomForest(formula = as.factor(categoria) ~ ., data = df_entrenamiento,      ntree = 700, strata = "Class", importance = T) 
##                Type of random forest: classification
##                      Number of trees: 700
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 1.43%
## Confusion matrix:
##          abierto agua barbecho cultivos urbano class.error
## abierto       42    0        0        0      0   0.0000000
## agua           0   57        0        0      0   0.0000000
## barbecho       0    0       13        0      0   0.0000000
## cultivos       0    0        0        9      0   0.0000000
## urbano         2    0        0        0     17   0.1052632

Al haber proporcionado un número óptimo de árboles, la estimación OOB de la tasa de error ha disminuido, por lo que se puede aplicar este último modelo entrenado sobre el conjunto de datos de verificación y evaluar su precisión.

rf_prediccion <- predict(rf_mod_final, df_verificacion)

Creación de la matriz de confusión del conjunto de datos de verificacion

confusionMatrix(rf_prediccion, as.factor(df_verificacion$categoria))
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction abierto agua barbecho cultivos urbano
##   abierto       14    0        0        0      0
##   agua           0   21        0        0      0
##   barbecho       1    0        8        0      0
##   cultivos       0    0        0       10      0
##   urbano         0    0        0        0      6
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9833          
##                  95% CI : (0.9106, 0.9996)
##     No Information Rate : 0.35            
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9781          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: abierto Class: agua Class: barbecho Class: cultivos
## Sensitivity                  0.9333        1.00          1.0000          1.0000
## Specificity                  1.0000        1.00          0.9808          1.0000
## Pos Pred Value               1.0000        1.00          0.8889          1.0000
## Neg Pred Value               0.9783        1.00          1.0000          1.0000
## Prevalence                   0.2500        0.35          0.1333          0.1667
## Detection Rate               0.2333        0.35          0.1333          0.1667
## Detection Prevalence         0.2333        0.35          0.1500          0.1667
## Balanced Accuracy            0.9667        1.00          0.9904          1.0000
##                      Class: urbano
## Sensitivity                    1.0
## Specificity                    1.0
## Pos Pred Value                 1.0
## Neg Pred Value                 1.0
## Prevalence                     0.1
## Detection Rate                 0.1
## Detection Prevalence           0.1
## Balanced Accuracy              1.0

La salida de la función confusionMatrix muestra claramente que la precisión de clasificación del modelo evaluado sobre el conjunto de datos de verificación es buena.

La importancia de cada variable independiente en el proceso de clasificación se obtiene creando el siguiente gráfico.

Ambos gráficos muestran que la reflectancia en las bandas NIR y SWIR-1 tiene la mayor importancia en comparación con la reflectancia en las restantes bandas en el modelo desarrollado.

varImpPlot(rf_mod_final)

La matriz que refleja la importancia de cada variable en cada clase se puede obtener de la función importance().

importance(rf_mod_final)
##         abierto      agua barbecho cultivos   urbano MeanDecreaseAccuracy
## blue   8.934408  5.791187 14.17053 16.84382 22.84785             25.75628
## green 14.304054  8.148588 19.49085 14.96796 22.84940             19.71687
## red   19.840186  9.512785 13.68675 18.40315 13.02815             21.68146
## NIR   28.100774 21.826396 23.68766 23.23257 18.40018             30.52013
## SWIR1 35.933082 21.169826 22.01441 18.25820 17.78745             34.71597
## SWIR2 14.423152 14.585700 16.86767 13.38689 13.88361             19.05014
##       MeanDecreaseGini
## blue          10.27008
## green         10.78394
## red           12.40887
## NIR           24.98732
## SWIR1         27.14967
## SWIR2         13.65014

El número de veces que cada variable es usada por el algorítmo randomForest puede ser obtenido con la funcion varUsed.

varUsed(rf_mod_final)
## [1] 788 659 706 887 892 660

Si desea conocer el efecto de los valores de reflectancia de una banda particular al predecir una clase específica, se puede crear un diagrama de dependencia parcial. Este gráfico representa el efecto marginal de una variable en la probabilidad de una clase. Supongamos que desea verificar el efecto de la variación en los valores de reflectancia NIR al predecir la clase “cultivos” en el modelo.

partialPlot(rf_mod_final, df_entrenamiento, NIR, "cultivos")

O también urbano

partialPlot(rf_mod_final, df_entrenamiento, red, "urbano")

En este gráfico, podemos ver que cuando el valor de reflectancia NIR es superior a 0,35, existe la mayor probabilidad de predicción de la clase ‘cultivos’. De manera similar, se puede crear un gráfico de dependencia parcial de la clase “agua” en la banda roja

Si desea obtener un árbol de decisión único del modelo randomForest, debe utilizarse la función getTree(). En el siguiente ejemplo se extrae el primer árbol de decisión del modelo desarrollado.

getTree(rf_mod_final, 1, labelVar = TRUE)
##    left daughter right daughter split var split point status prediction
## 1              2              3       NIR  0.09201556      1       <NA>
## 2              0              0      <NA>  0.00000000     -1       agua
## 3              4              5     SWIR1  0.33029534      1       <NA>
## 4              6              7      blue  0.13289451      1       <NA>
## 5              8              9     green  0.18210104      1       <NA>
## 6             10             11       red  0.07130501      1       <NA>
## 7              0              0      <NA>  0.00000000     -1     urbano
## 8              0              0      <NA>  0.00000000     -1    abierto
## 9              0              0      <NA>  0.00000000     -1     urbano
## 10             0              0      <NA>  0.00000000     -1   cultivos
## 11            12             13     SWIR1  0.27961412      1       <NA>
## 12             0              0      <NA>  0.00000000     -1   barbecho
## 13             0              0      <NA>  0.00000000     -1    abierto

Preparar datos e índices

imagen_matriz_completa <- values(imagen, dataframe = TRUE)
indices_validos <- which(complete.cases(imagen_matriz_completa))
imagen_matriz_limpia <- na.omit(imagen_matriz_completa)

Clasificar

prediccion_rf <- predict(rf_mod_final, 
                         imagen_matriz_limpia)

Crear el ráster y rellenar con IDs numéricos

raster_rf <- rast(imagen[[1]]) # Usamos la primera capa como molde
values(raster_rf) <- NA        # Limpiar valores previos
raster_rf[indices_validos] <- as.numeric(prediccion_rf)

REPRESENTACIÓN GRÁFICA

Comenzaremos asignando nombres a las clases. Para ello es necesario cConvertir la columna categoria a factor

df_entrenamiento$categoria <- as.factor(df_entrenamiento$categoria)

A continuación se extraen los nombres de las clases del modelo original

nombres_clases <- levels(df_entrenamiento$categoria)
print(nombres_clases) 
## [1] "abierto"  "agua"     "barbecho" "cultivos" "urbano"

Se crea la tabla de niveles para el ráster

levels(raster_rf) <- data.frame(ID = 1:length(nombres_clases), 
                                Clase = nombres_clases)

Finalmente, se dibujará la imagen con los píxeles clasificados según los resultados de randomForest.

ggR(raster_rf, geom_raster = TRUE) +                                          # PLot with ggplot2 
  ggtitle("Clasificación con randomForest") +                                           # Título
  labs(x="Longitud (m)", y="Latitud (m)") +                                     # Etiquetas de los ejes X e Y 
  theme(plot.title=element_text(hjust =0.5,                                     # Título: alineación en el centro
                                size =30),                                      # Título: tamaño
        axis.title=element_text(size=20),                                       # Etiquetas de los ejes: tamaño
        legend.key.size=unit(1, "cm"),                                          # Leyenda: tamaño.
        legend.title=element_text(size =20),                                    # Leyenda: tamaño del título.
        legend.text =element_text(size =15)) +                                  # Leyenda: tamaño del texto.
  # Usamos scale_fill_manual para asignar colores exactos a los nombres
  scale_fill_manual(values = c("grey", "burlywood", "wheat", "white", "darkgreen", "skyblue"),
                    name = "Categoría")

Para no favorecer la acumulación de objetos en el Global environment, se eliminan a continuación.

rm(list=ls())

RANDOM FOREST CON EL PAQUETE RStoolbox

Para aplicar Random Forest usando el paquete RStoolbox, se debe importar el fichero vectorial que contiene los sitios de entrenamiento.

puntos_entrenamiento_rf <- vect("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/puntos_entrenamiento.geojson")
puntos_entrenamiento_rf
##  class       : SpatVector 
##  geometry    : points 
##  dimensions  : 200, 2  (geometries, attributes)
##  extent      : -121.922, -121.4192, 37.85161, 38.15608  (xmin, xmax, ymin, ymax)
##  source      : puntos_entrenamiento.geojson
##  coord. ref. : lon/lat WGS 84 (EPSG:4326) 
##  names       :    id      uso
##  type        : <chr>    <chr>
##  values      :     1 cultivos
##                    5  abierto
##                    5  abierto

También se importará la escena Landsat.

imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/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, ...

Ambos deben estar proyectados en el mismo Sistema de referencia de coordenadas (CRS). Para confirmar si ambos ficheros comparten el mismo Sistema de coordenadas de referencia,

crs(imagen) == crs(puntos_entrenamiento_rf)
## [1] FALSE

Si poseyeran sistemas de proyección diferentes, se reproyectará el fichero en formato vectorial al mismo CRS del fichero raster.

puntos_entrenamiento_rf <- project(puntos_entrenamiento_rf, crs(imagen))

Verificar nuevamente que los CRS coincidan

identical(crs(imagen), crs(puntos_entrenamiento_rf))
## [1] TRUE

Para comprobar si los puntos caen dentro de la imagen, es decir, verificar la si la extensión (bounding box) de los puntos está contenida en la de la imagen:

ext(imagen) >= ext(puntos_entrenamiento_rf)
## [1] TRUE

Dado que la función Rstoolbox::superClass() requiere que el fichero vectorial esté en formato sf, se procede a su transformación.

puntos_entrenamiento_rf <- st_as_sf(puntos_entrenamiento_rf)

Una vez ambos tipos de ficheros comparten el mismo sistema de proyección, se puede iniciar el procedimiento de clasificación. En la función superClass() debe asignarse el argumento model = "rf". Además, debe utilizarse un 70 % de los datos para el entrenamiento y el 30 % de los datos restantes para la verificación asignando el argumento trainPartition = 0.7. La descripción de los restantes parámetros se proporciona en la sección dedicada al procedimiento de máxima verosimilitud.

rf_mod_rstoolbox <- superClass(imagen, 
                               trainData = puntos_entrenamiento_rf, 
                               responseCol = "uso", 
                               nSamples = 500, 
                               model = "rf", 
                               mode = "classification", 
                               trainPartition = 0.7, 
                               tuneLength = 3, 
                               kfold =10)
rf_mod_rstoolbox
## superClass results
## ************ Validation **************
## $validation
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction abierto agua barbecho cultivos urbano
##   abierto       15    0        0        0      1
##   agua           0   23        0        0      0
##   barbecho       0    0        6        0      0
##   cultivos       0    0        0        5      0
##   urbano         0    0        0        0      6
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9821          
##                  95% CI : (0.9045, 0.9995)
##     No Information Rate : 0.4107          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9753          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: abierto Class: agua Class: barbecho Class: cultivos
## Sensitivity                  1.0000      1.0000          1.0000         1.00000
## Specificity                  0.9756      1.0000          1.0000         1.00000
## Pos Pred Value               0.9375      1.0000          1.0000         1.00000
## Neg Pred Value               1.0000      1.0000          1.0000         1.00000
## Prevalence                   0.2679      0.4107          0.1071         0.08929
## Detection Rate               0.2679      0.4107          0.1071         0.08929
## Detection Prevalence         0.2857      0.4107          0.1071         0.08929
## Balanced Accuracy            0.9878      1.0000          1.0000         1.00000
##                      Class: urbano
## Sensitivity                 0.8571
## Specificity                 1.0000
## Pos Pred Value              1.0000
## Neg Pred Value              0.9800
## Prevalence                  0.1250
## Detection Rate              0.1071
## Detection Prevalence        0.1071
## Balanced Accuracy           0.9286
## 
## *************** Map ******************
## $map
## 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
## name        : uso_supervised 
## min value   :              1 
## max value   :              5

La salida del código R muestra que el modelo randomForest desarrollado tiene muy buena precisión. Podemos ver los atributos de la imagen raster ya clasificada y las etiquetas correspondintes.

rf_mod_rstoolbox$map
## 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
## name        : uso_supervised 
## min value   :              1 
## max value   :              5
rf_mod_rstoolbox$classMapping
##   classID    class
## 1       1  abierto
## 2       2     agua
## 3       3 barbecho
## 4       4 cultivos
## 5       5   urbano