Crandore Hub

xsdm

Demographic Approach to Species Distribution Model

Integrates concepts of stochastic demography into species distribution modelling. The main approach maximizes a likelihood function based on environmental information and presence/absence records. This is used to reconstruct species' fundamental ecological niches and to project their potential geographic range. Data requirements include species presence/absence records and a timeseries of environmental data.

README

---
output: github_document
---

<!-- README.md is generated from README.Rmd. Please edit that file -->



# xsdm

**xsdm** is an R package that integrates concepts of *stochastic demography*
into species distribution modelling (SDM). Instead of treating environmental
conditions as a static snapshot, xsdm uses **multi-year environmental
time-series** together with species presence/absence records to:

- Reconstruct a species' **fundamental ecological niche** via
  maximum-likelihood estimation.
- Account for **inter-annual climate variability** when estimating niche
  breadth and position.
- Project the species' **potential geographic range** under current or
  future climate scenarios.

The statistical underpinning is described in:

> Berti, E., Robles Fernández, A.L., Rosenbaum, B., Peterson, T.A.,
> Soberón, J., & Reuman, D.C. (2025). *The impacts of climate
> variability on the niche concept and distributions of species*.
> bioRxiv. <https://doi.org/10.1101/2024.10.30.621023>

---

## Installation

### CRAN

```r
install.packages("xsdm")
```

### Development version

Install the latest version directly from GitHub:

```r
# install.packages("remotes")
remotes::install_github("xsdm-project/xsdm-devel")
```

xsdm requires **R ≥ 4.1** and links to C++ via Rcpp/RcppParallel, so a
working compiler (e.g. Rtools on Windows, Xcode CLT on macOS) is needed.

---

## Full end-to-end workflow

### 1 · Load and prepare environmental time series

xsdm expects **bioclimatic time-series rasters** — one `SpatRaster` per
variable with each layer representing one year (or time step). The built-in
data cover southern New Mexico, USA, over 39 years (1980–2018) using
[CHELSA v2.1](https://www.chelsa-climate.org/) bio1 (mean annual
temperature) and bio12 (annual precipitation).


``` r
library(xsdm)
library(terra)

bio_1 <- terra::unwrap(example_1$bio01)
bio_1 <- bio_1 / 100

bio_12 <- terra::unwrap(example_1$bio12)
bio_12 <- bio_12 / 100
```

Take a look at the temporal means and standard deviations of the
environmental variables:


``` r
m_bio_1 <- terra::app(bio_1, mean)
m_bio_12 <- terra::app(bio_12, mean)
sd_bio_1 <- terra::app(bio_1, sd)
sd_bio_12 <- terra::app(bio_12, sd)
par(mfrow = c(2, 2), mar = c(3, 3, 2, 4))
terra::plot(m_bio_1, main = "Mean, bio1", xlab = "x", ylab = "y")
terra::plot(m_bio_12, main = "Mean, bio12", xlab = "x", ylab = "y")
terra::plot(sd_bio_1, main = "SD, bio1", xlab = "x", ylab = "y")
terra::plot(sd_bio_12, main = "SD, bio12", xlab = "x", ylab = "y")
```

<div class="figure">
<img src="man/figures/README-plot-env-1.png" alt="plot of chunk plot-env" width="100%" />
<p class="caption">plot of chunk plot-env</p>
</div>

### 2 · Species occurrence data

Load the occurrence data and visualize detections and non-detections on
top of the mean temperature map:


``` r
d <- example_1$occ_df
pts_0 <- terra::vect(d[d$presence == 0, ], geom = c("lon", "lat"),
                     crs = terra::crs(m_bio_1))
#> Error:
#> ! unable to find an inherited method for function 'vect' for signature 'x = "tbl_df"'
pts_1 <- terra::vect(d[d$presence == 1, ], geom = c("lon", "lat"),
                     crs = terra::crs(m_bio_1))
#> Error:
#> ! unable to find an inherited method for function 'vect' for signature 'x = "tbl_df"'
par(mfrow = c(1, 2), mar = c(3, 3, 2, 4))
terra::plot(m_bio_1, main = "Non-detections", xlab = "x", ylab = "y")
terra::plot(pts_0, add = TRUE, col = "black", pch = 20, cex = 0.2)
#> Error in `h()`:
#> ! error in evaluating the argument 'x' in selecting a method for function 'plot': object 'pts_0' not found
terra::plot(m_bio_1, main = "Detections", xlab = "x", ylab = "y")
```

<div class="figure">
<img src="man/figures/README-plot-occ-1.png" alt="plot of chunk plot-occ" width="100%" />
<p class="caption">plot of chunk plot-occ</p>
</div>

``` r
terra::plot(pts_1, add = TRUE, col = "red", pch = 20, cex = 0.2)
#> Error in `h()`:
#> ! error in evaluating the argument 'x' in selecting a method for function 'plot': object 'pts_1' not found
```

### 3 · Build the environmental data array

`env_data_array()` extracts and stacks environmental values at the
locations given in a presence/absence data frame, returning a 3-D array
`(locations × time × variables)`.


``` r
env_data <- list(bio_1 = bio_1, bio_12 = bio_12)
env_dat <- env_data_array(env_data, occ = d)
dim(env_dat)
#> [1] 4000   39    2
occ <- d$presence
```

### 4 · Fit the model

`optimize_likelihood()` runs multiple optimizations from Latin-hypercube
starting points and returns every solution sorted by decreasing
log-likelihood:


``` r
result <- optimize_likelihood(
  env_dat    = env_dat,
  occ        = occ,
  num_starts = 20L,
  parallel   = FALSE,
  verbose    = FALSE
)

head(result$solutions[, c("start_id", "loglik", "convergence")])
#>   start_id    loglik convergence
#> 1       19 -1009.447           4
#> 2        4 -1009.447           4
#> 3       20 -1009.447           4
#> 4       11 -1009.447           4
#> 5        5 -1009.447           4
#> 6        8 -1009.447           4
result$best$loglik
#> [1] -1009.447
```

### 5 · Interpret the fitted parameters

Convert the best math-scale parameter vector to the biologically
interpretable scale and plot the inferred log growth–environment
function:


``` r
best_bio <- math_to_bio(result$best$par)

par(mfrow = c(1, 2))
interpret_parameters(
  best_bio,
  plot_indices = c(1, 2),
  env_dat      = env_dat,
  occ          = occ
)
```

<div class="figure">
<img src="man/figures/README-interpret-1.png" alt="plot of chunk interpret" width="100%" />
<p class="caption">plot of chunk interpret</p>
</div>

### 6 · Habitat suitability map

Use the fitted parameters to project a habitat suitability map over the
full raster extent:


``` r
hab_suit <- habitat_suitability(
  param_list = best_bio,
  env_list   = env_data
)
terra::plot(hab_suit, main = "Habitat suitability", xlab = "x", ylab = "y")
```

<div class="figure">
<img src="man/figures/README-hab-suit-1.png" alt="plot of chunk hab-suit" width="100%" />
<p class="caption">plot of chunk hab-suit</p>
</div>

---

## Key functions

| Function | Purpose |
|---|---|
| `env_data_array()` | Build a `(locations × time × variables)` array from raster time series and an occurrence table |
| `optimize_likelihood()` | Multi-start MLE fitting; returns solutions sorted by log-likelihood |
| `loglik_math()` | Evaluate the log-likelihood at any math-scale parameter vector |
| `math_to_bio()` | Convert math-scale vector → biological-scale parameter list |
| `bio_to_math()` | Convert biological-scale parameter list → math-scale vector |
| `start_parms()` | Generate Latin-hypercube starting points from presence-only data |
| `profile_likelihood()` | Profile one parameter while re-optimising over the rest |
| `habitat_suitability()` | Produce a spatial probability-of-detection map |
| `interpret_parameters()` | Diagnostic plots of the niche shape |
| `dist_between_params()` | Distance between two parameter sets (Hungarian algorithm, equivalence-class aware) |

---

## For developers and contributors

### Install from source

```r
# Clone the repository and install locally
install.packages("devtools")
devtools::install_github("xsdm-project/xsdm-devel")
```

### Reporting issues

Please file bugs and feature requests on the [GitHub issue
tracker](https://github.com/xsdm-project/xsdm-devel/issues). When
reporting a bug, include the output of `sessionInfo()` and a minimal
reproducible example.

---

## Citation

If you use xsdm in published work, please cite:

    Berti, E., Robles Fernández, A.L., Rosenbaum, B., Peterson, T.A.,
    Soberón, J., & Reuman, D.C. (2025). The impacts of climate variability
    on the niche concept and distributions of species. bioRxiv.
    https://doi.org/10.1101/2024.10.30.621023

BibTeX:

```bibtex
@article{bertiXSDM1,
  title  = {The impacts of climate variability on the niche concept and
            distributions of species},
  author = {Berti, E. and Fern\'{a}ndez, ALR and Rosenbaum, B and
            Peterson, TA and Sober\'{o}n, J and Reuman, DC},
  journal = {bioRxiv},
  doi    = {10.1101/2024.10.30.621023},
  year   = {2025},
  url    = {https://doi.org/10.1101/2024.10.30.621023}
}
```

---

## License

xsdm is released under the GNU Affero General Public License, version 3
(AGPL-3.0) or any later version.

Versions across snapshots

VersionRepositoryFileSize
1.0.2 rolling linux/jammy R-4.5 xsdm_1.0.2.tar.gz 5.5 MiB
1.0.2 rolling linux/noble R-4.5 xsdm_1.0.2.tar.gz 5.0 MiB
1.0.2 rolling source/ R- xsdm_1.0.2.tar.gz 5.5 MiB
1.0.2 latest linux/jammy R-4.5 xsdm_1.0.2.tar.gz 5.5 MiB
1.0.2 latest linux/noble R-4.5 xsdm_1.0.2.tar.gz 5.0 MiB
1.0.2 latest source/ R- xsdm_1.0.2.tar.gz 5.5 MiB
1.0.2 2026-04-23 source/ R- xsdm_1.0.2.tar.gz 0 B

Dependencies (latest)

Imports

LinkingTo

Suggests