## ----setup, include=FALSE----------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 10, fig.height = 7, warning = FALSE, message = FALSE ) run_expensive <- identical(Sys.getenv("SHRINKR_RUN_VIGNETTES"), "true") ## ----packages----------------------------------------------------------------- library(shrinkr) library(brms) library(tidybayes) library(distributional) library(tidyverse) library(survival) library(posterior) library(patchwork) theme_set(theme_minimal(base_size = 12)) cell_types <- c("squamous", "smallcell", "adeno", "large") prior_specs <- list( very_strong = list(name = "Very Strong", scale = 0.1), strong = list(name = "Strong", scale = 0.25), moderate = list(name = "Moderate", scale = 0.5), weak = list(name = "Weak", scale = 1.0), very_weak = list(name = "Very Weak", scale = 2.0) ) ## ----load_cached_results, include=FALSE--------------------------------------- if (!run_expensive) { veteran_analysis <- get("veteran_analysis", envir = asNamespace("shrinkr")) } ## ----explore_data------------------------------------------------------------- data(veteran, package = "survival") head(veteran) table(veteran$celltype, veteran$trt) veteran %>% group_by(celltype, trt) %>% summarise( n = n(), deaths = sum(status), median_time = median(time), .groups = "drop" ) ## ----fit_brms_uninformative, eval=run_expensive------------------------------- # brms_uninformative <- brm( # time | cens(1 - status) ~ trt:celltype + karno + age, # data = veteran, # family = cox(), # chains = 4, # iter = 4000, # warmup = 1000, # seed = 123 # ) # # brms_uninformative_summary <- capture.output(print(summary(brms_uninformative))) ## ----fit_brms_uninformative_fallback, include=FALSE--------------------------- if (!run_expensive) { brms_uninformative_summary <- veteran_analysis$brms_uninformative_summary } ## ----show_brms_uninformative-------------------------------------------------- cat(brms_uninformative_summary, sep = "\n") ## ----extract_posteriors, eval=run_expensive----------------------------------- # brms_posteriors <- brms_uninformative %>% # spread_draws(`b_trt:celltypesquamous`, `b_trt:celltypesmallcell`, # `b_trt:celltypeadeno`, `b_trt:celltypelarge`) %>% # select(-c(.chain, .iteration, .draw)) %>% # pivot_longer(everything(), names_to = "celltype", values_to = "value") %>% # mutate(celltype = gsub("b_trt:celltype", "", celltype)) %>% # group_by(celltype) %>% # summarise(draws = list(matrix(value, ncol = 1)), .groups = "drop") %>% # deframe() ## ----extract_posteriors_fallback, include=FALSE------------------------------- if (!run_expensive) { brms_posteriors <- veteran_analysis$brms_posteriors } ## ----fit_mixture_explain, eval=run_expensive---------------------------------- # mix_brms <- fit_mixture(brms_posteriors, K_max = 3, verbose = TRUE) ## ----fit_mixture_explain_fallback, include=FALSE------------------------------ if (!run_expensive) { mix_brms <- veteran_analysis$mix_brms } ## ----show_mixture------------------------------------------------------------- print(mix_brms) plot(mix_brms, draws = brms_posteriors) ## ----define_moderate_prior---------------------------------------------------- priors_moderate <- list( mu = dist_normal(0, 1), tau = dist_truncated(dist_normal(0, 0.5), lower = 0) ) ## ----shrink_explain, eval=run_expensive--------------------------------------- # fit_twostage_brms <- shrink( # mixture = mix_brms, # hierarchical_priors = priors_moderate, # chains = 4, # iter = 4000, # warmup = 1000, # seed = 456 # ) # # moderate_brms_output <- capture.output(print(fit_twostage_brms)) ## ----shrink_explain_fallback, include=FALSE----------------------------------- if (!run_expensive) { moderate_brms_output <- veteran_analysis$sensitivity_summaries$moderate_brms$print_output } ## ----show_twostage_brms------------------------------------------------------- cat(moderate_brms_output, sep = "\n") ## ----fit_brms_hierarchical, eval=run_expensive-------------------------------- # brms_hierarchical <- brm( # time | cens(1 - status) ~ trt + (0 + trt | celltype) + karno + age, # data = veteran, # family = cox(), # prior = c( # prior(normal(0, 1), class = b, coef = "trt"), # prior(normal(0, 0.5), class = sd, group = "celltype", lb = 0) # ), # chains = 4, # iter = 4000, # warmup = 1000, # seed = 123 # ) # # brms_hierarchical_summary <- capture.output(print(summary(brms_hierarchical))) # # brms_hier_effects <- brms_hierarchical %>% # spread_draws(r_celltype[celltype, term], b_trt) %>% # filter(term == "trt") %>% # mutate(theta = b_trt + r_celltype) %>% # group_by(celltype) %>% # summarise( # hr_mean = exp(mean(theta)), # hr_lower = exp(quantile(theta, 0.025)), # hr_upper = exp(quantile(theta, 0.975)), # .groups = "drop" # ) ## ----fit_brms_hierarchical_fallback, include=FALSE---------------------------- if (!run_expensive) { brms_hierarchical_summary <- veteran_analysis$brms_hierarchical_summary brms_hier_effects <- veteran_analysis$brms_hier_effects } ## ----show_brms_hierarchical--------------------------------------------------- cat(brms_hierarchical_summary, sep = "\n") ## ----fit_cox, eval=run_expensive---------------------------------------------- # cox_model <- coxph( # Surv(time, status) ~ trt:celltype + karno + age, # data = veteran # ) # # cox_summary <- summary(cox_model) # # trt_idx <- grep("^trt:celltype", names(coef(cox_model))) # # trt_effects <- coef(cox_model)[trt_idx] # trt_vcov <- vcov(cox_model)[trt_idx, trt_idx, drop = FALSE] # # names(trt_effects) <- gsub("^trt:celltype", "", names(trt_effects)) # rownames(trt_vcov) <- colnames(trt_vcov) <- names(trt_effects) ## ----fit_cox_fallback, include=FALSE------------------------------------------ if (!run_expensive) { cox_summary <- veteran_analysis$cox_summary trt_effects <- veteran_analysis$trt_effects trt_vcov <- veteran_analysis$trt_vcov } ## ----show_cox----------------------------------------------------------------- print(cox_summary) ## ----show_cox_effects--------------------------------------------------------- print("Treatment effects (log HR):") print(trt_effects) print("\nStandard errors:") print(sqrt(diag(trt_vcov))) ## ----shrink_freq, eval=run_expensive------------------------------------------ # fit_twostage_freq <- shrink( # mle = trt_effects, # var_matrix = trt_vcov, # hierarchical_priors = priors_moderate, # chains = 4, # iter = 4000, # warmup = 1000, # seed = 456 # ) # # moderate_freq_output <- capture.output(print(fit_twostage_freq)) ## ----shrink_freq_fallback, include=FALSE-------------------------------------- if (!run_expensive) { moderate_freq_output <- veteran_analysis$sensitivity_summaries$moderate_freq$print_output } ## ----show_twostage_freq------------------------------------------------------- cat(moderate_freq_output, sep = "\n") ## ----comparison_table, eval=run_expensive------------------------------------- # theta_brms <- summary(fit_twostage_brms)$theta %>% # transmute( # celltype = group, # twostage_brms = mean # ) # # theta_freq <- summary(fit_twostage_freq)$theta %>% # transmute( # celltype = group, # twostage_freq = mean # ) # # comparison <- brms_hier_effects %>% # transmute( # celltype, # full_hier_brms = log(hr_mean) # ) %>% # left_join(theta_brms, by = "celltype") %>% # left_join(theta_freq, by = "celltype") %>% # mutate( # diff_two_stage_vs_full = twostage_brms - full_hier_brms # ) ## ----comparison_table_fallback, include=FALSE--------------------------------- if (!run_expensive) { comparison <- veteran_analysis$comparison } ## ----comparison_table_show---------------------------------------------------- knitr::kable( comparison[, 1:4], digits = 3, caption = "Comparison of treatment effects (log HR scale)" ) ## ----compare_approaches, fig.width=12, fig.height=8, eval=run_expensive------- # theta_brms_plot <- summary(fit_twostage_brms)$theta %>% # mutate( # approach = "Two-Stage (brms + shrinkr)", # hr_mean = exp(mean), # hr_lower = exp(q2.5), # hr_upper = exp(q97.5), # celltype = group # ) %>% # select(celltype, approach, hr_mean, hr_lower, hr_upper) # # theta_freq_plot <- summary(fit_twostage_freq)$theta %>% # mutate( # approach = "Two-Stage (Frequentist + shrinkr)", # hr_mean = exp(mean), # hr_lower = exp(q2.5), # hr_upper = exp(q97.5), # celltype = group # ) %>% # select(celltype, approach, hr_mean, hr_lower, hr_upper) # # all_approaches <- bind_rows( # theta_brms_plot, # brms_hier_effects %>% mutate(approach = "Full Hierarchical (brms)"), # theta_freq_plot # ) %>% # mutate( # approach = factor(approach, levels = c( # "Two-Stage (brms + shrinkr)", # "Full Hierarchical (brms)", # "Two-Stage (Frequentist + shrinkr)" # )) # ) ## ----compare_approaches_fallback, include=FALSE------------------------------- if (!run_expensive) { theta_brms_plot <- veteran_analysis$sensitivity_summaries$moderate_brms$theta_summary %>% mutate( approach = "Two-Stage (brms + shrinkr)", hr_mean = exp(mean), hr_lower = exp(q2.5), hr_upper = exp(q97.5), celltype = group ) %>% select(celltype, approach, hr_mean, hr_lower, hr_upper) theta_freq_plot <- veteran_analysis$sensitivity_summaries$moderate_freq$theta_summary %>% mutate( approach = "Two-Stage (Frequentist + shrinkr)", hr_mean = exp(mean), hr_lower = exp(q2.5), hr_upper = exp(q97.5), celltype = group ) %>% select(celltype, approach, hr_mean, hr_lower, hr_upper) all_approaches <- bind_rows( theta_brms_plot, veteran_analysis$brms_hier_effects %>% mutate(approach = "Full Hierarchical (brms)"), theta_freq_plot ) %>% mutate( approach = factor(approach, levels = c( "Two-Stage (brms + shrinkr)", "Full Hierarchical (brms)", "Two-Stage (Frequentist + shrinkr)" )) ) } ## ----compare_approaches_show, fig.width=12, fig.height=8---------------------- ggplot(all_approaches, aes(x = celltype, y = hr_mean, color = approach)) + geom_hline(yintercept = 1, linetype = "dashed", alpha = 0.5) + geom_pointrange( aes(ymin = hr_lower, ymax = hr_upper), position = position_dodge(width = 0.5), size = 0.8 ) + scale_y_log10() + scale_color_brewer(palette = "Set1") + labs( title = "Comparison of Three Modeling Approaches", subtitle = "Treatment effects by cell type (hazard ratios)", x = "Cell Type", y = "Hazard Ratio (log scale)", color = "Approach" ) + theme( legend.position = "bottom", panel.grid.minor = element_blank() ) ## ----show_priors-------------------------------------------------------------- prior_summary <- tibble( Strength = c("Very Strong", "Strong", "Moderate", "Weak", "Very Weak"), Prior = c( "Half-Normal(0, 0.1)", "Half-Normal(0, 0.25)", "Half-Normal(0, 0.5)", "Half-Normal(0, 1.0)", "Half-Normal(0, 2.0)" ), Scale = c(0.1, 0.25, 0.5, 1.0, 2.0), Interpretation = c( "Very similar effects expected", "Similar effects expected", "Moderate heterogeneity allowed", "Substantial differences allowed", "Large differences allowed" ) ) knitr::kable(prior_summary) ## ----sensitivity_fits, eval=run_expensive------------------------------------- # all_priors <- list( # very_strong = list( # mu = dist_normal(0, 1), # tau = dist_truncated(dist_normal(0, 0.1), lower = 0) # ), # strong = list( # mu = dist_normal(0, 1), # tau = dist_truncated(dist_normal(0, 0.25), lower = 0) # ), # moderate = list( # mu = dist_normal(0, 1), # tau = dist_truncated(dist_normal(0, 0.5), lower = 0) # ), # weak = list( # mu = dist_normal(0, 1), # tau = dist_truncated(dist_normal(0, 1.0), lower = 0) # ), # very_weak = list( # mu = dist_normal(0, 1), # tau = dist_truncated(dist_normal(0, 2.0), lower = 0) # ) # ) # # # --- brms fits --- # sensitivity_fits_brms <- lapply(all_priors, function(prior) { # shrink( # mixture = mix_brms, # hierarchical_priors = prior, # chains = 4, # iter = 4000, # warmup = 1000 # ) # }) # # # --- frequentist fits --- # sensitivity_fits_freq <- lapply(all_priors, function(prior) { # shrink( # mle = trt_effects, # var_matrix = trt_vcov, # hierarchical_priors = prior, # chains = 4, # iter = 4000, # warmup = 1000 # ) # }) # # # --- summaries --- # sensitivity_summaries <- c( # purrr::imap(sensitivity_fits_brms, function(fit, nm) { # summ <- summary(fit) # list( # theta_summary = summ$theta, # mu_tau_summary = summ$mu_tau, # print_output = capture.output(print(fit)) # ) # }), # purrr::imap(sensitivity_fits_freq, function(fit, nm) { # summ <- summary(fit) # list( # theta_summary = summ$theta, # mu_tau_summary = summ$mu_tau, # print_output = capture.output(print(fit)) # ) # }) # ) # # # --- name them clearly --- # names(sensitivity_summaries) <- c( # paste0(names(all_priors), "_brms"), # paste0(names(all_priors), "_freq") # ) ## ----sensitivity_fits_fallback, include=FALSE--------------------------------- if (!run_expensive) { sensitivity_summaries <- veteran_analysis$sensitivity_summaries prior_specs <- veteran_analysis$prior_specs } ## ----prior_densities, fig.width=10, fig.height=5------------------------------ tau_seq <- seq(0, 3, length.out = 200) prior_densities <- lapply(names(prior_specs), function(spec_name) { spec <- prior_specs[[spec_name]] tibble( tau = tau_seq, density = dnorm(tau_seq, 0, spec$scale) * 2, prior_strength = spec$name, scale = spec$scale ) }) %>% bind_rows() %>% mutate( prior_strength = factor(prior_strength, levels = c( "Very Strong", "Strong", "Moderate", "Weak", "Very Weak" )) ) ggplot(prior_densities, aes(x = tau, y = density, color = prior_strength)) + geom_line(linewidth = 1.2) + scale_color_brewer(palette = "RdYlBu", direction = -1) + labs( title = "Prior Densities for the Heterogeneity Parameter (tau)", subtitle = "Half-Normal(0, sigma) priors with increasing scale", x = "tau", y = "Density", color = "Prior Strength" ) + theme(legend.position = "right") ## ----tau_sensitivity---------------------------------------------------------- tau_results <- lapply(names(sensitivity_summaries), function(fit_name) { summary_obj <- sensitivity_summaries[[fit_name]] prior_name <- sub("_(brms|freq)$", "", fit_name) approach <- if (grepl("_brms$", fit_name)) "brms + shrinkr" else "Frequentist + shrinkr" summary_obj$mu_tau_summary %>% filter(parameter == "tau") %>% mutate( prior_strength = prior_specs[[prior_name]]$name, prior_scale = prior_specs[[prior_name]]$scale, approach = approach ) }) %>% bind_rows() %>% mutate( prior_strength = factor( prior_strength, levels = c("Very Strong", "Strong", "Moderate", "Weak", "Very Weak") ) ) if (all(c("q2.5", "q97.5") %in% names(tau_results))) { tau_results <- tau_results %>% mutate(lower = `q2.5`, upper = `q97.5`) } else if (all(c("q5", "q95") %in% names(tau_results))) { tau_results <- tau_results %>% mutate(lower = q5, upper = q95) } else { stop( "Could not find interval columns in sensitivity_summaries$mu_tau_summary. ", "Available columns are: ", paste(names(tau_results), collapse = ", ") ) } ggplot(tau_results, aes(x = prior_scale, y = mean, color = approach)) + geom_point(size = 3, position = position_dodge(width = 0.1)) + geom_errorbar( aes(ymin = lower, ymax = upper), width = 0.1, linewidth = 1, position = position_dodge(width = 0.1) ) + geom_line(aes(group = approach), position = position_dodge(width = 0.1)) + scale_x_log10(breaks = c(0.1, 0.25, 0.5, 1.0, 2.0)) + scale_color_brewer(palette = "Set2") + labs( title = "Sensitivity of the Heterogeneity Parameter (tau)", subtitle = "How prior scale affects the estimated between-cell-type variation", x = "Prior Scale (log scale)", y = "Posterior tau", color = "Stage 1 Approach" ) + theme(legend.position = "bottom") ## ----theta_sensitivity_prep--------------------------------------------------- theta_sensitivity <- lapply(names(sensitivity_summaries), function(fit_name) { summary_obj <- sensitivity_summaries[[fit_name]] prior_name <- sub("_(brms|freq)$", "", fit_name) approach <- if (grepl("_brms$", fit_name)) "brms + shrinkr" else "Frequentist + shrinkr" summary_obj$theta_summary %>% mutate( prior_strength = prior_specs[[prior_name]]$name, prior_scale = prior_specs[[prior_name]]$scale, approach = approach, hr_mean = exp(mean), hr_lower = exp(q2.5), hr_upper = exp(q97.5) ) }) %>% bind_rows() %>% mutate( prior_strength = factor(prior_strength, levels = c( "Very Strong", "Strong", "Moderate", "Weak", "Very Weak" )) ) ## ----theta_sensitivity_plot, fig.width=12, fig.height=10---------------------- ggplot(theta_sensitivity, aes(x = prior_scale, y = hr_mean, color = approach)) + geom_hline(yintercept = 1, linetype = "dashed", alpha = 0.5) + geom_point(size = 2, position = position_dodge(width = 0.1)) + geom_errorbar( aes(ymin = hr_lower, ymax = hr_upper), width = 0.1, position = position_dodge(width = 0.1) ) + geom_line(aes(group = approach), position = position_dodge(width = 0.1)) + facet_wrap(~group, ncol = 2, scales = "free_y") + scale_x_log10(breaks = c(0.1, 0.25, 0.5, 1.0, 2.0)) + scale_y_log10() + scale_color_brewer(palette = "Set2") + labs( title = "Sensitivity Analysis: Cell Type-Specific Treatment Effects", subtitle = "How the prior scale affects hazard ratio estimates", x = "Prior Scale (log scale)", y = "Hazard Ratio (log scale)", color = "Stage 1 Approach" ) + theme( legend.position = "bottom", panel.grid.minor = element_blank() ) ## ----session------------------------------------------------------------------ sessionInfo()