---
title: "Advanced Topics"
author: "Ricardo G. Tomáz"
description: "Advanced model specification, uncertainty, diagnostics, and computational considerations"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Advanced Topics}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  markdown: 
    wrap: 72
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.align = "center",
  message = FALSE,
  warning = FALSE
)
```

# Introduction

Most `EpiExposure` workflows can be completed using the default model
specification introduced in previous sections

However, advanced applications often require additional flexibility
regarding:

- model engines;
- uncertainty estimation;
- spline specification;
- identifiability diagnostics;
- computational performance.

This section discusses these advanced topics and provides
recommendations for robust DLNM analyses.

# Alternative model engines

`EpiExposure` separates exposure-lag specification from model
estimation.

Consequently, the same DLNM structure can be fitted using different
regression engines.

# Supported families by modeling engine

The modeling engines available in `fit_epidlnm()` and `find_bestfit()`
differ in the response families exposed through the `EpiExposure`
interface. The table below summarizes the current `EpiExposure` support.
It describes the package interface rather than every family accepted
natively by the underlying modeling packages.

| Engine    | Beta | Binomial       | Poisson | Gamma | Gaussian | Negative binomial |
|:----------|:----:|:---------------|:-------:|:-----:|:--------:|:-----------------:|
| `glm`     |  No  | Yes            |   Yes   |  Yes  |   Yes    |        No         |
| `glmmTMB` | Yes  | Yes            |   Yes   |  Yes  |   Yes    |     Yes, NB2      |
| `gam`     | Yes  | Yes            |   Yes   |  Yes  |   Yes    |     Yes, NB2      |
| `gamm`    |  No  | Yes            |   Yes   |  Yes  |   Yes    |        No         |
| `gls`     |  No  | No             |   No    |  No   |   Yes    |        No         |
| `spaMM`   | Yes  | Yes            |   Yes   |  Yes  |   Yes    |     Yes, NB2      |
| `brms`    | Yes  | Yes, Bernoulli |   Yes   |  Yes  |   Yes    |     Yes, NB2      |
| `INLA`    | Yes  | Yes            |   Yes   |  Yes  |   Yes    |     Yes, NB2      |
| `bdlnm`   | Yes  | Yes            |   Yes   |  Yes  |   Yes    |     Yes, NB2      |

**Note:** Negative-binomial support refers to the quadratic-variance NB2
parameterization. The table describes the families currently exposed
through the `EpiExposure` interface, not every family that may be
available natively in the underlying modeling packages. For INLA-backed
engines, availability also depends on the likelihoods provided by the
installed INLA version.

Because family specification follows the same `fit_epidlnm()` interface
across engines, the sections below do not repeat every supported
engine-family combination. Instead, each example highlights a capability
that differs meaningfully across engines, such as conventional random
effects, spatial autocorrelation, or Bayesian inference.

# Reference DLNM design

The examples below use the same cross-basis specification whenever
possible. This allows the modeling engines to be compared without
repeatedly rebuilding the exposure-lag design.

```{r}
library(EpiExposure)
```


```{r}
data("poisson_data")

head(poisson_data)
```

```{r}
cb <- define_exposures(
  data = poisson_data,
  vars = c(
    "tmean",
    "rain",
    "wetness"
  ),
  max_lag = 85,
  df_var = 3,
  df_lag = 2
)

dat <- build_design(
  data = poisson_data,
  cb_templates = cb,
  groups = c(
    "epi_id",
    "block"
  )
)
```

```{r}
dat_poisson <- prepare_response(
  data = dat,
  response = "y",
  family = "poisson"
)
```

# Fixed-effect reference model

A generalized linear model provides the simplest fixed-effect
specification. It is useful as a reference when random or spatial
dependence is not required.

```{r}
fit_glm <- fit_epidlnm(
  data = dat_poisson,
  model_engine = "glm",
  family = "poisson",
  epiexposure_spec = attr(
    cb,
    "spec"
  )
)
```

```{r}
summary(fit_glm)
```

# Conventional random effects

When epidemics are grouped within experimental blocks, observations from
the same block may share unmeasured conditions. A random intercept can
account for this hierarchical dependence.

```{r}
fit_glmm <- fit_epidlnm(
  data = dat_poisson,
  model_engine = "glmmTMB",
  family = "poisson",
  random_effect = "block",
  epiexposure_spec = attr(
    cb,
    "spec"
  )
)
```

```{r}
summary(fit_glmm)
```

The term generated internally is equivalent to:

`(1 | block)`

This effect represents discrete group-level heterogeneity. It does not
model spatial correlation as a function of distance.

## GAM estimation

The `gam` engine can fit the DLNM design with `mgcv`. Beta and
negative-binomial models use `mgcv` extended families and are fitted
through the supported REML route by default.

```{r}
data("nb2_data")

head(nb2_data)
```

```{r}
dat_nb2 <- build_design(
  data = nb2_data,
  cb_templates = cb,
  groups = c(
    "epi_id",
    "block"
  )
)

dat_nb2 <- prepare_response(
  data = dat_nb2,
  response = "y",
  family = "negative_binomial"
)
```

```{r}
fit_gam <- fit_epidlnm(
  data = dat_nb2,
  model_engine = "gam",
  family = "negative_binomial",
  epiexposure_spec = attr(
    cb,
    "spec"
  )
)
```

```{r}
summary(fit_gam)
```

## GAMM estimation

The `gamm` engine combines the fixed DLNM design with a conventional
random intercept. In the current `EpiExposure` interface,
`random_effect` is required.

```{r}
fit_gamm <- fit_epidlnm(
  data = dat_poisson,
  model_engine = "gamm",
  family = "poisson",
  random_effect = "block",
  epiexposure_spec = attr(
    cb,
    "spec"
  )
)
```

The fitted object contains two native components:

```{r}
summary(fit_gamm)
```

```{r}
summary(
  fit_gamm,
  component = "lme"
)
```

The default summary describes the fixed/population GAM component. The
`component = "lme"` option displays the mixed-model component.

For non-Gaussian responses, `mgcv::gamm()` uses a PQL-based fitting
route. Binary responses should therefore be treated cautiously.

# Spatially autocorrelated effects with `spaMM`

Consider a regional study of a foliar rice disease conducted at
spatially distributed field locations. Daily mean temperature, leaf
wetness duration, and rainfall are recorded during the 86 days preceding
the final assessment. The outcome is the final number of lesions per
plant.

The cross-basis terms estimate non-linear and delayed environmental effects. A
Matérn random effect accounts for residual geographic similarity among
nearby fields after those measured environmental effects have been considered.

## Spatially dependent fields

```{r}
data("st_poisson")

head(st_poisson)
```

```{r, eval=FALSE}

cb_spatial <- define_exposures(
  data = st_poisson,
  vars = c(
    "tmean",
    "rain",
    "wetness"
  ),
  max_lag = 85,
  df_var = 3,
  df_lag = 2
)

dat_spatial <- build_design(
  data = st_poisson,
  cb_templates = cb_spatial,
  groups = c(
    "epi_id",
    "x_coord",
    "y_coord"
  )
)

dat_spatial <- prepare_response(
  data = dat_spatial,
  response = "y",
  family = "poisson"
)
```

```{r, eval=FALSE}
fit_spatial <- fit_epidlnm(
  data = dat_spatial,
  model_engine = "spamm",
  family = "poisson",
  random_effect = NULL,
  spatial_effect = c(
    "x_coord",
    "y_coord"
  ),
  spatial_structure = "matern",
  spatial_group = NULL,
  epiexposure_spec = attr(
    cb_spatial,
    "spec"
  )
)

```

```{r, eval=FALSE}
summary(fit_spatial)
```

This specification generates:

`Matern(1 | x_coord + y_coord)`

The Matérn term is distinct from a conventional random intercept. It
represents autocorrelation that changes continuously with the distance
between locations.

## Independent spatial fields by year

If the disease process is expected to have a different spatial
realization in each growing season, `year` can define independent Matérn
fields:

```{r}
data("spatial_year")

head(spatial_year)
```

```{r, eval=FALSE}
dat_spatial_year <- build_design(
  data = spatial_year,
  cb_templates = cb,
  groups = c(
    "epi_id",
    "x_coord",
    "y_coord",
    "year",
    "location_id",
    "block_id"
  )
)

dat_spatial_year <- prepare_response(
  data = dat_spatial_year,
  response = "y",
  family = "poisson"
)
```

```{r, eval=FALSE}
fit_spatial_year <- fit_epidlnm(
  data = dat_spatial_year,
  model_engine = "spamm",
  family = "poisson",
  random_effect = "block_id",
  spatial_effect = c(
    "x_coord",
    "y_coord"
  ),
  spatial_structure = "matern",
  spatial_group = "year",
  epiexposure_spec = attr(
    cb_spatial,
    "spec"
  )
)

```

```{r, eval=FALSE}
summary(fit_spatial_year)
```

The resulting spatial term is:

`Matern(1 | x_coord + y_coord %in% year)`

In this model, `(1 | block_id)` represents conventional experimental
heterogeneity, whereas the Matérn term represents spatially
autocorrelated residual variation within each year.

## Independent spatial fields across epidemiological replicates

The grouping variable in a Matérn term can identify independent
repetitions of an entire epidemiological experiment. Consider a field
study conducted at 130 fixed locations. The same host, pathogen,
experimental layout, and observation period are used in four independent
repetitions.

Each repetition contains an 86-day exposure profile followed by one
final disease assessment at each location. Temperature, rainfall, and
leaf wetness can differ among repetitions, but the temporal positions
remain comparable: the most recent observation corresponds to lag zero,
and the oldest observation corresponds to lag 85.

Each combination of location and repetition represents a separate
epidemic:

`L001_Replicate_1`

`L001_Replicate_2`

`L001_Replicate_3`

`L001_Replicate_4`

The cross-basis terms use the exposure profile from each epidemic to
estimate the shared non-linear and delayed environmental associations.
However, the residual spatial pattern of disease is not required to
remain in the same locations across repetitions.

Independent residual spatial fields can be specified as:

`Matern(1 | x_coord + y_coord %in% epidemic_replicate)`

The model therefore allows each repetition to produce a different
realized map of residual disease intensity. For example, areas with high
residual disease in the first repetition do not need to remain high in
the second repetition.

The Matérn parameters remain shared among repetitions. Consequently, all
repetitions contribute jointly to estimation of the general magnitude,
smoothness, and spatial scale of residual dependence, while retaining
separate realized spatial fields.

```{r}
data("spatial_replicate")

head(spatial_replicate)
```

```{r, eval=FALSE}

cb_replicate <- define_exposures(
  data = spatial_replicate,
  vars = c(
    "tmean",
    "rain",
    "wetness"
  ),
  max_lag = 85,
  df_var = 3,
  df_lag = 2
)

dat_spatial_replicate <- build_design(
  data = spatial_replicate,
  cb_templates = cb_replicate,
  groups = c(
    "epi_id",
    "x_coord",
    "y_coord",
    "epidemic_replicate",
    "location_id",
    "block_id"
    
  )
)

dat_spatial_replicate <- prepare_response(
  data = dat_spatial_replicate,
  response = "y",
  family = "poisson"
)
```

```{r, eval=FALSE}
fit_spatial_replicate <- fit_epidlnm(
  data = dat_spatial_replicate,
  model_engine = "spamm",
  family = "poisson",
  random_effect = "block_id",
  spatial_effect = c(
    "x_coord",
    "y_coord"
  ),
  spatial_structure = "matern",
  spatial_group = "epidemic_replicate",
  epiexposure_spec = attr(
    cb_replicate,
    "spec"
  )
)
```

```{r, eval=FALSE}
summary(fit_spatial_replicate)
```

The conventional random intercept and the spatial term represent
different sources of dependence. The `block_id` effect accounts for
discrete experimental heterogeneity, whereas the Matérn term accounts
for continuous residual similarity among nearby locations within each
epidemiological repetition.

An epidemiological replicate must represent an independent repetition of
the complete disease process. Repeated assessments made during the
progression of a single epidemic should not be treated as independent
replicates.

# Gaussian generalized least squares

The `gls` engine is restricted to Gaussian outcomes with the identity
link and does not support the `EpiExposure` random-intercept interface.

```{r}
data("gaussian_data")

head(gaussian_data)
```

```{r, eval=FALSE}
cb_gaussian <- define_exposures(
  data = gaussian_data,
  vars = c(
    "tmean",
    "rain",
    "wetness"
  ),
  max_lag = 85,
  df_var = 3,
  df_lag = 2
)

dat_gaussian <- build_design(
  data = gaussian_data,
  cb_templates = cb_gaussian,
  groups = c(
    "epi_id"))

dat_gaussian <- prepare_response(
  data = dat_gaussian,
  response = "y",
  family = "gaussian"
)


```

```{r, eval=FALSE}
fit_gls <- fit_epidlnm(
  data = dat_gaussian,
  model_engine = "gls",
  family = "gaussian",
  epiexposure_spec = attr(
    cb_gaussian,
    "spec"
  ),
  method = "REML"
)

```

```{r, eval=FALSE}
summary(fit_gls)
```

 **Note:** The INLA and B-DLNM commands in the following sections are
 shown but not evaluated during vignette construction because these
 models may require substantial computational time. Users can execute
 the code interactively after installing the optional packages `INLA`,
 `bdlnm`, and `sn`.

# MCMC estimation with `brms`

The `brms` engine provides posterior sampling for the fixed DLNM
coefficients and any requested hierarchical components.

```{r, eval=FALSE}
fit_brms <- fit_epidlnm(
  data = dat_poisson,
  model_engine = "brms",
  family = "poisson",
  random_effect = "block",
  epiexposure_spec = attr(
    cb,
    "spec"
  ),
  chains = 4,
  iter = 2000,
  seed = 123
)


```

```{r, include = FALSE, eval=FALSE}
fit_brms = readRDS("data/fit_brms.rds")
```

```{r, eval=FALSE}
summary(fit_brms, eval=FALSE)
```


# Bayesian DLNM with `bdlnm`

The `bdlnm` engine provides a Bayesian implementation specifically
designed for distributed lag linear and non-linear models. Internally,
the native `bdlnm` package fits the model using Integrated Nested
Laplace Approximation through `INLA::inla()` and then draws samples from
the approximate posterior distribution using
`INLA::inla.posterior.sample()`. 【1-fe3db1】

Unlike the direct `inla` engine, which receives the epidemic-level
`cb_*` columns as conventional fixed predictors, the `bdlnm` engine
receives the original named `crossbasis` objects in the model formula.
The fitted object stores the underlying INLA model, the basis objects,
posterior coefficient draws, and summaries of those draws. 【1-fe3db1】

In `EpiExposure`, this engine is integrated into the same prediction and
uncertainty contract used by the other engines. Posterior fixed-effect
draws are retained for draw-by-draw propagation through
`summarise_effects()`, `predict_outcomes()`, and other downstream
functions.


```{r, eval=FALSE}
cb_bdlnm <- define_exposures(
  data = poisson_data,
  vars = c(
    "tmean",
    "rain",
    "wetness"
  ),
  max_lag = 85,
  df_var = 3,
  df_lag = 2
)


dat_bdlnm <- build_design(
  data = poisson_data,
  cb_templates = cb_bdlnm,
  groups = c(
    "epi_id",
    "block"
  )
)

dat_bdlnm <- prepare_response(
  data = dat_bdlnm,
  response = "y",
  family = "poisson"
)

bdlnm_bases <- attr(
  dat_bdlnm,
  "epiexposure_bdlnm_basis_objects",
  exact = TRUE
)
```

The model can then be fitted through the harmonized `EpiExposure`
interface:

```{r, eval=FALSE}
fit_bdlnm <- fit_epidlnm(
  data = dat_bdlnm,
  model_engine = "bdlnm",
  family = "poisson",
  random_effect = "block",
  random_effect_prior = list(
    prec = list(prior = "pc.prec",param = c(1,0.01))),
  epiexposure_spec = attr(dat_bdlnm, "epiexposure_spec"),
  sample.arg = list(n = 1000, seed = 123))
```

```{r, eval=FALSE}
summary(fit_bdlnm)
```

```{r, eval=FALSE}
fit_bdlnm$model
#fit_bdlnm$coefficients
fit_bdlnm$coefficients.summary


fit_bdlnm$model$summary.random
fit_bdlnm$model$summary.hyperpar

fit_bdlnm$model$summary.random$.epiexposure_re_index

head(fit_bdlnm$model$marginals.hyperpar)

```

The `random_effect = "block"` argument is translated internally to an
independent latent effect of the form

`f(block, model = "iid")`

for INLA-backed engines. When supplied, `random_effect_prior` is passed
directly to the `hyper` argument of the latent effect specification,
allowing custom hyperprior definitions for the random-effect precision.
For example,

`f(block, model = "iid", hyper = random_effect_prior)`

The `sample.arg` list controls posterior coefficient sampling performed
by the native `bdlnm` engine. A nonzero seed provides reproducible
posterior sampling. The native package uses 1,000 posterior samples by
default, although the number of samples can be changed according to the
required precision and available computational resources.

For deterministic `EpiExposure` predictions, posterior means of the
fixed/population coefficients define the central expected response. When
uncertainty is requested, the stored joint posterior coefficient draws
are propagated through the inverse link one draw at a time.

The resulting intervals describe uncertainty in the population-level
expected response. They do not include observation-level noise or
group-specific random effects, even when random effects are included in
the fitted model.

For both `model_engine = "inla"` and `model_engine = "bdlnm"`, random-
effect hyperparameters can be inspected through the underlying INLA fit
(e.g., `summary.hyperpar`), allowing users to evaluate posterior
distributions of random-effect precision parameters.

# Direct INLA versus B-DLNM

Both engines rely on INLA for Bayesian inference and support the same
likelihood families, random effects, and hyperprior specifications.
The main difference lies in how the DLNM basis is represented and what
is returned to the user.

- `model_engine = "inla"` fits the epidemic-level `cb_*` columns
  directly as fixed predictors through `INLA::inla()`. The resulting
  object contains the standard INLA outputs, including posterior
  summaries, marginal distributions, hyperparameter estimates, and
  random-effect summaries.

- `model_engine = "bdlnm"` passes named `crossbasis` objects to the
  native `bdlnm` interface. Internally, the model is fitted through
  INLA, but the original DLNM basis representation is preserved and
  posterior coefficient samples are generated automatically for
  simulation-based uncertainty propagation.
  
  
```{r, eval=FALSE}
fit_inla <- fit_epidlnm(
  data = dat_poisson,
  model_engine = "inla",
  family = "poisson",
  random_effect = "block",
  random_effect_prior = list(
      prec = list(
      prior = "pc.prec",
      param = c(1,0.01))),
  epiexposure_spec = attr(cb, "spec"))
```

### Outputs shared by INLA and B-DLNM

Fixed-effect posterior summaries:

```{r, eval=FALSE}
fit_inla$summary.fixed

fit_bdlnm$model$summary.fixed
```

Random-effect posterior summaries:

```{r, eval=FALSE}
fit_inla$summary.random

fit_bdlnm$model$summary.random
```

Posterior distributions of hyperparameters:

```{r, eval=FALSE}
fit_inla$marginals.hyperpar

fit_bdlnm$model$marginals.hyperpar
```

Marginal likelihood:

```{r, eval=FALSE}
fit_inla$mlik

fit_bdlnm$model$mlik
```

For both engines, random-effect hyperparameters can be inspected through
the INLA summaries. For example:

```{r, eval=FALSE}
fit_inla$summary.hyperpar

fit_bdlnm$model$summary.hyperpar
```

## Outputs specific to B-DLNM

```{r, eval=FALSE}
names(fit_bdlnm)
```

```{r, eval=FALSE}
names(fit_inla)
```

Original DLNM basis objects:

```{r, eval=FALSE}
fit_bdlnm$basis
```

Posterior coefficient draws:

```{r, eval=FALSE}

dim(fit_bdlnm$coefficients)

head(fit_bdlnm$coefficients)

```

Posterior coefficient summaries:

```{r, eval=FALSE}

fit_bdlnm$coefficients.summary
```

### Model objects

```{r, eval=FALSE}
class(fit_inla)

class(fit_bdlnm)

class(fit_bdlnm$model)
```

The direct INLA engine is useful when users want a conventional INLA
representation of the epidemic-level design matrix and direct access
to standard INLA outputs. The B-DLNM engine is useful when users want
to retain the native DLNM representation and work directly with
posterior coefficient samples, fitted basis objects, and Bayesian
DLNM workflows.
