First-week intervals: fit, profile, and boundary

This page is for an applied ecology or evolution user in the first week with drmTMB. It walks one short path: fit → inventory targets → profile a random-effect SD → read profile.boundary → decide when not to trust the interval. It restates the default uncertainty story in ?confint.drmTMB; it does not claim nominal coverage on every route.

For the longer post-fit toolbox (check_drm(), Wald shortcuts, bootstrap, newdata profiles), read Checking and using fitted models. For which cells have coverage evidence, read Can I fit and report this model?.

Default recipe (read once)

  1. Fixed effects and other routine Wald-ready targets: start with confint(fit).
  2. Random-effect SDs and other direct variance components: prefer confint(..., method = "profile") after profile_targets() shows the row is profile-ready.
  3. Always read conf.status and profile.boundary on the returned table.
  4. A computable interval is not coverage certification.

Fit a small random-intercept model

The example is intentionally tiny and non-phylogenetic so it stays CRAN-safe and fast:

library(drmTMB)

set.seed(13)
n_site <- 18
n_per_site <- 6
site <- factor(rep(seq_len(n_site), each = n_per_site))
site_effect <- rnorm(n_site, sd = 0.45)

fish_site <- data.frame(
  site = site,
  temperature = runif(n_site * n_per_site, -1.5, 1.5),
  habitat = factor(
    sample(c("reef", "kelp"), n_site * n_per_site, replace = TRUE)
  )
)

site_mu <- site_effect[as.integer(fish_site$site)]
fish_site$growth <- rnorm(
  nrow(fish_site),
  mean = 1 + 0.7 * fish_site$temperature +
    0.35 * (fish_site$habitat == "kelp") + site_mu,
  sd = 0.35
)

fit_site <- drmTMB(
  bf(growth ~ temperature + habitat + (1 | site), sigma ~ 1),
  family = gaussian(),
  data = fish_site
)

Run check_drm(fit_site) before interpreting estimates. A clean diagnostic table catches optimizer and Hessian problems; it does not certify coverage.

Inventory targets, then profile the RE-SD

profile_targets(fit_site)
#>                          parm         target_class  dpar        term
#> 1        fixef:mu:(Intercept)         fixed-effect    mu (Intercept)
#> 2        fixef:mu:temperature         fixed-effect    mu temperature
#> 3        fixef:mu:habitatreef         fixed-effect    mu habitatreef
#> 4     fixef:sigma:(Intercept)         fixed-effect sigma (Intercept)
#> 5                       sigma distributional-scale sigma  (constant)
#> 6            sd:mu:(1 | site)     random-effect-sd    mu  (1 | site)
#> 7 derived:repeatability(site)      derived-summary    mu  (1 | site)
#>   tmb_parameter index   estimate link_estimate    scale   transformation
#> 1       beta_mu     1  1.3588788     1.3588788     link linear_predictor
#> 2       beta_mu     2  0.6814229     0.6814229     link linear_predictor
#> 3       beta_mu     3 -0.2416812    -0.2416812     link linear_predictor
#> 4    beta_sigma     1 -0.9603029    -0.9603029     link linear_predictor
#> 5    beta_sigma     1  0.3827769    -0.9603029 response              exp
#> 6     log_sd_mu     1  0.3721619    -0.9884264 response              exp
#> 7          <NA>    NA  0.4859419            NA response   variance_ratio
#>   target_type profile_ready   profile_note
#> 1      direct          TRUE          ready
#> 2      direct          TRUE          ready
#> 3      direct          TRUE          ready
#> 4      direct          TRUE          ready
#> 5      direct          TRUE          ready
#> 6      direct          TRUE          ready
#> 7     derived         FALSE derived_target

Wald is the fast first pass for fixed effects and other Wald-ready rows:

confint(fit_site, parm = "fixed_effects")
#>                      parm level      lower       upper scale   transformation
#> 1    fixef:mu:(Intercept)  0.95  1.1553271  1.56243054  link linear_predictor
#> 2    fixef:mu:temperature  0.95  0.5880442  0.77480166  link linear_predictor
#> 3    fixef:mu:habitatreef  0.95 -0.3939464 -0.08941602  link linear_predictor
#> 4 fixef:sigma:(Intercept)  0.95 -1.1064326 -0.81417319  link linear_predictor
#>   tmb_parameter index method profile.engine conf.status profile.boundary
#> 1       beta_mu     1   wald           <NA>        wald               NA
#> 2       beta_mu     2   wald           <NA>        wald               NA
#> 3       beta_mu     3   wald           <NA>        wald               NA
#> 4    beta_sigma     1   wald           <NA>        wald               NA
#>   profile.message
#> 1            <NA>
#> 2            <NA>
#> 3            <NA>
#> 4            <NA>

For the site random-intercept SD, prefer the profile route and copy the exact target name from profile_targets():

ci_site <- confint(
  fit_site,
  parm = "sd:mu:(1 | site)",
  method = "profile"
)
ci_site
#>               parm level     lower    upper    scale transformation
#> 1 sd:mu:(1 | site)  0.95 0.2554993 0.561249 response            exp
#>   tmb_parameter index  method profile.engine conf.status profile.boundary
#> 1     log_sd_mu     1 profile       endpoint     profile            FALSE
#>   profile.message
#> 1              ok

Read profile.boundary before you trust the interval

Successful profile rows use conf.status = "profile". When the profile lands near a variance-component boundary, profile.boundary is TRUE and confint(method = "profile") warns with class drmTMB_profile_boundary_warning.

ci_site[, c("parm", "lower", "upper", "conf.status", "profile.boundary")]
#>               parm     lower    upper conf.status profile.boundary
#> 1 sd:mu:(1 | site) 0.2554993 0.561249     profile            FALSE

When not to trust a returned interval