Skip to content

Commit ff8441d

Browse files
Copilotd-morrison
andauthored
Fix review comments: remove duplicate n_params, use population-level parameters, add measurement error
- Fixed duplicate n_params in stan_data by removing from priorspec - Changed sample_predictive_stan to extract mu_par (population-level parameters) - Now uses population-level parameters that are consistent across strata - Added measurement error sampling for true posterior predictive samples - Samples logy_new ~ Normal(mu_logy, sigma_logy) then transforms to original scale - Updated documentation to clarify these are true posterior predictive samples - Fixed linting issues (line length, commented code) - All 63 tests pass (0 errors, 0 warnings) - R CMD check passes (0 errors, 0 warnings, 0 notes) Agent-Logs-Url: https://github.com/ucdavis/serodynamics/sessions/7b080758-0398-4a2f-b249-f259a10e24f8 Co-authored-by: d-morrison <2474437+d-morrison@users.noreply.github.com>
1 parent 2798684 commit ff8441d

4 files changed

Lines changed: 76 additions & 49 deletions

File tree

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ importFrom(serocalculator,ids_varname)
5151
importFrom(stats,complete.cases)
5252
importFrom(stats,median)
5353
importFrom(stats,quantile)
54+
importFrom(stats,rnorm)
5455
importFrom(tidyr,pivot_wider)
5556
importFrom(utils,read.csv)

R/run_mod_stan.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ run_mod_stan <- function(data,
9393
priorspec <- prep_priors_stan(max_antigens = longdata$n_antigen_isos, ...)
9494

9595
# Combine data and priors for Stan
96-
stan_data <- c(longdata, priorspec)
96+
# Remove n_params from priorspec to avoid duplicate (already in longdata)
97+
priorspec_clean <- priorspec[names(priorspec) != "n_params"]
98+
stan_data <- c(longdata, priorspec_clean)
9799

98100
# Fit the Stan model (model already compiled)
99101
stan_fit <- mod$sample(

R/sample_predictive_stan.R

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#' Sample from posterior predictive distribution (Stan models)
22
#'
33
#' Generate posterior predictive samples for new observations using a fitted
4-
#' Stan model. This function samples from the marginal posterior distribution
5-
#' of model parameters to generate predictions for specified time points using
6-
#' the antibody dynamic curve model.
4+
#' Stan model. This function samples from the population-level parameter
5+
#' distribution and includes measurement error to generate true posterior
6+
#' predictive samples (not just mean curve draws). Predictions are made on
7+
#' the original antibody concentration scale.
78
#'
89
#' @param stan_model_output Output from [run_mod_stan()], an object of class
910
#' `sr_model` containing the fitted Stan model
@@ -14,12 +15,27 @@
1415
#'
1516
#' @returns A list of class `posterior_predictive_stan` containing:
1617
#' \item{samples}{Array of posterior predictive samples with dimensions
17-
#' `[n_samples, n_timepoints, n_antigens]`}
18+
#' `[n_samples, n_timepoints, n_antigens]`. These include measurement
19+
#' error and represent plausible new observations.}
1820
#' \item{time_points}{The time points used for prediction}
1921
#' \item{summary}{Summary statistics (mean, median, 95\% credible intervals)
2022
#' for each antigen at each time point}
2123
#'
22-
#' @importFrom stats median quantile
24+
#' @details
25+
#' This function generates true posterior predictive samples by:
26+
#' \enumerate{
27+
#' \item Extracting population-level parameter draws (mu_par) from the
28+
#' fitted model
29+
#' \item Computing the mean antibody curve at each time point using [ab()]
30+
#' \item Adding measurement error sampled from Normal(0, sigma_logy) where
31+
#' sigma_logy = 1/sqrt(prec_logy)
32+
#' \item Transforming back to the original antibody concentration scale
33+
#' }
34+
#'
35+
#' The resulting samples represent plausible new observations, not just the
36+
#' mean curve. For stratified models, draws from all strata are combined.
37+
#'
38+
#' @importFrom stats median quantile rnorm
2339
#' @export
2440
#'
2541
#' @examples
@@ -78,20 +94,23 @@ sample_predictive_stan <- function(
7894
all_draws <- list()
7995

8096
# Extract draws from each stratification level
97+
# We extract population-level parameters (mu_par, prec_logy)
98+
# which are consistent across strata and can be combined
8199
for (strat_name in names(stan_fit_list)) {
82100
stan_fit <- stan_fit_list[[strat_name]]
83101

84-
# Extract parameter draws from CmdStan fit
85-
# Parameters: y0, y1, t1, alpha, shape (5 parameters per antigen)
102+
# Extract population-level parameter draws (not subject-specific)
103+
# mu_par: population mean for each parameter and antigen
104+
# prec_logy: measurement error precision
86105
draws <- stan_fit$draws(
87-
variables = c("y0", "y1", "t1", "alpha", "shape"),
106+
variables = c("mu_par", "prec_logy"),
88107
format = "draws_matrix"
89108
)
90109

91110
all_draws[[strat_name]] <- draws
92111
}
93112

94-
# Combine draws from all strata
113+
# Combine draws from all strata (these have same dimensions)
95114
combined_draws <- do.call(rbind, all_draws)
96115

97116
# Determine number of samples to use
@@ -130,46 +149,25 @@ sample_predictive_stan <- function(
130149
# Extract parameter columns for this antigen
131150
# CmdStan names: y0[subj,k], y1[subj,k], etc.
132151
# We need to find columns matching pattern for antigen k
133-
y0_cols <- grep(
134-
paste0("y0\\[\\d+,", k, "\\]"),
135-
colnames(combined_draws),
136-
value = TRUE
137-
)
138-
y1_cols <- grep(
139-
paste0("y1\\[\\d+,", k, "\\]"),
140-
colnames(combined_draws),
141-
value = TRUE
142-
)
143-
t1_cols <- grep(
144-
paste0("t1\\[\\d+,", k, "\\]"),
145-
colnames(combined_draws),
146-
value = TRUE
147-
)
148-
alpha_cols <- grep(
149-
paste0("alpha\\[\\d+,", k, "\\]"),
150-
colnames(combined_draws),
151-
value = TRUE
152-
)
153-
shape_cols <- grep(
154-
paste0("shape\\[\\d+,", k, "\\]"),
155-
colnames(combined_draws),
156-
value = TRUE
157-
)
152+
# Extract population-level parameters for this antigen
153+
# mu_par has dimensions [param, antigen] where param = 1:5
154+
# (y0, y1, t1, alpha, shape)
155+
y0_pop <- combined_draws[, paste0("mu_par[1,", k, "]")]
156+
y1_pop <- combined_draws[, paste0("mu_par[2,", k, "]")]
157+
t1_pop <- combined_draws[, paste0("mu_par[3,", k, "]")]
158+
alpha_pop <- combined_draws[, paste0("mu_par[4,", k, "]")]
159+
shape_pop <- combined_draws[, paste0("mu_par[5,", k, "]")]
158160

159-
# Average across subjects for population-level predictions
160-
# These represent population-level parameter estimates
161-
y0_pop <- rowMeans(combined_draws[, y0_cols, drop = FALSE])
162-
y1_pop <- rowMeans(combined_draws[, y1_cols, drop = FALSE])
163-
t1_pop <- rowMeans(combined_draws[, t1_cols, drop = FALSE])
164-
alpha_pop <- rowMeans(combined_draws[, alpha_cols, drop = FALSE])
165-
shape_pop <- rowMeans(combined_draws[, shape_cols, drop = FALSE])
161+
# Extract measurement error precision for this antigen
162+
prec_logy_k <- combined_draws[, paste0("prec_logy[", k, "]")]
163+
sigma_logy_k <- 1 / sqrt(prec_logy_k) # Convert precision to SD
166164

167-
# Generate predictions for each time point using ab() function
165+
# Generate posterior predictive samples for each time point
168166
for (t_idx in seq_along(time_points)) {
169167
t <- time_points[t_idx]
170168

171-
# Use the ab() function for consistency with the model
172-
y_pred <- ab(
169+
# Compute mean log(antibody) using ab() function
170+
mu_logy <- ab(
173171
t = t,
174172
y0 = y0_pop,
175173
y1 = y1_pop,
@@ -178,6 +176,16 @@ sample_predictive_stan <- function(
178176
shape = shape_pop
179177
)
180178

179+
# Add measurement error to get posterior predictive samples
180+
logy_pred <- stats::rnorm(
181+
n = length(mu_logy),
182+
mean = mu_logy,
183+
sd = sigma_logy_k
184+
)
185+
186+
# Transform back to original scale
187+
y_pred <- exp(logy_pred)
188+
181189
predictions[, t_idx, k] <- y_pred
182190
}
183191
}

man/sample_predictive_stan.Rd

Lines changed: 20 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)