---
title: "Intercept-only baseline"
---
::: {.workflow}
::: {}
**Input**
Saved prediction and summary files.
:::
::: {}
**Action**
Read the model record and build its figure.
:::
::: {}
**Output**
Model table, checks, and plot.
:::
::: {}
**Next**
Continue to the next model chapter.
:::
:::
```{r setup, include=FALSE}
source(here::here("Scripts", "14_epred_cache.R"))
cache_dir <- here::here("Rdata", "epred_draws")
cache <- read_epred_cache("m00", cache_dir)
```
## Phylogenetic covariance and model species
This model contains **253 species** (7,186 contrasts). The phylogenetic correlation matrix was constructed and aligned to those species as follows; `brms` uses it in `(1 | gr(sp_ncbi, cov = A))`.
```{r phylogenetic-covariance, eval=FALSE}
A_full <- prepR4pcm::pr_phylo_cor(tree_final)
# Fallback if needed: ape::vcv.phylo(tree_final, corr = TRUE)
dat_model <- dat_es
phylo <- prepare_phylo_and_data(dat_model, A_full, fill_unmatched = TRUE)
dat_model <- phylo$dat_model
A <- phylo$A_mod
formula <- brms::bf(
yi_lnM_safe ~ 1 + (1 | ref_id) +
(1 | gr(sp_ncbi, cov = A)) + (1 | gr(es_id_model, cov = V)),
sigma ~ 1
)
V <- phylo$V
fit <- brms::brm(formula, data = dat_model,
data2 = list(A = A, V = V))
```
The intercept-only model estimates the overall mean lnM and the baseline residual heterogeneity (sigma), with no moderators — the reference against which every moderator model is compared.
## Model summary
`summary()` is read from `Rdata/epred_draws/m00.rds`; the full `brms` fit is not loaded at render.
```{r summary}
print_cache_summary(cache)
```
::: {.callout-note title="Reading the scale-model intercept: log(σ), not σ"}
The model summary reports the scale-model intercept on the log-$\sigma$
scale, because the `brms` model uses `Links: mu = identity; sigma = log`. The
summary therefore gives
$$
\beta_{\mathrm{Overall}}^{(s)} = -0.59,
$$
which is really $\log(\sigma) = -0.59$, not $\sigma$ itself.
The orchard plot below explicitly back-transforms that coefficient with
`exp()` before plotting it:
$$
\sigma = \exp(-0.59) \approx 0.55.
$$
The plotting code confirms this directly (`sig_est <- exp(fe["sigma_Intercept", "Estimate"])`),
and the scale panel's x-axis is labelled "residual lnM (SD)", not log-sigma.
So the positive value shown in the orchard plot's scale panel is not a
residual $\ln M$ effect size, and it is not showing logged residuals. It is
the residual standard deviation of $\ln M$ — $\sigma$ — on its natural
scale. Because a standard deviation cannot be negative, it appears positive
even though the corresponding row in the summary table above is negative.
This applies to every scale-submodel coefficient in this book, not only the
intercept.
:::
## Main result in equivalent-d units
The overall posterior mean is shown on the $\ln M$ scale and as
$d_{\mathrm{eq}} \approx \sqrt{2\exp(2\ln M)}$. The transformation is applied
to the posterior 95% credible-interval endpoints.
```{r overall-d-eq}
source(here::here("Scripts", "18_contrasts_cache.R"))
kable_contrasts(
format_posterior_d_eq(cache$post$b_Intercept, "Overall mean"),
caption = "Overall mean lnM and equivalent d with 95% credible intervals."
)
```
## Heterogeneity decomposition
Location–scale $I^2$ from this intercept-only baseline, partitioned into between-study (reference), phylogenetic, and within-study (residual) components following the location–scale meta-analysis framework of Nakagawa et al. Values are read from `Rdata/summaries/heterogeneity_m00.rds`.
```{r het}
kable_contrasts(format_heterogeneity(read_heterogeneity()),
caption = "Location–scale heterogeneity (I², %) with 95% credible intervals.")
```
### Formula used for the precomputed heterogeneity
For each posterior draw, the heterogeneity denominator is:
$$
D = \sigma_u^2 + \sigma_{\mathrm{phylo}}^2 + \bar{\sigma}_e^2 + \bar{V},
$$
where $\sigma_u^2$ is the between-reference variance, $\sigma_{\mathrm{phylo}}^2$ is the phylogenetic variance, $\bar{\sigma}_e^2 = \exp(2 b_{\sigma,0})$ is the residual heterogeneity variance from the scale submodel, and $\bar{V}$ is the typical known sampling variance. Component-specific $I^2$ values are:
$$
I_x^2 = 100 \times \frac{\sigma_x^2}{D},
$$
with total heterogeneity:
$$
I_{\mathrm{total}}^2 =
100 \times
\frac{\sigma_u^2 + \sigma_{\mathrm{phylo}}^2 + \bar{\sigma}_e^2}{D}.
$$
The typical sampling variance is computed from the diagonal sampling-variance matrix $V$ as:
$$
\bar{V} = \frac{n - p}{\operatorname{tr}(P)}, \qquad
P = W - W X (X^\top W X)^{-1} X^\top W, \qquad
W = V^{-1}.
$$
```{r het-components}
source(here::here("Scripts", "18_contrasts_cache.R"))
kable_contrasts(format_heterogeneity_components(read_heterogeneity()),
caption = "Precomputed variance components used in the m00 heterogeneity denominator.")
```
## Orchard-like model figure
The orchard-style figure is read from `Rdata/figures/publication/orchard/`. The complete editable code used to build this plot is shown below.
```{r figure, fig.width=8, fig.height=9}
knitr::include_graphics(
here::here("Rdata", "figures", "publication", "orchard", "m00_orchard_combined.png")
)
```
### Editable plotting code
```{r build-orchard-m00, eval=FALSE}
library(here)
library(tidyverse)
library(brms)
library(ggbeeswarm)
library(patchwork)
out_dir <- here::here("Rdata", "figures", "publication", "orchard")
out_dir_pdf <- here::here("Figures", "publication", "orchard")
dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
dir.create(out_dir_pdf, recursive = TRUE, showWarnings = FALSE)
dat_es <- readRDS(here::here("Rdata", "effect_sizes", "proceed_lnm_safe.rds"))
fit <- readRDS(here::here("Rdata", "models", "m00_ls_intercept_only.rds"))
theme_orchard <- function() {
ggplot2::theme_classic(base_size = 13) +
ggplot2::theme(
axis.text.y = ggplot2::element_text(size = 11),
axis.title.x = ggplot2::element_text(size = 12),
axis.ticks.y = ggplot2::element_blank(),
panel.grid.major.x = ggplot2::element_line(colour = "grey92"),
legend.position = "bottom",
legend.title = ggplot2::element_text(size = 10),
plot.title = ggplot2::element_text(size = 13, face = "bold"),
plot.caption = ggplot2::element_text(size = 9, colour = "grey50")
)
}
save_plot <- function(p, stem, width = 9, height = 6) {
ggplot2::ggsave(file.path(out_dir_pdf, paste0(stem, ".pdf")), p,
width = width, height = height, device = cairo_pdf)
ggplot2::ggsave(file.path(out_dir, paste0(stem, ".png")), p,
width = width, height = height, dpi = 300, type = "cairo")
}
raw <- dat_es |>
dplyr::mutate(precision = 1 / sqrt(vi_lnM_safe))
fe <- brms::fixef(fit)
int_est <- fe["Intercept", "Estimate"]
int_lo <- fe["Intercept", "Q2.5"]
int_hi <- fe["Intercept", "Q97.5"]
sig_est <- exp(fe["sigma_Intercept", "Estimate"])
sig_lo <- exp(fe["sigma_Intercept", "Q2.5"])
sig_hi <- exp(fe["sigma_Intercept", "Q97.5"])
vc <- brms::VarCorr(fit)
caption_txt <- sprintf(
"n = %d effect sizes | study SD = %.2f | phylogeny SD = %.2f",
nrow(raw), vc$ref_id$sd["Intercept", "Estimate"],
vc$sp_ncbi$sd["Intercept", "Estimate"]
)
p_loc <- ggplot2::ggplot() +
ggplot2::geom_vline(xintercept = 0, linetype = "dashed", colour = "grey50") +
ggbeeswarm::geom_quasirandom(
data = raw,
ggplot2::aes(x = yi_lnM_safe, y = 1, size = precision),
alpha = 0.20, shape = 21, fill = "#88CCEE", colour = "#0072B2",
groupOnX = FALSE
) +
ggplot2::geom_linerange(
ggplot2::aes(y = 1, xmin = int_lo, xmax = int_hi),
linewidth = 1.5, colour = "grey15"
) +
ggplot2::geom_point(
ggplot2::aes(x = int_est, y = 1),
size = 5, shape = 21, fill = "white", colour = "grey10", stroke = 1.2
) +
ggplot2::scale_size_continuous(name = "Precision (1/SE)", range = c(0.3, 4)) +
ggplot2::scale_y_continuous(breaks = NULL) +
ggplot2::labs(
x = "Location effect (lnM)",
y = NULL,
title = "Location -- Overall baseline",
caption = caption_txt
) +
theme_orchard()
raw_sig <- raw |>
dplyr::mutate(abs_resid = abs(yi_lnM_safe - int_est))
p_scl <- ggplot2::ggplot() +
ggplot2::geom_vline(xintercept = 0, linetype = "dashed", colour = "grey50") +
ggbeeswarm::geom_quasirandom(
data = raw_sig,
ggplot2::aes(x = abs_resid, y = 1, size = precision),
alpha = 0.20, shape = 21, fill = "#88CCEE", colour = "#0072B2",
groupOnX = FALSE
) +
ggplot2::geom_linerange(
ggplot2::aes(y = 1, xmin = sig_lo, xmax = sig_hi),
linewidth = 3, colour = "white"
) +
ggplot2::geom_linerange(
ggplot2::aes(y = 1, xmin = sig_lo, xmax = sig_hi),
linewidth = 1.5, colour = "grey15"
) +
ggplot2::geom_point(
ggplot2::aes(x = sig_est, y = 1),
size = 5, shape = 21, fill = "white", colour = "grey10", stroke = 1.2
) +
ggplot2::scale_size_continuous(name = "Precision (1/SE)", range = c(0.3, 4)) +
ggplot2::scale_y_continuous(breaks = NULL) +
ggplot2::labs(
x = "residual lnM (SD)",
y = NULL,
title = "Scale -- Overall baseline",
caption = sprintf(
"residual SD = %.3f [%.3f, %.3f]",
sig_est, sig_lo, sig_hi
)
) +
theme_orchard()
p_comb <- patchwork::wrap_plots(p_loc, p_scl, ncol = 1)
save_plot(p_loc, "m00_orchard_location", width = 9, height = 4)
save_plot(p_scl, "m00_orchard_scale", width = 9, height = 4)
save_plot(p_comb, "m00_orchard_combined", width = 9, height = 8)
p_comb
```