Skip to content

Commit 3db4454

Browse files
Copilotd-morrison
andauthored
Fix mu_par indexing, add empty data validation, add multi-antigen test
- Fixed mu_par indexing in sample_predictive_stan: use [antigen, param] not [param, antigen] - Corrected comment to reflect Stan's actual array declaration - Added validation in prep_data_stan for empty input data - Added multi-antigen test case for sample_predictive_stan - Improved test clarity with explicit variable names and comments - All 63 tests pass (0 errors, 0 warnings) - R CMD check passes (0 errors, 0 warnings, 0 notes) - Parallel validation passes (Code Review + CodeQL) Agent-Logs-Url: https://github.com/ucdavis/serodynamics/sessions/b268f550-e4e5-4afd-b2a0-37c2a0a195d1 Co-authored-by: d-morrison <2474437+d-morrison@users.noreply.github.com>
1 parent ff8441d commit 3db4454

3 files changed

Lines changed: 85 additions & 6 deletions

File tree

R/prep_data_stan.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ prep_data_stan <- function(
5555

5656
# Convert to Stan format
5757
# Stan requires explicit max dimensions
58+
# Validate that we have at least one subject with observations
59+
if (length(jags_data$nsmpl) == 0 || all(jags_data$nsmpl == 0)) {
60+
cli::cli_abort(
61+
c(
62+
"No observations found in input data.",
63+
"i" = "Stan models require at least one subject with observations.",
64+
"i" = "Check that your input data is not empty."
65+
)
66+
)
67+
}
68+
5869
max_nsmpl <- max(jags_data$nsmpl)
5970

6071
# Create padded arrays (Stan doesn't handle ragged arrays like JAGS)

R/sample_predictive_stan.R

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,13 @@ sample_predictive_stan <- function(
150150
# CmdStan names: y0[subj,k], y1[subj,k], etc.
151151
# We need to find columns matching pattern for antigen k
152152
# Extract population-level parameters for this antigen
153-
# mu_par has dimensions [param, antigen] where param = 1:5
153+
# mu_par has dimensions [antigen, param] where param = 1:5
154154
# (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, "]")]
155+
y0_pop <- combined_draws[, paste0("mu_par[", k, ",1]")]
156+
y1_pop <- combined_draws[, paste0("mu_par[", k, ",2]")]
157+
t1_pop <- combined_draws[, paste0("mu_par[", k, ",3]")]
158+
alpha_pop <- combined_draws[, paste0("mu_par[", k, ",4]")]
159+
shape_pop <- combined_draws[, paste0("mu_par[", k, ",5]")]
160160

161161
# Extract measurement error precision for this antigen
162162
prec_logy_k <- combined_draws[, paste0("prec_logy[", k, "]")]

tests/testthat/test-stan-functions.R

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,74 @@ test_that("sample_predictive_stan works with fitted Stan model", {
208208
)
209209
})
210210

211+
test_that("sample_predictive_stan works with multiple antigens", {
212+
skip_if_not_installed("cmdstanr")
213+
skip_if(
214+
is.null(tryCatch(cmdstanr::cmdstan_version(), error = function(e) NULL)),
215+
"CmdStan not installed"
216+
)
217+
218+
withr::local_seed(2)
219+
# Use two antigens to test multi-antigen indexing
220+
# n=15 provides sufficient data while keeping test runtime reasonable
221+
# Two antigens (HlyE_IgA, HlyE_IgG) test parameter extraction for each antigen
222+
case_data <- serocalculator::typhoid_curves_nostrat_100 |>
223+
sim_case_data(n = 15, antigen_isos = c("HlyE_IgA", "HlyE_IgG"))
224+
225+
# Fit model with posterior samples
226+
model_output <- run_mod_stan(
227+
data = case_data,
228+
file_mod = serodynamics_example("model.stan"),
229+
nchain = 2,
230+
nadapt = 100,
231+
niter = 10,
232+
with_post = TRUE
233+
) |>
234+
suppressWarnings()
235+
236+
# Generate predictions
237+
n_samples <- 8
238+
time_points <- c(10, 50)
239+
antigen_isos <- c("HlyE_IgA", "HlyE_IgG")
240+
241+
predictions <- sample_predictive_stan(
242+
model_output,
243+
time_points = time_points,
244+
n_samples = n_samples
245+
)
246+
247+
# Check structure
248+
expect_s3_class(predictions, "posterior_predictive_stan")
249+
250+
# Check samples array dimensions
251+
# Dimensions: [n_samples, n_timepoints, n_antigens]
252+
expect_equal(
253+
dim(predictions$samples),
254+
c(n_samples, length(time_points), length(antigen_isos))
255+
)
256+
257+
# Check time points
258+
expect_equal(predictions$time_points, time_points)
259+
260+
# Check summary structure - should have 2 antigens
261+
expect_type(predictions$summary, "list")
262+
expect_equal(length(predictions$summary), length(antigen_isos))
263+
expect_true(all(antigen_isos %in% names(predictions$summary)))
264+
265+
# Check that each antigen has summary stats for each time point
266+
for (antigen in antigen_isos) {
267+
summary_df <- predictions$summary[[antigen]]
268+
expect_s3_class(summary_df, "data.frame")
269+
expect_equal(nrow(summary_df), length(time_points))
270+
expect_true(
271+
all(
272+
c("time_point", "mean", "median", "lower_95", "upper_95") %in%
273+
names(summary_df)
274+
)
275+
)
276+
}
277+
})
278+
211279
test_that("sample_predictive_stan errors without posterior samples", {
212280
skip_if_not_installed("cmdstanr")
213281
skip_if(

0 commit comments

Comments
 (0)