Explainable Artificial Intelligence Reveals Spatially Divergent Effects of Global Change on Mammals

A demo for the core part of the approach

Lei Song https://github.com/LLeiSong (Rutgers University)

Purpose of this demo

This demo illustrates the core steps of our analysis, as described in the manuscript “Explainable Artificial Intelligence Reveals Spatially Divergent Effects of Global Change on Mammals.” Specifically, it demonstrates how we integrate species distribution modeling (SDM) with SHAP (SHapley Additive exPlanations) values to attribute projected changes in contributions of individual environmental drivers to habitat suitability.

We use the African savanna elephant (Loxodonta africana) as an example species to walk through the modeling pipeline. This example showcases:

By focusing on a well-studied, conservation-relevant species, this demo provides an interpretable and reproducible example of how our approach identifies both localized risks and potential opportunities for biodiversity under global change.

Note: to make demo efficient to run, some of the settings are simplified.

Setup and environment

Install and load necessary libraries for this demo:

# Install (if necessary) and load required packages
required_packages <- c(
    "checkmate", "optparse", "dplyr", "stringr", # general packages
    "terra", "sf", "spatialEco", "blockCV", "dbarts", # Special packages
    "precrec", "randomForest", "intervals", "fastshap",
    "parallel", "pbmcapply", "doParallel" # parallel
)

# Install any missing packages
installed_packages <- installed.packages()[, "Package"]
to_install <- setdiff(required_packages, installed_packages)
if (length(to_install)) install.packages(to_install)

# Load all packages
lapply(required_packages, library, character.only = TRUE)

# Specific some  conflict functions
select <- dplyr::select
filter <- dplyr::filter
extract <- terra::extract

Define directories and source global util functions:

source(here("R/utils.R"))
root_dir <- here("demo")
set.seed(123)

# Remove existing results
flds <- list.files(file.path(root_dir, "results"), full.names = TRUE)
for (fld in flds) unlink(fld, recursive = TRUE)

Species-level variable seleciton

Select important and uncorrelated variables for the species.

tic("Variable selection")
# Load functions
source(here("R/var_selection.R"))

# Set parameters
sp <- "Loxodonta_africana"

var_path <- file.path(root_dir, "data/variables/Env/AllEnv.tif")
occ_dir <- file.path(root_dir, "data/occurrences")
range_dir <- file.path(root_dir, "data/IUCN/Expert_Maps")
aoh_dir <- file.path(root_dir, "data/IUCN_AOH_100m/Mammals")
dst_dir <- file.path(root_dir, "data/variables/variable_list")
tmp_dir <- file.path(root_dir, "data/tmp")

# Run variable section with iter = 5
var_selection(sp, var_path, occ_dir, range_dir, 
              aoh_dir, dst_dir, tmp_dir, iter = 5)
Reading layer `Loxodonta_africana' from data source 
  `/Users/pinot/Library/CloudStorage/Dropbox/projects/SClimpact/demo/data/IUCN/Expert_Maps/Loxodonta_africana.geojson' 
  using driver `GeoJSON'
Simple feature collection with 5 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -14.89546 ymin: -34.08534 xmax: 42.62364 ymax: 16.61945
Geodetic CRS:  WGS 84
|---------|---------|---------|---------|=========================================                                          
toc()
Variable selection: 727.658 sec elapsed

Environmental sampling

Do environmental sampling occurrences and make environmental balanced background samples.

tic("Environmental sampling")
# Load functions
source(here("R/env_sampling.R"))

# Set parameters
src_dir <- file.path(root_dir, "data/occurrences/CSVs")
var_dir <- file.path(root_dir, "data/variables")
range_dir <- file.path(root_dir, "data/IUCN/Expert_Maps")
region_dir <- file.path(root_dir, "data/terr-ecoregions-TNC")
occ_dir <- file.path(root_dir, "data/occurrences/CSVs_thin")
bg_dir <- file.path(root_dir, "data/occurrences/bg")

# Run environmental sampling with background sample = 500 and one core
env_sampling(sp, src_dir, var_dir, range_dir, 
             region_dir, occ_dir, bg_dir, 500, 123, 1)
  |                                                    |   0%, ETA NA  |=============================================| 100%, Elapsed 00:00
[[1]]
NULL
toc()
Environmental sampling: 0.184 sec elapsed

Species distribution modeling

Build the down-sampled random forest model.

tic("Build model")
# Load functions
source(here("R/rf_dws.R"))

# Set parameters
var_dir <- file.path(root_dir, "data/variables")
occ_dir <- file.path(root_dir, "data/occurrences")
range_dir <- file.path(root_dir, "data/IUCN/Expert_Maps")
dst_dir <- file.path(root_dir, "results/sdm")

# Build down-sampled random forest model
rf_dws(sp, occ_dir, var_dir, range_dir, dst_dir)
toc()
Build model: 15.494 sec elapsed

SHAP values calculation

Calculate SHAP values of each environmental variables for baseline and future.

tic("Calculate SHAP values")
# Load functions
source(here("R/shap.R"))

# Set parameters
work_dir <- file.path(root_dir, "results/sdm")
var_dir <- file.path(root_dir, "data/variables")
range_dir <- file.path(root_dir, "data/IUCN/Expert_Maps")

# Calculate SHAP values for variables with max_nshap = 100
shap(sp, work_dir, var_dir, range_dir, max_nshap = 100)
toc()
Calculate SHAP values: 82.162 sec elapsed

Analyze changes

Use SHAP values to track changes.

tic("Analyze changes")
# Load functions
source(here("R/climate_change.R"))

# Set parameters
scenario <- "ssp126_2011-2040"
feature <- "bio12"
sdm_dir <- file.path(root_dir, "results/sdm")
dst_dir <- file.path(root_dir, "results/climate_change", sp)
if (!dir.exists(dst_dir)) dir.create(dst_dir, recursive = TRUE)

# Analyze changes for this species
change_sp_demo(sp, scenario, feature, root_dir, sdm_dir, dst_dir)
toc()
Analyze changes: 4.037 sec elapsed

Visualize

Directional change of this single species.

dir_changes <- rast(
    file.path(dst_dir, sprintf("dir_change_%s_%s.tif", feature, scenario)))
plot(dir_changes)

Magnitude change of this single species.

mag_changes <- rast(
    file.path(dst_dir, sprintf("mag_change_%s_%s.tif", feature, scenario)))
mag_changes[mag_changes > 1] <- NA # Remove very rare abnormal pixel.
plot(mag_changes)

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".