--- title: "Which scale are you modelling?" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Which scale are you modelling?} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, 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") ) } ``` `drmTMB` deliberately separates residual scale from group-level scale. This article pairs each piece of R syntax with the symbolic model it represents. That pairing is the guardrail: before adding a feature to the package, the R syntax, equations, TMB parameters, tests, and documentation should describe the same model. Read this after you have fit a first model in [Distributional regression with drmTMB](drmTMB.html); this guide settles which scale each formula changes before you pick a family in [Choosing response families](distribution-families.html). Throughout this article, `Normal(a, b)` uses variance as the second argument. Thus `Normal(mu_i, sigma_i^2)` means a Gaussian distribution with mean `mu_i` and standard deviation `sigma_i`. For non-Gaussian families, `sigma` is still the public variability-facing scale, but its family-specific meaning changes. For example, beta and NB2 likelihoods use precision-like internal parameters and Tweedie uses dispersion `phi = sigma^2`, while `drmTMB` reports `sigma` so that larger fitted values mean larger modelled variability. See `vignette("distribution-families")` for the conversion table before comparing `sigma` with `phi`, `theta`, or other software-specific dispersion parameters. ## Residual standard deviation Implemented: ```r drmTMB( bf(growth ~ temperature, sigma ~ temperature), family = gaussian(), data = fish ) ``` Symbolically: \[ \begin{aligned} \text{growth}_i \mid \mu_i, \sigma_i &\sim \operatorname{Normal}(\mu_i, \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{temperature}_i,\\ \sigma_i &= \exp(\gamma_0 + \gamma_1 \text{temperature}_i). \end{aligned} \] The `sigma` formula models the residual standard deviation. If `gamma_1` is positive, warmer observations have a larger residual SD around their fitted mean. The coefficient is on the log-SD scale, so `exp(gamma_1)` is the multiplicative change in residual SD for a one-unit change in temperature. ## Mean-model random intercept Implemented: ```r drmTMB( bf(growth ~ temperature + (1 | population), sigma ~ temperature), family = gaussian(), data = fish ) ``` Symbolically: \[ \begin{aligned} \text{growth}_{ij} \mid \mu_{ij}, \sigma_{ij}, b_j &\sim \operatorname{Normal}(\mu_{ij}, \sigma_{ij}^2),\\ \mu_{ij} &= \beta_0 + \beta_1 \text{temperature}_{ij} + b_j,\\ \log(\sigma_{ij}) &= \gamma_0 + \gamma_1 \text{temperature}_{ij},\\ b_j &= sd_{\mu,population} u_j,\\ u_j &\sim \operatorname{Normal}(0, 1). \end{aligned} \] Here `sd_mu_population` is the among-population SD in expected growth. It is a group-level scale parameter for the mean model. It is not the residual standard deviation `sigma_ij`. ## Residual-scale random intercept Implemented: ```r drmTMB( bf( growth ~ temperature + (1 | population), sigma ~ temperature + (1 | population) ), family = gaussian(), data = fish ) ``` Symbolically: \[ \begin{aligned} \text{growth}_{ij} \mid \mu_{ij}, \sigma_{ij}, b_j, a_j &\sim \operatorname{Normal}(\mu_{ij}, \sigma_{ij}^2),\\ \mu_{ij} &= \beta_0 + \beta_1 \text{temperature}_{ij} + b_j,\\ \log(\sigma_{ij}) &= \gamma_0 + \gamma_1 \text{temperature}_{ij} + a_j,\\ b_j &= sd_{\mu,population} u_j,\\ u_j &\sim \operatorname{Normal}(0, 1),\\ a_j &= sd_{\sigma,population} v_j,\\ v_j &\sim \operatorname{Normal}(0, 1). \end{aligned} \] This asks whether populations differ in their residual variability after the fixed effects in the `sigma` formula have been accounted for. It is group-to-group variation in within-population variability. It is not the same as asking whether the among-population SD in mean growth depends on a predictor. That is the `sd(population) ~ x_group` model. ## Random-effect scale formula Implemented for one or more distinct unlabelled Gaussian `mu` random intercepts: ```r drmTMB( bf( growth ~ temperature + (1 | population), sigma ~ temperature, sd(population) ~ habitat ), family = gaussian(), data = fish ) ``` Symbolically: \[ \begin{aligned} \text{growth}_{ij} \mid \mu_{ij}, \sigma_{ij}, b_j &\sim \operatorname{Normal}(\mu_{ij}, \sigma_{ij}^2),\\ \mu_{ij} &= \beta_0 + \beta_1 \text{temperature}_{ij} + b_j,\\ \log(\sigma_{ij}) &= \gamma_0 + \gamma_1 \text{temperature}_{ij},\\ b_j &= sd_{\mu,population,j} u_j,\\ u_j &\sim \operatorname{Normal}(0, 1),\\ \log(sd_{\mu,population,j}) &= \alpha_0 + \alpha_1 \text{habitat}_j. \end{aligned} \] The right-hand side of `sd(population) ~ habitat` is group-level. `habitat_j` must be constant within each population after missing rows are removed. This model asks whether among-population differences in expected growth are larger in some habitats. That is a different question from `sigma ~ habitat`, which asks whether individual observations are more variable around their fitted mean in some habitats. A compact copy-run example: ```r set.seed(1) n_population <- 24 n_each <- 8 population_info <- data.frame( population = factor(seq_len(n_population)), habitat = rep(c("closed", "open"), length.out = n_population) ) fish <- population_info[rep(seq_len(n_population), each = n_each), ] fish$temperature <- rnorm(nrow(fish)) sd_population <- exp(-0.4 + 0.5 * (population_info$habitat == "open")) b_population <- rnorm(n_population, sd = sd_population) sigma <- exp(-0.6 + 0.25 * fish$temperature) fish$growth <- 1 + 0.7 * fish$temperature + b_population[fish$population] + rnorm(nrow(fish), sd = sigma) fit <- drmTMB( bf( growth ~ temperature + (1 | population), sigma ~ temperature, sd(population) ~ habitat ), family = gaussian(), data = fish ) coef(fit, "sd(population)") head(predict(fit, dpar = "sd(population)")) ``` The `sd(population)` coefficients are on the log-SD scale. A positive `habitatopen` coefficient means the estimated among-population SD in expected growth is larger for open-habitat populations than for the baseline habitat. ## One location model, several scale quantities A model can have one location predictor and several scale quantities. For example, suppose fish growth is measured across populations and sites: ```r drmTMB( bf( growth ~ temperature + (1 | population) + (1 | site), sigma ~ temperature, sd(population) ~ habitat, sd(site) ~ site_area ), family = gaussian(), data = fish ) ``` The matching symbolic model is: \[ \begin{aligned} \text{growth}_i \mid \mu_i, \sigma_i, b_{j[i]}, c_{k[i]} &\sim \operatorname{Normal}(\mu_i, \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i + b_{j[i]} + c_{k[i]},\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{temperature}_i,\\ b_j &= sd_{\mu,population,j} u_j,\\ u_j &\sim \operatorname{Normal}(0, 1),\\ \log(sd_{\mu,population,j}) &= \alpha_0 + \alpha_1 \text{habitat}_j,\\ c_k &= sd_{\mu,site,k} r_k,\\ r_k &\sim \operatorname{Normal}(0, 1),\\ \log(sd_{\mu,site,k}) &= \kappa_0 + \kappa_1 \text{site\_area}_k. \end{aligned} \] This has three scale quantities: | Quantity | Formula | Interpretation | |---|---|---| | `sigma_i` | `sigma ~ temperature` | residual SD for each observation | | `sd_mu_population,j` | `sd(population) ~ habitat` | among-population SD in expected growth | | `sd_mu_site,k` | `sd(site) ~ site_area` | among-site SD in expected growth | There are no random effects in the residual scale unless the `sigma` formula contains a term such as `(1 | population)`. There is also no correlation between the population and site random effects in this example; they are separate grouping factors. Correlated random intercept-slope blocks require a single grouping factor block such as `(1 + temperature | population)`. ## A copy-run scale audit This section uses one small ecological example to show what each scale means in fitted output. Suppose `growth` is a growth-rate measurement from several populations. Temperature may change the expected growth rate and the residual SD around that expectation. Populations may also differ in their mean growth, and that among-population SD may be larger in one habitat. ```{r 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: `sigma ~ temperature` The model \[ \begin{aligned} \text{growth}_i &\sim \operatorname{Normal}(\mu_i, \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{temperature}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{temperature}_i \end{aligned} \] is fitted by: ```{r 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) ``` The `sigma:temperature` coefficient is on the log-SD scale. A positive value means residual variation in growth is larger at warmer temperatures, after the mean temperature effect has been modelled. The displayed range of `sigma(fit)` is on the response scale, so it is the fitted residual SD, not a variance. Plot the residual scale on its own fitted axis. The raw response points belong on a `growth` axis, not on a `sigma` axis: ```{r 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: `weights = reliability` Likelihood weights do not define a new biological scale. They multiply the row log-likelihood contribution: \[ \ell(\theta) = \sum_i w_i \log f(y_i \mid \theta_i). \] The model syntax stays ordinary: ```{r 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) ``` Here rows with `reliability = 2` count twice in the likelihood. That may be a reasonable model-fitting choice for replicated or reliability-weighted rows, but it is not the same as telling the model that an effect size has known sampling variance. Known sampling variance belongs in `meta_V(V = V)`; deprecated `meta_known_V(V = V)` remains a compatibility alias. At present, dense full known-covariance models reject non-unit likelihood weights; keep row weighting and known sampling covariance as separate modelling decisions unless the fitted model explicitly supports their combination. ### Known sampling variance: `meta_V(V = V)` For meta-analysis, the known sampling variance is part of the observation model. In a diagonal Gaussian meta-analysis: \[ \begin{aligned} y_i &\sim \operatorname{Normal}(\mu_i, v_i + \sigma_i^2),\\ \mu_i &= \beta_0 + \beta_1 \text{treatment}_i,\\ \log(\sigma_i) &= \gamma_0 + \gamma_1 \text{treatment}_i . \end{aligned} \] The R syntax puts the known variance in the formula, not in `weights`: ```{r 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) ``` The `vi` values are known before the model is fitted. The fitted `sigma` describes extra heterogeneity after those known sampling variances have been included. In meta-analysis notation this extra heterogeneity is often written as `tau`, but `drmTMB` keeps the name `sigma` so Gaussian distributional models use one stable scale vocabulary. If the scientific summary needs a variance, square the fitted `sigma` to report extra heterogeneity variance. If it needs the total observation variance, add the known sampling variance `vi`. This is a reporting conversion, not a separate `tau ~` formula. ### Random-effect scale: `sd(population) ~ habitat` Now the question changes again. The model \[ \begin{aligned} \text{growth}_{ij} &\sim \operatorname{Normal}(\mu_{ij}, \sigma_{ij}^2),\\ \mu_{ij} &= \beta_0 + \beta_1 \text{temperature}_{ij} + b_j,\\ b_j &= sd_{\mu,population,j} u_j,\quad u_j \sim \operatorname{Normal}(0, 1),\\ \log(sd_{\mu,population,j}) &= \alpha_0 + \alpha_1 \text{habitat}_j \end{aligned} \] asks whether the among-population SD in expected growth differs by habitat: ```{r 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) ``` The `habitatgrassland` coefficient is on the log-SD scale for the population random intercept. In this example, grassland populations have a larger fitted among-population SD in expected growth. That is not residual variation among individual observations; it is variation among population-level means. That group-level SD can be plotted as a fitted model quantity. The current prediction table does not attach a supported interval to this modelled random-effect SD surface, so the figure shows point estimates only: ```{r 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: `rho12 ~ treatment` For two responses, coscale means residual coupling after both response means and residual SDs have been modelled. In the implemented bivariate Gaussian model: \[ \begin{aligned} \begin{bmatrix} activity_i \\ boldness_i \end{bmatrix} &\sim \mathrm{MVN} \left( \begin{bmatrix} \mu_{1i} \\ \mu_{2i} \end{bmatrix}, \begin{bmatrix} \sigma_{1i}^2 & \rho_{12i}\sigma_{1i}\sigma_{2i}\\ \rho_{12i}\sigma_{1i}\sigma_{2i} & \sigma_{2i}^2 \end{bmatrix} \right),\\ \rho_{12i} &= \tanh(\eta_{\rho12,i}),\\ \eta_{\rho12,i} &= \delta_0 + \delta_1 treatment_i . \end{aligned} \] The matching syntax is: ```{r 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) ``` The `rho12:treatment` coefficient is on the correlation-link scale. The final line shows the same fitted residual coupling on the response scale. In this simulated example, treatment changes how tightly activity and boldness remain coupled after their means and residual SDs have been modelled. ## Side-by-side guide | R syntax | Equation target | Question | |---|---|---| | `sigma ~ x` | `log(sigma_i) = X_sigma[i, ] beta_sigma` | Does `x` change residual SD? | | `sigma ~ x + (1 | id)` | `log(sigma_i) = X_sigma[i, ] beta_sigma + a_id[i]` | Do groups differ in residual SD? | | `sigma ~ x + (0 + w | id)` | `log(sigma_i) = X_sigma[i, ] beta_sigma + w_i a_id[i]` | Do groups differ in how `w` changes residual SD? | | `y ~ x + (1 | id)` | `mu_i = X_mu[i, ] beta_mu + b_id[i]` | Do groups differ in mean response? | | `sd(id) ~ x_group` | `log(sd_mu_id,j) = W_id[j, ] alpha_id` | Does `x_group` change among-group SD in the mean model? | | `weights = w` | `logLik = sum_i w_i log f(y_i | theta_i)` | Should row `i` contribute more or less to the likelihood? | | `meta_V(V = V)` | `y ~ MVN(mu, V + Omega_est)` | What sampling covariance is known before fitting? | | `y ~ x + (1 + x | id)` | `[b_0j, b_1j]' ~ MVN(0, Sigma_id)` | Are group intercepts and slopes correlated? | | `rho12 ~ x` | `eta_rho12_i = X_rho12[i, ] beta_rho12`; `rho12_i = tanh(eta_rho12_i)` | Does `x` change residual coupling between two responses? | Implementation detail: the C++ likelihood applies a tiny numerical guard to the `tanh()` transform so fitted covariance matrices stay strictly positive definite in floating-point arithmetic. The guard is not a biological scaling factor. Tutorials and model equations should be read as the standard Fisher-z-style map from an unconstrained predictor to a correlation. ## Correlations: group-level versus residual Implemented univariate random-slope correlation: ```r drmTMB( bf(growth ~ temperature + (1 + temperature | population), sigma ~ temperature), family = gaussian(), data = fish ) ``` Symbolically: \[ \begin{aligned} \mu_{ij} &= \beta_0 + \beta_1 \text{temperature}_{ij} + b_{0j} + \text{temperature}_{ij} b_{1j},\\ \begin{bmatrix} b_{0j}\\ b_{1j} \end{bmatrix} &\sim \operatorname{MVN}(\mathbf{0}, \Sigma_{population}),\\ \Sigma_{population} &= \begin{bmatrix} sd_0^2 & \rho_{re} sd_0 sd_1\\ \rho_{re} sd_0 sd_1 & sd_1^2 \end{bmatrix}. \end{aligned} \] This `rho_re` is a group-level random-effect correlation. Read it from the matching row of `corpairs(fit, level = "group")` or `summary(fit)$covariance`. Implemented bivariate residual correlation: ```r drmTMB( bf( mu1 = activity ~ treatment, mu2 = boldness ~ treatment, sigma1 = ~ treatment, sigma2 = ~ treatment, rho12 = ~ treatment ), family = c(gaussian(), gaussian()), data = behaviour ) ``` Symbolically: \[ \begin{aligned} \begin{bmatrix} \text{activity}_i\\ \text{boldness}_i \end{bmatrix} &\sim \operatorname{MVN} \left( \begin{bmatrix} \mu_{1i}\\ \mu_{2i} \end{bmatrix}, \Omega_i \right),\\ \Omega_i[1, 2] &= \rho_{12i}\sigma_{1i}\sigma_{2i},\\ \eta_{\rho12,i} &= \delta_0 + \delta_1 \text{treatment}_i,\\ \rho_{12i} &= \tanh(\eta_{\rho12,i}). \end{aligned} \] Here `rho12_i` is residual response-response correlation within an observation after the response-specific means and residual SDs have been modelled. If the correlation is among random intercepts or random slopes, it is a group-level covariance parameter. If the correlation is between the two residual responses in one row, it is `rho12`. Extract fitted response-scale residual correlations with `rho12(fit)`. Use `corpairs(fit)` when you want a table that keeps residual `rho12` separate from ordinary group-level random-effect correlations, including the implemented bivariate `mu1`/`mu2`, `sigma1`/`sigma2`, and same-response `mu`/`sigma` random-intercept correlations. ## Family A versus Family B Two model families can both sound like "scale random effects", but they are different likelihoods. Family A puts random effects inside distributional formulas. For example, `sigma ~ z + (1 | id)` adds a group-level residual-scale deviation to `log(sigma_ij)`. Matching labels such as `(1 | p | id)` across `mu` and `sigma`, or across `mu1`, `mu2`, `sigma1`, and `sigma2`, define latent covariance blocks. Read those correlations with `corpairs()`. Family B models the SD of a location random effect directly. For example, `sd(id) ~ habitat` changes the among-`id` SD in the `mu` random intercept. The phylogenetic direct-SD analogue is spelled `sd(species, level = "phylogenetic") ~ z_species`, which is the current generic syntax. The legacy `sd_phylo(species) ~ z_species` spelling is a soft-deprecated alias (as of 0.3.0): it still parses and fits identically, but emits a one-time deprecation warning per session and should not be used in new formulas. Do not mix the two families for the same latent layer. For example, a q=4 Family A block with matching labelled `phylo()` terms in `mu1`, `mu2`, `sigma1`, and `sigma2` already estimates one constant latent covariance block. Adding `sd1(species, level = "phylogenetic") ~ z` or `sd2(species, level = "phylogenetic") ~ z` to that same q=4 block would ask for a predictor-dependent direct-SD model at the same time, so `drmTMB()` rejects that combination before fitting. | Question | Use | Do not read it as | |---|---|---| | Do groups differ in residual SD? | `sigma ~ ... + (1 | group)` | a model for among-group mean differences | | Does a group-level predictor change among-group mean SD? | `sd(group) ~ x_group` | residual `sigma` regression | | Do two latent group effects move together? | matching labelled random effects plus `corpairs()` | residual `rho12` | | Does a species-level predictor change phylogenetic location SD? | `sd(species, level = "phylogenetic") ~ z_species` | a q=4 location-scale covariance block | ## Current boundary Current implemented Gaussian syntax covers: - `sigma ~ x` for residual SD fixed effects; - `y ~ x + (1 | id)` for `mu` random intercepts; - `y ~ x + (0 + x | id)` for independent numeric `mu` random slopes; - `y ~ x + (1 + x | id)` and `(1 + x | p | id)` for one-slope correlated `mu` blocks; - `y ~ x1 + x2 + x3 + (1 + x1 + x2 + x3 | id)` for ordinary q > 2 numeric multi-slope `mu` blocks, where random-slope SDs are direct targets and block correlations are derived-unavailable for direct profile intervals; - `sigma ~ x + (1 | id)` for residual-scale random intercepts; - `sigma ~ x + (0 + w | id)` and `sigma ~ x + (0 + w_id | id) + (0 + w_site | site)` for independent residual-scale random slopes, including separate grouping factors; - `sigma ~ x + (1 + w | id)` and unlabelled multi-slope variants for fitted within-block residual-scale correlations; - `sd(id) ~ x_group` and multiple distinct `sd(group) ~ x_group` formulas for standard deviations of unlabelled Gaussian `mu` random intercepts; - bivariate fixed-effect `rho12 ~ x` with `family = c(gaussian(), gaussian())`; - matching labelled `(1 | p | id)` random intercepts in bivariate `mu1` and `mu2`; - matching labelled `(1 | p | id)` random intercepts in bivariate `sigma1` and `sigma2`; - one same-response bivariate `mu`/`sigma` random-intercept covariance pair, such as matching `(1 | p | id)` terms in `mu1` and `sigma1`; - matching intercept-only `phylo(1 | species, tree = tree)` terms in bivariate `mu1` and `mu2`, with residual `rho12` kept as the within-observation residual correlation. Current planned but not implemented syntax includes: - slope-specific and labelled-block random-effect scale models; - labelled residual-scale slope blocks in `sigma` and cross-formula labelled `mu`/`sigma` random-slope covariance (unlabelled ordinary correlated intercept-slope and multi-slope `sigma` blocks are fitted); - residual-scale bivariate random slopes and full cross-parameter covariance blocks spanning more than one pair; - cross-formula covariance sharing from repeated slope labels outside the matching source-tested bivariate `mu1`/`mu2` q4/q6 location routes; - multiple phylogenetic slopes, phylogenetic slope correlations, standalone or partial phylogenetic scale terms, predictor-dependent q=4 phylogenetic location-scale correlations, structured `rho12` effects, mesh/SPDE spatial fields, spatial slope correlations, and non-Gaussian spatial structured effects outside the exact ordinary Poisson/NB2 q1 spatial `mu` intercept-plus-one-slope, recovery-grade NB2 q1 spatial `sigma`, Student-t spatial `mu`, Poisson spatial `zi`, fixed-`zi` Poisson spatial `mu`, and fixed-`zi` NB2 spatial `mu` gates. With the scale vocabulary settled, move on to [Choosing response families](distribution-families.html) to pick a family, then to a matching interpretation tutorial such as [When variance carries signal, Part 1](location-scale.html).