## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(drmTMB) ## ----------------------------------------------------------------------------- set.seed(196) n_onset <- 240 onset_data <- data.frame( canopy = factor( rep(c("open", "closed"), each = n_onset / 2), levels = c("open", "closed") ), ndvi = as.numeric(scale(runif(n_onset, 0.15, 0.85))) ) closed <- as.numeric(onset_data$canopy == "closed") mu_onset <- plogis(-0.85 - 0.35 * closed + 1.10 * onset_data$ndvi) onset_data$early_onset <- rbinom(n_onset, size = 1, prob = mu_onset) fit_onset <- drmTMB( bf(early_onset ~ canopy + ndvi), family = stats::binomial(link = "logit"), data = onset_data ) coef(fit_onset, "mu") ## ----------------------------------------------------------------------------- new_onset <- data.frame( canopy = factor(c("open", "closed"), levels = levels(onset_data$canopy)), ndvi = c(0, 0) ) data.frame( canopy = new_onset$canopy, ndvi = new_onset$ndvi, early_onset_probability = predict(fit_onset, newdata = new_onset, dpar = "mu") ) ## ----------------------------------------------------------------------------- set.seed(197) n <- 360 seed_trials <- data.frame( treatment = factor( rep(c("open", "sheltered"), each = n / 2), levels = c("open", "sheltered") ), moisture = as.numeric(scale(runif(n, 0.1, 0.9))), trials = sample(18:32, n, replace = TRUE) ) sheltered <- as.numeric(seed_trials$treatment == "sheltered") mu_seed <- plogis(-0.55 + 0.70 * sheltered + 0.45 * seed_trials$moisture) sigma_seed <- exp(-1.15 - 0.35 * sheltered) phi_seed <- 1 / sigma_seed^2 tray_probability <- rbeta( n, shape1 = mu_seed * phi_seed, shape2 = (1 - mu_seed) * phi_seed ) seed_trials$germinated <- rbinom(n, size = seed_trials$trials, prob = tray_probability) seed_trials$failed <- seed_trials$trials - seed_trials$germinated head(seed_trials) ## ----------------------------------------------------------------------------- fit_seed <- drmTMB( bf(cbind(germinated, failed) ~ treatment + moisture, sigma ~ treatment), family = beta_binomial(), data = seed_trials ) ## ----------------------------------------------------------------------------- check_drm(fit_seed) ## ----------------------------------------------------------------------------- coef(fit_seed, "mu") coef(fit_seed, "sigma") sigma_ratio <- exp(coef(fit_seed, "sigma")["treatmentsheltered"]) c( sigma_ratio_sheltered_vs_open = sigma_ratio, precision_ratio_sheltered_vs_open = sigma_ratio^(-2) ) ## ----------------------------------------------------------------------------- new_seed_trays <- data.frame( treatment = factor(c("open", "sheltered"), levels = levels(seed_trials$treatment)), moisture = c(0, 0), trials = c(24, 24) ) mu_hat <- predict(fit_seed, newdata = new_seed_trays, dpar = "mu") sigma_hat <- predict(fit_seed, newdata = new_seed_trays, dpar = "sigma") prop_var <- mu_hat * (1 - mu_hat) * (1 + new_seed_trays$trials * sigma_hat^2) / (new_seed_trays$trials * (1 + sigma_hat^2)) data.frame( treatment = new_seed_trays$treatment, trials = new_seed_trays$trials, expected_probability = mu_hat, expected_successes = new_seed_trays$trials * mu_hat, sigma = sigma_hat, phi = 1 / sigma_hat^2, proportion_sd = sqrt(prop_var) ) ## ----beta-binomial-tray-figure, eval=requireNamespace("ggplot2", quietly = TRUE), fig.width=7.2, fig.height=4.4, fig.cap="Beta-binomial tray summary for the seed-germination example. Faint points are observed tray proportions; overlaid points are fitted expected germination probabilities; vertical bars show plus or minus one fitted proportion-level standard deviation, not confidence intervals.", fig.alt="Jittered point plot of observed germination proportions for open and sheltered trays. Overlaid larger points show fitted expected probabilities, and vertical bars show one fitted proportion-level standard deviation for each treatment."---- library(ggplot2) seed_trials$observed_proportion <- seed_trials$germinated / seed_trials$trials seed_plot_summary <- data.frame( treatment = new_seed_trays$treatment, expected_probability = mu_hat, proportion_sd = sqrt(prop_var) ) seed_plot_summary$lower <- pmax( 0, seed_plot_summary$expected_probability - seed_plot_summary$proportion_sd ) seed_plot_summary$upper <- pmin( 1, seed_plot_summary$expected_probability + seed_plot_summary$proportion_sd ) ggplot(seed_trials, aes(treatment, observed_proportion, colour = treatment)) + geom_jitter(width = 0.12, height = 0, alpha = 0.18, size = 0.9) + geom_errorbar( data = seed_plot_summary, aes( y = expected_probability, ymin = lower, ymax = upper ), width = 0.12, linewidth = 0.8 ) + geom_point(data = seed_plot_summary, aes(y = expected_probability), size = 3) + scale_colour_manual(values = c("open" = "#0072B2", "sheltered" = "#009E73")) + coord_cartesian(ylim = c(0, 1)) + labs( title = "Denominator-aware proportions can still show raw trays", subtitle = "Bars show fitted tray-level scatter, not confidence intervals", x = "Microsite treatment", y = "Germinated proportion", colour = "Treatment" ) + guides(colour = "none") + theme_minimal(base_size = 11) + theme( panel.grid.minor = element_blank(), legend.position = "bottom", plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey30") ) ## ----------------------------------------------------------------------------- set.seed(198) n_cover <- 300 cover_data <- data.frame( grazing = factor( rep(c("ungrazed", "grazed"), each = n_cover / 2), levels = c("ungrazed", "grazed") ), moisture = as.numeric(scale(runif(n_cover, 0.05, 0.95))) ) grazed <- as.numeric(cover_data$grazing == "grazed") mu_cover <- plogis(0.35 - 0.75 * grazed + 0.35 * cover_data$moisture) sigma_cover <- exp(-1.10 + 0.45 * grazed) phi_cover <- 1 / sigma_cover^2 cover_data$cover <- rbeta( n_cover, shape1 = mu_cover * phi_cover, shape2 = (1 - mu_cover) * phi_cover ) fit_cover <- drmTMB( bf(cover ~ grazing + moisture, sigma ~ grazing), family = beta(), data = cover_data ) check_drm(fit_cover) ## ----------------------------------------------------------------------------- coef(fit_cover, "mu") coef(fit_cover, "sigma") new_cover <- data.frame( grazing = factor(c("ungrazed", "grazed"), levels = levels(cover_data$grazing)), moisture = c(0, 0) ) mu_cover_hat <- predict(fit_cover, newdata = new_cover, dpar = "mu") sigma_cover_hat <- predict(fit_cover, newdata = new_cover, dpar = "sigma") data.frame( grazing = new_cover$grazing, expected_cover = mu_cover_hat, sigma = sigma_cover_hat, phi = 1 / sigma_cover_hat^2, cover_sd = sqrt( mu_cover_hat * (1 - mu_cover_hat) * sigma_cover_hat^2 / (1 + sigma_cover_hat^2) ) )