vignettes/spatial-temporal.Rmd
spatial-temporal.Rmdtitle: “Spatial and Temporal Models” author: “Gilles Colling” date: “2026-08-04” output: rmarkdown::html_vignette vignette: > % % %
Overview
This vignette covers spatial and temporal extensions in numdenom. These allow borrowing strength across neighboring locations or time points, improving estimates in data-sparse regions.
Spatial Models
numdenom supports two spatial priors for areal (discrete region) data:
| Function | Prior | Use Case |
|---|---|---|
spatial_car() |
Intrinsic CAR (ICAR) | Simple spatial smoothing |
spatial_bym2() |
BYM2 | Interpretable spatial fraction |
Both require an adjacency matrix defining which regions are neighbors.
Creating Adjacency Matrices
From Coordinates (Grid Data)
# Camera trap grid
n <- 25
coords <- expand.grid(x = 1:5, y = 1:5)
# Queen contiguity (8 neighbors)
adj <- matrix(0, n, n)
for (i in 1:n) {
for (j in 1:n) {
if (i != j) {
dist <- sqrt((coords$x[i] - coords$x[j])^2 +
(coords$y[i] - coords$y[j])^2)
if (dist <= sqrt(2) + 0.01) adj[i, j] <- 1
}
}
}ICAR Model: spatial_car()
The intrinsic conditional autoregressive (ICAR) prior encourages neighboring regions to have similar effects:
where denotes neighbors of region .
fit <- tratio(
count | effort ~ x + (1 | region),
spatial = spatial_car(
adj = adj,
level = "group",
group_var = "region"
),
data = df,
family = ratiod_poisson_gamma()
)BYM2 Model: spatial_bym2()
The BYM2 reparameterization (Riebler et al., 2016) separates spatial structure from unstructured heterogeneity:
where: - is the scaled ICAR component - is unstructured noise - is the proportion of variance that is spatial
fit <- tratio(
cases | population ~ age + (1 | county),
spatial = spatial_bym2(
adj = adj,
level = "group",
group_var = "county"
),
data = df,
family = ratiod_binomial()
)The parameter is directly interpretable: means 80% of the random effect variance is spatially structured.
Temporal Models
numdenom supports temporal priors for time series structure:
| Function | Prior | Use Case |
|---|---|---|
temporal_rw() |
Random walk | Smooth trends |
temporal_ar1() |
AR(1) | Mean-reverting dynamics |
Random Walk: temporal_rw()
First-order random walk (RW1):
Second-order random walk (RW2) for smoother trends:
# First-order random walk
fit <- tratio(
count | effort ~ x + (1 | site),
temporal = temporal_rw(
time_var = "year",
order = 1
),
data = df,
family = ratiod_poisson_gamma()
)
# Second-order for smoother trends
fit <- tratio(
count | effort ~ x,
temporal = temporal_rw(
time_var = "year",
order = 2
),
data = df,
family = ratiod_poisson_gamma()
)Cyclic Random Walk
For seasonal patterns that wrap around (e.g., month 12 neighbors month 1):
fit <- tratio(
count | effort ~ x,
temporal = temporal_rw(
time_var = "month",
order = 1,
cyclic = TRUE
),
data = df,
family = ratiod_poisson_gamma()
)AR(1): temporal_ar1()
Autoregressive order 1:
The correlation parameter controls persistence.
fit <- tratio(
count | effort ~ x,
temporal = temporal_ar1(
time_var = "year"
),
data = df,
family = ratiod_poisson_gamma()
)Group-Specific Temporal Effects
By default, temporal effects are shared across all groups. For group-specific trends:
fit <- tratio(
count | effort ~ (1 | site),
temporal = temporal_rw(
time_var = "year",
group_var = "site",
shared = FALSE
),
data = df,
family = ratiod_poisson_gamma()
)Combining Spatial and Temporal
For spatiotemporal data, include both:
fit <- tratio(
count | effort ~ x + (1 | region),
spatial = spatial_bym2(adj, level = "group", group_var = "region"),
temporal = temporal_rw(time_var = "year"),
data = df,
family = ratiod_poisson_gamma()
)This fits additive spatial and temporal effects. For interaction effects, see the next section.
Spatiotemporal Interaction
When spatial patterns change over time (or temporal trends differ across space), additive models may be insufficient. numdenom supports spatiotemporal interaction effects following Knorr-Held (2000).
Interaction Types
| Type | Description | Parameters |
|---|---|---|
| Type I | Unstructured (IID) | |
| Type II | Structured time at each location | |
| Type III | Structured space at each time | |
| Type IV | Fully structured (Kronecker) | |
| Separable | For GP spatial/temporal |
Type I: Unstructured Interaction
The simplest form assumes independent random effects for each space-time combination:
fit <- tratio(
count | effort ~ x,
data = df,
family = ratiod_poisson_gamma(),
spatiotemporal = spatiotemporal(
spatial = spatial_car(adj, level = "group", group_var = "region"),
temporal = temporal_rw1("year"),
type = "I"
)
)Type II: Structured Time at Each Location
Each location has its own temporal random walk:
This captures location-specific temporal trends.
fit <- tratio(
count | effort ~ x,
data = df,
family = ratiod_poisson_gamma(),
spatiotemporal = spatiotemporal(
spatial = spatial_car(adj, level = "group", group_var = "region"),
temporal = temporal_rw1("year"),
type = "II"
)
)Type III: Structured Space at Each Time Point
Each time point has its own spatial field:
This captures time-specific spatial patterns.
fit <- tratio(
count | effort ~ x,
data = df,
family = ratiod_poisson_gamma(),
spatiotemporal = spatiotemporal(
spatial = spatial_car(adj, level = "group", group_var = "region"),
temporal = temporal_rw1("year"),
type = "III"
)
)Type IV: Fully Structured (Kronecker)
Precision is the Kronecker product of spatial and temporal precision:
This is the most constrained and often most appropriate model.
fit <- tratio(
count | effort ~ x,
data = df,
family = ratiod_poisson_gamma(),
spatiotemporal = spatiotemporal(
spatial = spatial_car(adj, level = "group", group_var = "region"),
temporal = temporal_rw1("year"),
type = "IV"
)
)Non-Separable Spatiotemporal GP
For continuous space-time data, use spatiotemporal_gp()
with non-separable covariance:
fit <- tratio(
count | effort ~ x,
data = df,
family = ratiod_poisson_gamma(),
spatiotemporal = spatiotemporal_gp(
~ lon + lat,
time_var = "date",
nonsep_type = "gneiting"
)
)Non-separability types:
-
"product": Separable (reference) -
"sum": Additive covariance -
"gneiting": Gneiting (2002) class -
"cressie_huang": Cressie-Huang (1999) class
Extracting Spatiotemporal Effects
# Summary format
st_effects <- spatiotemporal_effects(fit, format = "summary")
head(st_effects)
# Array format (S x T x draws)
st_array <- spatiotemporal_effects(fit, format = "array")
# Visualization
plot(st_effects, type = "heatmap")
plot(st_effects, type = "time_series")Model Selection
Compare interaction types using LOO-CV:
fit_type1 <- tratio(..., spatiotemporal = spatiotemporal(..., type = "I"))
fit_type4 <- tratio(..., spatiotemporal = spatiotemporal(..., type = "IV"))
loo_compare(loo(fit_type1), loo(fit_type4))Prior Specification
Spatial Priors
fit <- tratio(
...,
spatial = spatial_car(adj, level = "group", group_var = "region"),
priors = ratiod_priors(
tau_spatial_shape = 1.0, # Gamma shape for precision
tau_spatial_rate = 0.01 # Gamma rate for precision
)
)Temporal Priors
fit <- tratio(
...,
temporal = temporal_rw(time_var = "year"),
priors = ratiod_priors(
tau_temporal_shape = 2.0,
tau_temporal_rate = 0.5
)
)See Also
-
vignette("getting-started")- Basic usage -
vignette("workflows")- Complete analysis examples -
?spatial_car,?spatial_bym2- Function documentation -
?temporal_rw,?temporal_ar1- Function documentation