## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.1,
  dpi = 144
)
library(drmTMB)

which_scale_theme <- function() {
  ggplot2::theme_minimal(base_size = 11) +
    ggplot2::theme(
      panel.grid.minor = ggplot2::element_blank(),
      panel.grid.major.x = ggplot2::element_line(
        colour = "grey90",
        linewidth = 0.3
      ),
      panel.grid.major.y = ggplot2::element_line(
        colour = "grey87",
        linewidth = 0.35
      ),
      axis.title = ggplot2::element_text(colour = "grey15"),
      axis.text = ggplot2::element_text(colour = "grey25"),
      plot.title = ggplot2::element_text(
        face = "bold",
        colour = "grey10",
        margin = ggplot2::margin(b = 4)
      ),
      plot.subtitle = ggplot2::element_text(
        colour = "grey30",
        margin = ggplot2::margin(b = 8)
      ),
      legend.position = "bottom",
      legend.title = ggplot2::element_text(colour = "grey20")
    )
}

## ----scale-audit-data---------------------------------------------------------
set.seed(42)
n_population <- 32
n_each <- 6

population_info <- data.frame(
  population = factor(seq_len(n_population)),
  habitat = rep(c("forest", "grassland"), length.out = n_population)
)

fish <- population_info[rep(seq_len(n_population), each = n_each), ]
fish$temperature <- rnorm(nrow(fish))
fish$reliability <- ifelse(seq_len(nrow(fish)) %% 3 == 0, 2, 1)

pop_sd <- exp(-0.8 + 0.7 * (population_info$habitat == "grassland"))
b_population <- rnorm(n_population, sd = pop_sd)
fish$sigma_true <- exp(-0.7 + 0.3 * fish$temperature)
fish$growth <- 1.2 + 0.55 * fish$temperature +
  b_population[fish$population] +
  rnorm(nrow(fish), sd = fish$sigma_true)

## ----residual-scale-fit-------------------------------------------------------
fit_sigma <- drmTMB(
  bf(growth ~ temperature, sigma ~ temperature),
  family = gaussian(),
  data = fish
)

summary(fit_sigma)
round(coef(fit_sigma, "sigma"), 3)
round(range(sigma(fit_sigma)), 3)

## ----which-scale-residual-sigma-figure, fig.cap = "Fitted residual standard deviation over temperature for the `sigma ~ temperature` example; the ribbon is a 95% Wald confidence band from `predict_parameters()`.", fig.alt = "Line plot of fitted residual standard deviation over temperature with a 95 percent Wald confidence band. No raw growth points are shown on the sigma axis."----
sigma_temperature_grid <- prediction_grid(
  fit_sigma,
  focal = "temperature",
  at = list(
    temperature = seq(
      min(fish$temperature),
      max(fish$temperature),
      length.out = 80
    )
  )
)

sigma_temperature_surface <- predict_parameters(
  fit_sigma,
  newdata = sigma_temperature_grid,
  dpar = "sigma",
  conf.int = TRUE
)

unique(sigma_temperature_surface[, c(
  "dpar",
  "conf.status",
  "interval_source",
  "conf.level"
)])

if (requireNamespace("ggplot2", quietly = TRUE)) {
  plot_parameter_surface(
    sigma_temperature_surface,
    x = "temperature",
    dpar = "sigma",
    facet = NULL,
    point = FALSE
  ) +
    ggplot2::labs(
      title = "Residual scale is observation-level",
      subtitle = "Ribbon is a 95% Wald band for fitted sigma",
      x = "Temperature",
      y = "Fitted residual SD (sigma)"
    ) +
    which_scale_theme()
}

## ----likelihood-weights-fit---------------------------------------------------
fit_weighted <- drmTMB(
  bf(growth ~ temperature, sigma ~ 1),
  family = gaussian(),
  data = fish,
  weights = reliability
)

summary(fit_weighted)
head(weights(fit_weighted), 8)

## ----meta-known-v-fit---------------------------------------------------------
set.seed(101)
n_effect <- 50
meta <- data.frame(treatment = rep(c(0, 1), each = n_effect / 2))
meta$vi <- runif(n_effect, 0.02, 0.08)

mu_meta <- 0.1 + 0.25 * meta$treatment
sigma_meta <- exp(-1.1 + 0.35 * meta$treatment)
meta$yi <- rnorm(n_effect, mu_meta, sqrt(meta$vi + sigma_meta^2))

fit_meta <- drmTMB(
  bf(yi ~ treatment + meta_V(V = vi), sigma ~ treatment),
  family = gaussian(),
  data = meta
)

summary(fit_meta)

meta_report <- data.frame(
  treatment = meta$treatment,
  known_sampling_variance = meta$vi,
  extra_heterogeneity_sd = sigma(fit_meta)
)
meta_report$extra_heterogeneity_variance <-
  meta_report$extra_heterogeneity_sd^2
meta_report$total_observation_variance <-
  meta_report$known_sampling_variance +
  meta_report$extra_heterogeneity_variance

meta_summary <- aggregate(
  meta_report[c(
    "extra_heterogeneity_sd",
    "extra_heterogeneity_variance",
    "total_observation_variance"
  )],
  by = list(treatment = meta_report$treatment),
  FUN = mean
)
round(meta_summary, 3)

## ----random-effect-scale-fit--------------------------------------------------
fit_sd <- drmTMB(
  bf(
    growth ~ temperature + (1 | population),
    sigma ~ temperature,
    sd(population) ~ habitat
  ),
  family = gaussian(),
  data = fish
)

round(coef(fit_sd, "sd(population)"), 3)
round(tapply(
  predict(fit_sd, dpar = "sd(population)"),
  population_info$habitat,
  mean
), 3)

## ----which-scale-population-sd-figure, fig.width = 5.8, fig.height = 2.6, fig.cap = "Fitted among-population standard deviations by habitat for the `sd(population) ~ habitat` example; points are fitted random-effect SDs, with no interval drawn because the prediction table marks this random-effect-SD surface as interval-unavailable.", fig.alt = "Point plot comparing fitted among-population standard deviation for forest and grassland habitats, with no interval bars because this random-effect standard-deviation surface has no supported interval in the current prediction table."----
sd_population_rows <- predict_parameters(
  fit_sd,
  dpar = "sd(population)",
  conf.int = TRUE
)
sd_population_rows$habitat <- population_info$habitat[sd_population_rows$row]

unique(sd_population_rows[, c(
  "dpar",
  "component",
  "conf.status",
  "interval_source"
)])

sd_population_display <- aggregate(
  estimate ~ habitat,
  data = sd_population_rows,
  FUN = mean
)

if (requireNamespace("ggplot2", quietly = TRUE)) {
  ggplot2::ggplot(
    sd_population_display,
    ggplot2::aes(x = estimate, y = habitat)
  ) +
    ggplot2::geom_point(size = 3, colour = "#D55E00") +
    ggplot2::labs(
      title = "Group-level scale is not residual sigma",
      subtitle = "Points are fitted sd(population); no supported interval is drawn",
      x = "Fitted among-population SD",
      y = NULL
    ) +
    which_scale_theme()
}

## ----residual-coscale-fit-----------------------------------------------------
set.seed(12)
behaviour <- data.frame(treatment = rep(c(0, 1), each = 50))
Sigma0 <- matrix(c(0.6^2, 0.2 * 0.6 * 0.5,
                   0.2 * 0.6 * 0.5, 0.5^2), 2, 2)
Sigma1 <- matrix(c(0.6^2, 0.65 * 0.6 * 0.5,
                   0.65 * 0.6 * 0.5, 0.5^2), 2, 2)
Y <- matrix(NA_real_, nrow(behaviour), 2)

for (i in seq_len(nrow(behaviour))) {
  Sigma_i <- if (behaviour$treatment[i] == 0) Sigma0 else Sigma1
  mu_i <- c(1 + 0.2 * behaviour$treatment[i],
            0.5 + 0.1 * behaviour$treatment[i])
  Y[i, ] <- as.numeric(mu_i + t(chol(Sigma_i)) %*% rnorm(2))
}

behaviour$activity <- Y[, 1]
behaviour$boldness <- Y[, 2]

fit_rho12 <- drmTMB(
  bf(
    mu1 = activity ~ treatment,
    mu2 = boldness ~ treatment,
    sigma1 = ~ treatment,
    sigma2 = ~ treatment,
    rho12 = ~ treatment
  ),
  family = c(gaussian(), gaussian()),
  data = behaviour
)

summary(fit_rho12)
round(coef(fit_rho12, "rho12"), 3)
round(tapply(rho12(fit_rho12), behaviour$treatment, mean), 3)

