Conversation
Introduce gsd_engine_mvtnorm(), which bundles the only two group sequential computations the graphical procedure needs: repeated p-values and nominal p-value boundaries, backed by the existing repeated_p() and gs_boundaries(). gsd_test(), gsd_test_values_details(), gsd_test_values_look_back(), and gsd_boundary_table() now take an engine instead of spending_fn, so the graphical logic no longer depends on how those quantities are computed. graph_test_shortcut_gsd() builds the mvtnorm engine and passes it through; its interface and results are unchanged. This is a behavior-neutral refactor that prepares for a gsDesign-backed graph_test_shortcut_gsDesign(). The full test suite passes unchanged (562 expectations, 0 failures), and new engine contract tests pin the interface a second engine must satisfy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gs_boundaries() and repeated_p() integrate the joint distribution with randomized quasi-Monte Carlo (mvtnorm::GenzBretz), so two independent evaluations of the same boundary agree only to about 1e-5. The engine contract tests compared such pairs at expect_equal()'s default tolerance and failed on the third-analysis boundary. Seed the RNG before each paired evaluation, allow a 1e-3 tolerance for the integration error, and additionally assert that the two hypotheses (O'Brien-Fleming vs Pocock spending) produce different results, which is the actual per-hypothesis dispatch guard. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Code Coverage SummaryDiff against mainResults for commit: f7f9f70 Minimum allowed coverage is ♻️ This comment has been updated with latest results |
xidongdxi
marked this pull request as draft
September 19, 2026 02:38
…graphical testing
Introduce a second group sequential engine, gsd_engine_gsDesign(), that obtains every group sequential quantity from the gsDesign package: one design per hypothesis via gsDesign(test.type = 1, n.I = info_frac, maxn.IPlan = 1, usTime), repeated p-values from sequentialPValue() with the earlier analyses' Z-statistics set to -20 (gsDesign's own no-lower-bound value), and nominal boundaries from the design's upper bound at the allocated level. The spending time is always passed explicitly because sequentialPValue()'s default rescales it by the last observed information. The classical boundary families ("OF", "Pocock", "WT") use a root-find on the design boundary, since sequentialPValue() does not support them; single-analysis hypotheses use the univariate closed form.
graph_test_shortcut_gsDesign() runs the same graphical procedure as graph_test_shortcut_gsd() on this engine, taking gsDesign's spending terms (sfu, sfupar, usTime) in place of spending_fn and returning the same gsd_graph_report structure. gsDesign remains a suggested package, checked at runtime. The print method labels gsDesign spending functions by their own names and shows usTime. gsd_input_val() now accepts a NULL spending_fn and the analysis-name logic is shared via gsd_analysis_names().
The two functions agree to about 1e-5 in p-values and boundaries (gsDesign integrates deterministically, mvtnorm by randomized quasi-Monte Carlo) with identical rejection decisions across the test scenarios, and the Wang-Tsiatis repeated p-values were cross-validated against rpact. Adds tests, roxygen, a NEWS entry, and the pkgdown reference entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Report lists of spending functions or parameters of the wrong length through the validation message instead of a names<- error, reject gsDesign::sfPoints() with a pointer to sfLinear() (sequentialPValue() evaluates the spending function on partial schedules, which sfPoints() refuses), store the supplied spending function in the design so that a user-defined function built on gsDesign::spendingFunction() is used consistently even without the sf self-reference, and keep list-valued parameters such as sfTruncated()'s out of the printed label. Documents that list parameters must be wrapped per hypothesis. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mirrors group-sequential-testing.Rmd with graph_test_shortcut_gsDesign(): gsDesign's spending functions and their correspondence to the built-in ones, boundaries from gsDesign(), the sequentialPValue() mechanism with the earlier Z-statistics set to -20, both case studies, spending time via usTime, monitoring with usTime = planned and info_frac = actual (and the equivalent over-run representation), gsDesign's spending function library, and a user-defined spending function built on gsDesign::spendingFunction(). Knitting stops with a visible note if gsDesign is not installed. Points to the new function from the original vignette's gsDesign subsection, adds the pkgdown article and a NEWS entry, and adds the new words to the word list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
xidongdxi
marked this pull request as ready for review
September 19, 2026 21:06
"Graphical Approaches in Group Sequential Design" for the original vignette and "Graphical Approaches in Group Sequential Design: Combining graphicalMCP and gsDesign" for the new one, so that the titles read as a pair and neither suggests that gsDesign handles the graphical part. File names and pkgdown URLs are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR separates the group sequential calculations from the graphical calculations in
graph_test_shortcut_gsd()and addsgraph_test_shortcut_gsDesign(), which runs the same graphical group sequential procedure but obtains every group sequential quantity (boundaries and repeated p-values) from the gsDesign package, using gsDesign's own spending terms (sfu,sfupar,usTime). It comes with a dedicated vignette that reproduces the group sequential testing vignette with the new function.gsDesign stays in
Suggestsand is only required by the new function, which stops with an informative message if it is missing. The behavior ofgraph_test_shortcut_gsd()is unchanged.The PR is organized in four layers; the commits follow the same order.
1. Engine injection (behavior-neutral refactor)
The graphical loop in
gsd_test()needs exactly two group sequential operations per hypothesis: a repeated p-value and a set of nominal boundaries. These are now supplied by an engine, a list of two closuresrepeated_p(p, info_frac, j)andnominal_bounds(alpha, info_frac, j)(seeR/gsd_engine.R).gsd_engine_mvtnorm(spending_fn)wraps the existingrepeated_p()/gs_boundaries(), andgsd_test(),gsd_test_values_details(),gsd_test_values_look_back()andgsd_boundary_table()take anengineinstead ofspending_fn.seq_p = cummin(rep_p)stays in the graphical layer, so it is engine-agnostic.Neutrality was verified with the full existing test suite and by knitting both group sequential vignettes under a fixed seed against
main: the rendered markdown is byte-identical.2. gsDesign engine (
gsd_engine_gsDesign(), internal)Only gsDesign's public functions are used:
gsDesign(),sequentialPValue(), and thesf*spending functions.NAanalyses:gsDesign(k = K_j, test.type = 1, alpha, sfu, sfupar, n.I = info_frac_j, maxn.IPlan = 1, usTime)— one-sided efficacy boundary, information given directly as fractions of the planned maximum, so the correlation is the one implied byinfo_frac. The design is rebuilt at whatever level the graph currently allocates (weight × alpha).sequentialPValue()after setting the Z-statistics of analyses1..k-1to-20(gsDesign's own "no lower bound" value), so that only analysiskcan trigger a rejection. This is exact, not an approximation. gsDesign has norepeatedPValue()function (checked in 3.9.0 and CRAN 3.11.0).usTimeif given, elsepmin(info_frac, 1)).sequentialPValue()'s default rescales it by the last observed information, which silently changes interim spending when a trial over-runs its plan (error 4e-4 vs 2e-7 with an explicitusTime)."OF","Pocock","WT"are boundary shapes in gsDesign, not spending functions, andsequentialPValue()errors on them; their repeated p-value is found by a root-find ofb_k(alpha) = Z_kon the design's upper bound (beta = (1 - alpha)/2keepsgsDesign()valid near alpha ≈ 1; beta does not affect the bounds fortest.type = 1). Wang-Tsiatis repeated p-values were cross-validated against rpact's exact WT critical values (≤ 1e-7).gsDesign()requiresk >= 2).sequentialPValue()would error "Final spend must be > 0"; the search interval is raised to the first level with positive spend).gsDesign::spendingFunction()is stored in the design (upper$sf) so thatsequentialPValue()evaluates it rather than the template's linear spend.3.
graph_test_shortcut_gsDesign()(exported)Same signature as
graph_test_shortcut_gsd()withspending_fnreplaced bysfu(spending function or one of"OF","Pocock","WT"; single value or list ofm),sfupar(single value or list ofm), andusTime(NULL, vector of lengthK, orm × Kmatrix withinfo_frac'sNApattern). Returns the samegsd_graph_reportstructure, withinputs$engine = "gsDesign"; the print method labels spending functions by gsDesign's own$nameand showsusTime. Validation coverssfu/sfupar/usTimeshapes and contents, warns thatusTimeis ignored for classical families, and rejectsgsDesign::sfPoints()(it needs the full schedule at every call, whichsequentialPValue()cannot provide;sfLinear()gives the same spending). The roxygen@detailsdescribes how gsDesign is used.Mapping of the built-in spending functions:
spending_of=sfLDOF,spending_pocock=sfLDPocock,spending_hsd(gamma)=sfHSD+sfupar = gamma,spending_linear=sfPower+sfupar = 1,spending_wt(delta)="WT"+sfupar = delta,spending_with_time()=usTime.Agreement with
graph_test_shortcut_gsd(): gsDesign integrates deterministically, the mvtnorm engine uses randomized quasi-Monte Carlo, so results agree to about 1e-5 (repeated/sequential/adjusted p-values within 8e-6, boundaries within 1e-8) rather than bit-for-bit; rejection decisions, sequences, andtest_valuescoincide in all test scenarios (Maurer–Bretz look_back F/T, oncology NA-padded schedules, per-hypothesis spending/info fractions/look_back, over-running information, spending time, single-analysis hypotheses).4. Vignette
vignettes/group-sequential-testing-gsDesign.Rmd("Graphical Approaches in Group Sequential Design: Combining graphicalMCP and gsDesign") mirrorsgroup-sequential-testing.Rmdsection by section withgraph_test_shortcut_gsDesign(). The original vignette is retitled "Graphical Approaches in Group Sequential Design" so that the two read as a pair and neither suggests that gsDesign handles the graphical part; file names and pkgdown URLs are unchanged. Contents of the new vignette: gsDesign's spending functions and a correspondence table; boundaries fromgsDesign(); an explicit demonstration of thesequentialPValue()+-20mechanism (what the function does internally); both case studies with identical numbers; spending time viausTime; monitoring viausTime = planned, info_frac = actual, plus the equivalent over-run representation; gsDesign's spending library (incl."WT",sfTruncated, and whysfPointsis unsupported); a user-defined spending function ongsDesign::spendingFunction(). Per our discussion it has no cross-check section againstgraph_test_shortcut_gsd()(a separate vignette later; the equivalence lives in the tests) and no rpact material. If gsDesign is not installed, a visible note is printed and knitting stops (knitr::knit_exit()), which also covers the inline code. The original vignette's "Using Spending Functions from Other Packages" section now points to the new function, and the article is added to_pkgdown.ymland NEWS.Verification
devtools::test(): 1048 pass, 0 fail, 1 skip (lrstat ≥ 0.3.3 not installed locally); the original 572 tests are unchanged.devtools::check(args = c("--as-cran", "--no-manual")): 0 errors, 0 warnings; the only notes are the standard "development version" NEWS heading and a local scratch directory that is not in the branch.Suggested reading order for review
R/gsd_engine.R—gsd_engine_mvtnorm()thengsd_engine_gsDesign()(the whole gsDesign interaction is here, ~120 lines).R/graph_test_shortcut_gsd.R— theengineplumbing (gsd_test()and helpers),gsd_input_val(),gsd_analysis_names().R/graph_test_shortcut_gsDesign.R— interface, normalization, validation, roxygen.R/print.gsd_graph_report.R— the gsDesign branch andgsd_spending_label().tests/testthat/test-gsd_engine.R,tests/testthat/test-graph_test_shortcut_gsDesign.R.vignettes/group-sequential-testing-gsDesign.Rmd.Out of scope, noted for follow-up
repeated_p()/sequential_p()withspending_wt()in the mvtnorm path return 1 even at the final analysis (spending_wt()cannot calibrate its constant at alpha ≈ 1 and the error is swallowed); the gsDesign engine's"WT"path does not have this problem. Will be reported separately.🤖 Generated with Claude Code