---
title: "3. Spatio-temporal disease mapping"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{3. Spatio-temporal disease mapping}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE, purl = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE)
```

When the same regions are observed at several times, add `time =` and `SDALGCP2`
fits a separable space–time model. This tutorial is self-contained.

## The model

For region \(i\) at time \(t\), counts \(Y_{it}\) with offset \(m_{it}\):
\[
Y_{it}\mid S \sim \mathrm{Poisson}\!\big(m_{it}\,e^{\eta_{it}}\big),
\qquad \eta_{it}=d_{it}^\top\beta + S_{it},
\]
with a **separable** space–time covariance
\[
\mathrm{Cov}(S_{it},S_{js}) \;=\; \sigma^2\,R_s(\lVert\cdot\rVert;\phi)_{ij}\;R_t(|t-s|;\nu)_{ts},
\]
i.e. \(\mathrm{Cov}(\mathrm{vec}\,S)=\sigma^2\,(R_t(\nu)\otimes R_s(\phi))\), where
\(R_s\) is the aggregated spatial correlation (range \(\phi\)) and \(R_t\) a temporal
Matérn correlation (range \(\nu\)). `SDALGCP2` never forms the
\((N\!\cdot\!T)\times(N\!\cdot\!T)\) covariance — it uses the Kronecker identities
\(x^\top(R_t\otimes R_s)^{-1}x=\mathrm{tr}(R_s^{-1}M R_t^{-1}M^\top)\) and
\(\log|R_t\otimes R_s|=N\log|R_t|+T\log|R_s|\) — so it scales to many regions and
times. The temporal range \(\nu\) is estimated alongside \(\beta,\sigma^2,\phi\).

## The data

One row per region **and** time, with the geometry repeated. Here four years on a
6×6 lattice:

```{r, purl = FALSE}
library(SDALGCP2)
library(sf)

set.seed(7)
shp <- st_sf(geometry = st_make_grid(
  st_as_sfc(st_bbox(c(xmin = 0, ymin = 0, xmax = 18, ymax = 18))), n = c(6, 6)))
N <- nrow(shp); times <- 2019:2022; T <- length(times)

# simulate separable space-time counts (true beta=0.5)
pts <- sda_points(shp, delta = 1.4, method = 3)
Rs  <- precompute_corr(pts, 3)$R[, , 1]
Rt  <- SDALGCP2:::.temporal_corr(seq_len(T), 1.5, 0.5)
L   <- t(chol(0.4 * kronecker(Rt, Rs)))
x1  <- rnorm(N * T); pop <- round(runif(N * T, 1000, 5000))
y   <- rpois(N * T, pop * exp(-6 + 0.5 * x1 + as.numeric(L %*% rnorm(N * T))))

dat <- st_sf(
  data.frame(cases = y, x1 = x1, pop = pop, year = rep(times, each = N)),
  geometry = st_geometry(shp)[rep(seq_len(N), T)])
```

![](t3_data.png)

## Fit

```{r, purl = FALSE}
fit <- sdalgcp(cases ~ x1 + offset(log(pop)), data = dat, time = "year",
               control = sdalgcp_control(reanchor = 2))
summary(fit)
```

```
#> Coefficients:
#>             Estimate Std.Err z value Pr(>|z|)
#> (Intercept)  -6.167   0.107  -57.61   <2e-16 ***
#> x1            0.592   0.038   15.73   <2e-16 ***
#> sigma^2       0.491   0.205    2.39    0.017 *
#> nu            0.853   0.360    2.37    0.018 *
#>
#> Spatial scale phi: 2.02
```

`nu` is the temporal range (correlation length in years); `phi` the spatial range.

## Predict and map — pick a year and a quantity

`predict()` returns a long `sf` — one row per region **and** time, with columns
`relative_risk`, `adjusted_rr` and their standard errors (the same column names as
the spatial predictor, so you can `st_write()` or map it directly). The `plot()`
method selects a **time** and a **quantity** (`"relative_risk"`, `"adjusted_rr"`,
`"relative_risk_se"`, `"adjusted_rr_se"`, `"exceedance"`):

```{r, purl = FALSE}
pr <- predict(fit)

plot(pr, time = NULL, what = "adjusted_rr")          # facet all years
plot(pr, time = 2021, what = "relative_risk")        # relative risk, 2021 only
plot(pr, time = 2021, what = "exceedance", threshold = 1.3, which = "adjusted_rr")
```

Covariate-adjusted relative risk, all years:

![](t3_arr_facet.png)

| Relative risk, 2021 | P(adjusted RR > 1.3), 2021 |
|:---:|:---:|
| ![](t3_rr_2021.png) | ![](t3_exc_2021.png) |

You can equally call `plot(fit, time = 2021, what = "relative_risk")` directly on
the fit, and `pr` (the returned `sf`) is a long region × time table of every
quantity for further analysis or mapping.

## Covariates and confounding

The space–time model supports the same covariate extensions as the spatial one.
The covariate surface is taken to be **time-invariant** (a covariate that genuinely
changes over time can always go in as an ordinary `data` column); the intensity-
scale tilting is then computed once per region and reused across the times, fitted
by a Gauss–Newton loop around the space–time likelihood.

**Raster (intensity-scale) covariates** — a spatially continuous covariate supplied
as a `terra` raster, aggregated correctly under the log link (see the
[raster tutorial](raster-covariates.html)):

```{r, purl = FALSE}
library(terra)
fit_r <- sdalgcp(cases ~ elevation + offset(log(pop)), data = dat, time = "year",
                 rasters = elevation_raster)
```

**Misaligned covariates** — a covariate observed on a different (time-invariant)
support, e.g. pollution monitors, kriged to the candidate points with a Berkson
correction (see [misaligned covariates](misaligned-covariates.html)):

```{r, purl = FALSE}
fit_m <- sdalgcp(cases ~ pm25 + offset(log(pop)), data = dat, time = "year",
                 covariates = list(pm25 = monitors_sf))
```

**Spatial confounding** — when a covariate is spatially smooth it can be collinear
with the random effect; restricted spatial regression de-confounds the coefficient
(see [spatial confounding](spatial-confounding.html)). It generalises to space–time
by constraining the random effect orthogonal to the design over all region–times:

```{r, purl = FALSE}
fit_c <- sdalgcp(cases ~ x1 + offset(log(pop)), data = dat, time = "year",
                 control = sdalgcp_control(confounding = "restricted"))
```

The restricted space–time fit reduces exactly to the spatial restricted fit when
there is a single time, and forms the full \((N\!\cdot\!T)\times(N\!\cdot\!T)\)
covariance, so it is best suited to modest \(N\!\cdot\!T\).

## Tips

Spatio-temporal fits profile the spatial range `phi` on a grid (set it with
`sdalgcp_control(phi = ...)`); the temporal range `nu` is estimated continuously.
Increase `reanchor` if the variance parameters look unstable.
```
