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.
data("poisson_data")
head(poisson_data)
#> epi_id block time tmean wetness rain y
#> 1 1 B01 0 25.32685 14.55902 11.629805 5
#> 2 1 B01 1 25.72697 13.35332 0.000000 5
#> 3 1 B01 2 24.88694 13.00895 8.036259 5
#> 4 1 B01 3 26.04031 13.22694 0.000000 5
#> 5 1 B01 4 25.94690 15.30767 6.709351 5
#> 6 1 B01 5 24.85839 17.41708 8.826920 5
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"
)
)
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.
fit_glm <- fit_epidlnm(
data = dat_poisson,
model_engine = "glm",
family = "poisson",
epiexposure_spec = attr(
cb,
"spec"
)
)
summary(fit_glm)
#>
#> Call:
#> stats::glm(formula = y_model ~ 1 + cb_tmean_1 + cb_tmean_2 +
#> cb_tmean_3 + cb_tmean_4 + cb_tmean_5 + cb_tmean_6 + cb_rain_1 +
#> cb_rain_2 + cb_rain_3 + cb_rain_4 + cb_rain_5 + cb_rain_6 +
#> cb_wetness_1 + cb_wetness_2 + cb_wetness_3 + cb_wetness_4 +
#> cb_wetness_5 + cb_wetness_6, family = "poisson", data = dat_poisson)
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -3.303652 1.132719 -2.917 0.003539 **
#> cb_tmean_1 0.090028 0.013837 6.506 7.71e-11 ***
#> cb_tmean_2 0.012584 0.025493 0.494 0.621554
#> cb_tmean_3 0.140732 0.043795 3.213 0.001312 **
#> cb_tmean_4 0.270869 0.097282 2.784 0.005363 **
#> cb_tmean_5 -0.027014 0.016290 -1.658 0.097252 .
#> cb_tmean_6 0.162029 0.039149 4.139 3.49e-05 ***
#> cb_rain_1 0.091313 0.025531 3.577 0.000348 ***
#> cb_rain_2 -0.259121 0.033323 -7.776 7.48e-15 ***
#> cb_rain_3 0.078788 0.015872 4.964 6.91e-07 ***
#> cb_rain_4 -0.064189 0.022854 -2.809 0.004975 **
#> cb_rain_5 0.001674 0.036894 0.045 0.963806
#> cb_rain_6 0.035621 0.050073 0.711 0.476846
#> cb_wetness_1 0.051027 0.003629 14.062 < 2e-16 ***
#> cb_wetness_2 -0.086751 0.010892 -7.965 1.65e-15 ***
#> cb_wetness_3 0.049067 0.014053 3.492 0.000480 ***
#> cb_wetness_4 -0.098276 0.036715 -2.677 0.007434 **
#> cb_wetness_5 0.059851 0.009419 6.355 2.09e-10 ***
#> cb_wetness_6 -0.084802 0.018470 -4.591 4.40e-06 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for poisson family taken to be 1)
#>
#> Null deviance: 2866.42 on 519 degrees of freedom
#> Residual deviance: 955.26 on 501 degrees of freedom
#> AIC: 3133.2
#>
#> Number of Fisher Scoring iterations: 4Conventional 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.
fit_glmm <- fit_epidlnm(
data = dat_poisson,
model_engine = "glmmTMB",
family = "poisson",
random_effect = "block",
epiexposure_spec = attr(
cb,
"spec"
)
)
summary(fit_glmm)
#> Family: poisson ( log )
#> Formula:
#> y_model ~ 1 + cb_tmean_1 + cb_tmean_2 + cb_tmean_3 + cb_tmean_4 +
#> cb_tmean_5 + cb_tmean_6 + cb_rain_1 + cb_rain_2 + cb_rain_3 +
#> cb_rain_4 + cb_rain_5 + cb_rain_6 + cb_wetness_1 + cb_wetness_2 +
#> cb_wetness_3 + cb_wetness_4 + cb_wetness_5 + cb_wetness_6 +
#> (1 | block)
#> Data: dat_poisson
#>
#> AIC BIC logLik -2*log(L) df.resid
#> 2920.3 3005.4 -1440.2 2880.3 500
#>
#> Random effects:
#>
#> Conditional model:
#> Groups Name Variance Std.Dev.
#> block (Intercept) 0.04283 0.207
#> Number of obs: 520, groups: block, 20
#>
#> Conditional model:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -2.574612 1.158675 -2.222 0.026281 *
#> cb_tmean_1 0.081602 0.014101 5.787 7.17e-09 ***
#> cb_tmean_2 -0.008901 0.025844 -0.344 0.730544
#> cb_tmean_3 0.115294 0.044915 2.567 0.010260 *
#> cb_tmean_4 0.170722 0.098491 1.733 0.083031 .
#> cb_tmean_5 -0.023288 0.017047 -1.366 0.171890
#> cb_tmean_6 0.119456 0.039905 2.994 0.002758 **
#> cb_rain_1 0.129022 0.026462 4.876 1.08e-06 ***
#> cb_rain_2 -0.239983 0.033977 -7.063 1.63e-12 ***
#> cb_rain_3 0.056071 0.016278 3.445 0.000572 ***
#> cb_rain_4 -0.057666 0.023722 -2.431 0.015063 *
#> cb_rain_5 -0.038092 0.037558 -1.014 0.310473
#> cb_rain_6 0.060427 0.052017 1.162 0.245368
#> cb_wetness_1 0.052540 0.003729 14.091 < 2e-16 ***
#> cb_wetness_2 -0.093637 0.011114 -8.425 < 2e-16 ***
#> cb_wetness_3 0.053720 0.014580 3.685 0.000229 ***
#> cb_wetness_4 -0.093537 0.037422 -2.499 0.012437 *
#> cb_wetness_5 0.055911 0.009722 5.751 8.87e-09 ***
#> cb_wetness_6 -0.090618 0.018671 -4.853 1.21e-06 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1The 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.
data("nb2_data")
head(nb2_data)
#> epi_id block time tmean wetness rain y
#> 1 1 B01 0 28.51652 3.304398 9.234377 7
#> 2 1 B01 1 25.78990 5.600390 19.225955 7
#> 3 1 B01 2 26.86089 7.060567 0.000000 7
#> 4 1 B01 3 25.19746 5.064939 8.700396 7
#> 5 1 B01 4 25.80954 5.634410 0.000000 7
#> 6 1 B01 5 25.28982 6.813331 5.096005 7
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"
)
fit_gam <- fit_epidlnm(
data = dat_nb2,
model_engine = "gam",
family = "negative_binomial",
epiexposure_spec = attr(
cb,
"spec"
)
)
summary(fit_gam)
#>
#> Family: Negative Binomial(3.312)
#> Link function: log
#>
#> Formula:
#> y_model ~ 1 + cb_tmean_1 + cb_tmean_2 + cb_tmean_3 + cb_tmean_4 +
#> cb_tmean_5 + cb_tmean_6 + cb_rain_1 + cb_rain_2 + cb_rain_3 +
#> cb_rain_4 + cb_rain_5 + cb_rain_6 + cb_wetness_1 + cb_wetness_2 +
#> cb_wetness_3 + cb_wetness_4 + cb_wetness_5 + cb_wetness_6
#>
#> Parametric coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.184097 2.303002 -0.080 0.936287
#> cb_tmean_1 0.050136 0.028851 1.738 0.082253 .
#> cb_tmean_2 -0.129826 0.052604 -2.468 0.013588 *
#> cb_tmean_3 0.009794 0.089261 0.110 0.912633
#> cb_tmean_4 -0.166707 0.200448 -0.832 0.405595
#> cb_tmean_5 -0.052436 0.036715 -1.428 0.153242
#> cb_tmean_6 0.012849 0.082609 0.156 0.876399
#> cb_rain_1 0.198301 0.057344 3.458 0.000544 ***
#> cb_rain_2 -0.328951 0.071447 -4.604 4.14e-06 ***
#> cb_rain_3 0.027017 0.036851 0.733 0.463466
#> cb_rain_4 -0.094688 0.050348 -1.881 0.060015 .
#> cb_rain_5 -0.085088 0.085783 -0.992 0.321251
#> cb_rain_6 -0.085474 0.106752 -0.801 0.423318
#> cb_wetness_1 0.066901 0.006780 9.868 < 2e-16 ***
#> cb_wetness_2 -0.082650 0.021324 -3.876 0.000106 ***
#> cb_wetness_3 0.094385 0.025050 3.768 0.000165 ***
#> cb_wetness_4 -0.012930 0.072911 -0.177 0.859238
#> cb_wetness_5 0.058337 0.016182 3.605 0.000312 ***
#> cb_wetness_6 -0.036181 0.044278 -0.817 0.413850
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#>
#> R-sq.(adj) = 0.467 Deviance explained = 55%
#> -REML = 1848.3 Scale est. = 1 n = 520GAMM estimation
The gamm engine combines the fixed DLNM design with a
conventional random intercept. In the current EpiExposure
interface, random_effect is required.
fit_gamm <- fit_epidlnm(
data = dat_poisson,
model_engine = "gamm",
family = "poisson",
random_effect = "block",
epiexposure_spec = attr(
cb,
"spec"
)
)
#>
#> Maximum number of PQL iterations: 20The fitted object contains two native components:
summary(fit_gamm)
#>
#> Family: poisson
#> Link function: log
#>
#> Formula:
#> y_model ~ 1 + cb_tmean_1 + cb_tmean_2 + cb_tmean_3 + cb_tmean_4 +
#> cb_tmean_5 + cb_tmean_6 + cb_rain_1 + cb_rain_2 + cb_rain_3 +
#> cb_rain_4 + cb_rain_5 + cb_rain_6 + cb_wetness_1 + cb_wetness_2 +
#> cb_wetness_3 + cb_wetness_4 + cb_wetness_5 + cb_wetness_6
#>
#> Parametric coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) -2.573106 1.180352 -2.180 0.029726 *
#> cb_tmean_1 0.081602 0.014365 5.681 2.28e-08 ***
#> cb_tmean_2 -0.008901 0.026325 -0.338 0.735418
#> cb_tmean_3 0.115297 0.045756 2.520 0.012051 *
#> cb_tmean_4 0.170724 0.100318 1.702 0.089407 .
#> cb_tmean_5 -0.023289 0.017367 -1.341 0.180525
#> cb_tmean_6 0.119458 0.040642 2.939 0.003442 **
#> cb_rain_1 0.129018 0.026943 4.789 2.21e-06 ***
#> cb_rain_2 -0.239982 0.034612 -6.934 1.27e-11 ***
#> cb_rain_3 0.056073 0.016573 3.383 0.000772 ***
#> cb_rain_4 -0.057667 0.024167 -2.386 0.017396 *
#> cb_rain_5 -0.038088 0.038251 -0.996 0.319851
#> cb_rain_6 0.060423 0.052990 1.140 0.254717
#> cb_wetness_1 0.052540 0.003798 13.832 < 2e-16 ***
#> cb_wetness_2 -0.093637 0.011321 -8.271 1.20e-15 ***
#> cb_wetness_3 0.053718 0.014853 3.617 0.000329 ***
#> cb_wetness_4 -0.093536 0.038125 -2.453 0.014490 *
#> cb_wetness_5 0.055910 0.009904 5.645 2.77e-08 ***
#> cb_wetness_6 -0.090616 0.019022 -4.764 2.49e-06 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#>
#> R-sq.(adj) = 0.631
#> Scale est. = 1 n = 520
summary(
fit_gamm,
component = "lme"
)
#> Linear mixed-effects model fit by maximum likelihood
#> Data: data
#> AIC BIC logLik
#> 524.4704 609.547 -242.2352
#>
#> Random effects:
#> Formula: ~1 | block
#> (Intercept) Residual
#> StdDev: 0.2066749 1
#>
#> Variance function:
#> Structure: fixed weights
#> Formula: ~invwt
#> Fixed effects: list(fixed)
#> Value Std.Error DF t-value p-value
#> X(Intercept) -2.5731056 1.1803515 482 -2.179949 0.0297
#> Xcb_tmean_1 0.0816015 0.0143649 482 5.680623 0.0000
#> Xcb_tmean_2 -0.0089008 0.0263249 482 -0.338115 0.7354
#> Xcb_tmean_3 0.1152970 0.0457561 482 2.519817 0.0121
#> Xcb_tmean_4 0.1707239 0.1003175 482 1.701835 0.0894
#> Xcb_tmean_5 -0.0232888 0.0173666 482 -1.341008 0.1805
#> Xcb_tmean_6 0.1194585 0.0406424 482 2.939258 0.0034
#> Xcb_rain_1 0.1290176 0.0269427 482 4.788593 0.0000
#> Xcb_rain_2 -0.2399821 0.0346118 482 -6.933535 0.0000
#> Xcb_rain_3 0.0560726 0.0165732 482 3.383323 0.0008
#> Xcb_rain_4 -0.0576668 0.0241673 482 -2.386149 0.0174
#> Xcb_rain_5 -0.0380883 0.0382508 482 -0.995753 0.3199
#> Xcb_rain_6 0.0604231 0.0529900 482 1.140273 0.2547
#> Xcb_wetness_1 0.0525396 0.0037983 482 13.832359 0.0000
#> Xcb_wetness_2 -0.0936366 0.0113209 482 -8.271142 0.0000
#> Xcb_wetness_3 0.0537184 0.0148528 482 3.616711 0.0003
#> Xcb_wetness_4 -0.0935359 0.0381246 482 -2.453426 0.0145
#> Xcb_wetness_5 0.0559104 0.0099040 482 5.645222 0.0000
#> Xcb_wetness_6 -0.0906163 0.0190219 482 -4.763798 0.0000
#> Correlation:
#> X(Int) Xcb_t_1 Xcb_t_2 Xcb_t_3 Xcb_t_4 Xcb_t_5 Xcb_t_6 Xcb_r_1
#> Xcb_tmean_1 -0.879
#> Xcb_tmean_2 -0.730 0.756
#> Xcb_tmean_3 -0.945 0.821 0.673
#> Xcb_tmean_4 -0.866 0.910 0.863 0.773
#> Xcb_tmean_5 -0.381 0.210 0.420 0.559 0.147
#> Xcb_tmean_6 -0.664 0.655 0.596 0.513 0.845 -0.279
#> Xcb_rain_1 0.012 -0.028 -0.016 -0.002 -0.021 0.022 -0.013
#> Xcb_rain_2 0.032 -0.074 -0.071 -0.021 -0.077 0.021 -0.056 -0.076
#> Xcb_rain_3 -0.063 0.022 -0.051 0.052 -0.001 0.036 -0.027 -0.662
#> Xcb_rain_4 -0.004 0.061 0.057 0.001 0.057 -0.015 0.032 0.033
#> Xcb_rain_5 -0.034 0.001 -0.048 0.035 -0.017 0.040 -0.041 -0.602
#> Xcb_rain_6 0.005 0.052 0.031 -0.015 0.040 -0.033 0.028 0.050
#> Xcb_wetness_1 0.002 -0.062 -0.036 -0.048 -0.047 -0.018 -0.016 0.054
#> Xcb_wetness_2 -0.023 -0.011 0.002 -0.038 -0.014 -0.035 -0.023 0.002
#> Xcb_wetness_3 -0.198 -0.043 -0.035 0.007 -0.027 0.023 -0.003 -0.045
#> Xcb_wetness_4 -0.048 -0.047 0.000 -0.046 -0.052 0.019 -0.059 0.015
#> Xcb_wetness_5 -0.122 0.006 -0.013 0.009 0.010 -0.008 0.026 -0.097
#> Xcb_wetness_6 -0.033 -0.002 -0.014 -0.002 -0.043 0.016 -0.060 0.048
#> Xcb_r_2 Xcb_r_3 Xcb_r_4 Xcb_r_5 Xcb_r_6 Xcb_w_1 Xcb_w_2 Xcb_w_3
#> Xcb_tmean_1
#> Xcb_tmean_2
#> Xcb_tmean_3
#> Xcb_tmean_4
#> Xcb_tmean_5
#> Xcb_tmean_6
#> Xcb_rain_1
#> Xcb_rain_2
#> Xcb_rain_3 0.040
#> Xcb_rain_4 -0.605 -0.011
#> Xcb_rain_5 0.025 0.938 0.020
#> Xcb_rain_6 -0.669 -0.010 0.866 -0.004
#> Xcb_wetness_1 0.046 -0.102 0.058 -0.108 0.027
#> Xcb_wetness_2 -0.045 0.053 -0.011 0.072 -0.012 -0.109
#> Xcb_wetness_3 0.029 0.113 -0.097 0.108 -0.055 -0.149 0.122
#> Xcb_wetness_4 0.048 -0.035 -0.045 -0.016 -0.036 0.381 0.470 0.106
#> Xcb_wetness_5 0.017 0.153 -0.122 0.148 -0.073 -0.579 0.203 0.785
#> Xcb_wetness_6 0.051 -0.076 -0.008 -0.061 0.007 0.358 0.128 -0.118
#> Xcb_w_4 Xcb_w_5
#> Xcb_tmean_1
#> Xcb_tmean_2
#> Xcb_tmean_3
#> Xcb_tmean_4
#> Xcb_tmean_5
#> Xcb_tmean_6
#> Xcb_rain_1
#> Xcb_rain_2
#> Xcb_rain_3
#> Xcb_rain_4
#> Xcb_rain_5
#> Xcb_rain_6
#> Xcb_wetness_1
#> Xcb_wetness_2
#> Xcb_wetness_3
#> Xcb_wetness_4
#> Xcb_wetness_5 -0.123
#> Xcb_wetness_6 0.732 -0.433
#>
#> Standardized Within-Group Residuals:
#> Min Q1 Med Q3 Max
#> -2.81800137 -0.78615982 -0.07432645 0.70191859 5.10243548
#>
#> Number of Observations: 520
#> Number of Groups: 20The 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
data("st_poisson")
head(st_poisson)
#> epi_id block time x_coord y_coord tmean wetness rain y
#> 1 1 B01 0 -0.4210796 -0.1649767 24.06189 10.81782 0.000000 4
#> 2 1 B01 1 -0.4210796 -0.1649767 24.82615 11.15336 0.000000 4
#> 3 1 B01 2 -0.4210796 -0.1649767 26.11794 12.20862 0.000000 4
#> 4 1 B01 3 -0.4210796 -0.1649767 26.17299 11.70761 6.440981 4
#> 5 1 B01 4 -0.4210796 -0.1649767 26.39594 13.08992 0.000000 4
#> 6 1 B01 5 -0.4210796 -0.1649767 26.23450 14.78350 0.000000 4
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"
)
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"
)
)
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:
data("spatial_year")
head(spatial_year)
#> epi_id location_id year block block_id time x_coord y_coord tmean
#> 1 L001_2022 L001 2022 B01 2022_B01 0 -0.6698125 0.622697 24.38006
#> 2 L001_2022 L001 2022 B01 2022_B01 1 -0.6698125 0.622697 24.53832
#> 3 L001_2022 L001 2022 B01 2022_B01 2 -0.6698125 0.622697 24.90186
#> 4 L001_2022 L001 2022 B01 2022_B01 3 -0.6698125 0.622697 26.04159
#> 5 L001_2022 L001 2022 B01 2022_B01 4 -0.6698125 0.622697 23.60107
#> 6 L001_2022 L001 2022 B01 2022_B01 5 -0.6698125 0.622697 25.79864
#> wetness rain y
#> 1 4.903199 12.904436 3
#> 2 6.781023 7.651138 3
#> 3 11.195548 0.000000 3
#> 4 10.530333 0.000000 3
#> 5 10.322681 0.000000 3
#> 6 12.433350 0.000000 3
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"
)
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"
)
)
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.
data("spatial_replicate")
head(spatial_replicate)
#> epi_id location_id epidemic_replicate block block_id time
#> 1 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 0
#> 2 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 1
#> 3 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 2
#> 4 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 3
#> 5 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 4
#> 6 L001_Replicate_1 L001 Replicate_1 B01 Replicate_1_B01 5
#> x_coord y_coord tmean wetness rain y
#> 1 0.7586476 -0.4480224 23.89557 6.798144 0 5
#> 2 0.7586476 -0.4480224 24.45162 7.355172 0 5
#> 3 0.7586476 -0.4480224 24.49880 8.565241 0 5
#> 4 0.7586476 -0.4480224 24.68348 12.894026 0 5
#> 5 0.7586476 -0.4480224 25.65530 14.958210 0 5
#> 6 0.7586476 -0.4480224 23.98108 13.466749 0 5
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"
)
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"
)
)
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.
data("gaussian_data")
head(gaussian_data)
#> epi_id block time tmean wetness rain y
#> 1 1 B01 0 29.16636 11.58879 0.00000 12.4501
#> 2 1 B01 1 27.30294 12.71218 0.00000 12.4501
#> 3 1 B01 2 26.86088 12.74422 0.00000 12.4501
#> 4 1 B01 3 26.29974 11.92657 10.12782 12.4501
#> 5 1 B01 4 26.67772 13.41525 0.00000 12.4501
#> 6 1 B01 5 26.63974 15.59525 0.00000 12.4501
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"
)
fit_gls <- fit_epidlnm(
data = dat_gaussian,
model_engine = "gls",
family = "gaussian",
epiexposure_spec = attr(
cb_gaussian,
"spec"
),
method = "REML"
)
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.
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
)
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.
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:
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))
summary(fit_bdlnm)
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-levelcb_*columns directly as fixed predictors throughINLA::inla(). The resulting object contains the standard INLA outputs, including posterior summaries, marginal distributions, hyperparameter estimates, and random-effect summaries.model_engine = "bdlnm"passes namedcrossbasisobjects to the nativebdlnminterface. 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.
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:
fit_inla$summary.fixed
fit_bdlnm$model$summary.fixedRandom-effect posterior summaries:
fit_inla$summary.random
fit_bdlnm$model$summary.randomPosterior distributions of hyperparameters:
fit_inla$marginals.hyperpar
fit_bdlnm$model$marginals.hyperparMarginal likelihood:
fit_inla$mlik
fit_bdlnm$model$mlikFor both engines, random-effect hyperparameters can be inspected through the INLA summaries. For example:
fit_inla$summary.hyperpar
fit_bdlnm$model$summary.hyperparOutputs specific to B-DLNM
names(fit_bdlnm)
names(fit_inla)Original DLNM basis objects:
fit_bdlnm$basisPosterior coefficient draws:
Posterior coefficient summaries:
fit_bdlnm$coefficients.summaryModel objects
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.
