library(readxl)
library(tidyverse)
library(dplyr)
library(stringr)
library(ggplot2)
library(sf)
library(terra)
library(leaflet)
library(leaflet.extras)
library(geodata)
library(spdep)
library(dbscan)
library(RColorBrewer)
library(cluster)
library(factoextra)
library(inflection)
library(kableExtra)

Clustering Espacial de Incendios Forestales en Huánuco mediante Algoritmos de Machine Learning

Este análisis utiliza los registros de incendios forestales ocurridos en el departamento de Huánuco, ubicado en la zona centro-oriental del Perú. Al ser un territorio que conecta la sierra con la Amazonía, Huánuco presenta una alta diversidad ecológica, pero también una elevada vulnerabilidad a incendios, especialmente en áreas donde convergen actividades agrícolas, zonas boscosas y centros poblados.

El objetivo es identificar patrones espaciales de concentración de incendios aplicando métodos de Machine Learning para análisis geográfico, como K-means, DBSCAN y HDBSCAN. Estas técnicas permiten distinguir zonas críticas con alta densidad de eventos frente a puntos aislados, y los resultados se integran con la cartografía oficial del departamento —incluyendo provincias y distritos— para facilitar su interpretación territorial y su posible uso en la gestión del riesgo y la planificación ambiental.
#cargo de los datos y despues análisis estadístico exploratorio
incendios_huanuco2025 <- st_read("C:/_ALIDE/Documentos/data_ciencia/MPP-CDD-02/data/serfor/incendios_huanuco.shp") 
## Reading layer `incendios_huanuco' from data source 
##   `C:\_ALIDE\Documentos\data_ciencia\MPP-CDD-02\data\serfor\incendios_huanuco.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 1260 features and 43 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -77.2306 ymin: -10.47345 xmax: -74.86699 ymax: -8.466419
## Geodetic CRS:  WGS 84
# Ver las primeras filas
head(st_drop_geometry(incendios_huanuco2025))
summary(st_drop_geometry(incendios_huanuco2025))
##     FUENTE             DOCREG              FECREG              OBSERV         
##  Length:1260        Length:1260        Min.   :1899-12-30   Length:1260       
##  Class :character   Class :character   1st Qu.:1899-12-30   Class :character  
##  Mode  :character   Mode  :character   Median :1899-12-30   Mode  :character  
##                                        Mean   :1899-12-30                     
##                                        3rd Qu.:1899-12-30                     
##                                        Max.   :1899-12-30                     
##                                        NA's   :2                              
##      ZONUTM       ORIGEN     REFER               ANNO          
##  Min.   :18   Min.   :0   Length:1260        Length:1260       
##  1st Qu.:18   1st Qu.:0   Class :character   Class :character  
##  Median :18   Median :0   Mode  :character   Mode  :character  
##  Mean   :18   Mean   :0                                        
##  3rd Qu.:18   3rd Qu.:0                                        
##  Max.   :18   Max.   :0                                        
##                                                                
##      MES                DIA               DURAC              CODPIF         
##  Length:1260        Length:1260        Length:1260        Length:1260       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##     CODRIF             CODOIF             CODSIN              SUPTOT      
##  Length:1260        Length:1260        Length:1260        Min.   :  0.42  
##  Class :character   Class :character   Class :character   1st Qu.:  2.80  
##  Mode  :character   Mode  :character   Mode  :character   Median :  5.62  
##                                                           Mean   : 12.43  
##                                                           3rd Qu.: 12.34  
##                                                           Max.   :272.63  
##                                                                           
##     NOMDEP             NOMPRO             NOMDIS             CATDEP         
##  Length:1260        Length:1260        Length:1260        Length:1260       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##     CATPRO             CATDIS             VERAFECT     TIPRO  
##  Length:1260        Length:1260        Min.   :0   Min.   :0  
##  Class :character   Class :character   1st Qu.:0   1st Qu.:0  
##  Mode  :character   Mode  :character   Median :0   Median :0  
##                                        Mean   :0   Mean   :0  
##                                        3rd Qu.:0   3rd Qu.:0  
##                                        Max.   :0   Max.   :0  
##                                                               
##     CODCF              CODPAS             FFECHA            CODRVIF         
##  Length:1260        Length:1260        Length:1260        Length:1260       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##   created_us          created_da          last_edite       
##  Length:1260        Min.   :2025-09-22   Length:1260       
##  Class :character   1st Qu.:2025-09-22   Class :character  
##  Mode  :character   Median :2025-10-24   Mode  :character  
##                     Mean   :2025-10-10                     
##                     3rd Qu.:2025-10-24                     
##                     Max.   :2025-10-24                     
##                                                            
##    last_edi_1            GID_1              GID_0             COUNTRY         
##  Min.   :2025-10-09   Length:1260        Length:1260        Length:1260       
##  1st Qu.:2025-10-09   Class :character   Class :character   Class :character  
##  Median :2025-10-24   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :2025-10-17                                                           
##  3rd Qu.:2025-10-24                                                           
##  Max.   :2025-10-24                                                           
##                                                                               
##     NAME_1           VARNAME_1          NL_NAME_1            TYPE_1         
##  Length:1260        Length:1260        Length:1260        Length:1260       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##   ENGTYPE_1             CC_1              HASC_1             ISO_1          
##  Length:1260        Length:1260        Length:1260        Length:1260       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
## 
# Clase del objeto
class(incendios_huanuco2025)
## [1] "sf"         "data.frame"
names(incendios_huanuco2025)
##  [1] "FUENTE"     "DOCREG"     "FECREG"     "OBSERV"     "ZONUTM"    
##  [6] "ORIGEN"     "REFER"      "ANNO"       "MES"        "DIA"       
## [11] "DURAC"      "CODPIF"     "CODRIF"     "CODOIF"     "CODSIN"    
## [16] "SUPTOT"     "NOMDEP"     "NOMPRO"     "NOMDIS"     "CATDEP"    
## [21] "CATPRO"     "CATDIS"     "VERAFECT"   "TIPRO"      "CODCF"     
## [26] "CODPAS"     "FFECHA"     "CODRVIF"    "created_us" "created_da"
## [31] "last_edite" "last_edi_1" "GID_1"      "GID_0"      "COUNTRY"   
## [36] "NAME_1"     "VARNAME_1"  "NL_NAME_1"  "TYPE_1"     "ENGTYPE_1" 
## [41] "CC_1"       "HASC_1"     "ISO_1"      "geometry"
dim(incendios_huanuco2025)
## [1] 1260   44
# Información del CRS y tipo de geometría 
# ver si está en grados, sino reproyectaremos a metros
st_crs(incendios_huanuco2025)
## Coordinate Reference System:
##   User input: WGS 84 
##   wkt:
## GEOGCRS["WGS 84",
##     DATUM["World Geodetic System 1984",
##         ELLIPSOID["WGS 84",6378137,298.257223563,
##             LENGTHUNIT["metre",1]]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433]],
##     CS[ellipsoidal,2],
##         AXIS["latitude",north,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433]],
##         AXIS["longitude",east,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433]],
##     ID["EPSG",4326]]
#ver si son puntos
st_geometry_type(incendios_huanuco2025)
##    [1] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [13] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [25] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [37] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [49] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [61] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [73] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [85] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##   [97] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [109] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [121] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [133] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [145] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [157] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [169] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [181] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [193] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [205] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [217] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [229] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [241] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [253] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [265] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [277] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [289] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [301] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [313] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [325] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [337] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [349] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [361] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [373] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [385] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [397] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [409] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [421] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [433] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [445] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [457] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [469] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [481] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [493] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [505] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [517] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [529] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [541] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [553] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [565] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [577] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [589] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [601] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [613] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [625] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [637] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [649] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [661] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [673] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [685] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [697] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [709] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [721] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [733] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [745] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [757] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [769] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [781] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [793] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [805] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [817] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [829] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [841] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [853] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [865] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [877] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [889] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [901] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [913] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [925] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [937] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [949] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [961] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [973] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [985] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
##  [997] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1009] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1021] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1033] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1045] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1057] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1069] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1081] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1093] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1105] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1117] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1129] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1141] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1153] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1165] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1177] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1189] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1201] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1213] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1225] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1237] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## [1249] POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT POINT
## 18 Levels: GEOMETRY POINT LINESTRING POLYGON MULTIPOINT ... TRIANGLE
# Incluyo límites administrativos (nivel 1 = departamentos) y los pasamos a sf
peru_spat <- geodata::gadm("PER", level = 1, path = "C:/_ALIDE/tmp_gadm")
peru <- st_as_sf(peru_spat)

# Igualo CRS del límite al CRS actual de incendios (solo para poder dibujarlos juntos)
peru <- st_transform(peru, st_crs(incendios_huanuco2025))

# Filtro el departamento de Huánuco
huanuco <- peru[ peru$NAME_1 %in% c("Huánuco", "Huanuco"), ]

# Mapa combinado para ubica los puntos de ocurrencias o incendios en Huánuco.
ggplot() +
  geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1) +   # Polígono de Huánuco
  geom_sf(data = incendios_huanuco2025, color = "red", alpha = 0.6, size = 1) +  # Puntos de incendios
  labs(title = "Incendios en el Departamento de Huánuco",
       subtitle = "Puntos de ocurrencias",
       x = "Longitud", y = "Latitud") +
  theme_minimal()

 #PREPARACIÓN PARA CLUSTERING: COORDENADAS EN METROS

#Como K-means, DBSCAN esan tn grados reproyectamos los CRS en METROS (ajusta el EPSG a tu zona; 5347).
# Para extraer coordenadas (X,Y) en METROS (del CRS actual)
coords <- st_coordinates(incendios_huanuco2025)
incendios_huanuco2025$X <- coords[,1]
incendios_huanuco2025$Y <- coords[,2]
#lo convierte a metros

incendios_huanuco2025 <- st_transform(incendios_huanuco2025, 5347)

# 3. Mapa
ggplot() +
  geom_sf(data = incendios_huanuco2025, size = 0.7, alpha = 0.7, color = "red") +
  labs(title = "Puntos de incendios en Huánuco (CRS en metros)",
       subtitle = "Datos en metros para clustering",
       x = "Este (m)", y = "Norte (m)") +
   geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1) + 
  theme_minimal()

1 K-MEANS: PARTICIONAMIENTO EN K CLUSTERS

#Se tiene que sembrar una semilla de la aleatoriedad (centroides iniciales aleatorios)
set.seed(123)
# K=3
modelo_kmeans3 <- kmeans(x=incendios_huanuco2025 %>% st_coordinates(.), # Eso hace que lea lista como dos columnas de datos de coordenadas, st_Coordinates (da la estructura)
centers=3,
nstart=25,
iter.max =20)
modelo_kmeans3
## K-means clustering with 3 clusters of sizes 586, 238, 436
## 
## Cluster means:
##         X       Y
## 1 3664508 8874643
## 2 3612662 8976207
## 3 3725074 8839576
## 
## Clustering vector:
##    [1] 1 1 1 1 1 3 3 3 1 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 3
##   [38] 2 1 3 1 2 2 2 3 1 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 3 3 3 3 1 1 1 1 1 1 1 1 1
##   [75] 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [112] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [186] 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [223] 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [260] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [297] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [334] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3
##  [371] 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [408] 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [445] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [482] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [519] 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 2 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 3 3 3 1 1 1
##  [556] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [593] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [630] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [667] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [704] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [741] 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [778] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [815] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 1 1 3
##  [852] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1
##  [889] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [926] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3
##  [963] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 3 3 3 3 3 3 3 3 3 1 1
## [1000] 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1037] 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [1074] 1 1 1 1 1 1 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2
## [1111] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
## [1148] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 1 1 1 1 1 1 1 1 1
## [1185] 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2
## [1222] 2 2 1 1 1 1 1 1 3 1 1 1 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [1259] 1 1
## 
## Within cluster sum of squares by cluster:
## [1] 868125698752 177627508177 360734134382
##  (between_SS / total_SS =  78.1 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

El modelo K-means con K=3 dividió los incendios en tres zonas principales con tamaños relativamente grandes (586, 238 y 436 eventos). Los centroides muestran dónde se concentra cada grupo en el territorio. El 78% de la variabilidad espacial está explicada por esta partición, lo que indica una separación moderadamente clara entre clusters, aunque existe dispersión interna que sugiere que no todos los grupos son compactos.


# Asigna el número de cluster a cada punto 
incendios_huanuco2025 <- incendios_huanuco2025 %>%
mutate(cluster_kmeans=modelo_kmeans3$cluster)
#Mapa de K=3 (solo para ver la partición en el espacio)
ggplot()+
geom_sf(data=incendios_huanuco2025, aes(color=as.factor(cluster_kmeans)))+
   geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1) 

K-means identificó tres regiones prioritarias donde los incendios se concentran. Esto sugiere que existen factores territoriales o socioambientales que influyen en la recurrencia del fuego en esas áreas (uso de suelo, cercanía a campos agrícolas, zonas de transición bosque-selva, etc.), pero este metodo es mas limitado que el DBSCAN o HDBSCAN.

# K=8
modelo_kmeans8 <- kmeans(x = incendios_huanuco2025 %>%
                           st_coordinates(geometry),
                         centers = 8,
                         iter.max = 20,
                         nstart = 25)

modelo_kmeans8
## K-means clustering with 8 clusters of sizes 201, 209, 183, 173, 111, 222, 5, 156
## 
## Cluster means:
##         X       Y
## 1 3667735 8872056
## 2 3610637 8982440
## 3 3730720 8823766
## 4 3678274 8902616
## 5 3636327 8912009
## 6 3722680 8852664
## 7 3833443 8945707
## 8 3661615 8821570
## 
## Clustering vector:
##    [1] 1 1 1 1 1 6 3 6 8 6 6 6 4 4 4 4 4 4 4 5 5 5 5 5 5 4 4 5 5 5 5 5 5 5 6 1 7
##   [38] 2 4 3 5 2 2 2 6 4 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 6 6 6 8 4 4 4 4 4 4 4 4 4
##   [75] 4 4 4 4 4 4 4 4 6 1 1 1 1 1 8 1 1 5 1 5 5 1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [112] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 6 6 6 6 6
##  [186] 6 6 3 3 1 1 1 1 1 1 1 1 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 4 4 4 4 4
##  [223] 4 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 1 1 1 1 1 4 4 4 4 1 1 1 5 5 5 5 5
##  [260] 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 1 1 1 1 1 1 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
##  [297] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
##  [334] 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 2 2 2 2 2 2 2 3 3 6 8 6 8
##  [371] 8 6 6 6 6 8 8 8 8 8 8 8 8 1 1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [408] 6 6 6 6 6 1 1 1 1 1 1 1 1 1 1 1 8 8 8 8 1 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4
##  [445] 4 4 4 4 4 5 4 5 5 5 5 5 5 5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [482] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [519] 2 2 2 2 2 2 2 2 2 2 2 2 2 6 2 2 4 4 4 4 6 4 4 4 4 1 1 1 6 1 1 6 6 6 1 1 1
##  [556] 1 1 4 4 4 4 1 1 4 4 4 4 4 4 4 4 4 1 1 1 1 1 1 1 1 1 1 1 1 8 8 1 1 1 8 8 8
##  [593] 8 8 1 1 1 1 1 1 5 5 5 5 5 5 5 5 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [630] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 6 6
##  [667] 3 3 3 3 3 3 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [704] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 6 6 3 3 6 6 6 6 6 6 6 6 6 6
##  [741] 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 8 1 1 1 8 8 8 8 8
##  [778] 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
##  [815] 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 4 1 8 8 8 6 6 1 1 6
##  [852] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 4 4 4 4 4 5 5 5 5 5 5 5 5 1 1 1
##  [889] 1 1 1 1 1 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 4 4 4 4 4 4 4 4 1 1 1 1 4 4 5 5 5
##  [926] 5 5 5 5 5 5 5 5 5 5 5 5 5 5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3
##  [963] 3 3 3 3 3 3 3 6 6 6 6 6 6 6 6 6 6 8 8 8 8 8 8 8 8 8 6 8 6 8 8 6 6 6 6 1 1
## [1000] 1 1 1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1037] 6 6 6 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 1
## [1074] 1 1 1 4 4 4 7 7 7 7 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 4 2 2 2 2 2 2 2
## [1111] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
## [1148] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 3 3 3 5 8 8 8 8 8 1 1 1
## [1185] 1 1 1 1 1 6 6 6 6 6 6 6 6 6 6 6 6 3 6 6 3 3 8 8 8 8 6 2 2 2 2 2 2 2 2 2 2
## [1222] 2 2 5 5 5 5 5 5 6 1 8 8 3 6 6 6 6 6 6 6 6 6 6 8 1 1 1 1 1 1 1 1 1 1 1 1 1
## [1259] 1 1
## 
## Within cluster sum of squares by cluster:
## [1]  64797157310 100489822452  48657413518  52067139774  65758442772
## [6]  52449108401   2235358179  57337522606
##  (between_SS / total_SS =  93.1 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

El modelo K-means con 8 clusters revela que los incendios no se distribuyen uniformemente, sino que se concentran en al menos ocho zonas diferenciadas dentro del territorio de Huánuco. El 93 % de la variabilidad espacial total se explica por la agrupación, lo que indica una separación fuerte entre los centros definidos. Sin embargo, los valores desiguales de la suma de cuadrados sugieren que algunos clusters son compactos y bien definidos, mientras que otros presentan mayor dispersión interna.

Se observa que hay 8 cluster de los cuales, el clúster 7 tiene solo 5 puntos (numero de incendios). Aunque K-means lo está tratando como un grupo, pero probablemente sea ruido o puntos aislados (algo que DBSCAN identifica mejor).


incendios_huanuco2025 <- incendios_huanuco2025 %>%
  mutate(cluster_kmeans8 = modelo_kmeans8$cluster)
ggplot()+
  geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1)+ 
  geom_sf(data = incendios_huanuco2025, aes(color=as.factor(cluster_kmeans8)), inherit.aes = FALSE)+
  labs(color="Clusters")+
  scale_color_brewer(palette = "Paired")+
  theme_void()

Método del Codo ¿Cuántos K elegir? este método busca el punto de inflexión

fviz_nbclust(x = incendios_huanuco2025 %>%
               st_coordinates(.),
             FUN = kmeans,
             method = "wss")


La curva baja con fuerza entre 1, 2 y 3 clusters, lo que indica que agrupar mejora bastante en esos valores. A partir de 4, la línea se aplana y la reducción del error es mínima. Por tanto, el punto donde deja de bajar fuerte está entre 3 y 4, por lo que ese sería un número razonable de clusters.


Método silhouette

# - Silhouette: calidad promedio de asignación por K (1≈muy bien, 0≈frontera, <0≈mal)
sil_kmeans3 <- silhouette(x = modelo_kmeans3$cluster,
                          dist = incendios_huanuco2025 %>%
                            st_coordinates(.) %>%
                            dist())
sil_kmeans3 %>%
  as.data.frame() %>%
  summarise(sil_width=mean(sil_width))

Valores cercanos a 0 indican que el punto está próximo al límite entre dos clusters.

sil_kmeans8 <- silhouette(x = modelo_kmeans8$cluster,
                          dist = incendios_huanuco2025 %>%
                            st_coordinates(.) %>%
                            dist())
sil_kmeans8 %>%
  as.data.frame() %>%
  summarise(sil_width=mean(sil_width))
fviz_nbclust(x = incendios_huanuco2025 %>%
               st_coordinates(.),
             FUN = kmeans,
             method = "silhouette",
             k.max = 20)


Aquí, el valor máximo también ocurre en “k = 2”, lo que sugiere que 2 clusters es la cantidad óptima para una buena estructura de agrupamiento, ya que a partir de ese punto, la calidad de los clusters no mejora significativamente.

k-means supone clusters “compactos y de tamaño similar”. Si la forma es alargada o hay ruido/atípicos, DBSCAN/HDBSCAN suele ir mejor.

2 USO DBSCAN

CLUSTERS POR DENSIDAD (SIN FIJAR K)

#Identificar el nivel de eps y minPts optimo
# DBSCAN usa dos parámetros:
#   - eps: radio de vecindad (EN METROS, por nuestro CRS)
#   - minPts: mínimo de puntos en esa vecindad para ser “núcleo”
kNNdistplot(st_coordinates(incendios_huanuco2025), k = 20)
abline(h = 150, col = "red")

Aqui se busca el punto donde empieza la subida fuerte (el codo hacia arriba), que marca el límite entre puntos densos (en clusters) y puntos aislados (ruido).El valor correcto de eps debería estar justo antes de la pendiente fuerte, no en la parte plana ni en la parte muy alta.


#para Calcular distancias al k-ésimo vecino más cercano
k <- 20
dist_knn <- kNNdist(st_coordinates(incendios_huanuco2025), k = k)

# Ordena distancias (requisito para detectar el codo)
dist_sorted <- sort(dist_knn)
idx <- seq_along(dist_sorted)

# etecta automáticamente el punto de inflexión ("codo")
knee_index <- uik(x = idx, y = dist_sorted)  # uik = unit invariant knee
eps_optimo <- dist_sorted[knee_index]

eps_optimo
## [1] 2976.641

El eps óptimo ordenado es 3000 o 3 km

#Un primer intento (ajustado la escala a 3000 m o 3 km).
modelo_dbscan <- dbscan(x = incendios_huanuco2025 %>%
st_coordinates(.),
eps = 3000,
minPts = 15)
modelo_dbscan
## DBSCAN clustering for 1260 objects.
## Parameters: eps = 3000, minPts = 15
## Using euclidean distances and borderpoints = TRUE
## The clustering contains 11 cluster(s) and 1002 noise points.
## 
##    0    1    2    3    4    5    6    7    8    9   10   11 
## 1002   16   46   21   27   26   18   28   28   17   16   15 
## 
## Available fields: cluster, eps, minPts, metric, borderPoints

Con los parámetros iniciales (eps = 3000, minPts = 15), DBSCAN identificó 11 clústeres pequeños y clasificó el 79.5% de los incendios (1002 de 1260) como ruido, que puede verse en el gráfico lineas abajo. Esto indica que la densidad espacial de los eventos es baja o dispersa, y que los parámetros deben ajustarse (especialmente el valor de eps) para capturar agrupamientos más representativos del fenómeno.


# Etiquetamos: cluster 0 = ruido (no asignado a ningún núcleo)
incendios_huanuco2025 <- incendios_huanuco2025 %>%
mutate(cluster_dbscan=modelo_dbscan$cluster)
# Mapa: ruido en gris, clusters con color
ggplot()+
geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1)+ 
geom_sf(data=incendios_huanuco2025 %>%
filter(cluster_dbscan==0), color="gray", size=0.7)+
geom_sf(data=incendios_huanuco2025 %>%
filter(cluster_dbscan!=0), aes(color=as.factor(cluster_dbscan)))

#Para hacer un zoom hacemos un interactivo

leaflet() %>%
addTiles() %>%
addCircleMarkers(data=incendios_huanuco2025 %>%
filter(cluster_dbscan!=0) %>%
st_transform(4326))

El grafico muestra los clústeres que no son ruido. Las agrupaciones que sí aparecen son bastante compactas, lo que indica alta densidad local en áreas específicas del departamento.
pal <- colorFactor(palette = colorRampPalette(brewer.pal(12, "Set3"))(51),
                   domain = incendios_huanuco2025$cluster_dbscan)
leaflet() %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addCircleMarkers(
    data = incendios_huanuco2025 %>%
      st_transform(4326) %>%
      filter(cluster_dbscan != 0),
    radius = 3,
    color = ~pal(cluster_dbscan),
    fillOpacity = 0.8,
    stroke = TRUE,
    weight = 1
  ) %>%
  addMiniMap(tiles = providers$CartoDB.DarkMatter)

El mapa muestra la distribución espacial de los incendios que DBSCAN reconoció como parte de un clúster significativo. Cada color representa un grupo con alta densidad interna y separación respecto a otros. La concentración principal se localiza en la zona sur y centro de Huánuco, lo que sugiere áreas de recurrencia o condiciones territoriales favorables a la propagación del fuego. Los demás puntos del dataset (que no cumplen los requisitos mínimos de densidad) no se visualizan porque fueron clasificados como ruido.


#Envolventes convexas por cluster (para visualizar “manchas”)
incendios_dbscan <- incendios_huanuco2025 %>%
  filter(cluster_dbscan != 0) %>%
  group_by(cluster_dbscan) %>%
  summarise(geometry = st_union(geometry)) %>%
  st_convex_hull()
# Mapa interactivo (Web Mercator) con puntos/convex-hull
leaflet() %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addPolygons(data=incendios_dbscan %>%
                st_transform(4326),
              color = ~pal(cluster_dbscan),
              popup = ~paste0("Cluster N°: ", cluster_dbscan)) %>%
  addCircleMarkers(data=incendios_huanuco2025 %>%
                     st_transform(4326) %>%
                     filter(cluster_dbscan != 0),
                   radius = 3,
                   color = ~pal(cluster_dbscan),
                   fillOpacity = 0.8,
                   stroke = TRUE,
                   weight = 1) %>%
  addMiniMap(tiles = providers$CartoDB.DarkMatter)
sil_dbscan <- silhouette(modelo_dbscan$cluster, incendios_huanuco2025 %>%
                           st_coordinates(.) %>%
                           dist())
#Silhouette de DBSCAN (usualmente ignorando ruido = 0)
sil_dbscan %>%
  as.data.frame() %>%
  filter(cluster!=0) %>%
  summarise(sil_width=mean(sil_width))

El índice Silhouette mide qué tan bien asignado está cada punto a su clúster (toma valores entre -1 y 1), con 0.75 se puede decir que DBSCAN produjo grupos muy bien formados, con alta densidad interna y buena separación espacial.


AJUSTE AUTOMÁTICO DE eps CON “CODO” kNN

Método k-NN (K-Nearest Neighbors)

#Distancias al k-ésimo vecino (k=20), 2) ordenarlas, 3) buscar el “codo”
knndist_dbscan <- kNNdist(x = incendios_huanuco2025 %>%
                            st_coordinates(.),
                          k = 20)
summary(knndist_dbscan)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    2809    5042    6729    8829   10643  146849
knndist_dbscan_sorted <- sort(knndist_dbscan)
knndist_dbscan_seq <- seq_along(knndist_dbscan_sorted)
# índice del “codo”
knee_index <- uik(x = knndist_dbscan_seq,
                  y = knndist_dbscan_sorted)
knee_index
## [1] 2
 # valor eps sugerido
eps_optimo <- knndist_dbscan_sorted[knee_index]
eps_optimo
## [1] 2976.641
#Visual para documentar la elección
kNNdistplot(x = incendios_huanuco2025 %>%
              st_coordinates(.),
            k = 20)
abline(v = knee_index, col = "red")
abline(h = eps_optimo, col = "steelblue")

modelo_dbscan_ajustado <- dbscan(x = incendios_huanuco2025 %>%
                          st_coordinates(.),
                          eps = eps_optimo,
                          minPts = 20)
# Reentrenamos con eps óptimo (minPts puedes mantenerlo o ajustar)
modelo_dbscan_ajustado
## DBSCAN clustering for 1260 objects.
## Parameters: eps = 2976.64145565701, minPts = 20
## Using euclidean distances and borderpoints = TRUE
## The clustering contains 2 cluster(s) and 1215 noise points.
## 
##    0    1    2 
## 1215   22   23 
## 
## Available fields: cluster, eps, minPts, metric, borderPoints
sil_dbscan_ajustado <- silhouette(modelo_dbscan_ajustado$cluster, incendios_huanuco2025 %>%
                                    st_coordinates(.) %>%
                                    dist())
# Nueva silhouette (excluyendo ruido)
sil_dbscan_ajustado %>%
  as.data.frame() %>%
  filter(cluster!=0) %>%
  summarise(sil_width=mean(sil_width))

Al ajustar minPts de 15 → 20 (manteniendo eps = 3000), el índice Silhouette aumentó de 0.75 a 0.96, lo que indica un salto enorme en la calidad del clustering.


incendios_huanuco2025 <- incendios_huanuco2025 %>%
  mutate(cluster_dbscan_ajustado=modelo_dbscan_ajustado$cluster)
# Envolventes para los clusters ajustados (solo visual)
incendios_dbscan_ajustado <- incendios_huanuco2025 %>%
  filter(cluster_dbscan_ajustado != 0) %>%
  group_by(cluster_dbscan_ajustado) %>%
  summarise(geometry = st_union(geometry)) %>%
  st_convex_hull()
# Mapa interactivo con capas (clusters / ruido) con control
leaflet() %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addPolygons(data=incendios_dbscan_ajustado %>%
                st_transform(4326),
              color = ~pal(cluster_dbscan_ajustado),
              popup = ~paste0("Cluster N°: ", cluster_dbscan_ajustado)) %>%
  addCircleMarkers(data=incendios_huanuco2025 %>%
                     st_transform(4326) %>%
                     filter(cluster_dbscan_ajustado != 0),
                   radius = 3,
                   color = ~pal(cluster_dbscan_ajustado),
                   fillOpacity = 0.5,
                   stroke = TRUE,
                   weight = 1) %>%
  addMiniMap(tiles = providers$CartoDB.DarkMatter)

El mapa muestra menos clusters. Ahora, un punto ya no entra a un cluster si solo tiene 15 vecinos, para ello debe tener 20 vecinos dentro de 3 km. Eso elimina agrupamientos marginales. Al incrementar el parámetro minPts de 15 a 20, el algoritmo DBSCAN eliminó agrupaciones débiles y conservó únicamente los núcleos de incendios más densos. Esto redujo el número de clusters pero incrementó drásticamente su coherencia interna, tal como lo demuestra el índice silhouette, que pasó de 0.75 a 0.96.


leaflet() %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addPolygons(data=incendios_dbscan_ajustado %>%
                st_transform(4326),
              color = ~pal(cluster_dbscan_ajustado),
              popup = ~paste0("Cluster N°: ", cluster_dbscan_ajustado)) %>%
  addCircleMarkers(data=incendios_huanuco2025 %>%
                     st_transform(4326) %>%
                     filter(cluster_dbscan_ajustado != 0),
                   radius = 3,
                   color = "#7B1FA2",
                   fillOpacity = 0.8,
                   stroke = TRUE,
                   weight = 1) %>%
  addMiniMap(tiles = providers$CartoDB.DarkMatter)

3 Clusters con HDBSCAN

HDBSCAN: DENSIDAD JERÁRQUICA (SIN eps)

# Ventaja: no requiere eps y maneja densidades heterogéneas; entrega "scores" de estabilidad.
modelo_hdbscan <- hdbscan(x = incendios_huanuco2025 %>%
                            st_coordinates(.),
                          minPts = 20)
modelo_hdbscan
## HDBSCAN clustering for 1260 objects.
## Parameters: minPts = 20
## The clustering contains 6 cluster(s) and 196 noise points.
## 
##   0   1   2   3   4   5   6 
## 196 204  31  23  93 365 348 
## 
## Available fields: cluster, minPts, coredist, cluster_scores,
##                   membership_prob, outlier_scores, hc

HDBSCAN redujo el ruido de 1002 (DBSCAN) a 196 puntos, lo que significa que el patrón espacial de incendios sí pertenece a múltiples núcleos de densidad real, y no solo a unos pocos focos dispersos. Eso quiere decir que los incendios no están tan dispersos como parecía al inicio: realmente sí existen varias zonas donde se concentran con fuerza. Una ventaja importante de HDBSCAN es que no obliga a elegir cuántos clústeres habrá. En lugar de imponer un número (como ocurre con K-means), el método detecta por sí mismo cuántos grupos reales existen en los datos. Ahora, los clústeres aparecen porque están en los datos, no porque uno como analista los forzó. Esto hace que el resultado sea más real / fiel al comportamiento del fenómeno.


incendios_huanuco2025 <- incendios_huanuco2025 %>%
  mutate(cluster_hdbscan=modelo_hdbscan$cluster)
ggplot()+
geom_sf(data = huanuco, fill = NA, color = "black", linewidth = 1)+
  geom_sf(data=incendios_huanuco2025 %>%
            filter(cluster_hdbscan==0),
          color="gray", size=0.5)+
  geom_sf(data=incendios_huanuco2025 %>%
            filter(cluster_hdbscan!=0),
          aes(color=as.factor(cluster_hdbscan)))+
  labs(color="Clusters")+
  theme_void()

# Mapa: ruido (0) en gris, clusters coloreados
pal_hdbscan <- colorFactor(palette = colorRampPalette(brewer.pal(12, "Set3"))(90),
                           domain = incendios_huanuco2025$cluster_hdbscan)
# Envolventes convexas por cluster HDBSCAN (visual)
incendios_hdbscan <- incendios_huanuco2025 %>%
  dplyr::filter(cluster_hdbscan != 0) %>%
  dplyr::group_by(cluster_hdbscan) %>%
  dplyr::summarise(n = dplyr::n(), .groups = "drop") %>%
  sf::st_convex_hull()
# Leaflet de HDBSCAN (puntos + envolventes)
leaflet() %>% 
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  
  # Polígonos (envolventes)
  addPolygons(
    data = incendios_hdbscan %>% st_transform(4326),
    color = ~pal_hdbscan(cluster_hdbscan),
    fillColor = ~pal_hdbscan(cluster_hdbscan),
    fillOpacity = 0.25,
    weight = 2
  ) %>%
  
  # Puntos de clúster
  addCircleMarkers(
    data = incendios_huanuco2025 %>%
      st_transform(4326) %>%
      dplyr::filter(cluster_hdbscan != 0),
    radius = 4,
    color = ~pal_hdbscan(cluster_hdbscan),
    fillOpacity = 0.85,
    stroke = FALSE
  ) %>%
  
  # Puntos ruido (cluster 0) opcional
  addCircleMarkers(
    data = incendios_huanuco2025 %>%
      st_transform(4326) %>%
      dplyr::filter(cluster_hdbscan == 0),
    radius = 2,
    color = "gray70",
    fillOpacity = 0.4,
    stroke = FALSE
  ) %>%
  
  addMiniMap(tiles = providers$CartoDB.DarkMatter)

Este mapa muestra los clusters de incendios forestales identificados mediante HDBSCAN en el departamento de Huánuco y zonas aledañas. Cada color representa un grupo de incendios que el algoritmo detectó como una zona con alta densidad de ocurrencias, es decir, un foco territorial donde los incendios tienden a repetirse o concentrarse.

Los incendios no están distribuidos de manera uniforme; se agrupan en al menos 3–4 zonas principales, que podrían corresponder a áreas agrícolas, zonas de transición bosque–cultivo o ejes viales.

El método eliminó gran parte del “ruido”, es decir, los puntos aislados sin patrón claro. Cada envolvente (polígono) muestra la extensión geográfica de cada clúster, no solo puntos sueltos.

# Métrica silhouette (excluyendo ruido)
sil_hdbscan <- silhouette(modelo_hdbscan$cluster, incendios_huanuco2025 %>%
                            st_coordinates(.) %>%
                            dist())
sil_hdbscan %>%
  as.data.frame() %>%
  filter(cluster!=0) %>%
  summarise(sil_width=mean(sil_width))

El índice Silhouette de 0.54 indica que los clusters obtenidos son razonablemente consistentes: los incendios dentro de cada grupo tienden a estar relativamente cerca entre sí y suficientemente lejos de otros clusters. No es una separación perfecta, pero sí evidencia que la distribución espacial tiene una estructura clara y no es aleatoria.


# Estabilidad de clusters (propia de HDBSCAN), valores mayores = clusters más robustos
hdbscan_estabilidad <- incendios_huanuco2025 %>%
  filter(cluster_hdbscan!=0) %>%
  group_by(cluster_hdbscan) %>%
  summarise(n=n()) %>%
  mutate(estabilidad=modelo_hdbscan$cluster_scores)
hdbscan_estabilidad %>%
  arrange(desc(estabilidad)) %>%
  head(10)
HDBSCAN no solo detectó clusters, sino que midió qué tan “verdaderos” son. Los clusters 5, 1 y 6 no son casualidades: son zonas donde los incendios se concentran de forma consistente, lo que los convierte en áreas prioritarias para prevención y monitoreo.


#Vista “plana” del árbol de densidad (diagnóstico visual)
plot(modelo_hdbscan, show_flat = TRUE)


El gráfico muestra qué clusters son consistentes en los datos: los altos y gruesos son grupos reales, los cortos son débiles y lo que no tiene barra es ruido. ASí, el cluster 5 es el que se mantiene unido durante más tiempo cuando el algoritmo reduce la densidad, es decir, es el grupo más fuerte y consistente espacialmente.El segundo más estable es el cluster 1, seguido por el cluster 6.

El análisis demuestra que los incendios en Huánuco no ocurren de manera aleatoria: forman núcleos espaciales consistentes concentrados en zonas específicas.Entre los métodos probados, HDBSCAN ofrece la mejor capacidad para identificar estos patrones de forma realista, al reconocer diferencias de densidad, reducir ruido y permitir priorización territorial.Su aplicación puede servir como base técnica para diseñar estrategias de prevención, asignación de recursos y monitoreo satelital enfocado, superando el enfoque administrativo tradicional basado en distritos o provincias.