--- title: "Model selection with AIC and BIC" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Model selection with AIC and BIC} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` Model selection comes after candidate models have been chosen, fitted, and checked with the post-fit workflow in [Checking and using fitted models](https://itchyshin.github.io/drmTMB/articles/model-workflow.html). In `drmTMB`, AIC and BIC can compare fitted distributional-regression models, but they do not decide whether a candidate set is biologically sensible or whether a weak fit should be interpreted. AIC and BIC both use the fitted log likelihood and the number of estimated parameters: ```text AIC = -2 * logLik + 2 * k BIC = -2 * logLik + log(n) * k ``` `k` is the model degrees of freedom and `n` is the number of observations used by the fitted model. BIC penalizes extra parameters more strongly than AIC once `n > exp(2)`. Compare models only when they were fitted to the same response scale and the same analysis rows. Do not compare a model fit to raw counts with one fit to transformed counts, or two fits that silently dropped different missing rows. For Gaussian mixed models, use ML (`REML = FALSE`) when AIC or BIC compares different fixed-effect formulas. REML is useful for Gaussian variance-component estimation inside a fixed mean structure, but the restricted likelihood depends on the fixed-effect design that was integrated out. Use ML for binomial model selection and scientific reporting. The bounded binomial REML route is diagnostic-only and must not be used to compare different fixed-effect formulas. ```{r setup} library(drmTMB) criterion_converged <- function(fit) { is_converged(fit) } criterion_table <- function(...) { models <- list(...) out <- data.frame( model = names(models), AIC = vapply(models, stats::AIC, numeric(1)), BIC = vapply(models, stats::BIC, numeric(1)), converged = vapply(models, criterion_converged, logical(1)), stringsAsFactors = FALSE ) out$delta_AIC <- out$AIC - min(out$AIC) out$delta_BIC <- out$BIC - min(out$BIC) out } ``` ## Gaussian REML After Mean-Model Selection For Gaussian mixed models, first compare different fixed-effect formulas with ML. This example includes a random intercept for repeated measurements and a noise predictor `z` that was not used to generate the response. ```{r gaussian-reml-example} set.seed(2404) n_id <- 12L n_each <- 6L id <- factor(rep(seq_len(n_id), each = n_each)) x <- rnorm(n_id * n_each) z <- rnorm(n_id * n_each) u <- rnorm(n_id, sd = 0.65) mixed_dat <- data.frame( id = id, x = x, z = z, y = 0.2 + 0.7 * x + u[id] + rnorm(n_id * n_each, sd = 0.45) ) fit_mixed_x_ml <- drmTMB( bf(y ~ x + (1 | id), sigma ~ 1), family = gaussian(), data = mixed_dat ) fit_mixed_x_z_ml <- drmTMB( bf(y ~ x + z + (1 | id), sigma ~ 1), family = gaussian(), data = mixed_dat ) criterion_table(`y ~ x` = fit_mixed_x_ml, `y ~ x + z` = fit_mixed_x_z_ml) ``` Once the fixed-effect mean structure is chosen, refit that structure with `REML = TRUE` when the target is Gaussian variance-component estimation. The first REML slice covers ordinary univariate Gaussian random intercepts and slopes in `mu` with intercept-only `sigma`. ```{r gaussian-reml-fit} fit_mixed_x_reml <- drmTMB( bf(y ~ x + (1 | id), sigma ~ 1), family = gaussian(), data = mixed_dat, REML = TRUE ) ll_reml <- logLik(fit_mixed_x_reml) mixed_parameters <- summary(fit_mixed_x_reml)$parameters data.frame( estimator = fit_mixed_x_reml$estimator, restricted_logLik = as.numeric(ll_reml), df = attr(ll_reml, "df"), residual_sigma = unname(sigma(fit_mixed_x_reml)[1]), id_sd = mixed_parameters[ mixed_parameters$parm == "sd:mu:(1 | id)", "estimate" ] ) ``` When `lme4` is installed, the restricted log likelihood for this overlap model matches `lme4::lmer(..., REML = TRUE)`. ```{r gaussian-reml-lme4} if (requireNamespace("lme4", quietly = TRUE)) { fit_lme4_reml <- lme4::lmer( y ~ x + (1 | id), data = mixed_dat, REML = TRUE ) data.frame( engine = c("drmTMB", "lme4"), restricted_logLik = c( as.numeric(logLik(fit_mixed_x_reml)), as.numeric(logLik(fit_lme4_reml)) ) ) } ``` This example answers a Gaussian variance-component question. For a binomial response, keep `REML = FALSE` after selecting the location formula: the current ordinary-intercept and independent-slope REML routes have deterministic comparator evidence only, not recovery or coverage evidence for reporting. ## Tail Assumption A robust Student-t model is useful when the biological process or measurement process can produce unusually large residuals. Here the data are generated with heavy-tailed residuals, then fitted with a Gaussian model and a Student-t model that uses the same `mu` and `sigma` formulas. ```{r tail-example} set.seed(2401) n <- 220 x <- rnorm(n) tail_dat <- data.frame(x = x) tail_dat$y <- 0.2 + 0.7 * x + exp(-0.25) * rt(n, df = 4) fit_tail_gaussian <- drmTMB( bf(y ~ x, sigma ~ 1), family = gaussian(), data = tail_dat ) fit_tail_student <- drmTMB( bf(y ~ x, sigma ~ 1, nu ~ 1), family = student(), data = tail_dat ) criterion_table( Gaussian = fit_tail_gaussian, `Student-t` = fit_tail_student ) ``` The lower AIC/BIC model is the better criterion fit inside this two-model candidate set. That result is not a licence to ignore diagnostics: run `check_drm()` and inspect whether the fitted Student-t `nu` parameter is near the lower bound or so large that the Student-t model is effectively Gaussian. ```{r tail-check} check_drm(fit_tail_student) coef(fit_tail_student, "nu") ``` ## Structural Zeros For count responses, the extra parameter in a zero-inflated model has a specific interpretation: it is the probability of a separate structural-zero process. This example generates NB2 counts with structural zeros and compares ordinary NB2 against ZINB2. ```{r count-example} set.seed(2402) n <- 260 x <- rnorm(n) mu <- exp(log(2.3) + 0.5 * x) sigma <- 0.65 zi <- plogis(-0.8) count <- rnbinom(n, size = 1 / sigma^2, mu = mu) structural_zero <- runif(n) < zi count[structural_zero] <- 0L count_dat <- data.frame(count = count, x = x) fit_nb2 <- drmTMB( bf(count ~ x, sigma ~ 1), family = nbinom2(), data = count_dat ) fit_zinb2 <- drmTMB( bf(count ~ x, sigma ~ 1, zi ~ 1), family = nbinom2(), data = count_dat ) criterion_table(NB2 = fit_nb2, ZINB2 = fit_zinb2) ``` If the criterion difference is small, keep both explanations in view. NB2 can absorb some extra zeros by increasing overdispersion, while ZINB2 separates structural zeros from count variation. Use `zi` only when a structural-zero process is plausible for the response and sampling design. ## Scale Formula Model selection is not only family selection. It can also ask whether a distributional parameter needs a predictor. In this Gaussian example the mean changes with `x`, and the residual scale also changes with `x`. ```{r scale-example} set.seed(2403) n <- 220 x <- rnorm(n) sigma <- exp(-0.45 + 0.55 * x) scale_dat <- data.frame( x = x, y = 0.3 + 0.55 * x + rnorm(n, sd = sigma) ) fit_sigma_constant <- drmTMB( bf(y ~ x, sigma ~ 1), family = gaussian(), data = scale_dat ) fit_sigma_x <- drmTMB( bf(y ~ x, sigma ~ x), family = gaussian(), data = scale_dat ) criterion_table(`sigma ~ 1` = fit_sigma_constant, `sigma ~ x` = fit_sigma_x) ``` The `sigma ~ x` model estimates a log-scale slope. A positive slope means the modelled residual scale increases as `x` increases. After selecting this candidate, interpret the `sigma` coefficient on the ratio scale: ```{r scale-ratio} exp(coef(fit_sigma_x, "sigma")["x"]) ``` ## A 200-Replicate Article-Support Simulation The package includes a seeded article-support simulation. It uses 200 replicates per scenario, which is enough to make the table more stable than a plumbing smoke run while still being small enough to ship as documentation evidence. It is not a formal operating-characteristic study over sample sizes, effect sizes, and candidate-set misspecification. ```{r article-summary} summary_path <- system.file( "sim/reports/model-selection-article-summary.csv", package = "drmTMB" ) if (!nzchar(summary_path)) { candidates <- c( "../inst/sim/reports/model-selection-article-summary.csv", "inst/sim/reports/model-selection-article-summary.csv" ) summary_path <- candidates[file.exists(candidates)][1L] } model_selection_article <- read.csv(summary_path) display_article <- model_selection_article[, c( "scenario", "selection_target", "n_replicate", "aic_truth_selection_rate", "aic_truth_selection_mcse", "bic_truth_selection_rate", "bic_truth_selection_mcse", "candidate_convergence_rate", "candidate_pdHess_rate", "candidate_warning_rate" )] names(display_article) <- c( "scenario", "target", "replicates", "AIC selected target", "AIC MCSE", "BIC selected target", "BIC MCSE", "candidate convergence", "candidate pdHess", "candidate warning" ) knitr::kable(display_article, digits = 3) ``` The `normal_tail` row is deliberately useful: the Gaussian model is selected, but the unnecessary Student-t candidate often carries warning or weak-Hessian status because the extra tail parameter is unnecessary for normal data. The `heavy_tail` and `extra_zeros` rows show the criterion tradeoff: AIC more often keeps the extra Student-t or zero-inflation parameter, while BIC often prefers the simpler candidate under this sample size because its penalty is stronger. That is the workflow lesson. Selection criteria answer "which candidate has the smaller penalized likelihood score?", while diagnostics answer "is this candidate fit stable enough to use?". ## Practical Checklist Use this sequence when comparing fitted `drmTMB` models: 1. Define the candidate set before looking at AIC/BIC. 2. Fit all candidates to the same response, row set, offsets, and weights. For Gaussian mixed models with different fixed-effect formulas, keep `REML = FALSE`. 3. Run `check_drm()` and keep convergence, Hessian status, warnings, and boundary diagnostics beside the criterion table. 4. Exclude errored fits from selection and treat nonconverged or weak-Hessian fits as diagnostic findings, not as ordinary winners. 5. Report AIC and BIC differences, not only the winning model. 6. Interpret the selected model's `mu`, `sigma`, `nu`, `zi`, or `rho12` parameters in scientific units or ratios. When AIC and BIC disagree, describe the tradeoff. AIC is more willing to keep extra parameters when they improve fit; BIC asks for stronger evidence as sample size grows. In applied `drmTMB` work, that disagreement is often a sign to inspect predictions, residuals, and the scientific meaning of the extra distributional parameter rather than to declare one criterion universally correct.