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
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 }
0 commit comments