R y GRASS: Modelado de nicho
En esta última sesión, vamos a demostrar y ejemplificar el uso combinado de GRASS y R para modelar la distribución de Aedes aegypti en la provincia de Córdoba en función de variables ambientales. Algunas de estas variables provienen de los ejercicios realizados en la unidad de series de tiempo y otras serán generadas durante este ejercicio.
Antes de empezar y para ganar tiempo, conectemos nuestro drive e instalemos GRASS y los paquetes de R que vamos a usar en esta sesión.
Instalamos y cargamos al entorno el paquete de python que nos permite hacer interfaz con R dentro de una Jupyter notebook.
Chequeamos nuestra sesión de R.
Instalamos todos los paquetes necesarios para este ejercicio. This might take a while…
%%R
install.packages("rgrass")
install.packages("terra")
install.packages("raster")
install.packages("sf")
install.packages("biomod2")
install.packages("dismo")
install.packages("usdm")
install.packages("SDMtune")
install.packages("glmnet")
install.packages("zeallot")
install.packages("ggpubr")
install.packages("tmap")
install.packages("tmaptools")
rgrass
El paquete de R que hace las veces de interfaz entre R y GRASS GIS se denomina rgrass
. Este paquete ha sido desarrollado y sigue siendo mantenido por Bivand (2022) y se puede encontrar en: https://github.com/rsbivand/rgrass/.
Pueden ver la viñeta del paquete para más explicaciones, algo de contexto y ejemplos: https://rsbivand.github.io/rgrass/.
Las principales funciones que podemos encontrar en el paquete rgrass
son las siguientes:
initGRASS()
: inicia una sesión de GRASS GIS desde R, similar ags.setup.init()
ogj.init()
.execGRASS()
: ejecuta comandos de GRASS, similar ags.run_command()
gmeta()
: muestra los metadatos de localización de GRASS.read_VECT()
yread_RAST()
: leen mapas vectoriales y raster desde la base de datos de GRASS a objetosSpatVect
ySpatRast
del paquete terra de R.write_VECT()
ywrite_RAST()
: escriben objetos del paquete terra en la base de datos de GRASS GIS.
El paquete terra
es desarrollado y mantenido por Hijmans (2022) y eventualmente reemplazará a raster
. Detalles sobre el paquete terra pueden encontrarse en: https://rspatial.github.io/terra/reference/terra-package.html y https://rspatial.org/spatial/index.html.
Cómo usamos rgrass
?
GRASS GIS y R se pueden utilizar juntos de dos maneras:
A. Usar R dentro de una sesión de GRASS GIS, es decir, iniciar R (o RStudio) desde una sesión de GRASS
- escribimos
R
orstudio &
en la terminal GRASS GIS o en la pestaña Consola de la interfaz gráfica - una vez en R (o RStudio) cargamos el paquete
rgrass
(previo haberlo instalado) - usamos
read_VECT()
,read_RAST()
para leer datos de GRASS en R - accedemos a los módulos y la base de datos de GRASS GIS a través de
execGRASS()
- escribimos datos resultantes en la base de datos de GRASS con
write_VECT()
ywrite_RAST()
B. Iniciar y usar GRASS GIS dentro de una sesión de R, es decir, nos conectamos a la base de datos de GRASS GIS desde R (o RStudio).
- Primero cargamos el paquete
rgrass
- Necesitamos iniciar GRASS GIS con
initGRASS()
desde R y para ello, necesitamos especificar el ejecutable de GRASS y las ubicaciones de la base de datos, el proyecto (location) y mapset - Accedemos a los módulos GRASS GIS a través de
execGRASS()
- usamos
read_VECT()
,read_RAST()
,write_VECT()
ywrite_RAST()
para leer datos desde y hacia la base de datos GRASS.
Originalmente, rgrass
estaba destinado a aplicar funciones de GRASS en datos fuera de la base de datos de GRASS; de ahí que algunos prefieran crear proyectos (i.e., locations) desechables o temporarios. Por ejemplo:
SDM - modelado de la distribución de especies
El flujo de trabajo comúnmente utilizado para el modelado de la distribución de especies (SDM por su sigla en Inglés) consiste en la recopilación de sitios de ocurrencia de la especie o evento de interés (ej., casos de una enfermedad, focos de fuego, ietc.), así como de variables ambientales que puedan ser relevantes para explicar su distribución. Los datos de ocurrencia y las variables predictoras son las entradas de algún
algoritmo determinado o modelo (ej., GLM, Random Forest, MaxEnt, etc.). Una vez estamos “contentos” con el ajuste del modelo, el próximo paso es la predicción. En este caso, hablamos de prediccón ambiental cuando nos interesa explicar e interpretar la relación entre las variables y la probabilidad de ocurrencia (curvas de respuesta) y predicción geográfica cuando nos interesa ver plasmadas en el espacio geografico el resultado de esas relaciones (mapas).
Existen varios paquetes para realizar SDM. En este caso usaremos SDMtune desarrollado y mantenido por Vignali et al. (2020). Este paquete proporciona funciones que cubren todo el flujo de trabajo de SDM, desde la preparación de datos hasta la selección de variables, la optimización y evaluación de los modelos. Echen un vistazo a los artículos en el sitio web del paquete para obtener más detalles y tutoriales: https://consbiol-unibern.github.io/SDMtune/index.html.
Manos a la obra
Cargamos los paquetes de R
Iniciamos GRASS
Usaremos la opción B, es decir, iniciamos GRASS GIS desde R en un proyecto y mapset existentes. Notar las similitudes con gj.init()
de grass.jupyter
y gs.setup.init()
de grass.script
.
%%R
# path to GRASS binaries (run `grass --config path`)
grassbin <- system("grass --config path", intern = TRUE)
# path to GRASS database in GDrive
grassdata <- "/content/drive/MyDrive/curso_grass_2023/grassdata"
# path to project
project <- "posgar2007_4_cba"
# path to mapset
mapset <- "PERMANENT"
# start GRASS GIS from R
initGRASS(gisBase = grassbin,
gisDbase = grassdata,
location = project,
mapset = mapset,
override = TRUE,
remove_GISRC= TRUE)
Exploramos los mapas raster y vectoriales disponibles en el mapset PERMANENT:
Tenemos alguna máscara?
Lo siguiente antes de comenzar es establecer la región computacional.
Datos de presencia/ocurrencia
Para este ejercicio vamos a generar los puntos de ocurrencia del mosquito Aedes aegypti como un sub-conjunto al azar de las localidades (áreas edificadas) de la provincia de Córdoba que estén a una altura de menos de 850 m. Notar que estamos trabajando en la base de datos de GRASS GIS, pero desde R.
%%R
# Extract centroids from built-up area polygons
execGRASS("v.extract",
parameters = list(input="area_edificada_cba",
type="centroid",
output="area_edificada_cba_centroides",
random=250))
# convert centroids to points
execGRASS("v.type",
parameters = list(input="area_edificada_cba_centroides",
output="area_edificada_cba_puntos",
from_type="centroid",
to_type="point"))
# extract elevation data for points
execGRASS("v.what.rast",
parameters = list(map="area_edificada_cba_puntos",
raster="elevation",
column="elevation"))
# filter points by elevation, keep those <= 850m
execGRASS("v.extract",
parameters = list(input="area_edificada_cba_puntos",
where="elevation <= 850",
output="aedes_aegypti"))
Ahora leemos desde GRASS los datos de ocurrencia que generamos, los convertimos en un objeto sf
, y los graficamos.
Podemos usar la función plot()
para visualizar la geometría o la geometría y los atributos (o una selección de los mismos) de los objetos sf
.
Datos de background
El algoritmo MaxEnt que vamos a usar en este ejercicio, requiere contrastar las variables ambientales en los sitios de ocurrencia con el resto del ambiente disponible para la especie, el background. Entonces, necesitamos generar puntos de background para caracterizar este ambiente disponible.
Una opción es generar puntos al azar sobre nuestra área de estudio. No obstante, no es cierto que toda la extensión del área de estudio está disponible para Aedes aegypti. Esta especie de mosquito no cría en aguas abiertas y tampoco sobre las salinas. Entonces, vamos a enmascarar primero esas áreas del ambiente disponible.
Luego, leemos a R la máscara creada para visualizarla
Como la región tiene una resolución de 30 m, vamos a llevarla a 1 km, para asegurarnos una mejor separación de los puntos de background, y volvemos a leer la máscara dentro de R.
Ahora sí, generamos los puntos de background utilizando una función del paquete dismo
(Hijmans et al. 2023) y los convertimos a sf
para luego visualizarlos.
Con qué funcion de GRASS podríamos haber hecho algo similar?
Visualizamos el mapa que usamos como máscara junto con los vectores de presencia y background usando la librería tmap
(Tennekes 2018).
%%R
# Aux data
bbox <- st_bbox(mask)
fig_puntos <-
tm_shape(mask,
bbox = bbox) +
tm_raster(title = "Classes") +
tm_shape(presence) +
tm_dots(size = 0.02) +
tm_layout(main.title = "Aedes aegypti",
main.title.fontface = "italic",
main.title.size = 0.7,
main.title.position = "left",
legend.show = TRUE,
legend.outside = TRUE)
tmap_save(fig_puntos,
filename = "fig_puntos_y_mascara.png",
width = 1000, height = 1300)
Variables ambientales
Antes de leer las variables ambientales que obtuvimos a partir de las series de tiempo de LST y NDVI, vamos a generar dos mapas ráster que representan la distancia a fuentes de agua y rutas y caminos, respectivamente. Para eso, vamos a usar mapas ya disponibles en el mapset PERMANENT.
%%R
# Patch water lines + water bodies
execGRASS("v.patch",
parameters = list(input="lineas_aguas_continentales,areas_aguas_continentales,embalses",
output="lineas_y_cuerpos_de_agua_cba"))
# Convert to raster
execGRASS("v.to.rast",
parameters = list(input="lineas_y_cuerpos_de_agua_cba",
output="lineas_y_cuerpos_de_agua_cba",
use="val"))
# Distance to water and roads
execGRASS("r.grow.distance",
parameters = list(input="lineas_y_cuerpos_de_agua_cba",
distance="distancia_agua"))
%%R
# Patch primary + secondary roads
execGRASS("v.patch",
parameters = list(input="vial_primaria,vial_secundaria",
output="red_vial_cba"))
# Convert to raster
execGRASS("v.to.rast",
parameters = list(input="red_vial_cba",
output="red_vial_cba",
use="val"))
# Distance to roads
execGRASS("r.grow.distance",
parameters = list(input="red_vial_cba",
distance="distancia_caminos"))
Leemos los mapas generados y los visualizamos con plot()
, que en este caso reconoce los objetos SpatRast
.
También podemos usar tmap
y su función tm_facets()
Lectura de datos ráster de otros mapsets
Para leer mapas de otros mapsets, necesitamos agregar esos mapsets a la lista de mapsets accesibles en el mapset donde estamos ahora.
Leemos ahora algunas de las variables que derivamos de las series temporales de LST y NDVI. Primero, necesitamos aplicar la máscara de los límites de la provincia.
Agrupamos todos los rasters de variables ambientales.
Preparación de los datos
Ahora que hemos creado y/o importado los registros de presencia, los puntos de background y las variables predictivas, necesitamos preparar los datos en un formato llamado samples with data (SWD). Éste es básicamente una tabla con coordenadas de presencia y background más los valores correspondientes a las variables predictoras para cada punto.
Definición de parámetros
Aquí definimos algunos de los valores de entrada necesarios para el flujo de trabajo:
Datos de entrenamiento y evaluación
Entrenaremos el modelo con un 80% de muestras de presencia, y dejaremos el 20% restante para la evaluación al final.
Creación de folds para validación cruzada
Como usaremos validación cruzada durante el entrenamiento del modelo, creamos los folds con anticipación. En este caso utilizamos folds aleatorios, pero existen otros métodos de determinarlos. Como estamos limitados por la cantidad de registros de presencia, crearemos solo 4 folds o subconjuntos. El algoritmo utilizará iterativamente 3 subconjuntos para entrenar y 1 para validar, pero siempre dentro del entrenamiento.
Entrenamiento con validación cruzada
Primero entrenaremos un llamado modelo completo, es decir, un modelo con todos los predictores, y de allí eliminaremos aquellos que estén altamente correlacionados y cuya contribución a la predicción no sea importante.
Veamos las predicciones geográficas del modelo completo o full model
Selección de variables
Remover variables altamente correlacionadas
Luego procedemos a eliminar los predictores correlacionados ya que proporcionan información altamente redundante y pueden afectar el rendimiento de los modelos, es decir, como con todos los modelos, queremos que sea simple y del mayor rendimiento posible. Usaremos el área bajo la curva ROC (AUC) como métrica de rendimiento y eliminaremos las variables correlacionadas solo si el AUC disminuye si las mantenemos.
%%R
# Prepare background locations to test correlation
bg_sp <- prepareSWD(species = sp,
a = background_coords,
env = env)
# Remove variables with correlation higher than 0.7
# while accounting for the AUC
vs_sp <- varSel(full_model_sp,
metric = "auc",
bg4cor = bg_sp,
cor_th = cor_th,
permut = perm,
interactive = FALSE)
Exploremos el objeto de salida
Remover variables de menor importancia
Después de descartar las variables correlacionadas, también eliminaremos las variables que tengan una contribución porcentual o una importancia inferior al 10%, considerando como su mantenimiento o remoción afecta al AUC.
Exploremos el objeto resultante
Ahora necesitamos recrear el objeto SWD y los conjuntos de datos de entrenamiento y evaluación, pero solo con las variables seleccionadas, para poder ejecutar el modelo final y hacer predicciones.
%%R
# Get only relevant variables from the reduced model
retained_varnames <- names(reduc_var_sp@models[[1]]@data@data)
# Subset stack
env <- terra::subset(env, retained_varnames)
# SWD with the selected vars
subset_train_sp <- prepareSWD(species = sp,
p = presence_coords,
a = background_coords,
env = env)
c(train_sp, test_sp) %<-%
trainValTest(subset_train_sp,
test = perc_test,
only_presence = TRUE,
seed = seed)
Predicciones con el modelo seleccionado
Ahora entrenamos el modelo final con el conjunto de entrenamiento completo, ya no necesitamos los folds en este punto. Tengan en cuenta que también utilizamos las feature classes (fc) y la regularización (reg) del mejor modelo obtenido anteriormente. En este caso, son solo valores predeterminados, pero si también realizamos una optimización de hiperparámetros, pueden diferir.
Si les interesa conocer opciones de optimización de hiperparámetros en el contexto de los SDM, puedes chequear el siguiente artículo: https://consbiol-unibern.github.io/SDMtune/articles/tune-hyperparameters.html
Hagamos las predicciones en el espacio geográfico y exploremos el resultado
Guardamos la predicción en GRASS
Ahora podemos escribir el ráster con las predicciones del modelo final en la base de datos de GRASS.
Corroboramos que el mapa creado esté allí
Evaluación del modelo
Queremos saber qué tan bueno es nuestro modelo, por eso en este paso usamos el conjunto de datos de evaluación que separamos al principio. Un AUC de 0,5 significaría que el modelo funciona equivalentemente a lanzar una moneda al aire. AUC es lo que llamamos una métrica de evaluación independiente de umbral.
Normalmente el resultado del SDM se convierte en mapas de presencia/ausencia. Para determinar qué umbral utilizar, realizamos evaluaciones dependientes del umbral.
Elegimos un umbral y creamos un mapa binario, i.e., de presencia y ausencia
Importancia de las variables
La importancia de las variables es un indicador de la contribución variable a la predicción.
Curvas de respuesta
Las curvas de respuesta nos dan una idea de la relación entre las variables predictoras y la probabilidad de ocurrencia del evento de interés.
Cerramos el mapset y terminamos :)
Disclaimer
Recordar que éste es sólo un ejemplo sencillo para hacer SDM y sólo el comienzo… Hay: