EX Model evaluation

Most Species Distribution Model (SDM) outputs are continuous probabilities ranging from 0 to 1. A common pitfall for researchers is failing to realize how drastically a distribution map changes based on the threshold chosen to define a presence-absence map when calculating threshold-dependent evaluation metrics.

Task

Take one of your continuous suitability map for Aglais caschmirensis from Unit 03 and apply three different thresholds to create binary presence/absence maps:

  • Strict: (e.g., 0.8 - captures only high-confidence areas).
  • Balanced: (e.g., 0.5).
  • Liberal: (e.g., 0.3 - predicts presence even in marginal or transition areas).

Exercise

1) Transform your suitability maps to presence-absence maps with the three thresholds named above.

2) Create a panel map (a single figure displaying all three versions side-by-side) to visually compare how the predicted range expands or contracts based on your mathematical choices.

3) Calculate the TSS with the three different maps.


# 1. Setup and Data Loading
setwd("C:/Users/bald_local/DATA/Lehre/sdmWorkflow_Kurs")

testData <- sf::read_sf("Aglais_caschmirensis_testData.gpkg")
pred     <- terra::rast("prediction_aglais_caschmirensis.tif")

# 2. Create Binary Maps (Presence/Absence)
pred_strict   <- terra::ifel(pred >= 0.8, 1, 0)
pred_balanced <- terra::ifel(pred >= 0.5, 1, 0)
pred_liberal  <- terra::ifel(pred >= 0.3, 1, 0)

# 3. Extract Values for Evaluation
# Presence points
ext_p_strict   <- terra::extract(pred_strict, testData)[,2]
ext_p_balanced <- terra::extract(pred_balanced, testData)[,2]
ext_p_liberal  <- terra::extract(pred_liberal, testData)[,2]

# Generate 1000 Background points (Pseudo-absences)
bg_coords      <- terra::spatSample(pred, size = 1000, method = "random", na.rm = TRUE, as.points = TRUE)
ext_bg_strict   <- terra::extract(pred_strict, bg_coords)[,2]
ext_bg_balanced <- terra::extract(pred_balanced, bg_coords)[,2]
ext_bg_liberal  <- terra::extract(pred_liberal, bg_coords)[,2]

# 4. Define TSS Function
calc_tss <- function(pres_vec, bg_vec) {
  # True Positives (tp) and False Negatives (fn)
  tp <- sum(pres_vec == 1, na.rm = TRUE)
  fn <- sum(pres_vec == 0, na.rm = TRUE)
  
  # False Positives (fp) and True Negatives (tn)
  fp <- sum(bg_vec == 1, na.rm = TRUE)
  tn <- sum(bg_vec == 0, na.rm = TRUE)
  
  # TSS = Sensitivity + Specificity - 1
  sensitivity <- tp / (tp + fn)
  specificity <- tn / (tn + fp)
  tss <- sensitivity + specificity - 1
  
  return(round(tss, 3))
}

# 5. Calculate and Compare Results
results <- data.frame(
  Threshold_Map = c("Strict (0.8)", "Balanced (0.5)", "Liberal (0.3)"),
  TSS = c(
    calc_tss(ext_p_strict, ext_bg_strict),
    calc_tss(ext_p_balanced, ext_bg_balanced),
    calc_tss(ext_p_liberal, ext_bg_liberal)
  )
)

print(results)


What do you notice when you look at the results for the three metrics along with the prediction map? To support the analysis, you can also create a map showing the prediction and the test points in order to better interpret the results.

Updated: