---
title: "Notebook 3 : Enveloppes de convenance pour la sélection des espèces d'arbres"
subtitle: "Combiner des données d'occurrence d'arbres et des couches climatiques pour éclairer les décisions de plantation"
format:
  html:
    toc: true
    toc-depth: 3
    number-sections: true
    code-fold: true
    code-tools: true
    embed-resources: true
    theme: cosmo
execute:
  echo: true
  warning: false
  message: false
  cache: true
editor:
  markdown:
    wrap: 72
---

*Une fois terminé, n'hésitez pas à partager vos résultats et des
questions sur le groupe WhatsApp K4GGWA ou avec l'équipe ICRAF par
e-mail !*

## Aperçu général

L'objectif de ce notebook 3 est de donner un exemple simple et pratique
de la façon dont R peut appuyer la prise de décision à partir de données
spatiales et de sciences citoyennes. Dans ce notebook, nous utilisons
des données d'occurrence d'arbres provenant de GBIF
(<https://www.gbif.org>) et les données BIOCLIM comme variables
prédictives (<https://www.worldclim.org/data/bioclim.html>). L'objectif
est de démontrer comment R peut appuyer une prise de décision pratique
pour le choix des espèces à planter, en construisant une **enveloppe de
convenance** simple. Dans cet exemple, nous utilisons quatre espèces
d'arbres courantes, pertinentes pour des projets de restauration au
Sénégal.

Notez que toutes les données utilisées dans cet exemple peuvent être
récupérées directement dans R, sans passer par d'autres plateformes ni
téléchargements manuels. Notez également qu'il s'agit d'une version TRÈS
simplifiée de la cartographie de convenance des espèces d'arbres, et
qu'elle n'est pas suffisamment solide sur le plan écologique pour une
prise de décision réellement informée. Les étapes permettant d'améliorer
ce notebook et de rendre la cartographie de convenance plus précise sur
le plan écologique se trouvent plus bas. Toutefois, l'objectif de ce
notebook est de donner une première idée de la façon dont des données
publiquement disponibles peuvent être utilisées pour créer des workflows
simples de convenance des espèces d'arbres, pouvant appuyer la prise de
décision.

**Entrées :** données d'occurrence d'arbres GBIF (& si disponibles :
LDSF/application Regreening), variables BIOCLIM

**Sorties :** `species_envelope_table.csv` (utilisable pour des
visualisations QGIS) & cartes de convenance par espèce

[**Le concept de la cartographie de convenance de l'habitat des
arbres**]{.underline}

Plutôt que de s'appuyer sur des valeurs issues de la littérature pour ce
qu'une espèce peut tolérer (ce qui est important, mais aussi très
chronophage), nous dérivons la plage de tolérance de façon **empirique**
: nous regardons où l'espèce a réellement été enregistrée (collectée via
des applications de sciences citoyennes comme iNaturalist, l'application
Regreening, etc. -\> rassemblées dans GBIF), et nous relevons la plage
des conditions climatiques présentes à ces points. Nous utiliserons
uniquement les variables BIOCLIM (température, précipitations), mais
notez que d'autres données prédictives (sol, couvert arboré, occupation
du sol), ainsi que le STAC (SpatioTemporal Asset Catalog) présenté lors
du travail de groupe, peuvent également être utiles ici. La plage de ces
variables devient l'*enveloppe de convenance* de l'espèce, que nous
projetons ensuite sur l'ensemble du pays pour voir où ces conditions
sont également réunies et où l'espèce pourrait potentiellement exister
(SIMPLIFIÉ !). Cela peut appuyer une prise de décision informée pour
sélectionner la bonne espèce d'arbre pour certaines zones.

[**Comment cela peut influencer la prise de décision**]{.underline}

Dans un contexte opérationnel, ce workflow s'inscrirait aux côtés d'un
travail de vérification sur le terrain : un décideur peut utiliser la
carte de convenance pour présélectionner des espèces candidates pour un
site/une zone donnée, puis valider cette présélection à l'aide des
connaissances locales, d'échantillonnages de sol, et des priorités des
agriculteurs/communautés (fourrage, valeur marchande, statut indigène)
avant de s'engager dans une décision de plantation. Il est donc
important de noter que la carte réduit l'espace de recherche et vient
compléter l'information, mais ne remplace pas les connaissances locales.

### Objectifs d'apprentissage

À la fin de ce notebook, vous serez capable de :

- télécharger des enregistrements d'occurrence d'espèces directement
  depuis GBIF avec `rgbif` et récupérer des données climatiques globales
  (BIOCLIM) via `geodata`

- extraire des valeurs environnementales aux points d'occurrence avec
  `terra::extract()`

- construire un tableau d'enveloppe de convenance empirique par espèce

- reclasser des rasters en masques adapté/non-adapté et les visualiser

- comprendre les possibilités et les limites de cette méthode

- être capable d'étendre ce workflow avec des raffinements plus précis

  - *combiner des enregistrements d'occurrence provenant de plusieurs
    sources (GBIF + vos propres données de terrain (application
    Regreening/LDSF)) en un seul tableau propre*, avec une stratégie
    d'échantillonnage aléatoire

  - récupérer des données globales via un catalogue STAC

  - ajouter des variables prédictives plus diversifiées

### Espèces utilisées dans ce notebook

| Espèce d'arbre courante au Sénégal | Usage |
|--------------------------|----------------------------------------------|
| *Faidherbia albida* | Arbre de parc fixateur d'azote, phénologie inversée, largement conservé dans les champs cultivés |
| *Senegalia senegal* (syn. *Acacia senegal*) | Production de gomme arabique, restauration en zone aride |
| *Eucalyptus camaldulensis* | Espèce plantée (non indigène) à croissance rapide, courante |
| *Tamarindus indica* | Fournit une ombre dense, crée de la matière organique de qualité, et produit des gousses comestibles riches en nutriments utilisées dans la cuisine et les boissons locales |

Notez qu'il s'agit d'une sélection d'espèces subjective. N'importe
quelle autre espèce peut être utilisée tout au long du notebook.

## Mise en place

```{r}
#| label: setup
#| cache: false

# Packages utilisés dans ce notebook
# install.packages(c("rgbif", "geodata", "rstac", "terra", "tidyterra",   "dplyr", "ggplot2", "here", "rnaturalearth"))

library(rgbif)
library(geodata)
library(rstac)
library(terra)
library(tidyterra)
library(dplyr)
library(ggplot2)
library(here)
library(rnaturalearth)

# Réutilise la même structure de dossier de projet que les Notebooks 1-2
# (senegal_workshop_2026 -> notebooks, resultats, donnees)
```

------------------------------------------------------------------------

## Étape 1 : Définir la liste des espèces

Note :

- Vous pouvez remplacer ou ajouter des noms d'espèces selon votre
  intérêt.

- Il est important d'utiliser l'orthographe exacte telle qu'elle
  apparaît dans GBIF (nom canonique GBIF). En cas de doute, vous pouvez
  vérifier le nom directement dans la base GBIF
  (<https://www.gbif.org>).

```{r}
#| label: species-list

species_list <- c(
  "Faidherbia albida",
  "Senegalia senegal",       # synonyme d'Acacia senegal
  "Tamarindus indica",     
  "Eucalyptus camaldulensis")
```

------------------------------------------------------------------------

## Étape 2 : Télécharger les données d'occurrence depuis GBIF

Nous allons utiliser le package `rgbif`, qui permet d'interroger GBIF
directement depuis R. Nous restreignons à la zone du Sénégal
(`country = "SN"`) et exigeons la présence de coordonnées. La fonction
`occ_data()` (`rgbif`) envoie la requête réelle aux serveurs de GBIF.

```{r}
#| label: gbif-download
# construction de notre propre fonction utilitaire pour télécharger et nettoyer une espèce à la fois
# permet d'éviter de refaire la même chose pour les 4 espèces (boucle) --> species_name est le paramètre de la fonction, qu'on peut voir comme un espace réservé représentant une seule espèce à la fois

get_gbif_occurrences <- function(species_name, country_code = "SN") {
  trees <- occ_data( 
    scientificName = species_name, # species_list
    country        = country_code, # SN
    hasCoordinate  = TRUE) # sans coordonnées, les données ne sont pas utiles pour cet usage 
  
  if (is.null(trees$data) || nrow(trees$data) == 0) { # inclure if(is.null) au cas où une espèce n'a aucun enregistrement, pour renvoyer NULL -> sinon la boucle échouerait
    warning(paste("Aucun enregistrement GBIF trouvé", species_name))
    return(NULL)}

  trees$data %>%
    transmute( # transmute() fonctionne comme mutate() mais supprime toutes les colonnes non explicitement listées
      species = species_name,
      lon     = decimalLongitude,
      lat     = decimalLatitude,
      source  = "GBIF",
      coordinateUncertaintyInMeters
    ) %>%
    # on supprime les lignes sans coordonnées et celles dont l'incertitude GPS connue dépasse 100m 
    filter(
      !is.na(lon), !is.na(lat),
      is.na(coordinateUncertaintyInMeters) | coordinateUncertaintyInMeters <= 100
    ) %>%
    select(-coordinateUncertaintyInMeters)} # on retire cette colonne, qui n'était qu'un outil temporaire 


# nouvel objet contenant tous les points d'occurrence 
gbif_occurrences <- purrr::map_dfr(species_list, get_gbif_occurrences)
# map_dfr() appelle get_gbif_occurrences() une fois par élément de species_list
# puis combine le tout en un seul tableau 

# sauvegarde d'un csv avec ces données 
write.csv(
  gbif_occurrences,
  here("donnees", "occurrences", "gbif_occurrences_raw.csv"),
  row.names = FALSE)

head(gbif_occurrences)
```

Maintenant, en inspectant les données téléchargées, il est important de
vérifier combien d'enregistrements d'occurrence sont réellement
disponibles par espèce (n ?).

- Si une espèce a trop peu d'occurrences (comme Eucalyptus camaldulensis
  dans ce cas), cette espèce ne devrait pas être utilisée dans la suite
  du workflow, car trop peu d'occurrences ne constituent pas un ensemble
  représentatif de la répartition possible de l'espèce.
- *Notez que dans ce notebook, nous continuons avec toutes les espèces
  afin de montrer, en fin de parcours, qu'utiliser trop peu
  d'enregistrements d'espèce ne donnera pas un résultat fiable sur le
  plan écologique.*

[Notez les **limites** liées à l'utilisation des données GBIF (limites
supplémentaires plus bas) :]{.underline}

- Une espèce ayant peu d'enregistrements dans GBIF ne dit rien de sa
  réelle répartition.

- Les enregistrements se concentrent le long des zones facilement
  accessibles (routes, villes, stations de recherche, aires protégées)

  - Les applications de sciences citoyennes (iNaturalist, etc.)
    favorisent les personnes ayant accès à un smartphone/internet et les
    usagers urbains ou péri-urbains. Les communautés rurales et
    éloignées, ou les zones difficiles d'accès, sont structurellement
    sous-représentées dans les données.

- Les enregistrements plantés vs. sauvages ne sont pas distingués

- Risque d'erreur d'identification taxonomique

En résumé : les données d'occurrence GBIF indiquent où l'espèce *a été
trouvée et signalée*, ce qui est un mélange de convenance écologique,
d'effort d'échantillonnage humain, et d'accessibilité, et non une mesure
directe de la seule convenance écologique. C'est pourquoi l'utilisation
de données issues d'un plan d'échantillonnage aléatoire (comme le LDSF)
est bien plus défendable.

------------------------------------------------------------------------

## Étape 3a : Ajouter vos propres données : application Regreening/LDSF/autres données de terrain

Si vous en disposez, vous pouvez charger ici vos données **LDSF**
existantes ou des données d'occurrence d'arbres collectées via
l'**application Regreening**, et les ramener à la même structure à trois
colonnes (`species`, `lon`, `lat`, `source`) afin de pouvoir les
combiner avec les données GBIF.

Ce bloc de code n'est qu'une démonstration de ce à quoi pourrait
ressembler le chargement de vos propres données (Regreening/LDSF).

```{r}
### LDSF ####
# Charger les données dans R : remplacez par le chemin vers votre propre jeu de données LDSF/terrain
# ldsf_raw <- read.csv("votre chemin")

# Ajustez les noms de colonnes ci-dessous pour correspondre à votre fichier réel
# ldsf_occurrences <- ldsf_raw %>% transmute(
#    species = species_clean,     
 #   lon     = longitude,         
  #  lat     = latitude,          
   # source  = "LDSF"
#  ) %>%
 # filter(species %in% species_list) # ceci ne conserve que les espèces sélectionnées ci-dessus !

# glimpse(ldsf_occurrences)
```

```{r}
### Données de l'application Regreening ### 
# téléchargez les données depuis le DRS comme montré durant l'atelier 

# Charger les données dans R : remplacez par le chemin vers votre propre jeu de données Regreening
# regreening_raw <- read.csv("votre chemin")

# Ajustez les noms de colonnes ci-dessous pour correspondre à votre fichier réel
# regreenbing_occurrences <- regreening_raw %>% transmute(
#    species = species_clean,     
#   lon     = longitude,         
#  lat     = latitude,          
# source  = "LDSF"
#  ) %>%
# filter(species %in% species_list) # ceci ne conserve que les espèces sélectionnées ci-dessus !

# glimpse(regreening_occurrences)
```

------------------------------------------------------------------------

## Étape 3b : Combiner toutes les sources d'occurrence

Si vous disposez de plusieurs sources de données, vous pouvez également
les charger au format csv. Assurez-vous que les colonnes portent
exactement le même nom et la même orthographe (les colonnes du csv -
vous pourriez aussi les renommer dans R avec la fonction « mutate »).
Combinez ensuite les occurrences des 4 espèces, toutes sources
confondues, en un seul objet en fusionnant les lignes.

```{r}
# #| label: combine-occurrences
# 
# all_occurrences <- bind_rows(gbif_occurrences, ldsf_occurrences, regreening_occurences) %>%
#   filter(!is.na(lon), !is.na(lat))
# 
# write.csv(
#   all_occurrences,
#   here("donnees", "occurrences", "all_occurrences_combined.csv"),
#   row.names = FALSE
# )
# 
# all_occurrences %>%
#   count(species, source)
```

Notez que si ces étapes sont exécutées, les noms d'objets dans les blocs
suivants doivent être adaptés en conséquence.

------------------------------------------------------------------------

## Étape 4 : Cartographier les points d'occurrence

Une vérification visuelle rapide avant de passer à l'extraction - les
points se situent-ils bien uniquement à l'intérieur du Sénégal ? Il est
également important de voir le nombre de points d'occurrence pour chaque
espèce, car cela influence beaucoup la suite du travail avec les données
: quelle espèce doit être écartée, et laquelle doit être interprétée
avec plus de prudence :

```{r}
senegal_outline <- ne_countries(scale = "medium", country = "Senegal", returnclass = "sf")

# Ajouter le nombre d'occurrences à l'étiquette de chaque espèce
occ_with_counts <- gbif_occurrences %>%   # ou all_occurrences
  add_count(species, name = "n_occ") %>%
  mutate(species_label = paste0(species, " (n=", n_occ, ")"))

ggplot() +
  geom_sf(data = senegal_outline, fill = "grey95", color = "grey40") +
  geom_point(
    data = occ_with_counts,
    aes(x = lon, y = lat, color = species_label),
    alpha = 0.7, size = 1.5
  ) +
  coord_sf(xlim = c(-17.6, -11.3), ylim = c(12.2, 16.7)) +
  labs(title = "Enregistrements d'occurrence GBIF par espèce", color = "Espèce") +
  theme_minimal()
```

Comme vous pouvez le voir ici, Eucalyptus camaldulensis n'a que 2
occurrences enregistrées au Sénégal. Cette espèce ne devrait donc pas
être utilisée pour des prédictions de convenance, les données d'entrée
étant trop rares. Dans ce cas, nous la conservons uniquement pour
visualiser, en fin de parcours, que le résultat n'est pas réaliste sur
le plan écologique.

------------------------------------------------------------------------

## Étape 5 : Couches environnementales

Maintenant que nous disposons des points d'occurrence pour nos 4
espèces, nous avons besoin de données environnementales décrivant les
conditions à chacun de ces points. C'est la matière première à partir de
laquelle l'enveloppe de convenance est construite. Dans ce notebook,
nous utilisons uniquement BIOCLIM, mais d'autres variables peuvent être
utilisées (voir les options ci-dessous).

### 5a. Variables bioclimatiques via `geodata`

`geodata` télécharge directement dans R les couches bioclimatiques
WorldClim. La résolution de BIOCLIM est de 1km.

- Note sur la sous-sélection parmi les 19 variables bioclimatiques
  d'origine :
  - WorldClim fournit au total 19 variables « bioclim », chacune
    capturant un aspect différent de la température ou des
    précipitations (moyennes annuelles, saisonnalité, extrêmes des
    périodes les plus humides/sèches/chaudes/froides, etc.). Utiliser
    les 19 en même temps pose problème pour notre méthode de seuil
    simple : beaucoup de ces variables sont fortement corrélées entre
    elles (par ex. BIO1, BIO5, BIO6, BIO10, BIO11 sont toutes
    différentes déclinaisons de la « température »), donc empiler les 19
    comme contraintes strictes et indépendantes multiplie beaucoup trop
    les exclusions.
  - BIO1 (température moyenne annuelle) et BIO12 (précipitations
    annuelles) ont été choisies comme les deux axes climatiques les plus
    fondamentaux, complétées par BIO4 (saisonnalité de la température)
    et BIO15 (saisonnalité des précipitations) pour capturer la
    variabilité.

*Source : Hijmans, R.J., S.E. Cameron, J.L. Parra, P.G. Jones et A.
Jarvis, 2005. Very High Resolution Interpolated Climate Surfaces for
Global Land Areas. International Journal of Climatology 25: 1965-1978.
doi:10.1002/joc.1276.*

```{r}
#| label: bioclim-download
#library(geodata)

bioclim_all <- worldclim_country(
  country = "Senegal",
  var     = "bio",
  res     = 0.5,
  path    = here("donnees", "environment"))

# On conserve un sous-ensemble restreint et interprétable :
# BIO1  = température moyenne annuelle
# BIO12 = précipitations annuelles
# BIO4  = saisonnalité de la température
# BIO15 = saisonnalité des précipitations

bioclim_subset <- bioclim_all[[c("wc2.1_30s_bio_1", "wc2.1_30s_bio_12",  "wc2.1_30s_bio_4", "wc2.1_30s_bio_15")]]

names(bioclim_subset) <- c("bio1_temp_mean", "bio12_precip_annual", "bio4_temp_season", "bio15_precip_season")
```

**Une note sur des alternatives plus avancées et les limites de BIOCLIM
:**

Plutôt que de choisir manuellement un sous-ensemble restreint, vous
pourriez réduire les 19 variables en un plus petit nombre d'axes
*statistiquement indépendants* en utilisant une **ACP (Analyse en
Composantes Principales)** (qui identifie les principales directions de
variation parmi les variables corrélées et les combine) ou une **ENFA
(Ecological Niche Factor Analysis)**, conçue spécifiquement pour les
données écologiques. Les deux sont plus rigoureuses sur le plan
statistique, mais ont un coût dans un contexte d'atelier : une fois que
l'on travaille dans un espace « PC1/PC2 », le résultat n'est plus
directement interprétable comme « ce site a besoin de X mm de pluie »,
et un décideur ne peut plus lire le résultat sans le retraduire. Puisque
l'objectif de ce notebook est une première étape *transparente et
explicable* pour les praticiens, nous avons choisi manuellement des
variables écologiquement pertinentes à des fins de simplification.
L'intérêt de cette démarche dépend de la quantité de données
d'occurrence disponibles par espèce, car une ACP/ENFA sur très peu de
points (comme notre exemple Eucalyptus) n'améliorera pas les résultats.

Les variables bioclimatiques sont souvent utilisées car elles sont
gratuites, cohérentes à l'échelle mondiale, faciles d'accès directement
dans R, et capturent les principaux déterminants climatiques pertinents
pour une première évaluation de convenance de l'espèce. Elles présentent
toutefois plusieurs limites à prendre en compte, comme une résolution
large de 1km, suffisante pour des évaluations à grande échelle, mais
insuffisante pour des décisions de plantation à petite échelle, car elle
masque la variation locale du sol, du couvert végétal ou du microclimat.
Les variables elles-mêmes sont des moyennes de long terme (et non des
conditions en temps réel), et sont interpolées à partir d'un réseau
clairsemé de stations météorologiques. Le lissage par moyenne gomme
également les événements extrêmes qui déterminent souvent la survie bien
plus que la moyenne de long terme. Et puisque BIOCLIM ne capture que le
climat, elle ne dit rien du sol, de l'occupation du sol, de la pression
de pâturage, ou d'autres facteurs non climatiques/socio-économiques
pouvant tout aussi bien déterminer si un arbre s'établit réellement sur
un site. Elle ne reflète pas non plus la façon dont le climat pourrait
évoluer sur un horizon de restauration de 10 à 20 ans.

### 5b. Données supplémentaires via STAC

Comme mentionné plus haut, les arbres ne poussent pas uniquement en
fonction du climat et des précipitations, mais dépendent aussi des
conditions du sol (carbone organique, pH, texture) et de bien d'autres
facteurs. Il est important de prendre en compte autant de ces facteurs
que possible. On peut par exemple ajouter d'autres couches en utilisant
**STAC (SpatioTemporal Asset Catalog)**, une manière standardisée de
rechercher et d'accéder à des jeux de données géospatiales hébergées
dans le cloud, par localisation et par date (comme brièvement présenté
durant l'atelier), sans téléchargement manuel. Cela fonctionne
conceptuellement comme l'appel `geodata` ci-dessus, mais pour un
catalogue de données et un type de variable différents.

```{r}
# STAC   

# xxx_stac (SpatRaster), avec par ex. les couches "soc", "phh2o"
# xxx_stac <- rast(...)

```

Le résultat final devrait être un empilement `SpatRaster` de `terra`
appelé `xxx_stac`, découpé sur le Sénégal, avec des couches telles que
le carbone organique du sol (SOC) et le pH.

### 5c. Variables prédictives supplémentaires

Si le temps et la disponibilité des données le permettent, c'est ici
qu'on peut intégrer des prédicteurs supplémentaires — couvert arboré,
occupation du sol, distance aux routes/rivières, ou d'autres variables
prédictives accessibles dérivées de l'imagerie satellite. Celles-ci
doivent souvent être téléchargées séparément (via Google Earth Engine).
Sinon, toute cette section peut simplement être ignorée.

```{r}
# ne pas oublier d'aligner résolution/étendue avant l'empilement

# empiler toutes les variables ensemble 
# env_stack <- c(bioclim_subset, soil_stac)
```

Deux étapes techniques importantes avant de combiner des couches, quelle
qu'en soit la source :

- **Alignement de la résolution et de l'étendue :** différentes sources
  de données ont des résolutions différentes. `resample()` force un
  raster à se caler sur la même grille qu'un autre, ce qui est
  nécessaire avant de pouvoir les combiner (empiler).

- **Empilement :** une fois alignées, `c()` combine plusieurs objets
  `SpatRaster` en un seul objet multi-couches, afin que chaque couche
  puisse être extraite ensemble à chaque point d'occurrence en un seul
  appel, puis reclassée conjointement dans le masque de convenance
  final.

------------------------------------------------------------------------

## Étape 6 : Extraire les valeurs environnementales aux points d'occurrence

Extraction des variables prédictives à chaque point d'occurrence
d'espèce. Pensez à modifier les noms d'objets des occurrences et des
variables prédictives si nécessaire.

```{r}
#| label: extract-values
occ_vect <- vect(gbif_occurrences, geom = c("lon", "lat"), crs = "EPSG:4326")
# all_occurances si vous ajoutez d'autres sources de données 

env_values <- terra::extract(bioclim_subset, occ_vect, ID = FALSE) # ou env_stac
 
occurrence_env <- bind_cols(gbif_occurrences, env_values)
# all_occurances si vous ajoutez d'autres sources de données 

head(occurrence_env)
```

------------------------------------------------------------------------

## Étape 7 : Construire l'enveloppe de convenance empirique

Pour chaque espèce et chaque variable, on prend la plage du 5e au 95e
percentile parmi tous ses points d'occurrence, plutôt que le min/max
brut (sensible aux valeurs aberrantes ou aux erreurs GPS). Cela sera
ensuite considéré comme l'enveloppe d'habitat convenable de l'espèce. Un
pixel qui tombe à l'intérieur de cette enveloppe sera plus tard
considéré comme adapté, et tout autre pixel comme non adapté.

```{r}
#| label: envelope-table
#| eval: false
# vérification des noms utilisés 
names(env_values)

# enveloppe 
envelope_table <- occurrence_env %>%
  group_by(species) %>%
  summarise(
    across(
      c(bio1_temp_mean, bio12_precip_annual, bio4_temp_season, bio15_precip_season),
      list(p05 = ~ quantile(.x, 0.10, na.rm = TRUE),
           p95 = ~ quantile(.x, 0.90, na.rm = TRUE)),
      .names = "{.col}_{.fn}"),
    n_records = n())

write.csv(
  envelope_table,
  here("results", "species_envelope_table_gbif_bioclim.csv"),
  row.names = FALSE)

envelope_table
# pour chaque variable bioclim, on obtient le 5e et le 95e percentile = enveloppe. 
```

Comment interpréter : chaque paire (\_p05, \_p95) est la plage d'une
variable (5e et 95e percentile).

## Étape 8 : Reclasser les rasters en masques de convenance

Pour chaque espèce, nous allons maintenant marquer un pixel comme adapté
(1) uniquement s'il se situe dans la plage de l'enveloppe pour *chaque*
variable, et non adapté (0) dans le cas contraire.

**Note :** la méthode par seuil utilisée ici est volontairement simple.
Une alternative plus sophistiquée consisterait à générer des points de
(pseudo-)absence et à les utiliser pour entraîner un modèle Random
Forest (RF) ou un autre modèle d'apprentissage automatique, qui
apprendrait non seulement où l'espèce *a été* trouvée, mais aussi ce qui
distingue réellement ces points de ceux où elle ne l'a pas été. Cela
peut améliorer la précision par rapport à un simple seuil. Cependant,
faire cela avec des données de sciences citoyennes (comme GBIF) est
risqué : puisque les enregistrements d'occurrence sont eux-mêmes biaisés
par l'échantillonnage (voir les limites plus haut), un point d'« absence
» placé aléatoirement peut facilement tomber dans un endroit qui est en
réalité tout aussi adapté écologiquement qu'un point de présence, mais
qui n'a simplement pas été enregistré. Entraîner un modèle sur de telles
absences erronées lui enseigne le mauvais schéma. Avec des données
issues d'un plan d'échantillonnage aléatoire (comme le LDSF), les points
d'absence peuvent être générés en utilisant les parcelles où l'espèce
n'a pas été trouvée, ce qui rend alors possibles des approches de
machine learning plus complexes. Cela améliore très nettement la
précision des prédictions et la solidité écologique de l'approche.

```{r}
#| label: suitability-masks
 
build_suitability_mask <- function(species_row, stack) {
  bio1_ok  <- stack$bio1_temp_mean      >= species_row$bio1_temp_mean_p05      & stack$bio1_temp_mean      <= species_row$bio1_temp_mean_p95
  bio4_ok  <- stack$bio4_temp_season    >= species_row$bio4_temp_season_p05    & stack$bio4_temp_season    <= species_row$bio4_temp_season_p95
  bio12_ok <- stack$bio12_precip_annual >= species_row$bio12_precip_annual_p05 & stack$bio12_precip_annual <= species_row$bio12_precip_annual_p95
  bio15_ok <- stack$bio15_precip_season >= species_row$bio15_precip_season_p05 & stack$bio15_precip_season <= species_row$bio15_precip_season_p95

  bio1_ok * bio4_ok * bio12_ok * bio15_ok}

suitability_maps <- envelope_table %>%
  group_split(species) %>%
  purrr::map(~ build_suitability_mask(.x, bioclim_subset)) %>%
  purrr::set_names(envelope_table$species)
```

------------------------------------------------------------------------

## Étape 9 : Visualiser les cartes de convenance

### 9a. Convenance par espèce

```{r}
#| label: plot-suitability-species

# pour rassembler plusieurs ggplot en une seule sortie graphique 
library(patchwork)

suitability_stack <- rast(suitability_maps)
names(suitability_stack) <- names(suitability_maps)

sen_bbox <- sf::st_bbox(senegal_outline)

# Version numérique — masquée, utilisée pour tous les calculs arithmétiques
suitability_masked_num <- terra::mask(suitability_stack, terra::vect(senegal_outline))

species_colors <- c(
  "Eucalyptus camaldulensis" = "#1b9e77",
  "Faidherbia albida"        = "#d95f02",
  "Senegalia senegal"        = "#7570b3",
  "Tamarindus indica"        = "#e7298a"
)

plots <- purrr::map(names(suitability_masked_num), function(nm) {

  r <- suitability_masked_num[[nm]]

  # conversion en facteur faite couche par couche -- j'ai toujours rencontré un bug de levels() multi-couches, donc ceci le résout
  r_fct <- as.factor(r)
  levels(r_fct) <- list(data.frame(id = c(0, 1), suitability = c("Non adapté", "Adapté")))

  ggplot() +
    geom_spatraster(data = r_fct, aes(fill = suitability)) +
    geom_sf(data = senegal_outline, fill = NA, color = "black", linewidth = 0.3) +
    scale_fill_manual(
      values = c("Non adapté" = "grey90", "Adapté" = species_colors[[nm]]),
      na.value = "transparent", name = NULL
    ) +
    coord_sf(xlim = sen_bbox[c("xmin","xmax")], ylim = sen_bbox[c("ymin","ymax")], expand = FALSE) +
    labs(title = nm) +
    theme_minimal() +
    theme(legend.position = "none", plot.title = element_text(size = 10, face = "bold"))
})

wrap_plots(plots, ncol = 2)

```

### 9b. Nombre d'espèces adaptées par pixel

Comptage de pixels (% de surface adaptée au Sénégal par espèce) :

```{r}
#| label: pixel-counts

purrr::map_dbl(suitability_maps, ~ terra::global(.x, "sum", na.rm = TRUE)[[1]] / terra::ncell(.x) * 100)
```

### 9c : Convenance combinée par zone

```{r}
suitability_stack <- rast(suitability_maps)
names(suitability_stack) <- names(suitability_maps)

sen_bbox <- sf::st_bbox(senegal_outline)

#numérique
suitability_masked_num <- terra::mask(suitability_stack, terra::vect(senegal_outline))
#facteur
suitability_masked_fct <- terra::mask(suitability_stack, terra::vect(senegal_outline))

# Construire une table de catégories PAR COUCHE (4 copies identiques, une par espèce)
cat_table <- data.frame(id = c(0, 1), suitability = c("Non adapté", "Adapté"))
levels(suitability_masked_fct) <- rep(list(cat_table), terra::nlyr(suitability_masked_fct))

ggplot() +
  geom_spatraster(data = species_count) +
  geom_sf(data = senegal_outline, fill = NA, color = "black", linewidth = 0.4) +
  scale_fill_gradientn(colors = c("grey95", "#c7e9c0", "#74c476", "#238b45", "#00441b"),
                        breaks = 0:4, na.value = "transparent", name = "N espèces\nadaptées") +
  coord_sf(xlim = sen_bbox[c("xmin","xmax")], ylim = sen_bbox[c("ymin","ymax")], expand = FALSE) +
  labs(title = "Nombre d'espèces écologiquement adaptées par pixel") +
  theme_minimal()

```

## Étape 10 : Interprétation et discussion

Comme indiqué dans l'aperçu général, cette carte constitue une
présélection de départ. L'étape suivante consisterait à valider les
espèces candidates sur le terrain et à les pondérer selon les priorités
communautaires (fourrage, valeur marchande, statut indigène) avant de
s'engager.

[**Réflexions critiques et limites :**]{.underline}

- Est-ce que la carte a globalement du sens à grande échelle ?

- Combien de points étaient disponibles par espèce ? Le résultat pour
  Eucalyptus semble-t-il réaliste ? Sinon, pourquoi ?

- Grandes zones non adaptées : réellement non adaptées, ou simplement
  très peu de données dans ces zones ?

- À retenir : il s'agit d'une cartographie de convenance TRÈS large -\>
  pas réellement fondée sur le plan écologique, mais permettant de
  donner une idée de ce qu'une analyse simple peut apporter !

- Beaucoup plus de variables prédictives seraient nécessaires (ici
  seulement 2 variables climatiques -\> alors que des facteurs
  non-climatiques et socio-économiques influencent aussi fortement la
  répartition des espèces)

- Le seuil du 5e-95e percentile est en soi arbitraire. Pourquoi pas
  10e-90e, ou le min/max brut ?

  - Des seuils différents feraient croître ou rétrécir la zone adaptée
    de chaque espèce — que signifie réellement le « bon » seuil (...).

- Les enregistrements GBIF se concentrent souvent près des routes, des
  villes et des zones accessibles, qui ne sont pas nécessairement les
  zones les plus adaptées à l'espèce, mais celles où se rendent les gens
  (et en particulier les usagers d'applications de sciences citoyennes).

  - Il est préférable d'utiliser un plan d'échantillonnage bien conçu,
    comme les données LDSF.

- Toutes les variables sont traitées comme également importantes et
  indépendantes -\> alors que certaines sont peut-être plus
  déterminantes que d'autres... Il existe des méthodes pour ne retenir
  que les plus importantes (ENFA ; ACP). - Aucune donnée de validation
  ne permet de vérifier la performance réelle de cette méthode.

- Une résolution de 1km est très large pour une analyse à petite échelle
  (niveau départemental), mais suffisante à l'échelle du pays.

- Aucune comparaison pseudo-absence/fond de référence : cette méthode ne
  regarde que là où l'espèce *a été* trouvée, sans jamais vérifier si
  les mêmes conditions se retrouvent largement ailleurs *sans* la
  présence de l'espèce.

[**Prochaines étapes - améliorations possibles :**]{.underline}

- **Option 1 :** ajouter d'autres variables prédictives (couche de sol)
  téléchargées depuis SoilGrids Africa, et les stocker dans votre
  dossier de données pour les importer.

- **Option 2 :** ajouter des rasters STAC comme prédicteurs ou en
  superposition, pour examiner par ex. l'occupation du sol (LULC), le
  couvert arboré, le SOC. Pouvez-vous identifier des schémas
  intéressants ?

- **Option 3 :** utiliser d'autres espèces disposant de davantage
  d'enregistrements dans GBIF.

[**Questions critiques :**]{.underline}

1\. Quelle espèce a l'enveloppe empirique la plus large / la plus
étroite ? Est-ce cohérent avec ce que l'on sait de sa répartition ou de
sa spécialisation ?

2\. Où l'enveloppe empirique (issue des données d'occurrence)
diverge-t-elle des plages de tolérance issues de la littérature (par ex.
la base Agroforestree) ? Qu'est-ce qui pourrait expliquer cet écart --
enregistrements limités, biais d'échantillonnage, ou aire de répartition
de l'espèce s'étendant au-delà du Sénégal ?

3.  Si deux espèces présentent des zones de convenance qui se
    chevauchent, comment décideriez-vous entre elles pour un site donné
    ? De quelles informations auriez-vous besoin en plus de cette carte
    ?

[**Pour aller plus loin sur le sujet**]{.underline}

- <https://www.annualreviews.org/content/journals/10.1146/annurev.ecolsys.110308.120159>

- <https://jcoliver.github.io/learn-r/011-species-distribution-models.html>

- <https://www.sciencedirect.com/science/article/pii/S1470160X22009165>

- <https://besjournals.onlinelibrary.wiley.com/doi/10.1111/2041-210X.14466>

-  <https://github.com/datatas/Accessing_Mapping_GBIFdata>
