Note sur ce template Rmarkdown
Ce fichier constitute un gabarit complet pour la modélisation des habitats potentiels d’une espèce ou d’un groupe d’espèce. Il est fourni sous licence libre CC-BY 4.0.
Il a été testé fonctionnel sur la version de R x86_64-w64-mingw32, x86_64, mingw32, ucrt, x86_64, mingw32, , 4, 3.2, 2023, 10, 31, 85441, R, R version 4.3.2 (2023-10-31 ucrt), Eye Holes, avec les packages sf (1.0.15), terra (1.7.71), ggplot2 (3.4.4), scales (1.3.0), egg (0.4.5), virtualspecies (1.6), blockCV (3.1.3), biomod2 (4.2.5), dplyr (1.1.4), tidyterra (0.5.2).
Il est possible que des évolutions futures de packages (notamment, biomod2, qui est sujet à de nombreuses évolutions en 2023 et 2024) rendent certaines parties du fichier non fonctionnelles, ce qui nécessitera de corriger le code.
Pré-requis :
Chargement des packages et fonctions, chargement de données géographiques et des variables environnementales harmonisées
library(sf)
library(terra)
library(ggplot2)
library(scales)
library(egg)
library(virtualspecies)
library(blockCV)
library(biomod2)
library(dplyr)
source("scripts/functions.R")
# Shapefile de la Corse
corse <- st_read("data/corse.gpkg")
## Reading layer `corse' from data source `C:\Rprojects\SDMs_PNA_Corse\data\corse.gpkg' using driver `GPKG'
## Simple feature collection with 1 feature and 8 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 8.534717 ymin: 41.33323 xmax: 9.560364 ymax: 43.02755
## Geodetic CRS: WGS 84
Chargement et préparation des données d’occurrence
## Reading layer `serpentinites' from data source `C:\Rprojects\SDMs_PNA_Corse\data\donnees_brutes\taxa\serpentinites.shp' using driver `ESRI Shapefile'
## Simple feature collection with 1397 features and 69 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -1.363039 ymin: -5.983591 xmax: 9.42844 ymax: 43.0095
## Geodetic CRS: WGS 84
# 123 données problématiques à supprimer et réajouter avec un autre fichier
serpentinites <- serpentinites[-which(serpentinites$x_centroid < 0), ]
serpentinites$species <- simplify_species_name(serpentinites$nom_valide)
serpentinites <- serpentinites[, c("species", "x_centroid", "y_centroid",
"date_fin")]
colnames(serpentinites) <- c("species", "x", "y", "date", "geometry")
# Dates d'échantillonnage
serpentinites$year <- as.numeric(strtrim(serpentinites$date, 4))
serpentinites$month <- as.numeric(substr(serpentinites$date, 6, 7))
serpentinites2 <- st_read("data/donnees_brutes/taxa/CBNC_serpentinite_erregeom1.shp")
## Reading layer `CBNC_serpentinite_erregeom1' from data source
## `C:\Rprojects\SDMs_PNA_Corse\data\donnees_brutes\taxa\CBNC_serpentinite_erregeom1.shp' using driver `ESRI Shapefile'
## Simple feature collection with 123 features and 55 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: 9.210993 ymin: 42.09197 xmax: 9.4205 ymax: 42.87008
## Geodetic CRS: WGS 84
serpentinites2$species <- simplify_species_name(serpentinites2$nom_comple)
serpentinites2 <- serpentinites2[, c("species", "lon_wgs84", "lat_wgs84",
"date_relev")]
colnames(serpentinites2) <- c("species", "x", "y", "date", "geometry")
serpentinites2$year <- as.numeric(substr(serpentinites2$date, 7, 10))
serpentinites2$month <- as.numeric(substr(serpentinites2$date, 4, 5))
serpentinites <- rbind(serpentinites,
serpentinites2)
# Visualisation de la temporalité des occurrences
ggplot(serpentinites) +
geom_boxplot(aes(x = species,
y = year))+
coord_flip() +
scale_y_continuous(breaks = breaks_pretty()) +
theme_minimal()
Filtre temporel
Il faut établir un filtre temporel pour éliminer les données
imprécises, sachant que l’objectif est de modéliser à une résolution
assez fine, de l’ordre de 1km. Le champ precision
est peu
renseigné et donc peu utile ici, il nous faut donc poser une hypothèse
sur les données qui sont imprécises. On peut considérer que les GPS ont
commencé à être largement disponibles à partir de 1990, mais leur
utilisation ne s’est généralisée qu’à partir des années 2000, notamment
grâce à leur miniaturisation. Ainsi, on peut spéculer qu’avant les
années 2000, les données étaient moins précisés car possiblement
géolocalisées en utilisant des référentiels comme les lieu-dits ou les
communes, tandis qu’à partir des années 2000 la précision s’est
améliorée grâce à la géolocalisation par satellite.
Le nombre de données supprimées en fixant un seuil à l’année 2000 est limité :
# Les données avant 2000 représentent un % modéré du jeu de données :
100 * length(which(serpentinites$year < 2000)) / nrow(serpentinites)
## [1] 9.448819
La couverture temporelle sur l’année est variable selon les années :
ggplot(serpentinites) +
geom_boxplot(aes(x = species,
y = month)) +
facet_wrap(~year) +
coord_flip() +
scale_y_continuous(breaks = breaks_pretty()) +
theme_minimal()
L’emprise spatiale des données d’occurrence ne change pas de manière majeure avec ou sans les données pré-2000 :
p_serpentinites_all <- ggplot() +
geom_sf(data = corse) +
geom_sf(data = serpentinites, aes(col = year)) +
scale_color_continuous(type = "viridis") +
theme_minimal(base_size = 15) +
ggtitle("Toutes données\nserpentinites")
p_serpentinites_post2000 <- ggplot() +
geom_sf(data = corse) +
geom_sf(data = serpentinites[serpentinites$year >= 2000, ], aes(col = year)) +
scale_color_continuous(type = "viridis") +
theme_minimal(base_size = 15) +
ggtitle("Données post-2000\nserpentinites")
ggarrange(p_serpentinites_all,
p_serpentinites_post2000,
nrow = 1)
On pose donc l’hypothèse raisonnable qu’un filtre à 2000 va assurer une bonne précision dans la localisation des occurrences sans perdre d’information critique sur la répartition des espèces.
Rasterisation des occurrences
L’objectif ici est de ne garder qu’une occurrence par cellule à la résolution de nos variables environnementales afin d’éviter une forme extrême de pseudo-réplication.
# On rasterise les occurrences à la résolution de nos variables
# environnementales
serpentinites_r <- rasterize(serpentinites,
env_corse)
names(serpentinites_r) <- "serpentinites" # Attention il ne faut pas nommer
# la couche "serpentinites" car il y a des variables qui s'appellent
# serpentinites
plot(serpentinites_r)
On va ensuite éliminer les occurrence qui sont dans des zones sans
valeurs de variables environnementales. Pour cela on va combiner les
variables environnementales avec les occurrences rasterisées dans un
data.frame
, et supprimer les occurrences d’espèces qui
tombent sur des données environnementales manquantes.
On renomme la variable serpentinites pour qu’elle n’ait pas le même nom que le groupe d’espèces :
# On renomme la variable serpentinites pour qu'elle n'ait pas le même nom que
# le groupe d'espèces :
names(env_corse)[names(env_corse) == "serpentinites"] <-
"geol_serpentinites"
# On crée un stack avec nos occurrences rasterisées et les variables env
env_serpentinites <- c(env_corse,
serpentinites_r)
# On récupère les coordonnées XY de toutes les cellules, pour préparer nos
# données finales
coorXY <- xyFromCell(env_corse,
1:ncell(env_corse))
# On transforme le raster en data.frame
env_serpentinites_df <- values(env_serpentinites)
env_serpentinites_df[is.nan(env_serpentinites_df)] <- NA
# On regarde le nombre d'occurrences pour lesquelles il y a des données
# manquantes :
length(which(is.na(env_serpentinites_df[, "bio1"]) &
!is.na(env_serpentinites_df[, "serpentinites"])))
## [1] 3
On va maintenant supprimer les cellules pour lesquelles on n’a pas de données environnementales. Pour cela on va utiliser la première variable environnementale ici, car les données manquantes sont toutes les mêmes entre toutes les variables environnementales (cf. script harmonisation des données).
# On filtre d'abord sur l'objet qui contient les coordonnées
coorXY <- coorXY[-which(is.na(env_serpentinites_df[, 1])), ]
# Et ensuite sur le tableau avec variables env et présences d'espèces
env_serpentinites_df <- env_serpentinites_df[-which(is.na(env_serpentinites_df[, 1])), ]
# Comparaison du nombre d'occurrences :
# Avant rasterisation
nrow(serpentinites)
## [1] 1265
# Après rasterisation et élimination des données env manquantes
length(which(env_serpentinites_df[, "serpentinites"] == 1))
## [1] 95
Il s’agit donc du nombre d’occurrences que l’on va pouvoir utiliser pour calibrer nos modèles. Il n’y a que 95` occurrences donc on va limiter le nombre de variables utilisées pour les modèles.
On va maintenant formater ces occurrences en combinant coordonnées et
info sur l’occurrence dans un data.frame
pour préparer la
calibration de nos modèles
P_points <- data.frame(
# D'abord on récupère les coordonnées XY qui correspondent à nos cellules de présences
coorXY[which(!is.na(env_serpentinites_df[, "serpentinites"])), ],
# Ensuite, on récupère la colonne qui indique présence pour chaque cellule
occurrence = env_serpentinites_df[which(!is.na(env_serpentinites_df[, "serpentinites"])),
"serpentinites"])
P_points
Génération des points de background
La littérature statistique récente suggère que les meilleures pratiques consistent à générer un grand nombre de points de background (e.g., 10000) indépendamment de la localisation des points de présence (i.e., un point de background peut être localisé au même endroit qu’un point de présence). Cela permet d’assurer une bonne représentation de l’ensemble des conditions environnementales disponibles dans le modèle. Dans le cas de la Corse, le nombre de points de background sera limité par le nombre de pixels disponibles :
# Nous avons éliminé les données manquantes du tableau env_amphib_df
# Par conséquent, son nombre de lignes est égal au nombre total de pixels
# disponibles sur la Corse
nrow(env_serpentinites_df)
## [1] 13620
Ainsi, nous fixerons le nombre de background par défaut à 10000 ce qui sera suffisant pour une bonne calibration des modèles. Il n’est pas nécessaire de faire plusieurs répétitions, car le nombre de points de background est déjà suffisamment élevé, les résultats de calibration ne varieraient pas entre différentes répétitions.
On intègre le biais d’échantillonnage directement dans la génération des backgrounds avec la méthode du biais du groupe cible (target-group sampling bias).
# On réduit également le nombre de background pour avoir un effet du biais
background <- spatSample(env_corse[["occ_density_cbnc"]],
method = "weights",
size = 5000,
replace = FALSE, # Pas de remise
na.rm = TRUE, # Pas dans les données manquantes
xy = TRUE, # L'output inclut les coords XY
values = FALSE) # L'output exclut les variables
# background <- spatSample(env_corse,
# size = 10000,
# replace = FALSE, # Pas de remise
# na.rm = TRUE, # Pas dans les données manquantes
# xy = TRUE, # L'output inclut les coords XY
# values = FALSE) # L'output exclut les variables
# On ajoute les points de background aux données de présence
P_points <- rbind.data.frame(P_points,
data.frame(background,
occurrence = 0))
Sélection des variables environnementales
Climat
La littérature sur les variables prédictives à utiliser dans les modèles d’habitat pour les plantes est bien établie, et il est essentiel d’utiliser un ensemble de prédicteurs reflétant trois grandes catégories de facteurs climatiques (Mod et al. 2016) : la température, l’eau et le rayonnement solaire. Il est important d’utiliser des variables qui agissent directement sur les plantes (e.g., eau disponible pour les plantes) plutôt que des variables dont l’effet est indirect (e.g., régime de précipitations).
En ce qui concerne la température, les variables reflétant la saisonalité sont préférables aux variables moyennées sur l’année - ainsi nous explorerons l’effet de variables liées à la température reflétant les minimums et maximums annuels, ainsi que les degrés-jours de la saison de croissane des plantes.
En ce qui concerne l’eau, la disponibilité en eau pour les plantes dépend des précipitations, de l’évaporation, et de la nature du substrat, ce qui rend difficile l’obtention d’une variable reflétant réellement la disponibilité en eau. Néanmoins, il existe désormais l’indice d’humidité climatique dans les données CHELSA (Karger et al. 2017), qui reflètent les précipitations moins l’évapotranspiration, et est donc considérée comme une variable bien plus précise de l’eau disponible pour les plantes (Mod et al. 2016) ; nous utiliserons donc cette variable.
Enfin, en ce qui concerne le rayonnement solaire, il est préférable d’utiliser directement le rayonnement solaire plutôt que la pente ou l’exposition ; cependant il faudrait également prendre en compte les effets de la couverture nuageuse et de la saisonalité du rayonnement solaire. Nous utiliserons donc les variables de rayonnement solaire reflétant la saisonnalité sur l’année, et, si possible, la couverture nuageuse (actuellement indisponible, mais devrait être disponible dans CHELSA prochainement).
Noms des variables retenues :
températures les plus chaudes (bio5) et les plus froides de l’année (bio6)
nombre de jours de la saison de croissance (>10°C) (ngd5)
humidité relative minimale (cmi_min) et maximale (cmi_max)
rayonnement solaire mensuel moyen (rsds_mean)
Occupation du sol
La nature du substrat est également une catégorie de facteur indispensable à prendre en compte pour les plantes, mais les données sont souvent parcellaires. Pour ce groupe d’espèces, il existe une hypothèse forte indiquant qu’elles sont inféodées aux zones géologiques de type serpentinites, aussi ce facteur sera inclus comme hypothèse principale expliquant la répartition de ces espèces. Nous explorerons également l’effet de l’altération d’habitat sur ces espèces, avec l’hypothèse que les zones cultivées ou artificielles ont un effet négatif. Pour cela, nous utiliserons le degré d’intégrité biophysique.
Par ailleurs, il s’agit ici d’espèces inféodées aux milieux ouverts naturels, et donc nous utiliserons la distribution spatiale des milieux ouverts (CORINE Land Cover 332 et 333) pour expliquer la répartition des espèces du groupe.
Noms des variables retenues :
couche géologique des serpentinites (geol_serpentinites)
degré d’intégrité biophysique (integrite)
milieux ouverts (zones_ouvertes)
Biais d’échantillonnage
Pour prendre en compte le biais potentiel d’échantillonnage, nous utiliserons la probabilité d’échantillonnage basée sur la méthode dite du “biais d’échantillonnage du groupe cible”. Cette méthode consiste à utiliser toutes les occurrences du groupe cible dans la zone d’étude pour spatialiser le biais d’échantillonnage, en utilisant une fonction kernel bi-dimensionnelle. Le CBNC nous a fourni les occurrences de toutes les plantes de Corse, ce qui nous a permis de construire cette variable de biais d’échantillonnage. Elle sera utilisée pour simuler les points de background avec le même biais d’échantillonnage.
Variables anthropogéniques
Il n’y a pas d’hypothèse forte sur la sensibilité de ces espèces aux perturbations anthropiques (hors hypothèse sur l’intégrité biophysique).
Autres variables et commentaires
Les perturbations dûes au gel hivernal peuvent expliquer les limites supérieures de la végétation. Ainsi, nous testerons si la couverture neigeuse annuelle (nombre de jours sous la neige) semble avoir un effet négatif sur la probabilité d’observer ce groupe.
Noms des variables retenues :
- Nombre de jours sous la neige (scd)
Constitution du jeu de variables finales pour les plantes des serpentinites
Préparation des rasters
Etude de la colinéarité et réduction du nombre de variables
On étudie la colinéarité entre les variables avec le coefficient de corrélation de Spearman (car certaines variables ne sont pas distribuées normalement), en utilisant un seuil standard de 0.7.
var_groups <- removeCollinearity(env_serpentinites,
plot = TRUE,
multicollinearity.cutoff = 0.7,
method = "spearman")
Seules deux variables sont corrélées fortement : bio6 (température minimale annuelle) et ngd5 (nombre de degrés-jours lors de la saison de croissance). Nous conserverons seulement ngd5 ici car cette variable est conçue spécifiquement pour refléter un effet direct sur le développement des plantes.
Au total il y a 9 variables environnementales, pour environ 90 occurrences, ce qui est acceptable.
Préparation de la stratégie de validation croisée des modèles
Nous allons utiliser une procédure de validation croisée par bloc ce qui permet de réduire l’autocorrélation spatiale entre jeu de données de calibration et jeu de validation.
Définition de la taille des blocs
Il faut étudier le degré d’autocorrélation spatiale dans les variables environnementales pour avoir une idée de la taille des blocs. La taille des blocs est un compromis entre la diminution de l’autocorrélation spatiale et les contraintes des données.
# Pour étudier la taille des blocs à viser, il faut d'abord projeter le raster
env_serpentinites_l93 <- project(env_serpentinites,
"EPSG:2154")
# Ensuite on étudie le range d'autocorrélation spatiale
AC_range <- cv_spatial_autocor(env_serpentinites_l93)
##
|
| | 0%
|
|============== | 11%
|
|============================ | 22%
|
|========================================== | 33%
|
|======================================================== | 44%
|
|====================================================================== | 56%
|
|==================================================================================== | 67%
|
|================================================================================================== | 78%
|
|================================================================================================================ | 89%
|
|==============================================================================================================================| 100%
On obtient un range médian qui est de 1.2985219^{4}, ce qui est satisfaisant ici pour réaliser une validation croisée par blocs : il y a beaucoup de blocs, ce qui signifie que la répartition des blocs en plis sera probablement bien équilibrée.
P_points_sf <- st_as_sf(P_points,
coords = c("x", "y"),
crs = "EPSG:4326")
plis_cv <- cv_spatial(x = P_points_sf,
column = "occurrence", # Nom de la colonne des occurrences
k = 5, # Nombre de plis (folds) pour la k-fold CV
size = AC_range$range, # Taille des blocs en metres
selection = "random", # Attribution des blocs aléatoire dans
# les plis
iteration = 50, # Nombre d'essais pour trouver des plis
# équilibrés
biomod2 = TRUE, # Formater les données pour biomod2
r = env_serpentinites, # Pour le fond de carte
progress = FALSE)
##
## train_0 train_1 test_0 test_1
## 1 4057 66 943 29
## 2 3976 84 1024 11
## 3 4003 79 997 16
## 4 3878 81 1122 14
## 5 4086 70 914 25
On voit que nos plis sont plutôt bien équilibrés, avec environ 70-80 présences en moyenne pour la calibration, et de environ 10-20 présences pour l’évaluation, ce qui est faible et rend les évaluations peu fiables.
Dernière étape, biomod2 exige un format particulier pour les plis de validation croisée, donc on va préparer ce format ici :
Calibration des modèles
Tout d’abord on prépare les données pour biomod2.
coorxy <- P_points[, c("x", "y")]
occurrences <- P_points[, "occurrence"]
dir.create("models/serpentinites", recursive = T, showWarnings = FALSE)
run_data <- BIOMOD_FormatingData(
resp.name = "serpentinites", # Nom de l'espèce
resp.var = occurrences, # Présences + background
expl.var = env_serpentinites, # Variables environnementales prédictives
dir.name = "models", # Dossier dans lequel on va stocker les modèles
resp.xy = coorxy, # Coordonnées xy des présences et background
PA.strategy = NULL) # Pas de génération de points de background par biomod
##
## -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= serpentinites Data Formating -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
##
## ! No data has been set aside for modeling evaluation
## ! No data has been set aside for modeling evaluation
## !!! Some data are located in the same raster cell.
## Please set `filter.raster = TRUE` if you want an automatic filtering.
## ! No data has been set aside for modeling evaluation
## -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Done -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Car on en a généré nous-mêmes
saveRDS(run_data, file = paste0("models/serpentinites/run_data.RDS"))
Biomod nous indique deux choses : que nous n’avons pas de données indépendantes pour l’évaluation, ce qui est effectivement le cas à ce stade de l’étude. Par ailleurs, que plusieurs données peuvent être dans la même cellule, ce qui est également attendu car nous avons tiré aléatoirement nos background dans toute la zone d’étude et donc ils ont pu tomber dans les mêmes cellules que des points de présence. Pas d’inquiétudes, c’est ce que l’on avait prévu.
On va pouvoir désormais préparer la calibration des modèles, en les paramétrant de manière correcte. Ce qui est important de savoir ici c’est que nos modèles vont avoir deux grosses difficultés statistiques :
déséquilibre des classes : il y a au total 95 présences et 10000 backgrounds (qui seront considérés comme des valeurs de 0 par les modèles), ce qui crée un gros déséquilibre entre les 1 et les 0. C’est ce qu’on appelle le déséquilibre des classes
chevauchement des classes : il est probable que les présences et les backgrounds se chevauchent sur les gradients de variables environnementales (d’autant plus que nous pouvons avoir parfoit une présence et un background dans le même pixel), ce qui rend la distinction entre les 1 et les 0 difficile pour les modèles. C’est ce qu’on appelle le chevauchement des classes
La solution pour bien paramétrer les modèles face au déséquilibre et au chevauchement varie selon les modèles, mais le principe général est de réduire l’importance des backgrounds lors de la calibration par rapport au présence, afin de viser un ratio équilibre 50/50 entre importance des présences et importance des backgrounds. Par exemple, on va attribuer des poids aux présences et aux backgrounds de sorte que la somme du poids des présences et des backgrounds soit égale. Cependant, cette méthode fonctionne mal sur certains modèles comme le random forest, et il faut alors le paramétrer de manière plus fine avec un rééchantillonnage à 50/50 en interne.
Par ailleurs, il est important de noter que l’évaluation des modèles avec la validation croisée n’est pas un élément validant la robustesse du modèle. Elle est plutôt à considérer comme un élément qui élimine les mauvais modèles, mais elle ne constitute pas une preuve de robustesse quand elle est bonne, car elle est limitée à la fois par la nature des données (présence-seule, pas d’absences), et par la possibilité qu’il y ait des biais dans l’échantillonnage. Ainsi, il est difficile d’utiliser la validation croisée pour identifier les meilleurs modèles ; il vaut mieux donc se baser sur des paramètres établis pour être robustes en situation de présence-seule (e.g., Valavi et al. 2021).
Préparons donc la calibration de nos modèles :
calib_summary <-
summary(run_data, calib.lines = table_cv) %>%
filter(dataset == "calibration")
iwp <- (10^6)^(1 - occurrences)
RF_param_list <- NULL
GLM_param_list <- NULL
GBM_param_list <- NULL
XGBOOST_param_list <- NULL
XGBOOST_param_list <- NULL
GAM_param_list <- NULL
MARS_param_list <- NULL
XGBOOST_param_list <- NULL
for (cvrun in 1:nrow(calib_summary)) {
prNum <- calib_summary$Presences[cvrun]
bgNum <- calib_summary$True_Absences[cvrun]
wt <- ifelse(occurrences == 1, 1, prNum / bgNum)
RF_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(ntree = 1000,
sampsize = c("0" = prNum,
"1" = prNum),
replace = TRUE)
GLM_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(weights = wt)
GBM_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(interaction.depth = 5,
n.trees = 5000,
shrinkage = 0.001,
bag.fraction = 0.75,
cv.folds = 5,
weights = wt)
GAM_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(weights = wt,
method = "REML")
MARS_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(weights = wt)
XGBOOST_param_list[[paste0("_",
calib_summary$PA[[cvrun]],
"_",
calib_summary$run[[cvrun]])]] <-
list(nrounds = 10000,
eta = 0.001,
max_depth = 5,
subsample = 0.75,
gamma = 0,
colsample_bytree = 0.8,
min_child_weight = 1,
weight = wt,
verbose = 0)
}
model_parameters <- bm_ModelingOptions(
data.type = "binary",
models = c("GLM", "GBM", "GAM.mgcv.gam", "MARS", "RF", "MAXNET", "XGBOOST"),
strategy = "user.defined",
user.base = "default",
user.val = list(
GLM.binary.stats.glm = GLM_param_list,
GBM.binary.gbm.gbm = GBM_param_list,
GAM.binary.mgcv.gam = GAM_param_list,
MARS.binary.earth.earth = MARS_param_list,
RF.binary.randomForest.randomForest = RF_param_list,
XGBOOST.binary.xgboost.xgboost = XGBOOST_param_list
),
bm.format = run_data,
calib.lines = table_cv
)
##
## -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Build Modeling Options -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
##
## > GLM options (datatype: binary , package: stats , function: glm )...
## > GBM options (datatype: binary , package: gbm , function: gbm )...
## > GAM options (datatype: binary , package: mgcv , function: gam )...
## > MARS options (datatype: binary , package: earth , function: earth )...
## > RF options (datatype: binary , package: randomForest , function: randomForest )...
## > MAXNET options (datatype: binary , package: maxnet , function: maxnet )...
## > XGBOOST options (datatype: binary , package: xgboost , function: xgboost )...
##
## -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Done -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
model_runs <- BIOMOD_Modeling(
run_data,
modeling.id = "1", # ID de modélisation, on met 1 pour tous nos modèles ici
models = c("GLM", "GBM", "GAM", "MARS", # MARS éliminé ici car il plante
"MAXNET", "RF", "XGBOOST"), # idem pour MAXNET
OPT.strategy = "user.defined",
OPT.user = model_parameters, # Paramètres des modèles
CV.strategy = "user.defined", # Méthode de validation croisée
CV.user.table = table_cv, # Plis générés précéemment
CV.do.full.models = FALSE,
var.import = 10, # Nombre de répétitions d'importance des variables
metric.eval = "BOYCE",
do.progress = FALSE,
nb.cpu = 16 # Nombre de coeurs à utiliser pour la modélisation
# A ajuster selon votre ordinateur, ne pas en mettre trop !
)
saveRDS(model_runs, file = "models/serpentinites/model_runs.RDS")
Evaluation des modèles
Importance des variables
varimp <- get_variables_importance(model_runs)
varimp$expl.var <- reorder(varimp$expl.var,
varimp$var.imp,
median,
na.rm = TRUE)
library(dplyr)
varimp %>%
group_by(expl.var) %>%
summarise(median = median(var.imp))
Courbes de réponse
# Variables utilisées pour la calibration
cur_vars <- model_runs@expl.var.names
# Calcul des courbes de réponse
resp <- bm_PlotResponseCurves(bm.out = model_runs,
fixed.var = "mean",
data_species = occurrences,
do.plot = FALSE,
do.progress = FALSE)$tab
## No id variables; using all as measure variables
colnames(resp) <- c("Index", "Variable", "Var.value", "Model", "Response")
for (model in c("GLM", "GBM", "GAM", "MARS", "MAXNET",
"RF", "XGBOOST")) {
p <- ggplot(resp[grep(model, resp$Model), ], aes(x = Var.value, y = Response)) +
geom_line(alpha = 0.2, aes(group = Model)) +
stat_smooth() +
facet_wrap(~ Variable, scales = "free_x") +
theme_bw() +
ylim(0, 1.1) +
xlab("Variable value") +
ggtitle(model)
print(p)
}
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Cartes
# On ne va garder que les modèles qui ont un indice de Boyce suffisamment élevé
models_to_proj <- evals_boyce$full.name[which(evals_boyce$validation >= 0.5)]
projection_runs <- BIOMOD_Projection(
bm.mod = model_runs, # Modèles calibrés
proj.name = "corse", # Nom de la projection actuelle
new.env = env_serpentinites, # Données environnementales sur lesquelles on projette les modèles
models.chosen = models_to_proj, # Modèles à projeter
build.clamping.mask = TRUE, # Le clamping mask illustre les zones où les prédictions sont en dehors des valeurs
# utilisées lors de la calibration
nb.cpu = 4)
cartes_individuelles <- rast("models/serpentinites/proj_corse/proj_corse_serpentinites.tif")
# Rescaling des projections qui dépassent l'intervalle 0 - 1000
cartes_individuelles[cartes_individuelles < 0] <- 0
cartes_individuelles[cartes_individuelles > 1000] <- 1000
for(i in 1:ceiling(nlyr(cartes_individuelles) / 2)) {
plot(cartes_individuelles[[(i * 2 - 1):
min(nlyr(cartes_individuelles),
(i * 2))]],
col = viridis::inferno(12))
}
Carte finale
carte_finale <- mean(cartes_individuelles)
plot(carte_finale,
col = viridis::inferno(12))
points(y ~ x, data = P_points[which(P_points$occurrence == 1), ],
pch = 16, cex = .4,
col = "#21908CFF")
carte_incertitude <- app(cartes_individuelles, sd)
ggplot() +
geom_spatraster(data = carte_incertitude) +
scale_fill_continuous(type = "viridis") +
ggtitle("Incertitude\n(écart-type des probabilités)")
Carte de rendu, essai 1
favorabilite_presences <- extract(carte_finale,
P_points[which(P_points$occurrence == 1),
c("x", "y")],
ID = FALSE)
boxplot(favorabilite_presences)
qt_favorabilite <- quantile(favorabilite_presences$mean, probs = c(.10, .25))
carte_indice <- carte_finale
carte_indice[carte_finale < qt_favorabilite["10%"]] <- 0
carte_indice[carte_finale >= qt_favorabilite["10%"] &
carte_finale < qt_favorabilite["25%"]] <- 1
carte_indice[carte_finale >= qt_favorabilite["25%"]] <- 2
carte_indice <- as.factor(carte_indice)
ggplot() +
geom_spatraster(data = carte_indice) +
scale_fill_manual(values = viridis::plasma(3),
name = paste0("Favorabilité\n(% du total d'occurrences\n",
"observé dans cette classe\n",
"de favorabilité)"),
labels = c("Faible ou méconnue (< 10%)",
"Intermédiaire (10-25%)",
"Elevée (75%)"),
na.translate = F)
Carte de rendu, essai 2
# Exploration d'un indice de densité de présences par classe de favorabilité
fav <- values(carte_finale, data.frame = TRUE, na.rm = TRUE)
species_fav <- extract(carte_finale,
P_points[which(P_points$occurrence == 1),
c("x", "y")],
ID = FALSE)
step = 1
range = 50
res <- NULL
for (cur.int in seq(0, 950, by = step)) {
cur.int <- c(cur.int, cur.int + range)
nb_cells <- length(which(fav[, 1] > cur.int[1] & fav[, 1] <= cur.int[2]))
nb_occupied_cells <- length(which(species_fav[, 1] > cur.int[1] &
species_fav[, 1] <= cur.int[2]))
res <- rbind(res,
data.frame(low = cur.int[1],
high = cur.int[2],
nb_occupied_cells = nb_occupied_cells,
nb_cells = nb_cells,
ratio = nb_occupied_cells / nb_cells))
}
res$ratio_presences <- res$nb_occupied_cells / sum(res$nb_occupied_cells)
intermed_cutoff <- res[min(which(res$ratio > 0.05)), ]
high_cutoff <- res[min(which(res$ratio > 0.2)), ]
plot(res$ratio ~ res$high)
abline(v = intermed_cutoff$low)
abline(v = high_cutoff$low)
carte_indice <- carte_finale
carte_indice[carte_finale < intermed_cutoff$low] <- 0
carte_indice[carte_finale >= intermed_cutoff$low &
carte_finale < high_cutoff$low] <- 1
carte_indice[carte_finale >= high_cutoff$low] <- 2
carte_indice <- as.factor(carte_indice)
ggplot() +
geom_spatraster(data = carte_indice) +
scale_fill_manual(values = viridis::plasma(3),
name = "Favorabilité\n(% de cellules occupées)",
labels = c("Faible ou méconnue (<5%)",
"Intermédiaire (5-25%)",
"Elevée (>25%)"),
na.translate = F)