# Spatiotemporal example

#### Philip Mostert

#### 2026-02-18

Studying the complex ecological systems across both space and time is imperative in understanding the full dynamics of species. This vignette illustrates the construction of a spatiotemporal ISDM using PointedSDMs, using data of species Colinus virginianus across Alabama (United States of America). The first step in this vignette is to load the required packages:

    library(PointedSDMs)
    library(fmesher)
    library(inlabru)
    library(ggplot2)
    library(spocc)
    library(INLA)
    library(dplyr)
    library(sp)
    library(sf)

as well as define some objects required by the model to run.

    proj <- "+proj=utm +zone=17 +datum=WGS84 +units=km"

    AL <- USAboundaries::us_states(states = "Alabama", resolution = 'high')
    AL <- as(AL, "sf")
    AL <- st_transform(AL, proj)

    mesh <- fm_mesh_2d_inla(boundary = inla.sp2segment(AL[1]),
                            cutoff = 0.1 * 50,
                            max.edge = c(0.2, 0.8) * 70,
                            offset = c(0.1, 0.2) * 150,
                            crs = st_crs(proj))

The first dataset we consider is obtained from the North American Breeding Bird Survey. This dataset may be loaded directly from the package, and contains observations of the species between 2015 and 2017. This dataset is treated as replicate present-absent, where every point is assumed to be a visited site (or route).

    data("BBSColinusVirginianus")

The second dataset considered is obtained via the citizen science program, eBird. These data are obtained via the R package, spocc using the script below, where a separate object of data points was created for each year to ensure that the number of records per year is equal.

    eBird2015 <- spocc::occ(
      query = 'Colinus virginianus',
      from = 'gbif',
      date = c("2015-01-01", "2015-12-31"),
       geometry = st_bbox(st_transform(AL,
                    '+proj=longlat +datum=WGS84 +no_defs'))
    )$gbif

    eBird2016 <- spocc::occ(
      query = 'Colinus virginianus',
      from = 'gbif',
      date = c("2016-01-01", "2016-12-31"),
     geometry = st_bbox(st_transform(AL,
                    '+proj=longlat +datum=WGS84 +no_defs'))
    )$gbif

    eBird2017 <- spocc::occ(
      query = 'Colinus virginianus',
      from = 'gbif',
      date = c("2017-01-01", "2017-12-31"),
     geometry = st_bbox(st_transform(AL,
                    '+proj=longlat +datum=WGS84 +no_defs'))
    )$gbif

    eBird <- data.frame(eBird2015$data[[1]]) %>%
      bind_rows(data.frame(eBird2016$data[[1]])) %>%
      bind_rows(data.frame(eBird2017$data[[1]]))

    eBird <- st_as_sf(x = eBird,
                      coords = c('longitude', 'latitude'),
                      crs =  '+proj=longlat +datum=WGS84 +no_defs')

    eBird$Year <- eBird$year

    eBird <- st_transform(eBird, proj)

    eBird <- eBird[unlist(st_intersects(AL, eBird)),]

We then get onto the model description, which in this case includes a shared spatial field between the two datasets. This shared spatial field is characterized by an ar1 process. To add this structure into the model, we specify the parameter temporalModel in the function `startISDM` appropriately, Furthermore we specified the hyper parameters for both the random field and the temporal effect and the priors for the intercepts.

    hyperParams <- list(model = 'ar1',
                        hyper = list(rho = list(prior = "pc.prec", param = c(0.9, 0.1))))

    modelSetup <- startISDM(eBird, BBSColinusVirginianus,
                           temporalName = 'Year',
                           Boundary = AL,
                           Projection =  proj, Mesh = mesh,
                           responsePA =  'NPres', trialsPA = 'Ntrials')

    modelSetup$specifySpatial(sharedSpatial = TRUE, prior.sigma = c(1, 0.5),
                              prior.range = c(100, 0.5))

    modelSetup$specifyRandom(temporalModel = hyperParams)

    modelSetup$priorsFixed(Effect = 'intercept', mean.linear = 0, prec.linear = 1)

Furthermore, we include a spatio-temporally varying bias field for the eBird data. For simplicity, we do not correlate the spatial fields across time.

    modelSetup$addBias('eBird', temporalModel = list(model = 'iid'))

    modelSetup$specifySpatial(Bias = TRUE, prior.range = c(100, 0.5),
                              prior.sigma = c(1, 0.5))

The data is spread across the map like this

    modelSetup$plot()

The components for this model look like this:

    modelSetup$changeComponents()

Next we run the model, using the function `fitISDM`. Due to time considerations, inference for this model is not completed in the vignette. However, both the data and script is provided for the user to complete the analysis.

    mod <- fitISDM(modelSetup,
                    options = list(verbose = TRUE,
                                   num.threads = 2,
                                   control.inla = list(int.strategy = 'eb',
                                                       cmin = 0)))

    summary(mod)

And finally create predictions for the three time periods, and plot them.

    preds <- predict(mod, mask = AL, mesh = mesh, spatial = TRUE)

    plot_preds <- plot(preds, variable = c('median'), plot = FALSE)

    plot_preds +
      geom_sf(data = st_boundary(AL), lwd = 1.2) +
      scico::scale_color_scico(palette = "lajolla") +
      theme_minimal()

And predictions of the bias field.

    bias <- predict(mod, mask = AL, mesh = mesh, bias = TRUE)

    plot_bias <- plot(bias, variable = c('median'), plot = FALSE)

    plot_bias +
      geom_sf(data = st_boundary(AL), lwd = 1.2) +
      scico::scale_color_scico(palette = "lajolla") +
      theme_minimal()