| Title: | Group Sequential Design |
|---|---|
| Description: | Derives group sequential clinical trial designs and describes their properties. Particular focus on time-to-event, binary, and continuous outcomes. Largely based on methods described in Jennison, Christopher and Turnbull, Bruce W., 2000, "Group Sequential Methods with Applications to Clinical Trials" ISBN: 0-8493-0316-8. |
| Authors: | Keaven Anderson [aut, cre], Merck & Co., Inc., Rahway, NJ, USA and its affiliates [cph] (ROR: <https://ror.org/02891sr49>) |
| Maintainer: | Keaven Anderson <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 3.11.1.9028 |
| Built: | 2026-10-11 20:36:39 UTC |
| Source: | https://github.com/keaven/gsdesign |
as_gt() is deprecated in favor of lt(), which
produces a lightweight HTML table without the heavy gt dependency.
as_gt() is kept for one release so existing code that customizes the
output with gt functions keeps working; it still returns a
gt_tbl object and requires gt to be installed. New code should
use lt(); see lt-methods for the available
arguments, which mirror those of as_gt().
as_gt(x, ...) ## S3 method for class 'gsBinomialExactTable' as_gt( x, ..., title = "Operating Characteristics for the Truncated SPRT Design", subtitle = "Assumes trial evaluated sequentially after each response", theta_label = gt::html("Underlying<br>response rate"), bound_label = c("Futility bound", "Efficacy bound"), prob_decimals = 2, en_decimals = 1, rr_decimals = 0 )as_gt(x, ...) ## S3 method for class 'gsBinomialExactTable' as_gt( x, ..., title = "Operating Characteristics for the Truncated SPRT Design", subtitle = "Assumes trial evaluated sequentially after each response", theta_label = gt::html("Underlying<br>response rate"), bound_label = c("Futility bound", "Efficacy bound"), prob_decimals = 2, en_decimals = 1, rr_decimals = 0 )
x |
Object to be converted. |
... |
Other parameters that may be specific to the object. |
title |
Table title. |
subtitle |
Table subtitle. |
theta_label |
Label for theta. |
bound_label |
Label for bounds. |
prob_decimals |
Number of decimal places for probability of crossing. |
en_decimals |
Number of decimal places for expected number of observations when bound is crossed or when trial ends without crossing. |
rr_decimals |
Number of decimal places for response rates. |
A gt_tbl object that may be extended by overloaded versions of
as_gt().
lt(), lt-methods
Convert and save the summary table object created with as_table
to an RTF file using r2rtf; currently only implemented for
gsBinomialExact.
as_rtf(x, ...) ## S3 method for class 'gsBinomialExactTable' as_rtf( x, file, ..., title = paste("Operating Characteristics by Underlying Response Rate for", "Exact Binomial Group Sequential Design"), theta_label = "Underlying Response Rate", response_outcome = TRUE, bound_label = if (response_outcome) c("Futility Bound", "Efficacy Bound") else c("Efficacy Bound", "Futility Bound"), en_label = "Expected Sample Sizes", prob_decimals = 2, en_decimals = 1, rr_decimals = 0 ) ## S3 method for class 'gsBoundSummary' as_rtf( x, file, ..., title = "Boundary Characteristics for Group Sequential Design", footnote_p_onesided = "one-side p-value for experimental better than control", footnote_appx_effect_at_bound = NULL, footnote_p_cross_h0 = "Cumulative type I error assuming binding futility bound", footnote_p_cross_h1 = "Cumulative power under the alternate hypothesis", footnote_specify = NULL, footnote_text = NULL )as_rtf(x, ...) ## S3 method for class 'gsBinomialExactTable' as_rtf( x, file, ..., title = paste("Operating Characteristics by Underlying Response Rate for", "Exact Binomial Group Sequential Design"), theta_label = "Underlying Response Rate", response_outcome = TRUE, bound_label = if (response_outcome) c("Futility Bound", "Efficacy Bound") else c("Efficacy Bound", "Futility Bound"), en_label = "Expected Sample Sizes", prob_decimals = 2, en_decimals = 1, rr_decimals = 0 ) ## S3 method for class 'gsBoundSummary' as_rtf( x, file, ..., title = "Boundary Characteristics for Group Sequential Design", footnote_p_onesided = "one-side p-value for experimental better than control", footnote_appx_effect_at_bound = NULL, footnote_p_cross_h0 = "Cumulative type I error assuming binding futility bound", footnote_p_cross_h1 = "Cumulative power under the alternate hypothesis", footnote_specify = NULL, footnote_text = NULL )
x |
Object to be saved as RTF file. |
... |
Other parameters that may be specific to the object. |
file |
File path for the output. |
title |
Title of the report. |
theta_label |
Label for theta. |
response_outcome |
Logical values indicating if the outcome is
response rate ( |
bound_label |
Label for bounds. If the outcome is response rate, then the label is "Futility bound" and "Efficacy bound". If the outcome is failure rate, then the label is "Efficacy bound" and "Futility bound". |
en_label |
Label for expected number. |
prob_decimals |
Number of decimal places for probability of crossing. |
en_decimals |
Number of decimal places for expected number of observations when bound is crossed or when trial ends without crossing. |
rr_decimals |
Number of decimal places for response rates. |
footnote_p_onesided |
Footnote for one-side p-value. |
footnote_appx_effect_at_bound |
Footnote for approximate effect treatment at bound. |
footnote_p_cross_h0 |
Footnote for cumulative type I error. |
footnote_p_cross_h1 |
Footnote for cumulative power under the alternate hypothesis. |
footnote_specify |
Vector of string to put footnote super script. |
footnote_text |
Vector of string of footnote text. |
Currently only implemented for gsBinomialExact objects.
Creates a table to summarize an object.
For gsBinomialExact, this summarized operating characteristics
across a range of effect sizes.
as_rtf() returns the input x invisibly.
vignette("binomialSPRTExample")
# as_rtf for gsBinomialExact zz <- gsBinomialExact( k = 3, theta = seq(0.1, 0.9, 0.1), n.I = c(12, 24, 36), a = c(-1, 0, 11), b = c(5, 9, 12) ) zz |> as_table() |> as_rtf( file = tempfile(fileext = ".rtf"), title = "Power/Type I Error and Expected Sample Size for a Group Sequential Design" ) safety_design <- binomialSPRT( p0 = .04, p1 = .1, alpha = .04, beta = .2, minn = 4, maxn = 75 ) safety_power <- gsBinomialExact( k = length(safety_design$n.I), theta = seq(.02, .16, .02), n.I = safety_design$n.I, a = safety_design$lower$bound, b = safety_design$upper$bound ) safety_power |> as_table() |> as_rtf( file = tempfile(fileext = ".rtf"), theta_label = "Underlying\nAE Rate", prob_decimals = 3, bound_label = c("Low Rate", "High Rate") ) # as_rtf for gsBoundSummary xgs <- gsSurv(lambdaC = .2, hr = .5, eta = .1, T = 2, minfup = 1.5) gsBoundSummary(xgs, timename = "Year", tdigits = 1) |> as_rtf(file = tempfile(fileext = ".rtf")) ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025, ratio = 2 ) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) |> as_rtf(file = tempfile(fileext = ".rtf")) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) |> as_rtf(file = tempfile(fileext = ".rtf"), footnote_specify = "Z", footnote_text = "Z-Score")# as_rtf for gsBinomialExact zz <- gsBinomialExact( k = 3, theta = seq(0.1, 0.9, 0.1), n.I = c(12, 24, 36), a = c(-1, 0, 11), b = c(5, 9, 12) ) zz |> as_table() |> as_rtf( file = tempfile(fileext = ".rtf"), title = "Power/Type I Error and Expected Sample Size for a Group Sequential Design" ) safety_design <- binomialSPRT( p0 = .04, p1 = .1, alpha = .04, beta = .2, minn = 4, maxn = 75 ) safety_power <- gsBinomialExact( k = length(safety_design$n.I), theta = seq(.02, .16, .02), n.I = safety_design$n.I, a = safety_design$lower$bound, b = safety_design$upper$bound ) safety_power |> as_table() |> as_rtf( file = tempfile(fileext = ".rtf"), theta_label = "Underlying\nAE Rate", prob_decimals = 3, bound_label = c("Low Rate", "High Rate") ) # as_rtf for gsBoundSummary xgs <- gsSurv(lambdaC = .2, hr = .5, eta = .1, T = 2, minfup = 1.5) gsBoundSummary(xgs, timename = "Year", tdigits = 1) |> as_rtf(file = tempfile(fileext = ".rtf")) ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025, ratio = 2 ) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) |> as_rtf(file = tempfile(fileext = ".rtf")) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) |> as_rtf(file = tempfile(fileext = ".rtf"), footnote_specify = "Z", footnote_text = "Z-Score")
Create a tibble to summarize an object; currently only implemented for
gsBinomialExact.
as_table(x, ...) ## S3 method for class 'gsBinomialExact' as_table(x, ...)as_table(x, ...) ## S3 method for class 'gsBinomialExact' as_table(x, ...)
x |
Object to be summarized. |
... |
Other parameters that may be specific to the object. |
Currently only implemented for gsBinomialExact objects.
Creates a table to summarize an object.
For gsBinomialExact, this summarized operating characteristics
across a range of effect sizes.
A tibble which may have an extended class to enable further output processing.
vignette("binomialSPRTExample")
b <- binomialSPRT(p0 = .1, p1 = .35, alpha = .08, beta = .2, minn = 10, maxn = 25) b_power <- gsBinomialExact( k = length(b$n.I), theta = seq(.1, .45, .05), n.I = b$n.I, a = b$lower$bound, b = b$upper$bound ) b_power |> as_table() |> lt()b <- binomialSPRT(p0 = .1, p1 = .35, alpha = .08, beta = .2, minn = 10, maxn = 25) b_power <- gsBinomialExact( k = length(b$n.I), theta = seq(.1, .45, .05), n.I = b$n.I, a = b$lower$bound, b = b$upper$bound ) b_power |> as_table() |> lt()
Creates a power table for binomial tests with various control group response rates and treatment effects. The function can compute power and Type I error either analytically or through simulation. With large simulations, the function is still fast and can produce exact power values to within simulation error.
binomialPowerTable( pC = c(0.8, 0.9, 0.95), delta = seq(-0.05, 0.05, 0.025), n = 70, ratio = 1, alpha = 0.025, delta0 = 0, scale = "Difference", failureEndpoint = TRUE, simulation = FALSE, nsim = 1e+06, adj = 0, chisq = 0 )binomialPowerTable( pC = c(0.8, 0.9, 0.95), delta = seq(-0.05, 0.05, 0.025), n = 70, ratio = 1, alpha = 0.025, delta0 = 0, scale = "Difference", failureEndpoint = TRUE, simulation = FALSE, nsim = 1e+06, adj = 0, chisq = 0 )
pC |
Vector of control group response rates. |
delta |
Vector of treatment effects (differences in response rates). |
n |
Total sample size. |
ratio |
Ratio of experimental to control sample size. |
alpha |
Type I error rate. |
delta0 |
Non-inferiority margin. |
scale |
Scale for the test
( |
failureEndpoint |
Logical indicating if the endpoint is a
failure ( |
simulation |
Logical indicating whether to use simulation ( |
nsim |
Number of simulations to run when |
adj |
Use continuity correction for the testing (default is 0;
only used if |
chisq |
Chi-squared value for the test (default is 0;
only used if |
The function binomialPowerTable() creates a grid of all combinations of control group response rates and treatment effects.
All out of range values (i.e., where the experimental group response rate is not between 0 and 1) are filtered out.
For each combination, it computes the power either analytically using nBinomial() or through
simulation using simBinomial().
When using simulation, the simPowerBinomial() function (not exported) is called
internally to perform the simulations.
Assuming is the true probability of a positive test, the simulation standard error is
For example, when approximating an underlying Type I error rate of 0.025, the simulation standard error is 0.000156 with 1000000 simulations and the approximated power 95 is 0.025 +/- 1.96 * SE = 0.025 +/- 0.000306.
A data frame containing:
pCControl group response or failure rate.
deltaTreatment effect.
pEExperimental group response or failure rate.
PowerPower for the test (asymptotic or simulated).
# Create a power table with analytical power calculation power_table <- binomialPowerTable( pC = c(0.8, 0.9), delta = seq(-0.05, 0.05, 0.025), n = 70 ) # Create a power table with simulation power_table_sim <- binomialPowerTable( pC = c(0.8, 0.9), delta = seq(-0.05, 0.05, 0.025), n = 70, simulation = TRUE, nsim = 10000 )# Create a power table with analytical power calculation power_table <- binomialPowerTable( pC = c(0.8, 0.9), delta = seq(-0.05, 0.05, 0.025), n = 70 ) # Create a power table with simulation power_table_sim <- binomialPowerTable( pC = c(0.8, 0.9), delta = seq(-0.05, 0.05, 0.025), n = 70, simulation = TRUE, nsim = 10000 )
Utility functions to verify an objects's properties including whether it is
a scalar or vector, the class, the length, and (if numeric) whether the
range of values is on a specified interval. Additionally, the
checkLengths function can be used to ensure that all the supplied
inputs have equal lengths.
isInteger is similar to is.integer except that
isInteger(1) returns TRUE whereas is.integer(1) returns
FALSE.
checkScalar is used to verify that the input object is a scalar as
well as the other properties specified above.
checkVector is used to verify that the input object is an atomic
vector as well as the other properties as defined above.
checkRange is used to check whether the numeric input object's values
reside on the specified interval. If any of the values are outside the
specified interval, a FALSE is returned.
checkLength is used to check whether all of the supplied inputs have
equal lengths.
checkLengths(..., allowSingle = FALSE) checkRange( x, interval = 0:1, inclusion = c(TRUE, TRUE), varname = deparse(substitute(x)), tol = 0 ) checkScalar(x, isType = "numeric", ...) checkVector(x, isType = "numeric", ..., length = NULL) isInteger(x)checkLengths(..., allowSingle = FALSE) checkRange( x, interval = 0:1, inclusion = c(TRUE, TRUE), varname = deparse(substitute(x)), tol = 0 ) checkScalar(x, isType = "numeric", ...) checkVector(x, isType = "numeric", ..., length = NULL) isInteger(x)
... |
For the
|
allowSingle |
logical flag. If |
x |
any object. |
interval |
two-element numeric vector defining the interval over which
the input object is expected to be contained. Use the |
inclusion |
two-element logical vector defining the boundary behavior
of the specified interval. A |
varname |
character string defining the name of the input variable as
sent into the function by the caller. This is used primarily as a mechanism
to specify the name of the variable being tested when |
tol |
numeric scalar defining the tolerance to use in testing the intervals of the
|
isType |
character string defining the class that the input object is expected to be. |
length |
integer specifying the expected length of the object in the
case it is a vector. If |
isInteger: Boolean value as checking result
Other functions have no return value, called for side effects
# check whether input is an integer isInteger(1) isInteger(1:5) try(isInteger("abc")) # expect error # check whether input is an integer scalar checkScalar(3, "integer") # check whether input is an integer scalar that resides # on the interval on [3, 6]. Then test for interval (3, 6]. checkScalar(3, "integer", c(3, 6)) try(checkScalar(3, "integer", c(3, 6), c(FALSE, TRUE))) # expect error # check whether the input is an atomic vector of class numeric, # of length 3, and whose value all reside on the interval [1, 10) x <- c(3, pi, exp(1)) checkVector(x, "numeric", c(1, 10), c(TRUE, FALSE), length = 3) # do the same but change the expected length; expect error try(checkVector(x, "numeric", c(1, 10), c(TRUE, FALSE), length = 2)) # create faux function to check input variable foo <- function(moo) checkVector(moo, "character") foo(letters) try(foo(1:5)) # expect error with function and argument name in message # check for equal lengths of various inputs checkLengths(1:2, 2:3, 3:4) try(checkLengths(1, 2, 3, 4:5)) # expect error # check for equal length inputs but ignore single element vectors checkLengths(1, 2, 3, 4:5, 7:8, allowSingle = TRUE)# check whether input is an integer isInteger(1) isInteger(1:5) try(isInteger("abc")) # expect error # check whether input is an integer scalar checkScalar(3, "integer") # check whether input is an integer scalar that resides # on the interval on [3, 6]. Then test for interval (3, 6]. checkScalar(3, "integer", c(3, 6)) try(checkScalar(3, "integer", c(3, 6), c(FALSE, TRUE))) # expect error # check whether the input is an atomic vector of class numeric, # of length 3, and whose value all reside on the interval [1, 10) x <- c(3, pi, exp(1)) checkVector(x, "numeric", c(1, 10), c(TRUE, FALSE), length = 3) # do the same but change the expected length; expect error try(checkVector(x, "numeric", c(1, 10), c(TRUE, FALSE), length = 2)) # create faux function to check input variable foo <- function(moo) checkVector(moo, "character") foo(letters) try(foo(1:5)) # expect error with function and argument name in message # check for equal lengths of various inputs checkLengths(1:2, 2:3, 3:4) try(checkLengths(1, 2, 3, 4:5)) # expect error # check for equal length inputs but ignore single element vectors checkLengths(1, 2, 3, 4:5, 7:8, allowSingle = TRUE)
Support is provided for sample size estimation, power, testing, confidence intervals and simulation for fixed sample size trials (that is, not group sequential or adaptive) with two arms and binary outcomes. Both superiority and non-inferiority trials are considered. While all routines default to comparisons of risk-difference, options to base computations on risk-ratio and odds-ratio are also included.
nBinomial() computes sample size or power using the method of
Farrington and Manning (1990) for a trial to test the difference between two
binomial event rates. The routine can be used for a test of superiority or
non-inferiority. For a design that tests for superiority nBinomial()
is consistent with the method of Fleiss, Tytun, and Ury (but without the
continuity correction) to test for differences between event rates. This
routine is consistent with the Hmisc package routines bsamsize and
bpower for superiority designs. Vector arguments allow computing
sample sizes for multiple scenarios for comparative purposes.
testBinomial() computes a Z- or Chi-square-statistic that compares
two binomial event rates using the method of Miettinen and Nurminen (1980).
This can be used for superiority or non-inferiority testing. Vector
arguments allow easy incorporation into simulation routines for fixed, group
sequential and adaptive designs.
ciBinomial() computes confidence intervals for 1) the difference
between two rates, 2) the risk-ratio for two rates or 3) the odds-ratio for
two rates. This procedure provides inference that is consistent with
testBinomial() in that the confidence intervals are produced by
inverting the testing procedures in testBinomial(). The Type I error
alpha input to ciBinomial is always interpreted as 2-sided.
simBinomial() performs simulations to estimate the power for a
Miettinen and Nurminen (1985) test comparing two binomial rates for
superiority or non-inferiority. As noted in documentation for
bpower.sim() in the HMisc package, by using testBinomial() you
can see that the formulas without any continuity correction are quite
accurate. In fact, Type I error for a continuity-corrected test is
significantly lower (Gordon and Watson, 1996) than the nominal rate. Thus,
as a default no continuity corrections are performed.
varBinomial computes blinded estimates of the variance of the
estimate of 1) event rate differences, 2) logarithm of the risk ratio, or 3)
logarithm of the odds ratio. This is intended for blinded sample size
re-estimation for comparative trials with a binary outcome.
Testing is 2-sided when a Chi-square statistic is used and 1-sided when a Z-statistic is used. Thus, these 2 options will produce substantially different results, in general. For non-inferiority, 1-sided testing is appropriate.
You may wish to round sample sizes up using ceiling().
Farrington and Manning (1990) begin with event rates p1 and p2
under the alternative hypothesis and a difference between these rates under
the null hypothesis, delta0. From these values, actual rates under
the null hypothesis are computed, which are labeled p10 and
p20 when outtype=3. The rates p1 and p2 are used
to compute a variance for a Z-test comparing rates under the alternative
hypothesis, while p10 and p20 are used under the null
hypothesis. This computational method is also used to estimate variances in
varBinomial() based on the overall event rate observed and the input
treatment difference specified in delta0.
Sample size with scale="Difference" produces an error if
p1-p2=delta0. Normally, the alternative hypothesis under
consideration would be p1-p2-delta0$>0$. However, the alternative can
have p1-p2-delta0$<0$.
ciBinomial(x1, x2, n1, n2, alpha = 0.05, adj = 0, scale = "Difference") nBinomial( p1, p2, alpha = 0.025, beta = 0.1, delta0 = 0, ratio = 1, sided = 1, outtype = 1, scale = "Difference", n = NULL ) simBinomial( p1, p2, n1, n2, delta0 = 0, nsim = 10000, chisq = 0, adj = 0, scale = "Difference" ) testBinomial( x1, x2, n1, n2, delta0 = 0, chisq = 0, adj = 0, scale = "Difference", tol = 1e-11 ) varBinomial(x, n, delta0 = 0, ratio = 1, scale = "Difference")ciBinomial(x1, x2, n1, n2, alpha = 0.05, adj = 0, scale = "Difference") nBinomial( p1, p2, alpha = 0.025, beta = 0.1, delta0 = 0, ratio = 1, sided = 1, outtype = 1, scale = "Difference", n = NULL ) simBinomial( p1, p2, n1, n2, delta0 = 0, nsim = 10000, chisq = 0, adj = 0, scale = "Difference" ) testBinomial( x1, x2, n1, n2, delta0 = 0, chisq = 0, adj = 0, scale = "Difference", tol = 1e-11 ) varBinomial(x, n, delta0 = 0, ratio = 1, scale = "Difference")
x1 |
Number of “successes” in the control group |
x2 |
Number of “successes” in the experimental group |
n1 |
Number of observations in the control group |
n2 |
Number of observations in the experimental group |
alpha |
type I error; see |
adj |
With |
scale |
“Difference”, “RR”, “OR”; see the
|
p1 |
event rate in group 1 under the alternative hypothesis |
p2 |
event rate in group 2 under the alternative hypothesis |
beta |
type II error |
delta0 |
A value of 0 (the default) always represents no difference
between treatment groups under the null hypothesis. |
ratio |
sample size ratio for group 2 divided by group 1 |
sided |
2 for 2-sided test, 1 for 1-sided test |
outtype |
|
n |
If power is to be computed in |
nsim |
The number of simulations to be performed in
|
chisq |
An indicator of whether or not a chi-square (as opposed to Z)
statistic is to be computed. If |
tol |
Default should probably be used; this is used to deal with a rounding issue in interim calculations |
x |
Number of “successes” in the combined control and experimental groups. |
testBinomial() and simBinomial() each return a vector
of either Chi-square or Z test statistics. These may be compared to an
appropriate cutoff point (e.g., qnorm(.975) for normal or
qchisq(.95,1) for chi-square).
ciBinomial() returns a data frame with 1 row with a confidence
interval; variable names are lower and upper.
varBinomial() returns a vector of (blinded) variance estimates of the
difference of event rates (scale="Difference"), logarithm of the
odds-ratio (scale="OR") or logarithm of the risk-ratio
(scale="RR").
With the default outtype=1, nBinomial() returns a vector of
total sample sizes is returned. With outtype=2, nBinomial()
returns a data frame containing two vectors n1 and n2
containing sample sizes for groups 1 and 2, respectively; if n is
input, this option also returns the power in a third vector, Power.
With outtype=3, nBinomial() returns a data frame with the
following columns:
n |
A vector with total samples size required for each event rate comparison specified |
n1 |
A vector of sample sizes for group 1 for each event rate comparison specified |
n2 |
A vector of sample sizes for group 2 for each event rate comparison specified |
alpha |
As input |
sided |
As input |
beta |
As input; if
|
Power |
If |
sigma0 |
A vector containing the standard deviation of the
treatment effect difference under the null hypothesis times |
sigma1 |
A
vector containing the values as |
p1 |
As input |
p2 |
As input |
p10 |
group 1 event rate used for null hypothesis |
p20 |
group 2 event rate used for null hypothesis |
Keaven Anderson [email protected]
Farrington, CP and Manning, G (1990), Test statistics and sample size formulae for comparative binomial trials with null hypothesis of non-zero risk difference or non-unity relative risk. Statistics in Medicine; 9: 1447-1454.
Fleiss, JL, Tytun, A and Ury (1980), A simple approximation for calculating sample sizes for comparing independent proportions. Biometrics;36:343-346.
Gordon, I and Watson R (1985), The myth of continuity-corrected sample size formulae. Biometrics; 52: 71-76.
Miettinen, O and Nurminen, M (1985), Comparative analysis of two rates. Statistics in Medicine; 4 : 213-226.
# Compute z-test test statistic comparing 39/500 to 13/500 # use continuity correction in variance x <- testBinomial(x1 = 39, x2 = 13, n1 = 500, n2 = 500, adj = 1) x pnorm(x, lower.tail = FALSE) # Compute with unadjusted variance x0 <- testBinomial(x1 = 39, x2 = 23, n1 = 500, n2 = 500) x0 pnorm(x0, lower.tail = FALSE) # Perform 50k simulations to test validity of the above # asymptotic p-values # (you may want to perform more to reduce standard error of estimate) sum(as.double(x0) <= simBinomial(p1 = .078, p2 = .078, n1 = 500, n2 = 500, nsim = 10000)) / 10000 sum(as.double(x0) <= simBinomial(p1 = .052, p2 = .052, n1 = 500, n2 = 500, nsim = 10000)) / 10000 # Perform a non-inferiority test to see if p2=400 / 500 is within 5% of # p1=410 / 500 use a z-statistic with unadjusted variance x <- testBinomial(x1 = 410, x2 = 400, n1 = 500, n2 = 500, delta0 = -.05) x pnorm(x, lower.tail = FALSE) # since chi-square tests equivalence (a 2-sided test) rather than # non-inferiority (a 1-sided test), # the result is quite different pchisq(testBinomial( x1 = 410, x2 = 400, n1 = 500, n2 = 500, delta0 = -.05, chisq = 1, adj = 1 ), 1, lower.tail = FALSE) # now simulate the z-statistic witthout continuity corrected variance sum(qnorm(.975) <= simBinomial(p1 = .8, p2 = .8, n1 = 500, n2 = 500, nsim = 100000)) / 100000 # compute a sample size to show non-inferiority # with 5% margin, 90% power nBinomial(p1 = .2, p2 = .2, delta0 = .05, alpha = .025, sided = 1, beta = .1) # assuming a slight advantage in the experimental group lowers # sample size requirement nBinomial(p1 = .2, p2 = .19, delta0 = .05, alpha = .025, sided = 1, beta = .1) # compute a sample size for comparing 15% vs 10% event rates # with 1 to 2 randomization nBinomial(p1 = .15, p2 = .1, beta = .2, ratio = 2, alpha = .05) # now look at total sample size using 1-1 randomization n <- nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05) n # check if inputing sample size returns the desired power nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, n = n) # re-do with alternate output types nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, outtype = 2) nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, outtype = 3) # look at power plot under different control event rate and # relative risk reductions library(dplyr) library(ggplot2) p1 <- seq(.075, .2, .000625) len <- length(p1) p2 <- c(p1 * .75, p1 * 2/3, p1 * .6, p1 * .5) Reduction <- c(rep("25 percent", len), rep("33 percent", len), rep("40 percent", len), rep("50 percent", len)) df <- tibble(p1 = rep(p1, 4), p2, Reduction) |> mutate(`Sample size` = nBinomial(p1, p2, beta = .2, alpha = .025, sided = 1)) ggplot(df, aes(x = p1, y = `Sample size`, col = Reduction)) + geom_line() + xlab("Control group event rate") + ylim(0,6000) + ggtitle("Binomial sample size computation for 80 pct power") # compute blinded estimate of treatment effect difference x1 <- rbinom(n = 1, size = 100, p = .2) x2 <- rbinom(n = 1, size = 200, p = .1) # blinded estimate of risk difference variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0) # blinded estimate of log-risk-ratio variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0, scale = "RR") # blinded estimate of log-odds-ratio variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0, scale = "OR")# Compute z-test test statistic comparing 39/500 to 13/500 # use continuity correction in variance x <- testBinomial(x1 = 39, x2 = 13, n1 = 500, n2 = 500, adj = 1) x pnorm(x, lower.tail = FALSE) # Compute with unadjusted variance x0 <- testBinomial(x1 = 39, x2 = 23, n1 = 500, n2 = 500) x0 pnorm(x0, lower.tail = FALSE) # Perform 50k simulations to test validity of the above # asymptotic p-values # (you may want to perform more to reduce standard error of estimate) sum(as.double(x0) <= simBinomial(p1 = .078, p2 = .078, n1 = 500, n2 = 500, nsim = 10000)) / 10000 sum(as.double(x0) <= simBinomial(p1 = .052, p2 = .052, n1 = 500, n2 = 500, nsim = 10000)) / 10000 # Perform a non-inferiority test to see if p2=400 / 500 is within 5% of # p1=410 / 500 use a z-statistic with unadjusted variance x <- testBinomial(x1 = 410, x2 = 400, n1 = 500, n2 = 500, delta0 = -.05) x pnorm(x, lower.tail = FALSE) # since chi-square tests equivalence (a 2-sided test) rather than # non-inferiority (a 1-sided test), # the result is quite different pchisq(testBinomial( x1 = 410, x2 = 400, n1 = 500, n2 = 500, delta0 = -.05, chisq = 1, adj = 1 ), 1, lower.tail = FALSE) # now simulate the z-statistic witthout continuity corrected variance sum(qnorm(.975) <= simBinomial(p1 = .8, p2 = .8, n1 = 500, n2 = 500, nsim = 100000)) / 100000 # compute a sample size to show non-inferiority # with 5% margin, 90% power nBinomial(p1 = .2, p2 = .2, delta0 = .05, alpha = .025, sided = 1, beta = .1) # assuming a slight advantage in the experimental group lowers # sample size requirement nBinomial(p1 = .2, p2 = .19, delta0 = .05, alpha = .025, sided = 1, beta = .1) # compute a sample size for comparing 15% vs 10% event rates # with 1 to 2 randomization nBinomial(p1 = .15, p2 = .1, beta = .2, ratio = 2, alpha = .05) # now look at total sample size using 1-1 randomization n <- nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05) n # check if inputing sample size returns the desired power nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, n = n) # re-do with alternate output types nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, outtype = 2) nBinomial(p1 = .15, p2 = .1, beta = .2, alpha = .05, outtype = 3) # look at power plot under different control event rate and # relative risk reductions library(dplyr) library(ggplot2) p1 <- seq(.075, .2, .000625) len <- length(p1) p2 <- c(p1 * .75, p1 * 2/3, p1 * .6, p1 * .5) Reduction <- c(rep("25 percent", len), rep("33 percent", len), rep("40 percent", len), rep("50 percent", len)) df <- tibble(p1 = rep(p1, 4), p2, Reduction) |> mutate(`Sample size` = nBinomial(p1, p2, beta = .2, alpha = .025, sided = 1)) ggplot(df, aes(x = p1, y = `Sample size`, col = Reduction)) + geom_line() + xlab("Control group event rate") + ylim(0,6000) + ggtitle("Binomial sample size computation for 80 pct power") # compute blinded estimate of treatment effect difference x1 <- rbinom(n = 1, size = 100, p = .2) x2 <- rbinom(n = 1, size = 200, p = .1) # blinded estimate of risk difference variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0) # blinded estimate of log-risk-ratio variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0, scale = "RR") # blinded estimate of log-odds-ratio variance varBinomial(x = x1 + x2, n = 300, ratio = 2, delta0 = 0, scale = "OR")
Computes a fixed look Clopper–Pearson confidence interval for vaccine or prevention efficacy after conditioning on the total number of events.
ciBinomialExact( x, n, ratio = 1, conf.level = 0.95, alternative = c("two.sided", "lower", "upper") )ciBinomialExact( x, n, ratio = 1, conf.level = 0.95, alternative = c("two.sided", "lower", "upper") )
x |
Number of events in the experimental group. |
n |
Total number of events. |
ratio |
Experimental-to-control randomization ratio. |
conf.level |
Confidence level. |
alternative |
Character string specifying a two-sided interval, a lower confidence bound, or an upper confidence bound for efficacy. |
Conditional on n, x is binomial with event probability
p in the experimental group. Clopper–Pearson limits for p
are transformed using
efficacy = 1 - p / (ratio * (1 - p)). Because this transformation is
decreasing, the probability limits are reversed on the efficacy scale.
The same calculation applies to vaccine efficacy (VE) and prevention
efficacy (PE).
A one-row data frame containing the observed counts, randomization ratio, efficacy estimate, confidence limits, and confidence level.
repeatedCIBinomialExact,
sequentialCIBinomialExact
ciBinomialExact(x = 16, n = 78, ratio = 3)ciBinomialExact(x = 16, n = 78, ratio = 3)
ssrCP() adapts 2-stage group sequential designs to 2-stage sample
size re-estimation designs based on interim analysis conditional power. This
is a simple case of designs developed by Lehmacher and Wassmer, Biometrics
(1999). The conditional power designs of Bauer and Kohne (1994),
Proschan and Hunsberger (1995), Cui, Hung and Wang (1999) and Liu and Chi (2001),
Gao, Ware and Mehta (2008), and Mehta and Pocock (2011). Either the estimated
treatment effect at the interim analysis or any chosen effect size can be
used for sample size re-estimation. If not done carefully, these designs can
be very inefficient. It is probably a good idea to compare any design to a
simpler group sequential design; see, for example, Jennison and Turnbull
(2003). The a assumes a small Type I error is included with the interim
analysis and that the design is an adaptation from a 2-stage group
sequential design Related functions include 3 pre-defined combination test
functions (z2NC, z2Z, z2Fisher) that represent the
inverse normal combination test (Lehmacher and Wassmer, 1999), the
sufficient statistic for the complete data, and Fisher's combination test.
Power.ssrCP computes unconditional power for a conditional power
design derived by ssrCP.
condPower is a supportive routine that also is interesting in its own
right; it computes conditional power of a combination test given an interim
test statistic, stage 2 sample size and combination test statistic. While
the returned data frame should make general plotting easy, the function
plot.ssrCP() prints a plot of study sample size by stage 1 outcome
with multiple x-axis labels for stage 1 z-value, conditional power, and
stage 1 effect size relative to the effect size for which the underlying
group sequential design was powered.
Sample size re-estimation using conditional power and an interim estimate of treatment effect was proposed by several authors in the 1990's (see references below). Statistical testing for these original methods was based on combination tests since Type I error could be inflated by using a sufficient statistic for testing at the end of the trial. Since 2000, more efficient variations of these conditional power designs have been developed. Fully optimized designs have also been derived (Posch et all, 2003, Lokhnygina and Tsiatis, 2008). Some of the later conditional power methods allow use of sufficient statistics for testing (Chen, DeMets and Lan, 2004, Gao, Ware and Mehta, 2008, and Mehta and Pocock, 2011).
The methods considered here are extensions of 2-stage group sequential
designs that include both an efficacy and a futility bound at the planned
interim analysis. A maximum fold-increase in sample size (maxinc)from
the supplied group sequential design (x) is specified by the user, as
well as a range of conditional power (cpadj) where sample size should
be re-estimated at the interim analysis, 1-the targeted conditional power to
be used for sample size re-estimation (beta) and a combination test
statistic (z2) to be used for testing at the end of the trial. The
input value overrun represents incremental enrollment not included in
the interim analysis that is not included in the analysis; this is used for
calculating the required number of patients enrolled to complete the trial.
Whereas most of the methods proposed have been based on using the interim
estimated treatment effect size (default for ssrCP), the variable
theta allows the user to specify an alternative; Liu and Chi (2001)
suggest that using the parameter value for which the trial was originally
powered is a good choice.
condPower( z1, n2, z2 = z2NC, theta = NULL, x = gsDesign(k = 2, timing = 0.5, beta = beta), ... ) ssrCP( z1, theta = NULL, maxinc = 2, overrun = 0, beta = x$beta, cpadj = c(0.5, 1 - beta), x = gsDesign(k = 2, timing = 0.5), z2 = z2NC, ... ) ## S3 method for class 'ssrCP' plot( x, z1ticks = NULL, mar = c(7, 4, 4, 4) + 0.1, ylab = "Adapted sample size", xlaboffset = -0.2, lty = 1, col = 1, ... ) z2NC(z1, x, ...) z2Z(z1, x, n2 = x$n.I[2] - x$n.I[1], ...) z2Fisher(z1, x, ...) Power.ssrCP(x, theta = NULL, delta = NULL, r = 18)condPower( z1, n2, z2 = z2NC, theta = NULL, x = gsDesign(k = 2, timing = 0.5, beta = beta), ... ) ssrCP( z1, theta = NULL, maxinc = 2, overrun = 0, beta = x$beta, cpadj = c(0.5, 1 - beta), x = gsDesign(k = 2, timing = 0.5), z2 = z2NC, ... ) ## S3 method for class 'ssrCP' plot( x, z1ticks = NULL, mar = c(7, 4, 4, 4) + 0.1, ylab = "Adapted sample size", xlaboffset = -0.2, lty = 1, col = 1, ... ) z2NC(z1, x, ...) z2Z(z1, x, n2 = x$n.I[2] - x$n.I[1], ...) z2Fisher(z1, x, ...) Power.ssrCP(x, theta = NULL, delta = NULL, r = 18)
z1 |
Scalar or vector with interim standardized Z-value(s). Input of multiple values makes it easy to plot the revised sample size as a function of the interim test statistic. |
n2 |
stage 2 sample size to be used in computing sufficient statistic when combining stage 1 and 2 test statistics. |
z2 |
a combination function that returns a test statistic cutoff for
the stage 2 test based in the interim test statistic supplied in |
theta |
If |
x |
A group sequential design with 2 stages ( |
... |
Allows passing of arguments that may be needed by the
user-supplied function, codez2. In the case of |
maxinc |
Maximum fold-increase from planned maximum sample size in
underlying group sequential design provided in |
overrun |
The number of patients enrolled before the interim analysis is completed who are not included in the interim analysis. |
beta |
Targeted Type II error (1 - targeted conditional power); used for conditional power in sample size reestimation. |
cpadj |
Range of values strictly between 0 and 1 specifying the range
of interim conditional power for which sample size re-estimation is to be
performed. Outside of this range, the sample size supplied in |
z1ticks |
Test statistic values at which tick marks are to be made on
x-axis; automatically calculated under default of |
mar |
Plot margins; see help for |
ylab |
y-axis label |
xlaboffset |
offset on x-axis for printing x-axis labels |
lty |
line type for stage 2 sample size |
col |
line color for stage 2 sample size |
delta |
Natural parameter values for power calculation; see
|
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
ssrCP returns a list with the following items:
x |
As input. |
z2fn |
As input in |
theta |
standardize effect
size used for conditional power; if |
maxinc |
As input. |
overrun |
As input. |
beta |
As input. |
cpadj |
As input. |
dat |
A data frame containing the input
|
Keaven Anderson [email protected]
Bauer, Peter and Kohne, F., Evaluation of experiments with adaptive interim analyses, Biometrics, 50:1029-1041, 1994.
Chen, YHJ, DeMets, DL and Lan, KKG. Increasing the sample size when the unblinded interim result is promising, Statistics in Medicine, 23:1023-1038, 2004.
Gao, P, Ware, JH and Mehta, C, Sample size re-estimation for adaptive sequential design in clinical trials, Journal of Biopharmaceutical Statistics", 18:1184-1196, 2008.
Jennison, C and Turnbull, BW. Mid-course sample size modification in clinical trials based on the observed treatment effect. Statistics in Medicine, 22:971-993", 2003.
Lehmacher, W and Wassmer, G. Adaptive sample size calculations in group sequential trials, Biometrics, 55:1286-1290, 1999.
Liu, Q and Chi, GY., On sample size and inference for two-stage adaptive designs, Biometrics, 57:172-177, 2001.
Mehta, C and Pocock, S. Adaptive increase in sample size when interim results are promising: A practical guide with examples, Statistics in Medicine, 30:3267-3284, 2011.
library(ggplot2) # quick trick for simple conditional power based on interim z-value, stage 1 and 2 sample size # assumed treatment effect and final alpha level # and observed treatment effect alpha <- .01 # set final nominal significance level timing <- .6 # set proportion of sample size, events or statistical information at IA n2 <- 40 # set stage 2 sample size events or statistical information hr <- .6 # for this example we will derive conditional power based on hazard ratios n.fix <- nEvents(hr=hr,alpha=alpha) # you could otherwise make n.fix an arbitrary positive value # this just derives a group sequential design that should not change sample size from n.fix # due to stringent IA bound x <- gsDesign(k=2,n.fix=n.fix,alpha=alpha,test.type=1,sfu=sfHSD, sfupar=-20,timing=timing,delta1=log(hr)) # derive effect sizes for which you wish to compute conditional power hrpostIA = seq(.4,1,.05) # in the following, we convert HR into standardized effect size based on the design in x powr <- condPower(x=x,z1=1,n2=x$n.I[2]-x$n.I[1],theta=log(hrpostIA)/x$delta1*x$theta[2]) ggplot( data.frame( x = hrpostIA, y = condPower( x = x, z1 = 1, n2 = x$n.I[2] - x$n.I[1], theta = log(hrpostIA) / x$delta1 * x$theta[2] ) ), aes(x = x, y = y) ) + geom_line() + labs( x = "HR post IA", y = "Conditional power", title = "Conditional power as a function of assumed HR" ) # Following is a template for entering parameters for ssrCP # Natural parameter value null and alternate hypothesis values delta0 <- 0 delta1 <- 1 # timing of interim analysis for underlying group sequential design timing <- .5 # upper spending function sfu <- sfHSD # upper spending function paramater sfupar <- -12 # maximum sample size inflation maxinflation <- 2 # assumed enrollment overrrun at IA overrun <- 25 # interim z-values for plotting z <- seq(0, 4, .025) # Type I error (1-sided) alpha <- .025 # Type II error for design beta <- .1 # Fixed design sample size n.fix <- 100 # conditional power interval where sample # size is to be adjusted cpadj <- c(.3, .9) # targeted Type II error when adapting sample size betastar <- beta # combination test (built-in options are: z2Z, z2NC, z2Fisher) z2 <- z2NC # use the above parameters to generate design # generate a 2-stage group sequential design with x <- gsDesign( k = 2, n.fix = n.fix, timing = timing, sfu = sfu, sfupar = sfupar, alpha = alpha, beta = beta, delta0 = delta0, delta1 = delta1 ) # extend this to a conditional power design xx <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2 ) # plot the stage 2 sample size plot(xx) # demonstrate overlays on this plot # overlay with densities for z1 under different hypotheses lines(z, 80 + 240 * dnorm(z, mean = 0), col = 2) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2]), col = 3) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2] / 2), col = 4) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2] * .75), col = 5) axis(side = 4, at = 80 + 240 * seq(0, .4, .1), labels = as.character(seq(0, .4, .1))) mtext(side = 4, expression(paste("Density for ", z[1])), line = 2) text(x = 1.5, y = 90, col = 2, labels = expression(paste("Density for ", theta, "=0"))) text(x = 3.00, y = 180, col = 3, labels = expression(paste("Density for ", theta, "=", theta[1]))) text(x = 1.00, y = 180, col = 4, labels = expression(paste("Density for ", theta, "=", theta[1], "/2"))) text(x = 2.5, y = 140, col = 5, labels = expression(paste("Density for ", theta, "=", theta[1], "*.75"))) # overall line for max sample size nalt <- xx$maxinc * x$n.I[2] lines(x = par("usr")[1:2], y = c(nalt, nalt), lty = 2) # compare above design with different combination tests # use sufficient statistic for final testing xxZ <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2Z ) # use Fisher combination test for final testing xxFisher <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2Fisher ) # combine data frames from these designs y <- rbind( data.frame(xx$dat, Test = "Normal combination"), data.frame(xxZ$dat, Test = "Sufficient statistic"), data.frame(xxFisher$dat, Test = "Fisher combination") ) # plot stage 2 statistic required for positive combination test ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = z2, col = Test)) + ggplot2::geom_line() # plot total sample size versus stage 1 test statistic ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = n2, col = Test)) + ggplot2::geom_line() # check achieved Type I error for sufficient statistic design Power.ssrCP(x = xxZ, theta = 0) # compare designs using observed vs planned theta for conditional power xxtheta1 <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2, theta = x$delta ) # combine data frames for the 2 designs y <- rbind( data.frame(xx$dat, "CP effect size" = "Obs. at IA"), data.frame(xxtheta1$dat, "CP effect size" = "Alt. hypothesis") ) # plot stage 2 sample size by design ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = n2, col = CP.effect.size)) + ggplot2::geom_line() # compare power and expected sample size y1 <- Power.ssrCP(x = xx) y2 <- Power.ssrCP(x = xxtheta1) # combine data frames for the 2 designs y3 <- rbind( data.frame(y1, "CP effect size" = "Obs. at IA"), data.frame(y2, "CP effect size" = "Alt. hypothesis") ) # plot expected sample size by design and effect size ggplot2::ggplot(data = y3, ggplot2::aes(x = delta, y = en, col = CP.effect.size)) + ggplot2::geom_line() + ggplot2::xlab(expression(delta)) + ggplot2::ylab("Expected sample size") # plot power by design and effect size ggplot2::ggplot(data = y3, ggplot2::aes(x = delta, y = Power, col = CP.effect.size)) + ggplot2::geom_line() + ggplot2::xlab(expression(delta))library(ggplot2) # quick trick for simple conditional power based on interim z-value, stage 1 and 2 sample size # assumed treatment effect and final alpha level # and observed treatment effect alpha <- .01 # set final nominal significance level timing <- .6 # set proportion of sample size, events or statistical information at IA n2 <- 40 # set stage 2 sample size events or statistical information hr <- .6 # for this example we will derive conditional power based on hazard ratios n.fix <- nEvents(hr=hr,alpha=alpha) # you could otherwise make n.fix an arbitrary positive value # this just derives a group sequential design that should not change sample size from n.fix # due to stringent IA bound x <- gsDesign(k=2,n.fix=n.fix,alpha=alpha,test.type=1,sfu=sfHSD, sfupar=-20,timing=timing,delta1=log(hr)) # derive effect sizes for which you wish to compute conditional power hrpostIA = seq(.4,1,.05) # in the following, we convert HR into standardized effect size based on the design in x powr <- condPower(x=x,z1=1,n2=x$n.I[2]-x$n.I[1],theta=log(hrpostIA)/x$delta1*x$theta[2]) ggplot( data.frame( x = hrpostIA, y = condPower( x = x, z1 = 1, n2 = x$n.I[2] - x$n.I[1], theta = log(hrpostIA) / x$delta1 * x$theta[2] ) ), aes(x = x, y = y) ) + geom_line() + labs( x = "HR post IA", y = "Conditional power", title = "Conditional power as a function of assumed HR" ) # Following is a template for entering parameters for ssrCP # Natural parameter value null and alternate hypothesis values delta0 <- 0 delta1 <- 1 # timing of interim analysis for underlying group sequential design timing <- .5 # upper spending function sfu <- sfHSD # upper spending function paramater sfupar <- -12 # maximum sample size inflation maxinflation <- 2 # assumed enrollment overrrun at IA overrun <- 25 # interim z-values for plotting z <- seq(0, 4, .025) # Type I error (1-sided) alpha <- .025 # Type II error for design beta <- .1 # Fixed design sample size n.fix <- 100 # conditional power interval where sample # size is to be adjusted cpadj <- c(.3, .9) # targeted Type II error when adapting sample size betastar <- beta # combination test (built-in options are: z2Z, z2NC, z2Fisher) z2 <- z2NC # use the above parameters to generate design # generate a 2-stage group sequential design with x <- gsDesign( k = 2, n.fix = n.fix, timing = timing, sfu = sfu, sfupar = sfupar, alpha = alpha, beta = beta, delta0 = delta0, delta1 = delta1 ) # extend this to a conditional power design xx <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2 ) # plot the stage 2 sample size plot(xx) # demonstrate overlays on this plot # overlay with densities for z1 under different hypotheses lines(z, 80 + 240 * dnorm(z, mean = 0), col = 2) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2]), col = 3) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2] / 2), col = 4) lines(z, 80 + 240 * dnorm(z, mean = sqrt(x$n.I[1]) * x$theta[2] * .75), col = 5) axis(side = 4, at = 80 + 240 * seq(0, .4, .1), labels = as.character(seq(0, .4, .1))) mtext(side = 4, expression(paste("Density for ", z[1])), line = 2) text(x = 1.5, y = 90, col = 2, labels = expression(paste("Density for ", theta, "=0"))) text(x = 3.00, y = 180, col = 3, labels = expression(paste("Density for ", theta, "=", theta[1]))) text(x = 1.00, y = 180, col = 4, labels = expression(paste("Density for ", theta, "=", theta[1], "/2"))) text(x = 2.5, y = 140, col = 5, labels = expression(paste("Density for ", theta, "=", theta[1], "*.75"))) # overall line for max sample size nalt <- xx$maxinc * x$n.I[2] lines(x = par("usr")[1:2], y = c(nalt, nalt), lty = 2) # compare above design with different combination tests # use sufficient statistic for final testing xxZ <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2Z ) # use Fisher combination test for final testing xxFisher <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2Fisher ) # combine data frames from these designs y <- rbind( data.frame(xx$dat, Test = "Normal combination"), data.frame(xxZ$dat, Test = "Sufficient statistic"), data.frame(xxFisher$dat, Test = "Fisher combination") ) # plot stage 2 statistic required for positive combination test ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = z2, col = Test)) + ggplot2::geom_line() # plot total sample size versus stage 1 test statistic ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = n2, col = Test)) + ggplot2::geom_line() # check achieved Type I error for sufficient statistic design Power.ssrCP(x = xxZ, theta = 0) # compare designs using observed vs planned theta for conditional power xxtheta1 <- ssrCP( x = x, z1 = z, overrun = overrun, beta = betastar, cpadj = cpadj, maxinc = maxinflation, z2 = z2, theta = x$delta ) # combine data frames for the 2 designs y <- rbind( data.frame(xx$dat, "CP effect size" = "Obs. at IA"), data.frame(xxtheta1$dat, "CP effect size" = "Alt. hypothesis") ) # plot stage 2 sample size by design ggplot2::ggplot(data = y, ggplot2::aes(x = z1, y = n2, col = CP.effect.size)) + ggplot2::geom_line() # compare power and expected sample size y1 <- Power.ssrCP(x = xx) y2 <- Power.ssrCP(x = xxtheta1) # combine data frames for the 2 designs y3 <- rbind( data.frame(y1, "CP effect size" = "Obs. at IA"), data.frame(y2, "CP effect size" = "Alt. hypothesis") ) # plot expected sample size by design and effect size ggplot2::ggplot(data = y3, ggplot2::aes(x = delta, y = en, col = CP.effect.size)) + ggplot2::geom_line() + ggplot2::xlab(expression(delta)) + ggplot2::ylab("Expected sample size") # plot power by design and effect size ggplot2::ggplot(data = y3, ggplot2::aes(x = delta, y = Power, col = CP.effect.size)) + ggplot2::geom_line() + ggplot2::xlab(expression(delta))
eEvents() is used to calculate the expected number of events for a
population with a time-to-event endpoint. It is based on calculations
demonstrated in Lachin and Foulkes (1986) and is fundamental in computations
for the sample size method they propose. Piecewise exponential survival and
dropout rates are supported as well as piecewise uniform enrollment. A
stratified population is allowed. Output is the expected number of events
observed given a trial duration and the above rate parameters.
eEvents( lambda = 1, eta = 0, gamma = 1, R = 1, S = NULL, T = 2, Tfinal = NULL, minfup = 0, digits = 4 ) ## S3 method for class 'eEvents' print(x, digits = 4, ...)eEvents( lambda = 1, eta = 0, gamma = 1, R = 1, S = NULL, T = 2, Tfinal = NULL, minfup = 0, digits = 4 ) ## S3 method for class 'eEvents' print(x, digits = 4, ...)
lambda |
Scalar, vector or matrix of event hazard rates; rows represent time periods while columns represent strata; a vector implies a single stratum. |
eta |
Scalar, vector or matrix of dropout hazard rates; rows represent time periods while columns represent strata; if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
gamma |
A scalar, vector or matrix of rates of entry by time period (rows) and strata (columns); if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
R |
A scalar or vector of durations of time periods for recruitment
rates specified in rows of |
S |
A scalar or vector of durations of piecewise constant event rates
specified in rows of |
T |
Time of analysis; if |
Tfinal |
Study duration; if |
minfup |
Time from end of planned enrollment ( |
digits |
Which controls number of digits for printing. |
x |
An object of class |
... |
Other arguments that may be passed to the generic print function. |
eEvents() produces an object of class eEvents with the number
of subjects and events for a set of pre-specified trial parameters, such as
accrual duration and follow-up period. The underlying power calculation is
based on Lachin and Foulkes (1986) method for proportional hazards assuming
a fixed underlying hazard ratio between 2 treatment groups. The method has
been extended here to enable designs to test non-inferiority. Piecewise
constant enrollment and failure rates are assumed and a stratified
population is allowed. See also nSurvival for other Lachin and
Foulkes (1986) methods assuming a constant hazard difference or exponential
enrollment rate.
print.eEvents() formats the output for an object of class
eEvents and returns the input value.
eEvents() and print.eEvents() return an object of
class eEvents which contains the following items:
lambda |
As input; converted to a matrix on output. |
eta |
As input; converted to a matrix on output. |
gamma |
As input. |
R |
As input. |
S |
As input. |
T |
As input. |
Tfinal |
Planned duration of study. |
minfup |
As input. |
d |
Expected number of events. |
n |
Expected sample size. |
digits |
As input. |
Keaven Anderson [email protected]
Lachin JM and Foulkes MA (1986), Evaluation of Sample Size and Power for Analyses of Survival with Allowance for Nonuniform Patient Entry, Losses to Follow-Up, Noncompliance, and Stratification. Biometrics, 42, 507-519.
Bernstein D and Lagakos S (1978), Sample size and power determination for stratified clinical trials. Journal of Statistical Computation and Simulation, 8:65-73.
vignette("gsDesignPackageOverview"), plot.gsDesign,
gsDesign, gsHR,
nSurvival
# 3 enrollment periods, 3 piecewise exponential failure rates str(eEvents( lambda = c(.05, .02, .01), eta = .01, gamma = c(5, 10, 20), R = c(2, 1, 2), S = c(1, 1), T = 20 )) # Control group for example from Bernstein and Lagakos (1978) lamC <- c(1, .8, .5) n <- eEvents( lambda = matrix(c(lamC, lamC * 2 / 3), ncol = 6), eta = 0, gamma = matrix(.5, ncol = 6), R = 2, T = 4 )# 3 enrollment periods, 3 piecewise exponential failure rates str(eEvents( lambda = c(.05, .02, .01), eta = .01, gamma = c(5, 10, 20), R = c(2, 1, 2), S = c(1, 1), T = 20 )) # Control group for example from Bernstein and Lagakos (1978) lamC <- c(1, .8, .5) n <- eEvents( lambda = matrix(c(lamC, lamC * 2 / 3), ncol = 6), eta = 0, gamma = matrix(.5, ncol = 6), R = 2, T = 4 )
gsBinomialExact computes power/Type I error and expected sample size
for a group sequential design in a single-arm trial with a binary outcome.
This can also be used to compare event rates in two-arm studies. The print
function has been extended using print.gsBinomialExact to print
gsBinomialExact objects. Similarly, a plot function has
been extended using plot.gsBinomialExact to plot
gsBinomialExact objects.
binomialSPRT computes a truncated binomial sequential probability
ratio test (SPRT) which is a specific instance of an exact binomial group
sequential design for a single arm trial with a binary outcome.
gsBinomialPP computes a truncated binomial (group) sequential design
based on predictive probability.
nBinomial1Sample uses exact binomial calculations to compute power
and sample size for single arm binomial experiments.
gsBinomialExact is based on the book "Group Sequential Methods with
Applications to Clinical Trials," Christopher Jennison and Bruce W.
Turnbull, Chapter 12, Section 12.1.2 Exact Calculations for Binary Data.
This computation is often used as an approximation for the distribution of
the number of events in one treatment group out of all events when the
probability of an event is small and sample size is large.
An object of class gsBinomialExact is returned. On output, the values
of theta input to gsBinomialExact will be the parameter values
for which the boundary crossing probabilities and expected sample sizes are
computed.
Note that a[1] equal to -1 lower bound at n.I[1] means 0 successes continues at interim 1; a[2]==0 at interim 2 means 0 successes stops trial for futility at 2nd analysis. For final analysis, set a[k] equal to b[k]-1 to incorporate all possibilities into non-positive trial; see example.
The sequential probability ratio test (SPRT) is a sequential testing scheme
allowing testing after each observation. This likelihood ratio is used to
determine upper and lower cutoffs which are linear and parallel in the
number of responses as a function of sample size. binomialSPRT
produces a variation the the SPRT that tests only within a range of sample
sizes. While the linear SPRT bounds are continuous, actual bounds are the
integer number of response at or beyond each linear bound for each sample
size where testing is performed. Because of the truncation and
discretization of the bounds, power and Type I error achieve will be lower
than the nominal levels specified by alpha and beta which can
be altered to produce desired values that are achieved by the planned sample
size. See also example that shows computation of Type I error when futility
bound is considered non-binding.
Note that if the objective of a design is to demonstrate that a rate (e.g.,
failure rate) is lower than a certain level, two approaches can be taken.
First, 1 minus the failure rate is the success rate and this can be used for
planning. Second, the role of beta becomes to express Type I error
and alpha is used to express Type II error.
Plots produced include boundary plots, expected sample size, response rate at the boundary and power.
gsBinomial1Sample uses exact binomial computations based on the base
R functions qbinom() and pbinom(). The tabular output may be
convenient for plotting. Note that input variables are largely not checked,
so the user is largely responsible for results; it is a good idea to do a
run with outtype=3 to check that you have done things appropriately.
If n is not ordered (a bad idea) or not sequential (maybe OK), be
aware of possible consequences.
nBinomial1Sample is based on code from Marc Schwartz [email protected].
The possible sample size vector n needs to be selected in such a fashion
that it covers the possible range of values that include the true minimum.
NOTE: the one-sided evaluation of significance is more conservative than using the 2-sided exact test in binom.test.
gsBinomialExact( k = 2, theta = c(0.1, 0.2), n.I = c(50, 100), a = c(3, 7), b = c(20, 30) ) binomialSPRT( p0 = 0.05, p1 = 0.25, alpha = 0.1, beta = 0.15, minn = 10, maxn = 35 ) ## S3 method for class 'gsBinomialExact' plot(x, plottype = 1, ...) ## S3 method for class 'binomialSPRT' plot(x, plottype = 1, ...) nBinomial1Sample( p0 = 0.9, p1 = 0.95, alpha = 0.025, beta = NULL, n = 200:250, outtype = 1, conservative = FALSE )gsBinomialExact( k = 2, theta = c(0.1, 0.2), n.I = c(50, 100), a = c(3, 7), b = c(20, 30) ) binomialSPRT( p0 = 0.05, p1 = 0.25, alpha = 0.1, beta = 0.15, minn = 10, maxn = 35 ) ## S3 method for class 'gsBinomialExact' plot(x, plottype = 1, ...) ## S3 method for class 'binomialSPRT' plot(x, plottype = 1, ...) nBinomial1Sample( p0 = 0.9, p1 = 0.95, alpha = 0.025, beta = NULL, n = 200:250, outtype = 1, conservative = FALSE )
k |
Number of analyses planned, including interim and final.
For |
theta |
Vector of possible underling binomial probabilities for a single binomial sample. |
n.I |
Sample size at analyses (increasing positive integers); vector of length k. |
a |
Number of "successes" required to cross lower bound cutoffs to
reject |
b |
Number of "successes" required to cross upper bound cutoffs for
rejecting |
p0 |
Lower of the two response (event) rates hypothesized. |
p1 |
Higher of the two response (event) rates hypothesized. |
alpha |
Nominal probability of rejecting response (event) rate
|
beta |
Nominal probability of rejecting response (event) rate |
minn |
Minimum sample size at which sequential testing begins. |
maxn |
Maximum sample size. |
x |
Item of class |
plottype |
1 produces a plot with counts of response at bounds (for
|
... |
arguments passed through to |
n |
sample sizes to be considered for |
outtype |
Operative when |
conservative |
operative when |
gsBinomialExact() returns a list of class
gsBinomialExact and gsProbability (see example); when
displaying one of these objects, the default function to print is
print.gsProbability(). The object returned from
gsBinomialExact() contains the following elements:
k |
As input. |
theta |
As input. |
n.I |
As input. |
lower |
A list
containing two elements: |
upper |
A list of the same form as |
en |
A vector of the same length as |
binomialSPRT produces an object of class binomialSPRT that is
an extension of the gsBinomialExact class. The values returned in
addition to those returned by gsBinomialExact are:
intercept |
A vector of length 2 with the intercepts for the two SPRT bounds. |
slope |
A scalar with the common slope of the SPRT bounds. |
alpha |
As input. Note that this will exceed the actual Type I error achieved by the design returned. |
beta |
As input. Note that this will exceed the actual Type II error achieved by the design returned. |
p0 |
As input. |
p1 |
As input. |
nBinomial1Sample produces a data frame with power for each input value in n
if beta=NULL. Otherwise, a sample size achieving the desired power is returned unless
the minimum power for the values input in n is greater than or equal to the target or
the maximum yields power less than the target, in which case an error message is shown.
The input variable outtype has no effect if beta=NULL.
Otherwise, outtype=1 results in return of an integer sample size and outtype=2
results in a data frame with one record which includes the desired sample size.
When a data frame is returned, the variables include:
p0 |
Input null hypothesis event (or response) rate. |
p1 |
Input alternative hypothesis
(or response) rate; must be |
alpha |
Input Type I error. |
beta |
Input Type II error except when input is |
n |
sample size. |
b |
cutoff given |
alphaR |
Type I error achieved for each
output value of |
Power |
Power achieved
for each output value of |
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Jon Hartzel, Yevgen Tymofyeyev and Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Code for nBinomial1Sample was based on code developed by [email protected].
library(ggplot2) zz <- gsBinomialExact( k = 3, theta = seq(0.1, 0.9, 0.1), n.I = c(12, 24, 36), a = c(-1, 0, 11), b = c(5, 9, 12) ) # let's see what class this is class(zz) # because of "gsProbability" class above, following is equivalent to # \code{print.gsProbability(zz)} zz # also plot (see also plots below for \code{binomialSPRT}) # add lines using geom_line() plot(zz) + ggplot2::geom_line() # now for SPRT examples x <- binomialSPRT(p0 = .05, p1 = .25, alpha = .1, beta = .2) # boundary plot plot(x) # power plot plot(x, plottype = 2) # Response (event) rate at boundary plot(x, plottype = 3) # Expected sample size at boundary crossing or end of trial plot(x, plottype = 6) # sample size for single arm exact binomial # plot of table of power by sample size # note that outtype need not be specified if beta is NULL nb1 <- nBinomial1Sample(p0 = 0.05, p1=0.2,alpha = 0.025, beta=NULL, n = 25:40) nb1 library(scales) ggplot2::ggplot(nb1, ggplot2::aes(x = n, y = Power)) + ggplot2::geom_line() + ggplot2::geom_point() + ggplot2::scale_y_continuous(labels = percent) # simple call with same parameters to get minimum sample size yielding desired power nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40) # change to 'conservative' if you want all larger sample # sizes to also provide adequate power nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40, conservative = TRUE) # print out more information for the selected derived sample size nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40, conservative = TRUE, outtype = 2) # Reproduce realized Type I error alphaR stats::pbinom(q = 5, size = 39, prob = .05, lower.tail = FALSE) # Reproduce realized power stats::pbinom(q = 5, size = 39, prob = 0.2, lower.tail = FALSE) # Reproduce critical value for positive finding stats::qbinom(p = 1 - .025, size = 39, prob = .05) + 1 # Compute p-value for 7 successes stats::pbinom(q = 6, size = 39, prob = .05, lower.tail = FALSE) # what happens if input sample sizes not sufficient? ## Not run: nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:30) ## End(Not run)library(ggplot2) zz <- gsBinomialExact( k = 3, theta = seq(0.1, 0.9, 0.1), n.I = c(12, 24, 36), a = c(-1, 0, 11), b = c(5, 9, 12) ) # let's see what class this is class(zz) # because of "gsProbability" class above, following is equivalent to # \code{print.gsProbability(zz)} zz # also plot (see also plots below for \code{binomialSPRT}) # add lines using geom_line() plot(zz) + ggplot2::geom_line() # now for SPRT examples x <- binomialSPRT(p0 = .05, p1 = .25, alpha = .1, beta = .2) # boundary plot plot(x) # power plot plot(x, plottype = 2) # Response (event) rate at boundary plot(x, plottype = 3) # Expected sample size at boundary crossing or end of trial plot(x, plottype = 6) # sample size for single arm exact binomial # plot of table of power by sample size # note that outtype need not be specified if beta is NULL nb1 <- nBinomial1Sample(p0 = 0.05, p1=0.2,alpha = 0.025, beta=NULL, n = 25:40) nb1 library(scales) ggplot2::ggplot(nb1, ggplot2::aes(x = n, y = Power)) + ggplot2::geom_line() + ggplot2::geom_point() + ggplot2::scale_y_continuous(labels = percent) # simple call with same parameters to get minimum sample size yielding desired power nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40) # change to 'conservative' if you want all larger sample # sizes to also provide adequate power nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40, conservative = TRUE) # print out more information for the selected derived sample size nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:40, conservative = TRUE, outtype = 2) # Reproduce realized Type I error alphaR stats::pbinom(q = 5, size = 39, prob = .05, lower.tail = FALSE) # Reproduce realized power stats::pbinom(q = 5, size = 39, prob = 0.2, lower.tail = FALSE) # Reproduce critical value for positive finding stats::qbinom(p = 1 - .025, size = 39, prob = .05) + 1 # Compute p-value for 7 successes stats::pbinom(q = 6, size = 39, prob = .05, lower.tail = FALSE) # what happens if input sample sizes not sufficient? ## Not run: nBinomial1Sample(p0 = 0.05, p1 = 0.2, alpha = 0.025, beta = .2, n = 25:30) ## End(Not run)
gsBound() and gsBound1() are lower-level functions used to
find boundaries for a group sequential design. They are not recommended
(especially gsBound1()) for casual users. These functions do not
adjust sample size as gsDesign() does to ensure appropriate power for
a design.
gsBound() computes upper and lower bounds given boundary crossing
probabilities assuming a mean of 0, the usual null hypothesis.
gsBound1() computes the upper bound given a lower boundary, upper
boundary crossing probabilities and an arbitrary mean (theta).
The function gsBound1() requires special attention to detail and
knowledge of behavior when a design corresponding to the input parameters
does not exist.
gsBound(I, trueneg, falsepos, tol = 1e-06, r = 18, printerr = 0) gsBound1(theta, I, a, probhi, tol = 1e-06, r = 18, printerr = 0)gsBound(I, trueneg, falsepos, tol = 1e-06, r = 18, printerr = 0) gsBound1(theta, I, a, probhi, tol = 1e-06, r = 18, printerr = 0)
I |
Vector containing statistical information planned at each analysis. |
trueneg |
Vector of desired probabilities for crossing upper bound assuming mean of 0. |
falsepos |
Vector of desired probabilities for crossing lower bound assuming mean of 0. |
tol |
Tolerance for error (scalar; default is 0.000001). Normally this will not be changed by the user. This does not translate directly to number of digits of accuracy, so use extra decimal places. |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
printerr |
If this scalar argument set to 1, this will print messages
from underlying C program. Mainly intended to notify user when an output
solution does not match input specifications. This is not intended to stop
execution as this often occurs when deriving a design in |
theta |
Scalar containing mean (drift) per unit of statistical information. |
a |
Vector containing lower bound that is fixed for use in
|
probhi |
Vector of desired probabilities for crossing upper bound assuming mean of theta. |
Both routines return a list. Common items returned by the two routines are:
k |
The length of vectors input; a scalar. |
theta |
As input in |
I |
As input. |
a |
For |
b |
The derived upper boundary required to yield the input boundary crossing probabilities under the null hypothesis. |
tol |
As input. |
r |
As input. |
error |
Error code. 0 if no error; greater than 0 otherwise. |
gsBound() also returns the following items:
rates |
a list containing two items: |
falsepos |
vector of upper boundary crossing probabilities as input. |
trueneg |
vector of lower boundary crossing probabilities as input. |
gsBound1() also returns the following items:
problo |
vector of lower boundary crossing probabilities; computed using input lower bound and derived upper bound. |
probhi |
vector of upper boundary crossing probabilities as input. |
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("gsDesignPackageOverview"), gsDesign,
gsProbability
# set boundaries so that probability is .01 of first crossing # each upper boundary and .02 of crossing each lower boundary # under the null hypothesis x <- gsBound( I = c(1, 2, 3) / 3, trueneg = rep(.02, 3), falsepos = rep(.01, 3) ) x # use gsBound1 to set up boundary for a 1-sided test x <- gsBound1( theta = 0, I = c(1, 2, 3) / 3, a = rep(-Inf, 3), probhi = c(.001, .009, .015) ) x$b # check boundary crossing probabilities with gsProbability y <- gsProbability(k = 3, theta = 0, n.I = x$I, a = x$a, b = x$b)$upper$prob # Note that gsBound1 only computes upper bound # To get a lower bound under a parameter value theta: # use minus the upper bound as a lower bound # replace theta with -theta # set probhi as desired lower boundary crossing probabilities # Here we let set lower boundary crossing at 0.05 at each analysis # assuming theta=2.2 y <- gsBound1( theta = -2.2, I = c(1, 2, 3) / 3, a = -x$b, probhi = rep(.05, 3) ) y$b # Now use gsProbability to look at design # Note that lower boundary crossing probabilities are as # specified for theta=2.2, but for theta=0 the upper boundary # crossing probabilities are smaller than originally specified # above after first interim analysis gsProbability(k = length(x$b), theta = c(0, 2.2), n.I = x$I, b = x$b, a = -y$b)# set boundaries so that probability is .01 of first crossing # each upper boundary and .02 of crossing each lower boundary # under the null hypothesis x <- gsBound( I = c(1, 2, 3) / 3, trueneg = rep(.02, 3), falsepos = rep(.01, 3) ) x # use gsBound1 to set up boundary for a 1-sided test x <- gsBound1( theta = 0, I = c(1, 2, 3) / 3, a = rep(-Inf, 3), probhi = c(.001, .009, .015) ) x$b # check boundary crossing probabilities with gsProbability y <- gsProbability(k = 3, theta = 0, n.I = x$I, a = x$a, b = x$b)$upper$prob # Note that gsBound1 only computes upper bound # To get a lower bound under a parameter value theta: # use minus the upper bound as a lower bound # replace theta with -theta # set probhi as desired lower boundary crossing probabilities # Here we let set lower boundary crossing at 0.05 at each analysis # assuming theta=2.2 y <- gsBound1( theta = -2.2, I = c(1, 2, 3) / 3, a = -x$b, probhi = rep(.05, 3) ) y$b # Now use gsProbability to look at design # Note that lower boundary crossing probabilities are as # specified for theta=2.2, but for theta=0 the upper boundary # crossing probabilities are smaller than originally specified # above after first interim analysis gsProbability(k = length(x$b), theta = c(0, 2.2), n.I = x$I, b = x$b, a = -y$b)
gsBoundCP() computes the total probability of crossing future upper
bounds given an interim test statistic at an interim bound. For each interim
boundary, assumes an interim test statistic at the boundary and computes the
probability of crossing any of the later upper boundaries.
See Conditional power section of manual for further clarification. See also Muller and Schaffer (2001) for background theory.
gsBoundCP(x, theta = "thetahat", r = 18)gsBoundCP(x, theta = "thetahat", r = 18)
x |
An object of type |
theta |
if |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
For one-sided designs, a vector of length x$k-1 containing
conditional power at each interim upper bound. For two-sided designs, a
matrix with x$k-1 rows and columns CPlo and CPhi,
containing conditional power at the lower and upper bounds, respectively.
Designs with harm bounds (test.type 7 or 8) also have a
CPharm column. An absent bound has conditional power NA.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Muller, Hans-Helge and Schaffer, Helmut (2001), Adaptive group sequential designs for clinical trials: combining the advantages of adaptive and classical group sequential approaches. Biometrics;57:886-891.
# set up a group sequential design x <- gsDesign(k = 5) x # compute conditional power based on interim treatment effects gsBoundCP(x) # compute conditional power based on original x$delta gsBoundCP(x, theta = x$delta)# set up a group sequential design x <- gsDesign(k = 5) x # compute conditional power based on interim treatment effects gsBoundCP(x) # compute conditional power based on original x$delta gsBoundCP(x, theta = x$delta)
Compatibility wrapper for gsCPOSFutilitySpending(mode = "fixed_information"),
retaining the legacy argument, diagnostic and error names.
Fit beta spending parameters to conditional assurance targets while holding
the reference information and efficacy boundaries fixed. Overall power may
change. Total beta is solved internally so that the spending rule and the
terminal decision at the fixed efficacy boundary are consistent.
gsCAFutilitySpending( x, target_ca, i = seq_along(target_ca), sfl = x$lower$sf, prior, control = list() )gsCAFutilitySpending( x, target_ca, i = seq_along(target_ca), sfl = x$lower$sf, prior, control = list() )
x |
A |
target_ca |
Conditional assurance targets strictly between zero and one. |
i |
Unique active interim futility indices, one per target. |
sfl |
Supported lower spending function or its name. Defaults to
|
prior |
List with finite numeric vectors |
control |
Named numerical controls. Use |
This is the fixed information counterpart of
gsCPOSFutilitySpending. The prior, information, efficacy
boundaries, spending times and testing indicators are held fixed.
For each candidate spending parameter set, gsBound1() derives interim
futility bounds, and gsProbability() evaluates total beta when final
lower and upper bounds coincide. An internal scalar solve makes that beta
agree with the beta used by the spending function.
The probability target conditions on continuation through an analysis, not
on an observed statistic at a boundary. An externally calculated fixed design
gsPOS() benchmark can be supplied as a target. This implements a
spending-based fixed information conditional assurance rule, not a full
sample size re-estimation procedure.
With nonbinding futility (test.type = 4), the efficacy-only type I
error specification is unchanged. With binding futility (test.type = 3),
changing futility while freezing efficacy can change actual type I error,
including increasing it above the reference alpha. Inspect
achieved_type1; this function does not promise preservation of alpha
or power in that case. The reference nominal alpha remains in x$alpha.
Replay by calling this function with the original reference, the fitted
spending family, prior and targets, and control$start set to the fitted
free parameters. A usual power-preserving gsDesign() call is not an
equivalent reconstruction. The internally fitted beta and spending
parameters together specify the lower spending rule at the fixed information.
A c("gsCAFutilitySpending", "gsDesign") object, with
caFutilitySpending diagnostics analogous to those of
gsCPOSFutilitySpending, using target_ca, achieved_ca
and ca_tol. Additional fields include achieved_beta,
achieved_type1, and fixed_information. The returned
beta is achieved overall beta, not the reference beta.
Error classes use the prefix gsCAFutilitySpending.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsCPOSFutilitySpending, gsCPOS,
gsPOS, gsBound1
x <- gsDesign(k = 2, test.type = 4, sflpar = 0) prior <- list(z = c(0, x$delta), wgts = c(.2, .8)) target <- gsCPOS(1, x, prior$z, prior$wgts) fit <- gsCAFutilitySpending(x, target, prior = prior, control = list(start = 1)) fit$caFutilitySpending[c("sflpar", "achieved_ca", "achieved_beta")] stopifnot(identical(fit$n.I, x$n.I), identical(fit$upper$bound, x$upper$bound))x <- gsDesign(k = 2, test.type = 4, sflpar = 0) prior <- list(z = c(0, x$delta), wgts = c(.2, .8)) target <- gsCPOS(1, x, prior$z, prior$wgts) fit <- gsCAFutilitySpending(x, target, prior = prior, control = list(start = 1)) fit$caFutilitySpending[c("sflpar", "achieved_ca", "achieved_beta")] stopifnot(identical(fit$n.I, x$n.I), identical(fit$upper$bound, x$upper$bound))
gsCP() computes conditional boundary crossing probabilities at future
planned analyses for a given group sequential design assuming an interim
z-statistic at a specified interim analysis. While gsCP() is designed
toward computing conditional power for a variety of underlying parameter
values, condPower is built to compute conditional power for a
variety of interim test statistic values which is useful for sample size
adaptation (see ssrCP). gsPP() averages conditional
power across a posterior distribution to compute predictive power.
gsPI() computes Bayesian prediction intervals for future analyses
corresponding to results produced by gsPP(). gsPosterior()
computes the posterior density for the group sequential design parameter of
interest given a prior density and an interim outcome that is exact or in an
interval. gsPOS() computes the probability of success for a trial
using a prior distribution to average power over a set of theta
values of interest. gsCPOS() assumes no boundary has been crossed
before and including an interim analysis of interest, and computes the
probability of success based on this event. Note that gsCP() and
gsPP() take only the interim test statistic into account in computing
conditional probabilities, while gsCPOS() conditions on not crossing
any bound through a specified interim analysis.
See Conditional power section of manual for further clarification. See also Muller and Schaffer (2001) for background theory.
For gsPP(), gsPI(), gsPOS() and gsCPOS(), the
prior distribution for the standardized parameter theta () for a
group sequential design specified through a gsDesign object is specified
through the arguments theta and wgts. This can be a discrete
or a continuous probability density function. For a discrete function,
generally all weights would be 1. For a continuous density, the wgts
would contain integration grid weights, such as those provided by
normalGrid.
For gsPosterior, a prior distribution in prior must be
composed of the vectors z density. The vector z
contains points where the prior is evaluated and density the
corresponding density or, for a discrete distribution, the probabilities of
each point in z. Densities may be supplied as from
normalGrid() where grid weights for numerical integration are
supplied in gridwgts. If gridwgts are not supplied, they are
defaulted to 1 (equal weighting). To ensure a proper prior distribution, you
must have sum(gridwgts * density) equal to 1; this is NOT checked,
however.
gsCP(x, theta = NULL, i = 1, zi = 0, r = 18) gsPP( x, i = 1, zi = 0, theta = c(0, 3), wgts = c(0.5, 0.5), r = 18, total = TRUE ) gsPI( x, i = 1, zi = 0, j = 2, level = 0.95, theta = c(0, 3), wgts = c(0.5, 0.5) ) gsPosterior(x = gsDesign(), i = 1, zi = NULL, prior = normalGrid(), r = 18) gsPOS(x, theta, wgts) gsCPOS(i, x, theta, wgts)gsCP(x, theta = NULL, i = 1, zi = 0, r = 18) gsPP( x, i = 1, zi = 0, theta = c(0, 3), wgts = c(0.5, 0.5), r = 18, total = TRUE ) gsPI( x, i = 1, zi = 0, j = 2, level = 0.95, theta = c(0, 3), wgts = c(0.5, 0.5) ) gsPosterior(x = gsDesign(), i = 1, zi = NULL, prior = normalGrid(), r = 18) gsPOS(x, theta, wgts) gsCPOS(i, x, theta, wgts)
x |
An object of type |
theta |
a vector with |
i |
analysis at which interim z-value is given; must be from 1 to
|
zi |
interim z-value at analysis i (scalar) |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
wgts |
Weights to be used with grid points in |
total |
The default of |
j |
specific analysis for which prediction is being made; must be
|
level |
The level to be used for Bayes credible intervals (which
approach confidence intervals for vague priors). The default
|
prior |
provides a prior distribution in the form produced by
|
gsCP() returns an object of the class gsProbability.
Based on the input design and the interim test statistic, the output
gsDesign object has bounds for test statistics computed based on solely on
observations after interim i. Boundary crossing probabilities are
computed for the input values. See manual and examples.
gsPP() if total==TRUE, returns a real value indicating the predictive
power of the trial conditional on the interim test statistic zi at
analysis i; otherwise returns vector with predictive power for each
future planned analysis.
gsPI() returns an interval (or point estimate if level=0)
indicating 100level% credible interval for the z-statistic at
analysis j conditional on the z-statistic at analysis i<j.
The interval does not consider intervending interim analyses. The
probability estimate is based on the predictive distribution used for
gsPP() and requires a prior distribution for the group sequential
parameter theta specified in theta and wgts.
gsPosterior() returns a posterior distribution containing the the
vector z input in prior$z, the posterior density in
density, grid weights for integrating the posterior density as input
in prior$gridwgts or defaulted to a vector of ones, and the product
of the output values in density and gridwgts in wgts.
gsPOS() returns a real value indicating the probability of a positive
study weighted by the prior distribution input for theta.
gsCPOS() returns a real value indicating the probability of a
positive study weighted by the posterior distribution derived from the
interim test statistic and the prior distribution input for theta
conditional on an interim test statistic.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Proschan, Michael A., Lan, KK Gordon and Wittes, Janet Turk (2006), Statistical Monitoring of Clinical Trials. NY: Springer.
Muller, Hans-Helge and Schaffer, Helmut (2001), Adaptive group sequential designs for clinical trials: combining the advantages of adaptive and classical group sequential approaches. Biometrics;57:886-891.
normalGrid, gsDesign,
gsProbability, gsBoundCP, ssrCP,
condPower
library(ggplot2) # set up a group sequential design x <- gsDesign(k = 5) x # set up a prior distribution for the treatment effect # that is normal with mean .75*x$delta and standard deviation x$delta/2 mu0 <- .75 * x$delta sigma0 <- x$delta / 2 prior <- normalGrid(mu = mu0, sigma = sigma0) # compute POS for the design given the above prior distribution for theta gsPOS(x = x, theta = prior$z, wgts = prior$wgts) # assume POS should only count cases in prior where theta >= x$delta/2 gsPOS(x = x, theta = prior$z, wgts = prior$wgts * (prior$z >= x$delta / 2)) # assuming a z-value at lower bound at analysis 2, what are conditional # boundary crossing probabilities for future analyses # assuming theta values from x as well as a value based on the interim # observed z CP <- gsCP(x, i = 2, zi = x$lower$bound[2]) CP # summing values for crossing future upper bounds gives overall # conditional power for each theta value CP$theta t(CP$upper$prob) %*% c(1, 1, 1) # compute predictive probability based on above assumptions gsPP(x, i = 2, zi = x$lower$bound[2], theta = prior$z, wgts = prior$wgts) # if it is known that boundary not crossed at interim 2, use # gsCPOS to compute conditional POS based on this gsCPOS(x = x, i = 2, theta = prior$z, wgts = prior$wgts) # 2-stage example to compare results to direct computation x <- gsDesign(k = 2) z1 <- 0.5 n1 <- x$n.I[1] n2 <- x$n.I[2] - x$n.I[1] thetahat <- z1 / sqrt(n1) theta <- c(thetahat, 0, x$delta) # conditional power direct computation - comparison w gsCP pnorm((n2 * theta + z1 * sqrt(n1) - x$upper$bound[2] * sqrt(n1 + n2)) / sqrt(n2)) gsCP(x = x, zi = z1, i = 1)$upper$prob # predictive power direct computation - comparison w gsPP # use same prior as above mu0 <- .75 * x$delta * sqrt(x$n.I[2]) sigma2 <- (.5 * x$delta)^2 * x$n.I[2] prior <- normalGrid(mu = .75 * x$delta, sigma = x$delta / 2) gsPP(x = x, zi = z1, i = 1, theta = prior$z, wgts = prior$wgts) t <- .5 z1 <- .5 b <- z1 * sqrt(t) # direct from Proschan, Lan and Wittes eqn 3.10 # adjusted drift at n.I[2] pnorm(((b - x$upper$bound[2]) * (1 + t * sigma2) + (1 - t) * (mu0 + b * sigma2)) / sqrt((1 - t) * (1 + sigma2) * (1 + t * sigma2))) # plot prior then posterior distribution for unblinded analysis with i=1, zi=1 xp <- gsPosterior(x = x, i = 1, zi = 1, prior = prior) plot(x = xp$z, y = xp$density, type = "l", col = 2, xlab = expression(theta), ylab = "Density") points(x = x$z, y = x$density, col = 1) # add posterior plot assuming only knowlede that interim bound has # not been crossed at interim 1 xpb <- gsPosterior(x = x, i = 1, zi = 1, prior = prior) lines(x = xpb$z, y = xpb$density, col = 4) # prediction interval based in interim 1 results # start with point estimate, followed by 90% prediction interval gsPI(x = x, i = 1, zi = z1, j = 2, theta = prior$z, wgts = prior$wgts, level = 0) gsPI(x = x, i = 1, zi = z1, j = 2, theta = prior$z, wgts = prior$wgts, level = .9)library(ggplot2) # set up a group sequential design x <- gsDesign(k = 5) x # set up a prior distribution for the treatment effect # that is normal with mean .75*x$delta and standard deviation x$delta/2 mu0 <- .75 * x$delta sigma0 <- x$delta / 2 prior <- normalGrid(mu = mu0, sigma = sigma0) # compute POS for the design given the above prior distribution for theta gsPOS(x = x, theta = prior$z, wgts = prior$wgts) # assume POS should only count cases in prior where theta >= x$delta/2 gsPOS(x = x, theta = prior$z, wgts = prior$wgts * (prior$z >= x$delta / 2)) # assuming a z-value at lower bound at analysis 2, what are conditional # boundary crossing probabilities for future analyses # assuming theta values from x as well as a value based on the interim # observed z CP <- gsCP(x, i = 2, zi = x$lower$bound[2]) CP # summing values for crossing future upper bounds gives overall # conditional power for each theta value CP$theta t(CP$upper$prob) %*% c(1, 1, 1) # compute predictive probability based on above assumptions gsPP(x, i = 2, zi = x$lower$bound[2], theta = prior$z, wgts = prior$wgts) # if it is known that boundary not crossed at interim 2, use # gsCPOS to compute conditional POS based on this gsCPOS(x = x, i = 2, theta = prior$z, wgts = prior$wgts) # 2-stage example to compare results to direct computation x <- gsDesign(k = 2) z1 <- 0.5 n1 <- x$n.I[1] n2 <- x$n.I[2] - x$n.I[1] thetahat <- z1 / sqrt(n1) theta <- c(thetahat, 0, x$delta) # conditional power direct computation - comparison w gsCP pnorm((n2 * theta + z1 * sqrt(n1) - x$upper$bound[2] * sqrt(n1 + n2)) / sqrt(n2)) gsCP(x = x, zi = z1, i = 1)$upper$prob # predictive power direct computation - comparison w gsPP # use same prior as above mu0 <- .75 * x$delta * sqrt(x$n.I[2]) sigma2 <- (.5 * x$delta)^2 * x$n.I[2] prior <- normalGrid(mu = .75 * x$delta, sigma = x$delta / 2) gsPP(x = x, zi = z1, i = 1, theta = prior$z, wgts = prior$wgts) t <- .5 z1 <- .5 b <- z1 * sqrt(t) # direct from Proschan, Lan and Wittes eqn 3.10 # adjusted drift at n.I[2] pnorm(((b - x$upper$bound[2]) * (1 + t * sigma2) + (1 - t) * (mu0 + b * sigma2)) / sqrt((1 - t) * (1 + sigma2) * (1 + t * sigma2))) # plot prior then posterior distribution for unblinded analysis with i=1, zi=1 xp <- gsPosterior(x = x, i = 1, zi = 1, prior = prior) plot(x = xp$z, y = xp$density, type = "l", col = 2, xlab = expression(theta), ylab = "Density") points(x = x$z, y = x$density, col = 1) # add posterior plot assuming only knowlede that interim bound has # not been crossed at interim 1 xpb <- gsPosterior(x = x, i = 1, zi = 1, prior = prior) lines(x = xpb$z, y = xpb$density, col = 4) # prediction interval based in interim 1 results # start with point estimate, followed by 90% prediction interval gsPI(x = x, i = 1, zi = z1, j = 2, theta = prior$z, wgts = prior$wgts, level = 0) gsPI(x = x, i = 1, zi = z1, j = 2, theta = prior$z, wgts = prior$wgts, level = .9)
Computes exact conditional probabilities of crossing future boundaries, given the cumulative experimental arm event count at an interim analysis.
gsCPBinomialExact( x, i = 1, x.i, theta = NULL, ve = NULL, ratio = x$ratio, binding = TRUE )gsCPBinomialExact( x, i = 1, x.i, theta = NULL, ve = NULL, ratio = x$ratio, binding = TRUE )
x |
An exact binomial spending design returned by [toBinomialExact()]. |
i |
Interim analysis at which conditioning occurs; must be less than 'x$k'. |
x.i |
Cumulative experimental arm events observed at analysis 'i'. |
theta |
Conditional probabilities that a future event occurs in the experimental arm. If 'NULL', the observed probability and the values in 'x$theta' are used. |
ve |
Vaccine or prevention efficacy assumptions. These are converted to conditional event probabilities using 'ratio'. Specify at most one of 'theta' and 've'. |
ratio |
Experimental-to-control randomization ratio. This defaults to the ratio retained by [toBinomialExact()]. It is required when 've' is supplied and is also used to report efficacy corresponding to 'theta'. |
binding |
Logical indicating whether future futility or harm boundaries stop the trial. With 'FALSE', conditional efficacy power ignores these future non-binding boundaries. This argument does not restrict the observed result at analysis 'i'. |
Conditional on the interim count, future experimental arm event increments are independent binomial random variables. Their distribution is propagated through the remaining integer boundaries. Thus the calculation is exact under the same conditional binomial assumptions used by [gsBinomialExact()].
The calculation conditions only on the statistic at analysis 'i', as [gsCP()] does. It is computed regardless of whether the observed count has crossed a current efficacy, futility, or harm boundary. This gives a hypothetical projection if follow-up were to continue; it does not reverse a stopping decision. The 'binding' argument controls only whether futility or harm boundaries at future analyses are enforced. Thus, the default 'binding = TRUE' includes future non-efficacy stopping, whereas 'binding = FALSE' ignores it.
An object of class 'gsBinomialExactCP'. Components include future absolute event counts, assumed conditional event probabilities and efficacies, per-analysis efficacy and futility crossing probabilities, continuation probabilities, and total conditional power and futility.
[gsCP()], [toBinomialExact()], [VEtable()]
design <- gsSurv( k = 2, test.type = 4, timing = .5, ratio = 3, hr = .3, hr0 = .7 ) exact_design <- toBinomialExact(design) gsCPBinomialExact(exact_design, i = 1, x.i = 20, ve = c(.5, .7))design <- gsSurv( k = 2, test.type = 4, timing = .5, ratio = 3, hr = .3, hr0 = .7 ) exact_design <- toBinomialExact(design) gsCPBinomialExact(exact_design, i = 1, x.i = 20, ve = c(.5, .7))
gsCPFutilitySpending() selects parameters for a beta spending futility
boundary so that conditional power at one or more interim futility bounds
matches specified targets. Each candidate design is reconstructed with
gsDesign(), which recalculates the statistical information required to
retain the reference design's unconditional power.
gsCPFutilitySpending( x, target_cp, i = seq_along(target_cp), sfl = x$lower$sf, theta = NULL, control = list() )gsCPFutilitySpending( x, target_cp, i = seq_along(target_cp), sfl = x$lower$sf, theta = NULL, control = list() )
x |
A |
target_cp |
Numeric vector of conditional power targets strictly between
zero and one, one per interim index in |
i |
Interim analysis indices corresponding to |
sfl |
Futility spending function, supplied as a supported function or
its character name. By default, use |
theta |
Optional future effect for conditional power. A scalar is
recycled; otherwise its length must equal |
control |
Optional named list of numerical solver settings. Unspecified components retain their defaults; ordinary use does not require this argument. These settings affect the search, not the effect assumption or CP target. Unknown names and invalid values produce input errors.
|
Conditional power is the probability of crossing any efficacy bound after
the selected interim, through the final analysis, conditional on the interim
statistic equaling the candidate lower bound. When theta
is NULL, the future effect is the observed effect implied by that bound,
lower$bound[i] / sqrt(n.I[i]), following the default convention in
gsCP(). The calculation conditions on the interim statistic even though it
is at a stopping boundary; future futility bounds remain part of the
conditional power calculation.
For a three-analysis design, target_cp = .3, i = 1 targets probability
0.3 of efficacy at analysis 2 or 3, conditional on , where
is the fitted futility bound. With a two-parameter spending family,
target_cp = c(.3, .5), i = c(1, 2) additionally targets probability
0.5 of efficacy at analysis 3 conditional on . Each target
uses its own interim conditioning value and, when theta = NULL, its
own bound-implied future effect. These are total future conditional powers,
not probabilities of efficacy only at the next analysis or probabilities
that are added together across targeted interims.
One-target calibration supports the one-parameter families sfHSD,
sfPower, sfExponential, and sfLDOF. Two-target calibration supports
sfLogistic, sfBetaDist, sfCauchy, sfNormal, sfExtremeValue, and
sfExtremeValue2. sfLinear may be used with any number of targets and is
the intended choice for more than two. Its knot times are fixed at the lower
spending times for the targeted analyses; the fitted cumulative spending
proportions are constrained to be strictly increasing and between zero and
one.
With multiple targets, joint constrained optimization first uses the
reference or supplied starting parameters. If needed, a latest-to-earliest
coordinate solve supplies alternative starting values for another joint fit.
A result is returned only when every conditional power residual is within
control$cp_tol. Searches may stop early once all residuals are at most
min(control$cp_tol / 10, 1e-7); computations retain double precision.
The fitted lower spending parameters depend on the complete design,
including efficacy spending. For test.type 7 and 8 they may also depend on
harm spending. Changing any of those specifications requires recalibration.
toInteger() carries the fitted spending function and parameters forward,
but rounding information can change the achieved conditional power and does
not trigger recalibration.
A calibrated design inheriting from gsCPFutilitySpending and
gsDesign, retaining gsSurv and gsSurvPower when applicable.
The cpFutilitySpending component contains targets, achieved conditional
powers, effects, fitted spending metadata, information, reference efficacy
and harm specifications, and solver diagnostics.
Invalid inputs raise a gsCPFutilitySpending_input_error. A well-formed
target that cannot be attained raises a
gsCPFutilitySpending_infeasible_error; optimizer failure raises a
gsCPFutilitySpending_convergence_error.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
When not supplied in control, parameter limits and fallback starting
values are selected by family:
| Family | Start | Lower | Upper |
sfHSD |
-2 | -40 | 40 |
sfPower |
1 | 1e-4 | 50 |
sfExponential |
0.5 | 1e-4 | 1.5 |
sfLDOF |
1 | 0.005 | 20 |
sfBetaDist |
c(1, 1) | c(1e-3, 1e-3) | c(50, 50) |
| Other supported two-parameter families | c(0, 1) | c(-20, 1e-3) | c(20, 50) |
| Custom function | Required | -20 per parameter | 20 per parameter |
For sfLinear, start instead contains one cumulative spending
proportion per target, strictly increasing and in (0, 1). When omitted,
starting proportions are derived from the reference design's cumulative
lower spending divided by beta and adjusted to satisfy these constraints.
User-supplied lower and upper are not supported for its
constrained parameterization.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsDesign, gsSurv, gsCP, sfLinear,
toInteger, gsPPFutilitySpending
# Lan-DeMets O'Brien-Fleming efficacy spending with futility only at IA 1. x <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = 1, testLower = c(TRUE, FALSE, FALSE) ) target_cp <- .3 # With theta = NULL (the default), CP uses the observed effect implied # by the interim futility bound. fit <- gsCPFutilitySpending(x, target_cp = target_cp, i = 1) fit$cpFutilitySpending[c("target_cp", "achieved_cp", "sflpar")] # Use the fitted spending parameter in the final gsDesign. final_design <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$cpFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) # The final design has CP 0.3000 at the first interim futility bound. gsBoundSummary( final_design, exclude = "B-value" ) # Use the same test type, timing, and spending in a survival design. # Other survival inputs use gsSurv() defaults. The IA 1 futility CP row # again shows 0.3000, agreeing with target_cp up to numerical tolerance. surv_design <- gsSurv( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$cpFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) gsBoundSummary(surv_design, exclude = "B-value") # Survival designs can also be calibrated directly. surv_fit <- gsCPFutilitySpending(surv_design, target_cp = .7, i = 1) gsBoundSummary(surv_fit, exclude = "B-value") # Optionally require an absolute CP residual no larger than 0.000001. fit_tight <- gsCPFutilitySpending( x, target_cp = target_cp, i = 1, control = list(cp_tol = 1e-6) ) abs(fit_tight$cpFutilitySpending$achieved_cp - target_cp) <= 1e-6 # Two targets use a two-parameter family and active futility at both interims. x_two <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfl = sfLogistic, sflpar = c(0, 1) ) fit_two <- gsCPFutilitySpending( x_two, target_cp = c(.3, .5), i = c(1, 2), theta = NULL ) # IA 1: efficacy at IA 2 or FA; IA 2: efficacy at FA. # Independently recompute total future CP using each fitted bound's effect. achieved <- vapply(1:2, function(j) { cp <- gsCP(fit_two, i = j, zi = fit_two$lower$bound[j], theta = fit_two$lower$bound[j] / sqrt(fit_two$n.I[j])) sum(cp$upper$prob[, 1]) }, numeric(1)) stopifnot(max(abs(achieved - c(.3, .5))) <= 1e-4, isTRUE(all.equal(fit_two$timing, x_two$timing)))# Lan-DeMets O'Brien-Fleming efficacy spending with futility only at IA 1. x <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = 1, testLower = c(TRUE, FALSE, FALSE) ) target_cp <- .3 # With theta = NULL (the default), CP uses the observed effect implied # by the interim futility bound. fit <- gsCPFutilitySpending(x, target_cp = target_cp, i = 1) fit$cpFutilitySpending[c("target_cp", "achieved_cp", "sflpar")] # Use the fitted spending parameter in the final gsDesign. final_design <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$cpFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) # The final design has CP 0.3000 at the first interim futility bound. gsBoundSummary( final_design, exclude = "B-value" ) # Use the same test type, timing, and spending in a survival design. # Other survival inputs use gsSurv() defaults. The IA 1 futility CP row # again shows 0.3000, agreeing with target_cp up to numerical tolerance. surv_design <- gsSurv( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$cpFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) gsBoundSummary(surv_design, exclude = "B-value") # Survival designs can also be calibrated directly. surv_fit <- gsCPFutilitySpending(surv_design, target_cp = .7, i = 1) gsBoundSummary(surv_fit, exclude = "B-value") # Optionally require an absolute CP residual no larger than 0.000001. fit_tight <- gsCPFutilitySpending( x, target_cp = target_cp, i = 1, control = list(cp_tol = 1e-6) ) abs(fit_tight$cpFutilitySpending$achieved_cp - target_cp) <= 1e-6 # Two targets use a two-parameter family and active futility at both interims. x_two <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfl = sfLogistic, sflpar = c(0, 1) ) fit_two <- gsCPFutilitySpending( x_two, target_cp = c(.3, .5), i = c(1, 2), theta = NULL ) # IA 1: efficacy at IA 2 or FA; IA 2: efficacy at FA. # Independently recompute total future CP using each fitted bound's effect. achieved <- vapply(1:2, function(j) { cp <- gsCP(fit_two, i = j, zi = fit_two$lower$bound[j], theta = fit_two$lower$bound[j] / sqrt(fit_two$n.I[j])) sum(cp$upper$prob[, 1]) }, numeric(1)) stopifnot(max(abs(achieved - c(.3, .5))) <= 1e-4, isTRUE(all.equal(fit_two$timing, x_two$timing)))
Select beta spending parameters to match gsCPOS() targets at selected
interims, either preserving reference power or fixing reference information.
gsCPOSFutilitySpending( x, target_cpos, i = seq_along(target_cpos), sfl = x$lower$sf, prior, control = list(), mode = c("preserve_power", "fixed_information") )gsCPOSFutilitySpending( x, target_cpos, i = seq_along(target_cpos), sfl = x$lower$sf, prior, control = list(), mode = c("preserve_power", "fixed_information") )
x |
A |
target_cpos |
Numeric conditional assurance targets strictly between zero and one, one per selected interim. |
i |
Unique active interim futility indices, defaulting to
|
sfl |
Supported lower spending function or its name. Defaults to
|
prior |
List with finite numeric vectors |
control |
Named list of solver controls. |
mode |
Design constraint: |
Both modes target the existing gsCPOS() calculation. In fixed information
mode, gsBound1() derives lower bounds and an internal beta solve makes
spending consistent with the final decision. For binding futility, changing
lower bounds while fixing efficacy can increase actual type I error above
nominal alpha; inspect achieved_type1. Replay this mode with the
original reference and fitted parameters as control$start; an ordinary
power-preserving gsDesign() call is not equivalent. The legacy
gsCAFutilitySpending interface is a compatibility wrapper.
Conditional assurance is the prior-averaged probability of future efficacy
rejection given that neither stopping boundary has been crossed through
interim i. It includes the entire previous stopping history.
Conditioning on continuation reweights the effect distribution; this differs
from gsPP(), which conditions on an exact interim statistic, and from
unconditional gsPOS(). A point prior gives fixed effect success
conditional on continuation, not conditional power at the futility bound.
A fixed design gsPOS() value at a separately chosen feasible sample
size can be computed once and supplied as a benchmark target. The benchmark
must not be recomputed from candidates. Preserving power while adjusting
information is an extension of a fixed information conditional assurance
rule, not an implementation of sample size re-estimation. Inspect sample size
inflation, overall operating characteristics and effect sizes at all bounds.
The supported spending families and requirement for one target per free
parameter are the same as for gsPPFutilitySpending, including
two-parameter families and sfLinear. The shared solver accepts only
fits meeting every target tolerance; failure is not a proof of global
infeasibility.
Candidate continuation probabilities at or below
sqrt(.Machine$double.eps) are rejected to avoid unstable conditioning.
Harm bound designs are unsupported because gsCPOS() does not account
for their harm stopping probability in its denominator.
Replay parameters with the complete reference design and the same prior.
Spending functions retain their usual timing flexibility, but changed
timing, testing indicators or rounding need not retain exact target values.
A c("gsCPOSFutilitySpending", "gsDesign") object, retaining
survival classes when applicable. Component
cposFutilitySpending contains targets (target_cpos), achieved
values (achieved_cpos), residuals, indices, normalized prior, fitted
parameters, information and power, reference settings and solver diagnostics.
It also records continuation_probability, joint_future_efficacy
and unconditional_pos. Errors inherit from
gsCPOSFutilitySpending_error, with suffixes _input_error,
_infeasible_error or _convergence_error.
Both modes also record mode, fixed_information,
achieved_beta and achieved_type1.
When not supplied in control, parameter limits and fallback starting
values are selected by family:
| Family | Start | Lower | Upper |
sfHSD |
-2 | -40 | 40 |
sfPower |
1 | 1e-4 | 50 |
sfExponential |
0.5 | 1e-4 | 1.5 |
sfLDOF |
1 | 0.005 | 20 |
sfBetaDist |
c(1, 1) | c(1e-3, 1e-3) | c(50, 50) |
| Other supported two-parameter families | c(0, 1) | c(-20, 1e-3) | c(20, 50) |
| Custom function | Required | -20 per parameter | 20 per parameter |
For sfLinear, start instead contains one cumulative spending
proportion per target, strictly increasing and in (0, 1). When omitted,
starting proportions are derived from the reference design's cumulative
lower spending divided by beta and adjusted to satisfy these constraints.
User-supplied lower and upper are not supported for its
constrained parameterization.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsCPOS, gsPOS,
gsPPFutilitySpending, gsCPFutilitySpending
x <- gsDesign(k = 3, test.type = 4, timing = c(.5, .75), sflpar = 1) prior <- list(z = c(0, x$delta), wgts = c(.2, .8)) target <- gsCPOS(i = 1, x = x, theta = prior$z, wgts = prior$wgts) fit <- gsCPOSFutilitySpending( x, target_cpos = target, i = 1, prior = prior, control = list(start = 0) ) fit$cposFutilitySpending$sflpar gsCPOS(i = 1, x = fit, theta = prior$z, wgts = prior$wgts) fixed <- gsCPOSFutilitySpending( x, target_cpos = target, prior = prior, mode = "fixed_information" ) fixed$cposFutilitySpending[c("mode", "achieved_cpos", "achieved_beta")] stopifnot(identical(fixed$n.I, x$n.I))x <- gsDesign(k = 3, test.type = 4, timing = c(.5, .75), sflpar = 1) prior <- list(z = c(0, x$delta), wgts = c(.2, .8)) target <- gsCPOS(i = 1, x = x, theta = prior$z, wgts = prior$wgts) fit <- gsCPOSFutilitySpending( x, target_cpos = target, i = 1, prior = prior, control = list(start = 0) ) fit$cposFutilitySpending$sflpar gsCPOS(i = 1, x = fit, theta = prior$z, wgts = prior$wgts) fixed <- gsCPOSFutilitySpending( x, target_cpos = target, prior = prior, mode = "fixed_information" ) fixed$cposFutilitySpending[c("mode", "achieved_cpos", "achieved_beta")] stopifnot(identical(fixed$n.I, x$n.I))
Given an interim analysis i of a group sequential design and a vector
of real values zi, gsDensity() computes an interim density
function at analysis i at the values in zi. For each value in
zi, this interim density is the derivative of the probability that
the group sequential trial does not cross a boundary prior to the
i-th analysis and at the i-th analysis the interim Z-statistic
is less than that value. When integrated over the real line, this density
computes the probability of not crossing a bound at a previous analysis. It
corresponds to the subdistribution function at analysis i that
excludes the probability of crossing a bound at an earlier analysis.
The initial purpose of this routine was as a component needed to compute the
predictive power for a trial given an interim result; see
gsPP.
See Jennison and Turnbull (2000) for details on how these computations are performed.
gsDensity(x, theta = 0, i = 1, zi = 0, r = 18)gsDensity(x, theta = 0, i = 1, zi = 0, r = 18)
x |
An object of type |
theta |
a vector with |
i |
analysis at which interim z-values are given; must be from 1 to
|
zi |
interim z-value at analysis |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
zi |
The input vector |
theta |
The input vector
|
density |
A matrix with |
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
gsDesign, gsProbability,
gsBoundCP
library(ggplot2) # set up a group sequential design x <- gsDesign() # set theta values where density is to be evaluated theta <- x$theta[2] * c(0, .5, 1, 1.5) # set zi values from -1 to 7 where density is to be evaluated zi <- seq(-3, 7, .05) # compute subdensity values at analysis 2 y <- gsDensity(x, theta = theta, i = 2, zi = zi) # plot sub-density function for each theta value plot(y$zi, y$density[, 3], type = "l", xlab = "Z", ylab = "Interim 2 density", lty = 3, lwd = 2 ) lines(y$zi, y$density[, 2], lty = 2, lwd = 2) lines(y$zi, y$density[, 1], lwd = 2) lines(y$zi, y$density[, 4], lty = 4, lwd = 2) title("Sub-density functions at interim analysis 2") legend( x = c(3.85, 7.2), y = c(.27, .385), lty = 1:5, lwd = 2, cex = 1.5, legend = c( expression(paste(theta, "=0.0")), expression(paste(theta, "=0.5", delta)), expression(paste(theta, "=1.0", delta)), expression(paste(theta, "=1.5", delta)) ) ) # add vertical lines with lower and upper bounds at analysis 2 # to demonstrate how likely it is to continue, stop for futility # or stop for efficacy at analysis 2 by treatment effect lines(rep(x$upper$bound[2], 2), c(0, .4), col = 2) lines(rep(x$lower$bound[2], 2), c(0, .4), lty = 2, col = 2) # Replicate part of figures 8.1 and 8.2 of Jennison and Turnbull text book # O'Brien-Fleming design with four analyses x <- gsDesign(k = 4, test.type = 2, sfu = "OF", alpha = .1, beta = .2) z <- seq(-4.2, 4.2, .05) d <- gsDensity(x = x, theta = x$theta, i = 4, zi = z) plot(z, d$density[, 1], type = "l", lwd = 2, ylab = expression(paste(p[4], "(z,", theta, ")"))) lines(z, d$density[, 2], lty = 2, lwd = 2) u <- x$upper$bound[4] text(expression(paste(theta, "=", delta)), x = 2.2, y = .2, cex = 1.5) text(expression(paste(theta, "=0")), x = .55, y = .4, cex = 1.5)library(ggplot2) # set up a group sequential design x <- gsDesign() # set theta values where density is to be evaluated theta <- x$theta[2] * c(0, .5, 1, 1.5) # set zi values from -1 to 7 where density is to be evaluated zi <- seq(-3, 7, .05) # compute subdensity values at analysis 2 y <- gsDensity(x, theta = theta, i = 2, zi = zi) # plot sub-density function for each theta value plot(y$zi, y$density[, 3], type = "l", xlab = "Z", ylab = "Interim 2 density", lty = 3, lwd = 2 ) lines(y$zi, y$density[, 2], lty = 2, lwd = 2) lines(y$zi, y$density[, 1], lwd = 2) lines(y$zi, y$density[, 4], lty = 4, lwd = 2) title("Sub-density functions at interim analysis 2") legend( x = c(3.85, 7.2), y = c(.27, .385), lty = 1:5, lwd = 2, cex = 1.5, legend = c( expression(paste(theta, "=0.0")), expression(paste(theta, "=0.5", delta)), expression(paste(theta, "=1.0", delta)), expression(paste(theta, "=1.5", delta)) ) ) # add vertical lines with lower and upper bounds at analysis 2 # to demonstrate how likely it is to continue, stop for futility # or stop for efficacy at analysis 2 by treatment effect lines(rep(x$upper$bound[2], 2), c(0, .4), col = 2) lines(rep(x$lower$bound[2], 2), c(0, .4), lty = 2, col = 2) # Replicate part of figures 8.1 and 8.2 of Jennison and Turnbull text book # O'Brien-Fleming design with four analyses x <- gsDesign(k = 4, test.type = 2, sfu = "OF", alpha = .1, beta = .2) z <- seq(-4.2, 4.2, .05) d <- gsDensity(x = x, theta = x$theta, i = 4, zi = z) plot(z, d$density[, 1], type = "l", lwd = 2, ylab = expression(paste(p[4], "(z,", theta, ")"))) lines(z, d$density[, 2], lty = 2, lwd = 2) u <- x$upper$bound[4] text(expression(paste(theta, "=", delta)), x = 2.2, y = .2, cex = 1.5) text(expression(paste(theta, "=0")), x = .55, y = .4, cex = 1.5)
gsDesign() is used to find boundaries and trial size required for a
group sequential design.
Many parameters normally take on default values and thus do not require
explicit specification. One- and two-sided designs are supported. Two-sided
designs may be symmetric or asymmetric. Wang-Tsiatis designs, including
O'Brien-Fleming and Pocock designs can be generated. Designs with common
spending functions as well as other built-in and user-specified functions
for Type I error and futility are supported. Type I error computations for
asymmetric designs may assume binding or non-binding lower bounds. The print
function has been extended using print.gsDesign() to print
gsDesign objects; see examples.
The user may ignore the structure of the value returned by gsDesign()
if the standard printing and plotting suffice; see examples.
delta and n.fix are used together to determine what sample
size output options the user seeks. The default, delta=0 and
n.fix=1, results in a ‘generic’ design that may be used with
any sampling situation. Sample size ratios are provided and the user
multiplies these times the sample size for a fixed design to obtain the
corresponding group sequential analysis times. If delta>0,
n.fix is ignored, and delta is taken as the standardized
effect size - the signal to noise ratio for a single observation; for
example, the mean divided by the standard deviation for a one-sample normal
problem. In this case, the sample size at each analysis is computed. When
delta=0 and n.fix>1, n.fix is assumed to be the sample
size for a fixed design with no interim analyses. See examples below.
Following are further comments on the input argument test.type which
is used to control what type of error measurements are used in trial design.
The manual may also be worth some review in order to see actual formulas for
boundary crossing probabilities for the various options. Options 3 and 5
assume the trial stops if the lower bound is crossed for Type I and Type II
error computation (binding lower bound). For the purpose of computing Type
I error, options 4 and 6 assume the trial continues if the lower bound is
crossed (non-binding lower bound); that is a Type I error can be made by
crossing an upper bound after crossing a previous lower bound.
Beta-spending refers to error spending for the lower bound crossing
probabilities under the alternative hypothesis (options 3 and 4). In this
case, the final analysis lower and upper boundaries are assumed to be the
same. The appropriate total beta spending (power) is determined by adjusting
the maximum sample size through an iterative process for all options. Since
options 3 and 4 must compute boundary crossing probabilities under both the
null and alternative hypotheses, deriving these designs can take longer than
other options. Options 5 and 6 compute lower bound spending under the null
hypothesis. Options 7 and 8 add a harm bound to beta spending futility:
option 7 treats both futility and harm bounds as binding for Type I error,
while option 8 treats both as non-binding. Futility and harm are mutually
exclusive lower tail stopping outcomes with separately specified spending
functions.
gsDesign( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, astar = 0, delta = 0, n.fix = 1, timing = 1, sfu = sfHSD, sfupar = -4, sfl = sfHSD, sflpar = -2, sfharm = sfLDPocock, sfharmparam = -2, tol = 1e-06, r = 18, n.I = 0, maxn.IPlan = 0, nFixSurv = 0, endpoint = NULL, delta1 = 1, delta0 = 0, overrun = 0, usTime = NULL, lsTime = NULL, testUpper = TRUE, testLower = TRUE, testHarm = TRUE ) ## S3 method for class 'gsDesign' xtable( x, caption = NULL, label = NULL, align = NULL, digits = NULL, display = NULL, ... )gsDesign( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, astar = 0, delta = 0, n.fix = 1, timing = 1, sfu = sfHSD, sfupar = -4, sfl = sfHSD, sflpar = -2, sfharm = sfLDPocock, sfharmparam = -2, tol = 1e-06, r = 18, n.I = 0, maxn.IPlan = 0, nFixSurv = 0, endpoint = NULL, delta1 = 1, delta0 = 0, overrun = 0, usTime = NULL, lsTime = NULL, testUpper = TRUE, testLower = TRUE, testHarm = TRUE ) ## S3 method for class 'gsDesign' xtable( x, caption = NULL, label = NULL, align = NULL, digits = NULL, display = NULL, ... )
k |
Number of analyses planned, including interim and final. |
test.type |
|
alpha |
Type I error, always one-sided. Default value is 0.025. |
beta |
Type II error, default value is 0.1 (90% power). |
astar |
Total spending for the lower (test.type 5 or 6) or harm
(test.type 7 or 8) bound under the null hypothesis. Default is 0.
For |
delta |
Effect size for theta under alternative hypothesis. This can be
set to the standardized effect size to generate a sample size if
|
n.fix |
Sample size for fixed design with no interim; used to find
maximum group sequential sample size. For a time-to-event outcome, input
number of events required for a fixed design rather than sample size and
enter fixed design sample size (optional) in |
timing |
Sets relative timing of interim analyses. Default of 1
produces equally spaced analyses. Otherwise, this is a vector of length
|
sfu |
A spending function or a character string indicating a boundary
type (that is, “WT” for Wang-Tsiatis bounds, “OF” for
O'Brien-Fleming bounds and “Pocock” for Pocock bounds). For
one-sided and symmetric two-sided testing is used to completely specify
spending ( |
sfupar |
Real value, default is |
sfl |
Specifies the spending function for lower boundary crossing
probabilities when asymmetric, two-sided testing is performed
( |
sflpar |
Real value, default is |
sfharm |
A spending function for the harm bound, used with
|
sfharmparam |
Real value, default is |
tol |
Tolerance for error (default is 0.000001). Normally this will not be changed by the user. This does not translate directly to number of digits of accuracy, so use extra decimal places. |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
n.I |
Used for re-setting bounds when timing of analyses changes from initial design; see examples. |
maxn.IPlan |
Used for re-setting bounds when timing of analyses changes from initial design; see examples. |
nFixSurv |
If a time-to-event variable is used, |
endpoint |
An optional character string that should represent the type
of endpoint used for the study. This may be used by output functions. Types
most likely to be recognized initially are "TTE" for time-to-event outcomes
with fixed design sample size generated by |
delta1 |
|
delta0 |
|
overrun |
Scalar or vector of length |
usTime |
Default is NULL in which case upper bound spending time is
determined by |
lsTime |
Default is NULL in which case lower bound spending time is
determined by |
testUpper |
Indicator of which analyses should include an upper
(efficacy) bound.
A single value of |
testLower |
Indicator of which analyses should include a lower
(futility) bound.
A single value of |
testHarm |
Indicator of which analyses should include a harm bound.
A single value of |
x |
An R object of class found among |
caption |
Character vector of length 1 or 2 containing the
table's caption or title. If length is 2, the second item is the
"short caption" used when LaTeX generates a "List of Tables". Set to
|
label |
Character vector of length 1 containing the LaTeX label
or HTML anchor. Set to |
align |
Character vector of length equal to the number of columns
of the resulting table, indicating the alignment of the corresponding
columns. Also, |
digits |
Numeric vector of length equal to one (in which case it will be
replicated as necessary) or to the number of columns of the
resulting table or matrix of the same size as the resulting
table, indicating the number of digits to display in the
corresponding columns. Since the row names are printed in the first
column, the length of the vector |
display |
Character vector of length equal to the number of columns of the
resulting table, indicating the format for the corresponding columns.
Since the row names are printed in the first column, the length of
|
... |
Additional arguments. (Currently ignored.) |
An object of the class gsDesign. This class has the following
elements and upon return from gsDesign() contains:
k |
As input. |
test.type |
As input. |
alpha |
As input. |
beta |
As input. |
astar |
As input, except when |
delta |
The standardized effect size for which the
design is powered. Will be as input to |
n.fix |
Sample size
required to obtain desired power when effect size is |
timing |
A vector of length |
tol |
As input. |
r |
As input. |
n.I |
Vector of length |
maxn.IPlan |
As input. |
nFixSurv |
As input. |
nSurv |
Sample
size for Lachin and Foulkes method when |
endpoint |
As input. |
delta1 |
As input. |
delta0 |
As input. |
overrun |
As input. |
usTime |
As input. |
lsTime |
As input. |
testUpper |
Logical vector of length |
testLower |
Logical vector of length |
testHarm |
Logical vector of length |
upper |
Upper bound spending function,
boundary and boundary crossing probabilities under the NULL and alternate
hypotheses. See |
lower |
Lower bound spending function, boundary and boundary
crossing probabilities at each analysis. Lower spending is under alternative
hypothesis (beta spending) for |
harm |
Harm bound spending function, boundary, and crossing
probabilities at each analysis for |
theta |
Standarized
effect size under null (0) and alternate hypothesis. If |
falseprobnb |
For
|
en |
Expected sample size accounting for early
stopping. For time-to-event outcomes, this would be the expected number of
events (although |
An object of class "xtable" with attributes specifying formatting options for a table
Boundary crossing probabilities are computed by the recursive numerical
integration of Jennison and Turnbull (2000, Chapter 19). By default the
integration grid of that chapter is used (Simpson's rule on 12 r - 3
points concentrated around the mean of the test statistic), which gives
probabilities accurate to about with the default
r = 18. Setting options(gsDesign.quadrature = "gl") switches
all computations (gsDesign(), gsProbability(),
gsBound(), gsBound1(), gsDensity() and everything
built on them) to Gauss-Legendre quadrature on the continuation region of
each analysis, with a number of nodes that adapts to the width of the
region relative to the neighboring information increments. In benchmarked
designs this is several times faster and accurate to about ,
with differences from the default grid within the accuracy of that grid.
The rule uses at most 992 nodes, so extremely small information increments
can reduce accuracy. normalGrid() always returns the Jennison and
Turnbull grid.
For types 7 and 8, harm spending is calibrated under the null as if futility
stopping were ignored. Earlier harm and efficacy stops are retained.
This convention is separate from binding versus non-binding efficacy
calibration: type 7 protects alpha assuming both lower stopping rules are
followed, whereas type 8 protects alpha even if they are ignored.
Reported harm$prob, power, expected information, and
gsBoundSummary() crossing probabilities assume all active stopping
rules are followed. Thus actual harm probabilities may be smaller than
cumsum(harm$spend). Where both lower bounds are active, harm is capped
at futility to retain their ordering; this can also prevent full spending.
The defaults astar = 0.1 (selected by input 0) and
sfharm = sfLDPocock provide relatively early spending and aim to flag
moderately small one-sided p-values favoring harm. At harm boundary ,
that nominal p-value is , not the cumulative spending and not
the efficacy direction p-value displayed by gsBoundSummary().
There is no fixed nominal p-value cutoff guaranteed at every analysis.
With more analyses, a larger total harm spending astar may be desired
to retain similarly permissive nominal harm thresholds, at the cost of more
false harm signals under the null. This changes harm spending, not futility
beta spending. Inspect the resulting nominal thresholds for the actual
analysis schedule. See vignette("HarmBound") for examples.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall. Lan KK, DeMets DL (1989). Group sequential procedures: calendar versus information time. Statistics in medicine 8(10):1191-8. Liu, Q, Lim, P, Nuamah, I, and Li, Y (2012), On adaptive error spending approach for group sequential trials with random information levels. Journal of biopharmaceutical statistics; 22(4), 687-699.
vignette("gsDesignPackageOverview"), gsBoundSummary,
plot.gsDesign,
gsProbability, vignette("SpendingFunctionOverview"),
library(ggplot2) # symmetric, 2-sided design with O'Brien-Fleming-like boundaries # lower bound is non-binding (ignored in Type I error computation) # sample size is computed based on a fixed design requiring n=800 x <- gsDesign(k = 5, test.type = 2, n.fix = 800) # note that "x" below is equivalent to print(x) and print.gsDesign(x) x plot(x) plot(x, plottype = 2) # Assuming after trial was designed actual analyses occurred after # 300, 600, and 860 patients, reset bounds y <- gsDesign( k = 3, test.type = 2, n.fix = 800, n.I = c(300, 600, 860), maxn.IPlan = x$n.I[x$k] ) y # asymmetric design with user-specified spending that is non-binding # sample size is computed relative to a fixed design with n=1000 sfup <- c(.033333, .063367, .1) sflp <- c(.25, .5, .75) timing <- c(.1, .4, .7) x <- gsDesign( k = 4, timing = timing, sfu = sfPoints, sfupar = sfup, sfl = sfPoints, sflpar = sflp, n.fix = 1000 ) x plot(x) plot(x, plottype = 2) # same design, but with relative sample sizes gsDesign( k = 4, timing = timing, sfu = sfPoints, sfupar = sfup, sfl = sfPoints, sflpar = sflp )library(ggplot2) # symmetric, 2-sided design with O'Brien-Fleming-like boundaries # lower bound is non-binding (ignored in Type I error computation) # sample size is computed based on a fixed design requiring n=800 x <- gsDesign(k = 5, test.type = 2, n.fix = 800) # note that "x" below is equivalent to print(x) and print.gsDesign(x) x plot(x) plot(x, plottype = 2) # Assuming after trial was designed actual analyses occurred after # 300, 600, and 860 patients, reset bounds y <- gsDesign( k = 3, test.type = 2, n.fix = 800, n.I = c(300, 600, 860), maxn.IPlan = x$n.I[x$k] ) y # asymmetric design with user-specified spending that is non-binding # sample size is computed relative to a fixed design with n=1000 sfup <- c(.033333, .063367, .1) sflp <- c(.25, .5, .75) timing <- c(.1, .4, .7) x <- gsDesign( k = 4, timing = timing, sfu = sfPoints, sfupar = sfup, sfl = sfPoints, sflpar = sflp, n.fix = 1000 ) x plot(x) plot(x, plottype = 2) # same design, but with relative sample sizes gsDesign( k = 4, timing = timing, sfu = sfPoints, sfupar = sfup, sfl = sfPoints, sflpar = sflp )
Select efficacy, futility or harm spending parameters, separately or jointly, to match approximate observed effects at interim boundaries. Candidate designs are rebuilt with gsDesign(), preserving nominal error budgets and planned power while allowing maximum information to change.
gsEffectSpending( x, target_effect, i = seq_along(target_effect), bound = "futility", spending = NULL, scale = "difference", effect = list(), control = list() )gsEffectSpending( x, target_effect, i = seq_along(target_effect), bound = "futility", spending = NULL, scale = "difference", effect = list(), control = list() )
x |
A |
target_effect |
Finite natural scale effect targets. |
i |
Interim indices, one per target. Duplicate boundary/index pairs and inactive boundaries are not allowed. |
bound |
"efficacy", "futility" or "harm", scalar or one per target. |
spending |
Spending function/name for a single boundary, or a named list keyed by targeted boundaries. NULL retains the reference families. |
scale |
"difference", "rr" or "hr". |
effect |
Endpoint metadata. For differences, optional endpoint is "mean_difference" or "risk_difference"; for RR it is "risk_ratio". Difference/RR designs require nondegenerate delta0/delta1 metadata and n.fix > 1. For RR, delta0/delta1 must be log ratios. HR requires information = "events", ratio (experimental/control), hr0 (null HR), and hr1 (alternative HR), all explicitly supplied. For statistical designs, the reference delta must agree with abs(log(hr1/hr0)) * sqrt(ratio)/(1 + ratio), verifying its event count scale. For survival designs, metadata must match the stored allocation and hazard ratios; the selected survival method determines the drift. |
control |
Named solver list. start, lower and upper are parameter vectors for one boundary or named lists by boundary for joint fits. Defaults use the reference/family settings described in gsCPFutilitySpending. effect_tol is a positive finite absolute tolerance in natural effect units, scalar or one per target (default 1e-4). maxit is a positive integer (default 500), reltol is positive finite (default 1e-10), and trace is a scalar logical (default FALSE). Unknown controls are errors. sfLinear starts are increasing cumulative proportions; user limits are unsupported for sfLinear, whose internal logit coordinates are searched over [-12, 12]. |
One target per free parameter is required for each selected family. Supported families are shared with gsCPFutilitySpending(), including two-parameter functions and piecewise linear spending. Joint fits check all targets simultaneously. Untargeted spending specifications remain fixed, but numerical boundaries may change as information is recalculated.
These are approximate effects at bounds, not true effect assumptions, posterior estimates or bias-adjusted sequential estimates. HRs and RRs are supplied as ratios, not logs. Risk differences must lie in [-1, 1]. An effect is transformed using each candidate's information.
Count equality does not ensure identifiability. A numerical Jacobian rank and condition number are returned as local diagnostics, not proofs of uniqueness. Harm/futility coincidence is reported and may indicate capping. A failed search does not prove mathematical infeasibility. Inspect all operating characteristics and sample size inflation before choosing a design. Changing timing or rounding need not retain the calibrated effects.
A gsDesign object also inheriting from gsEffectSpending, retaining survival classes when applicable. Component effectSpending contains a target/achieved/residual table, named spending specifications and free parameters, effect metadata, solver diagnostics, information inflation, power and local identifiability diagnostics. replay contains arguments for do.call(gsDesign, ...); for HR, also restore the returned hr/hr0/ratio metadata for HR summaries. Errors inherit from gsEffectSpending_error with input_error or convergence_error suffixes.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsCPFutilitySpending, gsDelta, gsRR, gsHR, gsBoundSummary
x <- gsDesign(k = 3, test.type = 4, n.fix = 200, delta1 = .2, sflpar = 1) target <- gsDelta(x$lower$bound[1], 1, x) fit <- gsEffectSpending(x, target, spending = sfHSD, control = list(start = 0)) fit$effectSpending$targets replay <- do.call(gsDesign, fit$effectSpending$replay) gsDelta(replay$lower$bound[1], 1, replay)x <- gsDesign(k = 3, test.type = 4, n.fix = 200, delta1 = .2, sflpar = 1) target <- gsDelta(x$lower$bound[1], 1, x) fit <- gsEffectSpending(x, target, spending = sfHSD, control = list(start = 0)) fit$effectSpending$targets replay <- do.call(gsDesign, fit$effectSpending$replay) gsDelta(replay$lower$bound[1], 1, replay)
Select one free beta spending parameter to match the unconditional
prior predictive probability of success gsPOS(). Recalculate maximum
information to preserve reference frequentist power.
gsPOSFutilitySpending(x, target_pos, sfl = x$lower$sf, prior, control = list())gsPOSFutilitySpending(x, target_pos, sfl = x$lower$sf, prior, control = list())
x |
A |
target_pos |
A single probability of success strictly between zero and one for the complete design. This is not an interim-specific target. |
sfl |
A supported one-parameter lower spending function or its name.
Defaults to |
prior |
List with finite numeric vectors |
control |
Named numerical controls. |
gsPOS() averages the probability of any efficacy rejection over the
supplied effect prior, before observing trial data or conditioning on
continuation. The prior weights are normalized and then held fixed during
fitting. No interim index is required because POS is a single scalar for the
entire trial. Consequently, an unconstrained two-parameter spending family
cannot be identified from POS alone and is rejected. A custom one-parameter
wrapper may fix the other parameters explicitly.
Unconditional POS calibration is not Dragalin's conditional assurance
criterion. Compare gsCPOSFutilitySpending and
gsCAFutilitySpending for continuation-conditioned targets.
For a point prior at the planned alternative, POS equals the power already
preserved by the design builder: the spending shape is then not identified
by that target. A valid starting solution can be returned, but does not imply
uniqueness. Other priors can also produce flat or nonmonotone objectives.
Inspect information inflation and all operating characteristics.
A c("gsPOSFutilitySpending", "gsDesign") object, retaining
survival classes when applicable, with
posFutilitySpending diagnostics. These include target_pos,
achieved_pos, residual, normalized prior, fitted parameters,
information, frequentist power, reference settings and solver diagnostics
including pos_tol. There is no interim target index.
Error classes use the prefix gsPOSFutilitySpending.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsPOS, gsCPOSFutilitySpending,
gsCAFutilitySpending
x <- gsDesign(k = 3, test.type = 4, sflpar = 1) prior <- list(z = c(0, x$delta / 2, x$delta), wgts = c(.1, .4, .5)) target <- gsPOS(x, prior$z, prior$wgts) fit <- gsPOSFutilitySpending(x, target, prior = prior, control = list(start = 0)) fit$posFutilitySpending$sflpar gsPOS(fit, prior$z, prior$wgts)x <- gsDesign(k = 3, test.type = 4, sflpar = 1) prior <- list(z = c(0, x$delta / 2, x$delta), wgts = c(.1, .4, .5)) target <- gsPOS(x, prior$z, prior$wgts) fit <- gsPOSFutilitySpending(x, target, prior = prior, control = list(start = 0)) fit$posFutilitySpending$sflpar gsPOS(fit, prior$z, prior$wgts)
Select beta spending futility parameters so that posterior predictive power
at selected interim lower bounds matches the requested targets. Each candidate
is rebuilt with gsDesign(), recalculating information to preserve the
reference design's target unconditional power.
gsPPFutilitySpending( x, target_pp, i = seq_along(target_pp), sfl = x$lower$sf, prior = normalGrid(mu = x$delta/2, sigma = 10/sqrt(x$n.fix)), control = list() )gsPPFutilitySpending( x, target_pp, i = seq_along(target_pp), sfl = x$lower$sf, prior = normalGrid(mu = x$delta/2, sigma = 10/sqrt(x$n.fix)), control = list() )
x |
A |
target_pp |
Numeric vector of predictive power targets strictly between zero and one. |
i |
Unique active interim futility analysis indices, one per target,
in |
sfl |
Supported lower spending function or its character name. Defaults
to |
prior |
A list containing finite numeric vectors |
control |
Optional named list of numerical solver settings. Unspecified settings retain their defaults; unknown names and invalid values cause input errors. These controls change the search, not the prior or target.
|
gsPP() averages conditional power over the posterior effect
distribution to give the probability of future efficacy rejection. This is
not the posterior probability of a positive effect, observed effect CP, or
CP H1. The prior is held fixed throughout calibration, but the posterior is
recomputed at each candidate lower bound using its information. As in
gsPP(), conditioning uses the interim statistic, not additionally the
event of surviving previous looks. The current bound is a conditioning state,
not a reason to set predictive power to zero.
One target supports sfHSD, sfPower, sfExponential, and
sfLDOF; two targets support sfLogistic, sfBetaDist,
sfCauchy, sfNormal, sfExtremeValue, and
sfExtremeValue2. sfLinear supports one or more targets with
fixed knots at their lower spending times. The target count must equal the
number of free parameters. The solver is shared with
gsCPFutilitySpending(), including its joint fit and optional
latest-to-earliest fallback. Only fits meeting all target tolerances are
returned. Searches can stop once all residuals are at most
min(control$pp_tol / 10, 1e-7), while retaining double precision.
Preserve the complete reference design when replaying fitted parameters.
Changing timing, efficacy or harm spending, or testing indicators requires
recalibration. Use the same prior and effect scale when verifying PP with
gsBoundSummary(). Survival designs can be calibrated directly.
toInteger() retains the spending specification but does not
recalibrate PP after rounding. Extremely remote prior support can make the
likelihood normalization in gsPP() numerically undefined; such
candidates are rejected and the diagnostic reports this if no valid design
can be constructed.
A calibrated object with class
c("gsPPFutilitySpending", "gsDesign"), with survival classes
retained when applicable. Its
ppFutilitySpending component records target_pp,
achieved_pp, residuals, analysis indices, normalized prior,
fitted spending parameters, information, unconditional power, reference
settings, and solver diagnostics (including pp_tol). Invalid inputs,
infeasible searches, and convergence failures raise respectively
gsPPFutilitySpending_input_error,
gsPPFutilitySpending_infeasible_error, and
gsPPFutilitySpending_convergence_error, all inheriting from
gsPPFutilitySpending_error. These describe the implemented search,
not a global certificate of mathematical infeasibility.
When not supplied in control, parameter limits and fallback starting
values are selected by family:
| Family | Start | Lower | Upper |
sfHSD |
-2 | -40 | 40 |
sfPower |
1 | 1e-4 | 50 |
sfExponential |
0.5 | 1e-4 | 1.5 |
sfLDOF |
1 | 0.005 | 20 |
sfBetaDist |
c(1, 1) | c(1e-3, 1e-3) | c(50, 50) |
| Other supported two-parameter families | c(0, 1) | c(-20, 1e-3) | c(20, 50) |
| Custom function | Required | -20 per parameter | 20 per parameter |
For sfLinear, start instead contains one cumulative spending
proportion per target, strictly increasing and in (0, 1). When omitted,
starting proportions are derived from the reference design's cumulative
lower spending divided by beta and adjusted to satisfy these constraints.
User-supplied lower and upper are not supported for its
constrained parameterization.
All six spending calibrators keep the reference analysis information
fractions x$timing fixed. These are the cumulative information
fractions x$n.I / x$n.I[x$k], ending at 1. For example,
x$timing = c(.5, .75, 1) keeps the analyses at 50%, 75%, and 100%
of the final information. Power-preserving calibration may change the
maximum information and thus the absolute information n.I at every
analysis while retaining these fractions. Fixed information calibration
(gsCAFutilitySpending() and
gsCPOSFutilitySpending(mode = "fixed_information")) also holds
n.I and the efficacy boundaries fixed.
Spending times are the inputs to the spending functions, stored in
x$upper$sTime and x$lower$sTime. These are also retained during
calibration but may differ from the information fractions; for example,
calendar-based spending uses fractions of calendar time. Thus fixed
information fractions do not mean that spending must use information time,
or that absolute calendar analysis dates must be fixed. See
Survival designs for how survival calendar times are handled.
Survival inputs retain their survival classes and endpoint assumptions.
Candidate probabilities are evaluated on the statistical event count scale.
Power-preserving probability calibration of fixed duration, rate-scaled
designs rebuilds the survival plan only for the selected fit and checks all
targets again on the returned object. If the deferred search or that check
fails, calibration retries once with full survival reconstruction, retaining
the best available internal parameters as starting values. Effect calibration
and accrual or follow-up duration solves reconstruct the plan for every
candidate.
For gsSurv() and gsSurvCalendar() inputs, information fractions,
spending times, and the enrollment/follow-up constraint are retained;
enrollment rates or durations are recalculated as required. Calendar designs
with fixed enrollment and follow-up retain their calendar schedule up to
numerical tolerance. Stored calls are not evaluated.
For gsSurvPower() inputs, power-preserving calibration fixes the
realized calendar times and enrollment periods and rescales enrollment rates
to attain the fitted event counts. The evaluated alternative x$hr
and its achieved power are used, even if the original design alternative
x$hr1 differed. Original event trigger and calendar cap rules are not
reapplied: the realized schedule becomes the new plan. Fixed information
conditional POS/CA calibration instead retains the survival plan, event
counts and efficacy bounds while updating futility and achieved power.
Priors and explicit theta remain standardized drifts per square root
event, not hazard ratios. Rounding with toInteger() after calibration
can change the target; calibration of an already rounded reference may
return noninteger event counts. The final analysis is not a valid target
index for interim calibration: i identifies the interim bound or
continuation event at which the target is evaluated.
gsCPFutilitySpending, gsPP,
normalGrid, gsBoundSummary
x <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = 1, testLower = c(TRUE, FALSE, FALSE) ) # Both calibration and summary use the same default prior. fit <- gsPPFutilitySpending(x, target_pp = .3, i = 1) final_design <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$ppFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) gsBoundSummary(final_design, exclude = "B-value") # Optional tighter predictive power acceptance tolerance: fit_tight <- gsPPFutilitySpending( x, target_pp = .3, i = 1, control = list(pp_tol = 1e-6) ) fit_tight$ppFutilitySpending$residualx <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = 1, testLower = c(TRUE, FALSE, FALSE) ) # Both calibration and summary use the same default prior. fit <- gsPPFutilitySpending(x, target_pp = .3, i = 1) final_design <- gsDesign( k = 3, test.type = 4, timing = c(.5, .75), sfu = sfLDOF, sfl = sfHSD, sflpar = fit$ppFutilitySpending$sflpar, testLower = c(TRUE, FALSE, FALSE) ) gsBoundSummary(final_design, exclude = "B-value") # Optional tighter predictive power acceptance tolerance: fit_tight <- gsPPFutilitySpending( x, target_pp = .3, i = 1, control = list(pp_tol = 1e-6) ) fit_tight$ppFutilitySpending$residual
Computes power/Type I error and expected sample size for a group sequential
design across a selected set of parameter values for a given set of analyses
and boundaries. The print function has been extended using
print.gsProbability to print gsProbability objects; see
examples.
Depending on the calling sequence, an object of class gsProbability
or class gsDesign is returned. If it is of class gsDesign then
the members of the object will be the same as described in
gsDesign. If d is input as NULL (the default),
all other arguments (other than r) must be specified and an object of
class gsProbability is returned. If d is passed as an object
of class gsProbability or gsDesign the only other argument
required is theta; the object returned has the same class as the
input d. On output, the values of theta input to
gsProbability will be the parameter values for which the design is
characterized.
gsProbability(k = 0, theta, n.I, a, b, r = 18, d = NULL, overrun = 0) ## S3 method for class 'gsProbability' print(x, ...)gsProbability(k = 0, theta, n.I, a, b, r = 18, d = NULL, overrun = 0) ## S3 method for class 'gsProbability' print(x, ...)
k |
Number of analyses planned, including interim and final. |
theta |
Vector of standardized effect sizes for which boundary crossing probabilities are to be computed. |
n.I |
Sample size or relative sample size at analyses; vector of length
k. See |
a |
Lower bound cutoffs (z-values) for futility or harm at each analysis, vector of length k. |
b |
Upper bound cutoffs (z-values) for futility at each analysis; vector of length k. |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
d |
If not |
overrun |
Scalar or vector of length |
x |
An item of class |
... |
Not implemented (here for compatibility with generic print input). |
k |
As input. |
theta |
As input. |
n.I |
As input. |
lower |
A list containing two elements: |
upper |
A list of the same form as
|
en |
A vector of the same length as |
r |
As input. |
Note:
print.gsProbability() returns the input x.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
plot.gsDesign, gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # making a gsDesign object first may be easiest... x <- gsDesign() # take a look at it x # default plot for gsDesign object shows boundaries plot(x) # \code{plottype=2} shows boundary crossing probabilities plot(x, plottype = 2) # now add boundary crossing probabilities and # expected sample size for more theta values y <- gsProbability(d = x, theta = x$delta * seq(0, 2, .25)) class(y) # note that "y" below is equivalent to \code{print(y)} and # \code{print.gsProbability(y)} y # the plot does not change from before since this is a # gsDesign object; note that theta/delta is on x axis plot(y, plottype = 2) # now let's see what happens with a gsProbability object z <- gsProbability( k = 3, a = x$lower$bound, b = x$upper$bound, n.I = x$n.I, theta = x$delta * seq(0, 2, .25) ) # with the above form, the results is a gsProbability object class(z) z # default plottype is now 2 # this is the same range for theta, but plot now has theta on x axis plot(z)library(ggplot2) # making a gsDesign object first may be easiest... x <- gsDesign() # take a look at it x # default plot for gsDesign object shows boundaries plot(x) # \code{plottype=2} shows boundary crossing probabilities plot(x, plottype = 2) # now add boundary crossing probabilities and # expected sample size for more theta values y <- gsProbability(d = x, theta = x$delta * seq(0, 2, .25)) class(y) # note that "y" below is equivalent to \code{print(y)} and # \code{print.gsProbability(y)} y # the plot does not change from before since this is a # gsDesign object; note that theta/delta is on x axis plot(y, plottype = 2) # now let's see what happens with a gsProbability object z <- gsProbability( k = 3, a = x$lower$bound, b = x$upper$bound, n.I = x$n.I, theta = x$delta * seq(0, 2, .25) ) # with the above form, the results is a gsProbability object class(z) z # default plottype is now 2 # this is the same range for theta, but plot now has theta on x axis plot(z)
This is like gsSurv(), but the timing of analyses is specified in
calendar time units.
Information fraction is computed from the input rates and the calendar times.
Spending can be based on information fraction as in Lan and DeMets (1983) or
calendar time units as in Lan and DeMets (1989).
gsSurvCalendar( test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, astar = 0, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfHSD, sflpar = -2, sfharm = gsDesign::sfLDPocock, sfharmparam = -2, calendarTime = c(12, 24, 36), spending = c("information", "calendar"), lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, minfup = 18, ratio = 1, r = 18, tol = .Machine$double.eps^0.25, testUpper = TRUE, testLower = TRUE, testHarm = TRUE, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") )gsSurvCalendar( test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, astar = 0, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfHSD, sflpar = -2, sfharm = gsDesign::sfLDPocock, sfharmparam = -2, calendarTime = c(12, 24, 36), spending = c("information", "calendar"), lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, minfup = 18, ratio = 1, r = 18, tol = .Machine$double.eps^0.25, testUpper = TRUE, testLower = TRUE, testHarm = TRUE, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") )
test.type |
|
alpha |
Type I error rate. Default is 0.025 since 1-sided testing is default. |
sided |
|
beta |
Type II error rate. Default is 0.10 (90% power); |
astar |
Total spending for the lower (test.type 5 or 6) or harm
(test.type 7 or 8) bound under the null hypothesis. Default is 0.
For |
sfu |
A spending function or a character string indicating a boundary
type (that is, “WT” for Wang-Tsiatis bounds, “OF” for
O'Brien-Fleming bounds and “Pocock” for Pocock bounds). For
one-sided and symmetric two-sided testing is used to completely specify
spending ( |
sfupar |
Real value, default is |
sfl |
Specifies the spending function for lower boundary crossing
probabilities when asymmetric, two-sided testing is performed
( |
sflpar |
Real value, default is |
sfharm |
A spending function for the harm bound, used with
|
sfharmparam |
Real value, default is |
calendarTime |
Vector of increasing positive numbers with calendar times of analyses. Time 0 is start of randomization. |
spending |
Select between calendar-based spending and information-based spending. |
lambdaC |
Scalar, vector or matrix of event hazard rates for the control group; rows represent time periods while columns represent strata; a vector implies a single stratum. Note that rates corresponding the final time period are extended indefinitely. |
hr |
Hazard ratio (experimental/control) under the alternate hypothesis
(scalar, > 0, must differ from |
hr0 |
Hazard ratio (experimental/control) under the null hypothesis
(scalar, > 0, must differ from |
eta |
Scalar, vector or matrix of dropout hazard rates for the control group; rows represent time periods while columns represent strata; if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
etaE |
Matrix dropout hazard rates for the experimental group specified
in like form as |
gamma |
A scalar, vector or matrix of rates of entry by time period (rows) and strata (columns); if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
R |
A scalar or vector of durations of time periods for
recruitment rates specified in rows of |
S |
A scalar or vector of durations of piecewise constant event rates
specified in rows of |
minfup |
A non-negative scalar less than the maximum value
in |
ratio |
Randomization ratio of experimental treatment divided by control; normally a scalar, but may be a vector with length equal to number of strata. |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
tol |
Tolerance for error passed to the |
testUpper |
Indicator of which analyses should include an upper
(efficacy) bound.
A single value of |
testLower |
Indicator of which analyses should include a lower
(futility) bound.
A single value of |
testHarm |
Indicator of which analyses should include a harm bound.
A single value of |
method |
One of |
An object of class c("gsSurv", "gsDesign") with
group sequential boundaries and expected counts at each calendar
analysis time. See gsSurv for full component details.
Lan KKG and DeMets DL (1983), Discrete Sequential Boundaries for Clinical Trials. Biometrika, 70, 659-663.
Lan KKG and DeMets DL (1989), Group Sequential Procedures: Calendar vs. Information Time. Statistics in Medicine, 8, 1191-1198.
Schoenfeld D (1981), The Asymptotic Properties of Nonparametric Tests for Comparing Survival Distributions. Biometrika, 68, 316-319.
Freedman LS (1982), Tables of the Number of Patients Required in Clinical Trials Using the Logrank Test. Statistics in Medicine, 1, 121-129.
vignette("SurvivalEnrollmentPlanning", package = "gsDesign")
for enrollment ramp-up and duration planning,
vignette("SeqDesignSurvival", package = "gsDesign") for a SAS
PROC SEQDESIGN sample size translation example and
vignette("gsSurvPower", package = "gsDesign") for power
calculations with fixed calendar analysis assumptions.
gsSurv, gsSurvPower, gsDesign,
gsBoundSummary
# First example: while timing is calendar-based, spending is event-based x <- gsSurvCalendar() |> toInteger() gsBoundSummary(x) # Second example: both timing and spending are calendar-based # This results in less spending at interims and leaves more for final analysis y <- gsSurvCalendar(spending = "calendar") |> toInteger() gsBoundSummary(y) # Note that calendar timing for spending relates to planned timing for y # rather than timing in y after toInteger() conversion # Values plugged into spending function for calendar time y$usTime # Actual calendar fraction from design after toInteger() conversion y$T / max(y$T) # Four-period enrollment ramp-up with fixed study duration and follow-up. ramp_calendar <- gsSurvCalendar( calendarTime = c(12, 18, 26), minfup = 12, gamma = 1:4, R = rep(1, 4) )# First example: while timing is calendar-based, spending is event-based x <- gsSurvCalendar() |> toInteger() gsBoundSummary(x) # Second example: both timing and spending are calendar-based # This results in less spending at interims and leaves more for final analysis y <- gsSurvCalendar(spending = "calendar") |> toInteger() gsBoundSummary(y) # Note that calendar timing for spending relates to planned timing for y # rather than timing in y after toInteger() conversion # Values plugged into spending function for calendar time y$usTime # Actual calendar fraction from design after toInteger() conversion y$T / max(y$T) # Four-period enrollment ramp-up with fixed study duration and follow-up. ramp_calendar <- gsSurvCalendar( calendarTime = c(12, 18, 26), minfup = 12, gamma = 1:4, R = rep(1, 4) )
gsSurvPower() computes power for a group sequential survival design
with specified enrollment, dropout, treatment effect, and analysis timing.
Unlike gsSurv() and gsSurvCalendar() which solve for sample
size to achieve target power, gsSurvPower() takes fixed design
assumptions and computes the resulting power. Its two primary uses are
computing achieved power when sample size is not being derived and
evaluating alternative enrollment, failure, treatment effect, and analysis
timing scenarios. It computes one set of assumptions at a time; scenario
grids are evaluated with separate calls.
For k = 1, power is computed through the fixed design
nSurv(beta = NULL) path. The returned object is normalized as a
single-analysis gsSurv object so it can be passed to
toInteger and gsBoundSummary.
gsSurvPower( x = NULL, k = NULL, test.type = NULL, alpha = NULL, sided = NULL, astar = NULL, sfu = NULL, sfupar = NULL, sfl = NULL, sflpar = NULL, sfharm = NULL, sfharmparam = NULL, r = NULL, usTime = NULL, lsTime = NULL, testUpper = NULL, testLower = NULL, testHarm = NULL, lambdaC = NULL, hr = NULL, hr0 = NULL, hr1 = NULL, eta = NULL, etaE = NULL, gamma = NULL, R = NULL, targetN = NULL, S = NULL, ratio = NULL, minfup = NULL, method = NULL, spending = c("information", "calendar", "min_planned_actual"), plannedCalendarTime = NULL, targetEvents = NULL, maxExtension = NULL, minTimeFromPreviousAnalysis = NULL, minN = NULL, minFollowUp = NULL, informationRates = NULL, fullSpendingAtFinal = FALSE, tol = .Machine$double.eps^0.25, targetEventsPerStratum = NULL, maxCalendarTime = NULL, minNPerStratum = NULL )gsSurvPower( x = NULL, k = NULL, test.type = NULL, alpha = NULL, sided = NULL, astar = NULL, sfu = NULL, sfupar = NULL, sfl = NULL, sflpar = NULL, sfharm = NULL, sfharmparam = NULL, r = NULL, usTime = NULL, lsTime = NULL, testUpper = NULL, testLower = NULL, testHarm = NULL, lambdaC = NULL, hr = NULL, hr0 = NULL, hr1 = NULL, eta = NULL, etaE = NULL, gamma = NULL, R = NULL, targetN = NULL, S = NULL, ratio = NULL, minfup = NULL, method = NULL, spending = c("information", "calendar", "min_planned_actual"), plannedCalendarTime = NULL, targetEvents = NULL, maxExtension = NULL, minTimeFromPreviousAnalysis = NULL, minN = NULL, minFollowUp = NULL, informationRates = NULL, fullSpendingAtFinal = FALSE, tol = .Machine$double.eps^0.25, targetEventsPerStratum = NULL, maxCalendarTime = NULL, minNPerStratum = NULL )
x |
Optional |
k |
Number of analyses planned, including interim and final. |
test.type |
|
alpha |
Type I error rate. Default is 0.025 since 1-sided testing
is default. Internally divided by |
sided |
1 for 1-sided, 2 for 2-sided testing. Used to convert
|
astar |
Total lower bound spending for types 5 or 6, or harm spending
for types 7 or 8. Without a reference design, 0 selects |
sfu |
Upper bound spending function (default |
sfupar |
Parameter for |
sfl |
Lower bound spending function (default |
sflpar |
Parameter for |
sfharm |
Spending function for the harm bound, used with
|
sfharmparam |
Real value, default |
r |
Integer grid parameter for numerical integration (default 18). |
usTime |
Upper spending time override; vector of length |
lsTime |
Lower spending time override; vector of length |
testUpper |
Indicator of which analyses include an efficacy test.
|
testLower |
Indicator of which analyses include a futility test.
|
testHarm |
Indicator of which analyses include a harm bound.
|
lambdaC |
Scalar, vector, or matrix of control event hazard rates. Rows = time periods, columns = strata. |
hr |
Assumed hazard ratio (experimental/control) for power computation. This is the "what-if" treatment effect. |
hr0 |
Null hazard ratio. Set |
hr1 |
Design alternative hazard ratio used to calibrate futility bounds
( |
eta |
Scalar, vector, or matrix of control dropout hazard rates. |
etaE |
Experimental dropout hazard rates; if |
gamma |
Scalar, vector, or matrix of enrollment rates by period (rows) and strata (columns). |
R |
Scalar or vector of enrollment period durations. |
targetN |
Target total sample size. When specified, |
S |
Scalar or vector of piecewise failure period durations; |
ratio |
Randomization ratio (experimental/control). Default 1. |
minfup |
Minimum follow-up time. When explicitly supplied,
event-driven analyses cannot place the final analysis before the end of
enrollment plus |
method |
Sample size variance formulation. One of
|
spending |
One of |
plannedCalendarTime |
Calendar times for analyses (time 0 = start of
randomization). Scalar (recycled) or vector of length |
targetEvents |
Overall target number of events at each analysis.
Scalar (recycled) or vector of length |
maxExtension |
Maximum time extension beyond the floor time to wait
for |
minTimeFromPreviousAnalysis |
Minimum elapsed time since the previous
analysis. Scalar or vector of length |
minN |
Minimum total sample size enrolled before analysis can proceed.
Scalar or vector of length |
minFollowUp |
Minimum follow-up time after all active |
informationRates |
Numeric vector of length |
fullSpendingAtFinal |
Logical. When |
tol |
Tolerance for |
targetEventsPerStratum |
Per-stratum event requirements. Numeric matrix
with |
maxCalendarTime |
Absolute calendar time cap for each analysis. Scalar
or vector of length |
minNPerStratum |
Per-stratum minimum enrollment requirements. Numeric
matrix with |
Accepting a gsSurv object:
An optional gsSurv-class object x provides defaults for all
parameters. This includes output from gsSurv() and
gsSurvCalendar(). User-specified parameters override these defaults,
enabling "what-if" analyses: e.g., gsSurvPower(x = design, hr = 0.8)
evaluates power under HR = 0.8 using all other parameters from the design.
When x is not provided, all design parameters must be specified
directly.
Interpretation and limitations: Enrollment, dropout, event accumulation, and analysis timing are represented by their expected values under the supplied assumptions. Consequently, event- and enrollment-triggered analysis times are expected analysis times, not simulated realizations. The calculation does not represent trial-to-trial variation in enrollment, failure, dropout, or operational timing. Use simulation when that variation may materially affect operating characteristics or the probability that combined timing rules are met; see, for example, the simtrial package.
Hazard ratio roles:
Two distinct hazard ratios serve different purposes. hr is the
assumed treatment effect under which power is evaluated.
hr1 is the design alternative used to calibrate futility bounds
(for test.type 3, 4, 7, 8). It is not used for test.type
5 or 6 (which use H0 spending for the lower bound) or for harm bounds.
When x is provided, hr1 defaults
to x$hr, so futility bounds remain calibrated to the original design
even when power is evaluated under a different hr.
Beta spending in scenario analyses:
For beta spending test types 3, 4, 7, and 8, x$beta is the design
beta used with hr1, sfl, and sflpar to calibrate
futility bounds. The beta returned by gsSurvPower() instead
equals 1 - power under the scenario assumptions and can differ
substantially from the design beta.
When information fractions and spending inputs are unchanged,
futility bounds are reused from x.
When information fractions or spending times change, futility
bounds are recomputed on the new schedule using the design beta and
hr1; achieved beta is then evaluated under hr.
When only alpha or upper bound spending changes with timing fixed,
futility bounds from x are preserved, apart from clipping a
lower bound that exceeds the new efficacy bound.
Test types 5 and 6 spend lower bound probability under the null rather than beta under the alternative. Harm spending for test types 7 and 8 is also a separate null-based calculation.
Analysis timing:
Analysis times are determined by per-analysis criteria. The scalar
minfup applies only to the final analysis. Each other timing rule
parameter can be a scalar (recycled to all k analyses), a vector of
length k, or NA at position i to indicate that the rule
does not apply to analysis i. For the per-stratum matrix arguments,
NA deactivates only that analysis-by-stratum requirement.
The choice between plannedCalendarTime and overall
targetEvents has an important consequence for sensitivity analyses:
Used alone, plannedCalendarTime fixes calendar times;
when combined with other criteria, it supplies a timing floor. Expected
events are recomputed under the assumed HR. A worse HR produces more
events at the same calendar time (the experimental arm fails faster).
This gives an "unconditional" power.
targetEvents fixes overall event counts; calendar times
adjust. Since events are held constant, information fractions do not
change with HR, and results match the gsDesign power plot
(plot(x, plottype = 2)) to numerical precision.
How criteria combine within a single analysis:
For analysis i, the analysis time T[i] is determined as:
Compute floor times from applicable criteria:
plannedCalendarTime[i],
T[i-1] + minTimeFromPreviousAnalysis[i], and
time when minN[i] enrolled + minFollowUp[i], and the
corresponding time for every active column of
minNPerStratum[i, ]. When minfup is explicitly supplied,
the final analysis also has a floor at the end of enrollment plus
minfup.
floor_time = max(all applicable floor times).
Find the expected time for the overall targetEvents[i] and
for every active column of targetEventsPerStratum[i, ]. Their
maximum is t_events, so all active requirements use AND logic.
If t_events <= floor_time, analysis at floor_time. If
t_events > floor_time and
maxExtension[i] is set, analysis at
min(t_events, floor_time + maxExtension[i]). Otherwise, analysis
at t_events.
If no event requirement is active: analysis at floor_time.
maxExtension defines a hard deadline: the analysis time
is never pushed
beyond plannedCalendarTime[i] + maxExtension[i] (or
T[i-1] + maxExtension[i] when no calendar time is specified),
even if other criteria such as minTimeFromPreviousAnalysis
or minN + minFollowUp would require a later time. Each analysis
using maxExtension must have a floor timing criterion such as
plannedCalendarTime, minTimeFromPreviousAnalysis,
minN, minNPerStratum, or explicit final analysis
minfup.
Finally, maxCalendarTime[i] applies an absolute calendar time
cap. If both cap types are supplied, the earlier cap applies. This
mirrors the realized cut grammar in simtrial, where the argument
named max_extension_for_target_event is applied as an absolute
analysis date.
Normalization and consistency:
When x is provided, x$n.fix is used for the
gsDesign::gsDesign() call to ensure the internal drift parameter
and bounds match the original design exactly.
The assumed HR's drift is obtained by scaling:
.
Power is computed via gsDesign::gsProbability() with actual expected
events as n.I. At the design HR, this reproduces the design power
exactly.
Stratified timing requirements:
targetEvents accepts a scalar (recycled) or a vector of length
k for overall event targets. Use targetEventsPerStratum for a
k-by-nstrata matrix of per-stratum event requirements and
minNPerStratum for per-stratum enrollment requirements. Overall and
per-stratum requirements may be combined and all active requirements must
be met unless a cap intervenes. NA omits a requirement. A matrix
passed through targetEvents is a deprecated alias for
targetEventsPerStratum; unlike the earlier row sum interpretation,
its entries are enforced by stratum.
Bound recalculation when parameters change:
When x is provided, the handling of bounds depends on which
parameters change relative to the original design:
No bound parameters changed (same alpha, sfu,
sfupar) and timing matches: both bounds are reused from x
exactly.
Upper bound parameters changed (alpha, sfu,
or sfupar) but timing matches: new efficacy bounds are computed
using the non-binding efficacy convention at the new alpha while
preserving the original testUpper schedule and futility bounds
from x. Any futility bound that exceeds the new efficacy bound
is clipped. This follows the same convention as
gsBoundSummary(). Lower bound spending settings from x
are intentionally kept in this branch, which avoids complications with
astar validation for binding types.
For non-binding test types 1, 4, 6, and 8, this calculation can be used
to evaluate alpha propagated by a graphical multiple testing procedure.
For binding test types 2, 3, 5, and 7, it is a planning sensitivity
calculation only; it is not a Maurer–Bretz sequential p-value or
graphical alpha recycling procedure.
Timing changed (different target events or calendar
times): both bounds are recomputed from scratch using the full
test.type and all spending parameters.
An object of class
c("gsSurvPower", "gsSurv", "gsDesign") containing:
k |
Number of analyses. |
n.I |
Total expected events at each analysis. |
timing |
Information fractions at each analysis. |
T |
Calendar times of analyses. |
eDC, eDE
|
Expected events by stratum (control, experimental). |
eNC, eNE
|
Expected sample sizes by stratum (control, experimental). |
N |
Cumulative total expected enrollment at each analysis. |
upper, lower
|
Bounds and crossing probabilities. |
harm |
Harm bound information when |
en, theta
|
Expected sample size summary and drift values returned by
|
hr, hr0, hr1
|
Assumed, null, and design hazard ratios. |
power |
Overall power (sum of upper bound crossing probabilities under the assumed HR). |
beta |
Type II error ( |
variable |
Always |
test.type, alpha, sided, method, spending, call
|
Design settings and the original call used for the power calculation. |
inputs |
Evaluated arguments supplied to |
informationRates, fullSpendingAtFinal
|
Planned information fraction caps and final effective spending time setting used for bound spending. |
testUpper, testLower, testHarm
|
Logical indicators of which analyses include each bound type, when relevant. |
lambdaC, etaC, etaE, gamma, R, S, ratio, minfup
|
Rate and timing inputs used in the calculation. |
vignette("gsSurvPower", package = "gsDesign") for
worked examples including calendar spending, stratified event targets,
and biomarker subgroup analyses.
vignette("gsSurvBasicExamples", package = "gsDesign") for deriving
survival sample size designs and vignette("SeqDesignSurvival",
package = "gsDesign") for reproducing SAS PROC SEQDESIGN survival output.
gsSurv, gsSurvCalendar,
gsDesign, gsProbability
# Create a design, then evaluate power at the design HR design <- gsSurv( k = 3, test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, lambdaC = log(2) / 12, hr = 0.7, eta = 0.01, gamma = 10, R = 16, minfup = 12, T = 28 ) pwr <- gsSurvPower(x = design, plannedCalendarTime = design$T) pwr$power # should be 0.9 # Power under a worse HR gsSurvPower(x = design, hr = 0.8, plannedCalendarTime = design$T)$power # Event-driven timing (matches gsDesign power plot) design_events <- design$n.I gsSurvPower(x = design, hr = 0.8, targetEvents = design_events)$power # Without a reference design gsSurvPower( k = 2, test.type = 4, alpha = 0.025, sided = 1, lambdaC = log(2) / 6, hr = 0.65, eta = 0.01, gamma = 8, R = 18, ratio = 1, plannedCalendarTime = c(24, 36) )$power # Use targetN without R to solve enrollment duration from relative rates pwr_target_n <- gsSurvPower( k = 2, test.type = 1, alpha = 0.025, sided = 1, lambdaC = log(2) / 15, hr = 0.7, eta = 0.001, gamma = c(10, 20, 30, 40), targetN = 500, ratio = 1.5, plannedCalendarTime = c(24, 36), testLower = FALSE ) sum(rowSums(pwr_target_n$gamma) * pwr_target_n$R) # Require event counts within each stratum pwr_stratified <- gsSurvPower( k = 2, test.type = 1, alpha = 0.025, sided = 1, lambdaC = matrix(log(2) / c(6, 12), ncol = 2), hr = 0.7, eta = 0.01, gamma = matrix(c(5, 5), ncol = 2), R = 12, ratio = 1, targetEventsPerStratum = matrix( c(20, 10, 40, 20), nrow = 2, byrow = TRUE ) ) pwr_stratified$eDC + pwr_stratified$eDE# Create a design, then evaluate power at the design HR design <- gsSurv( k = 3, test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, lambdaC = log(2) / 12, hr = 0.7, eta = 0.01, gamma = 10, R = 16, minfup = 12, T = 28 ) pwr <- gsSurvPower(x = design, plannedCalendarTime = design$T) pwr$power # should be 0.9 # Power under a worse HR gsSurvPower(x = design, hr = 0.8, plannedCalendarTime = design$T)$power # Event-driven timing (matches gsDesign power plot) design_events <- design$n.I gsSurvPower(x = design, hr = 0.8, targetEvents = design_events)$power # Without a reference design gsSurvPower( k = 2, test.type = 4, alpha = 0.025, sided = 1, lambdaC = log(2) / 6, hr = 0.65, eta = 0.01, gamma = 8, R = 18, ratio = 1, plannedCalendarTime = c(24, 36) )$power # Use targetN without R to solve enrollment duration from relative rates pwr_target_n <- gsSurvPower( k = 2, test.type = 1, alpha = 0.025, sided = 1, lambdaC = log(2) / 15, hr = 0.7, eta = 0.001, gamma = c(10, 20, 30, 40), targetN = 500, ratio = 1.5, plannedCalendarTime = c(24, 36), testLower = FALSE ) sum(rowSums(pwr_target_n$gamma) * pwr_target_n$R) # Require event counts within each stratum pwr_stratified <- gsSurvPower( k = 2, test.type = 1, alpha = 0.025, sided = 1, lambdaC = matrix(log(2) / c(6, 12), ncol = 2), hr = 0.7, eta = 0.01, gamma = matrix(c(5, 5), ncol = 2), R = 12, ratio = 1, targetEventsPerStratum = matrix( c(20, 10, 40, 20), nrow = 2, byrow = TRUE ) ) pwr_stratified$eDC + pwr_stratified$eDE
Convert a summary table object created with as_table
to an lt_tbl object; currently only implemented for
gsBinomialExact.
## S3 method for class 'gsBinomialExactTable' lt( data, ..., title = "Operating Characteristics for the Truncated SPRT Design", subtitle = "Assumes trial evaluated sequentially after each response", theta_label = I("Underlying<br>response rate"), bound_label = c("Futility bound", "Efficacy bound"), prob_decimals = 2, en_decimals = 1, rr_decimals = 0 )## S3 method for class 'gsBinomialExactTable' lt( data, ..., title = "Operating Characteristics for the Truncated SPRT Design", subtitle = "Assumes trial evaluated sequentially after each response", theta_label = I("Underlying<br>response rate"), bound_label = c("Futility bound", "Efficacy bound"), prob_decimals = 2, en_decimals = 1, rr_decimals = 0 )
data |
Object to be converted. |
... |
Other parameters that may be specific to the object. |
title |
Table title. |
subtitle |
Table subtitle. |
theta_label |
Label for theta. |
bound_label |
Label for bounds. |
prob_decimals |
Number of decimal places for probability of crossing. |
en_decimals |
Number of decimal places for expected number of observations when bound is crossed or when trial ends without crossing. |
rr_decimals |
Number of decimal places for response rates. |
Currently only implemented for gsBinomialExact objects.
Creates a table to summarize an object.
For gsBinomialExact, this summarized operating characteristics
across a range of effect sizes.
An lt_tbl object.
vignette("binomialSPRTExample")
safety_design <- binomialSPRT( p0 = .04, p1 = .1, alpha = .04, beta = .2, minn = 4, maxn = 75 ) safety_power <- gsBinomialExact( k = length(safety_design$n.I), theta = seq(.02, .16, .02), n.I = safety_design$n.I, a = safety_design$lower$bound, b = safety_design$upper$bound ) safety_power |> as_table() |> lt( theta_label = I("Underlying<br>AE rate"), prob_decimals = 3, bound_label = c("low rate", "high rate") )safety_design <- binomialSPRT( p0 = .04, p1 = .1, alpha = .04, beta = .2, minn = 4, maxn = 75 ) safety_power <- gsBinomialExact( k = length(safety_design$n.I), theta = seq(.02, .16, .02), n.I = safety_design$n.I, a = safety_design$lower$bound, b = safety_design$upper$bound ) safety_power |> as_table() |> lt( theta_label = I("Underlying<br>AE rate"), prob_decimals = 3, bound_label = c("low rate", "high rate") )
Applies standard labels, number formats, spanners, and explanatory
footnotes to a table returned by VEtable.
## S3 method for class 'gsVETable' lt( data, ..., title = "Design Bounds and Operating Characteristics", subtitle = NULL, efficacy_label = c("VE", "PE"), time_decimals = 1, efficacy_decimals = 2, spending_decimals = 4 )## S3 method for class 'gsVETable' lt( data, ..., title = "Design Bounds and Operating Characteristics", subtitle = NULL, efficacy_label = c("VE", "PE"), time_decimals = 1, efficacy_decimals = 2, spending_decimals = 4 )
data |
A |
... |
Additional arguments passed to |
title |
Table title. |
subtitle |
Optional table subtitle. |
efficacy_label |
Whether to label efficacy as vaccine efficacy
( |
time_decimals |
Number of decimal places for analysis time. |
efficacy_decimals |
Number of decimal places for efficacy at the boundaries and power. |
spending_decimals |
Number of decimal places for alpha and beta spending. |
An lt_tbl object.
design <- gsSurv( k = 2, test.type = 4, timing = .6, ratio = 3, hr = .3, hr0 = .7 ) VEtable(toBinomialExact(design), ve = c(.5, .7)) |> lt()design <- gsSurv( k = 2, test.type = 4, timing = .6, ratio = 3, hr = .3, hr0 = .7 ) VEtable(toBinomialExact(design), ve = c(.5, .7)) |> lt()
Compute the modeled population median follow-up at a calendar cutoff, or solve for the earliest cutoff achieving a target median. The population includes everyone in the complete planned enrollment, assigning zero follow-up to participants not yet enrolled. Enrollment is not extended or resized by these functions.
medianFollowUp( x = NULL, T = NULL, gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 ) minMedianFollowUp( x = NULL, ..., target, gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 )medianFollowUp( x = NULL, T = NULL, gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 ) minMedianFollowUp( x = NULL, ..., target, gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 )
x |
Optional |
T |
Finite, nonnegative calendar cutoff vector. Defaults to |
gamma |
Enrollment rates, using the control arm rate convention of
|
R |
Positive enrollment period durations. Enrollment stops at
|
eta |
Control dropout hazards; finite and nonnegative. Rows are participant time hazard intervals, columns are strata. A scalar is constant over intervals and strata; a vector specifies intervals. Standalone default is zero. |
etaE |
Experimental dropout hazards, in the same form as |
lambdaC |
Control event hazards, in the same form as |
hr |
Positive experimental/control event hazard ratio, required when
event stopping is enabled. Experimental event hazards are
|
S |
Positive durations of event/dropout hazard intervals, excluding
the final interval, which extends indefinitely. These intervals measure
time since each participant enrolled, not calendar time. With |
ratio |
Positive experimental/control allocation ratio; scalar or one value per stratum. Standalone default is 1. |
stopAtEvent |
Nonmissing logical scalar. If |
tol |
Positive absolute numerical tolerance in follow-up time units,
default |
... |
Must be empty. In |
target |
Required named nonnegative scalar median follow-up target. |
Omitted model inputs inherit the design's stored assumptions;
explicit non-NULL inputs override them. The exceptions with meaningful
explicit NULL values are S and etaE, as documented above.
Incompatible dimensions are errors, not silently truncated inputs.
Within each arm and stratum, event/dropout survival is calculated from
piecewise constant hazards on participant time. For follow-up u >= 0,
the proportion with follow-up greater than u is the planned population
fraction enrolled before T - u, multiplied by the probability of
remaining uncensored through u, and summed over arms and strata.
Arm/stratum weights reflect planned enrollment and randomization.
This is a population quantile, not the median of individual expected times,
the expected finite sample median, or a reverse Kaplan-Meier estimate.
The lower 0.5 quantile resolves non-unique medians. In particular, the
median is zero until more than half the planned population has enrolled.
Quantile bisection retains this convention across enrollment pauses and flat
portions of the distribution. Without dropout or event stopping, uniform
enrollment over 12 months gives median max(0, T - 6).
The inverse uses bounded bisection: by cutoff sum(R) + target, all
planned participants have had the opportunity to attain the target. If the
median is still below target, dropout/event stopping makes that target
unattainable, and an informative error is returned. A zero target returns
zero. The achieved median is checked against target within tol.
This replaces the former forward minMedianFollowUp(x, calendarTime)
calculation among participants enrolled to date. Migrate forward calls to
medianFollowUp(x, T = ...); inverse calls require
minMedianFollowUp(x, target = ...). This intentionally changes the
population definition as well as the function's role.
medianFollowUp() returns a numeric median per cutoff;
minMedianFollowUp() returns one calendar cutoff.
medianFollowUp(T = c(3, 6, 12, 18), gamma = 10, R = 12) minMedianFollowUp(target = 6, gamma = 10, R = 12) x <- gsSurv(gamma = 10, R = 12, T = 30, minfup = 18) medianFollowUp(x) medianFollowUp(x, stopAtEvent = TRUE) minMedianFollowUp(x, target = 3, stopAtEvent = TRUE) medianFollowUp(T = c(12, 18, 24), gamma = c(5, 10), R = c(6, 6), eta = c(.01, .02), etaE = c(.005, .01), lambdaC = c(.08, .04), hr = .7, S = 6, stopAtEvent = TRUE)medianFollowUp(T = c(3, 6, 12, 18), gamma = 10, R = 12) minMedianFollowUp(target = 6, gamma = 10, R = 12) x <- gsSurv(gamma = 10, R = 12, T = 30, minfup = 18) medianFollowUp(x) medianFollowUp(x, stopAtEvent = TRUE) minMedianFollowUp(x, target = 3, stopAtEvent = TRUE) medianFollowUp(T = c(12, 18, 24), gamma = c(5, 10), R = c(6, 6), eta = c(.01, .02), etaE = c(.005, .01), lambdaC = c(.08, .04), hr = .7, S = 6, stopAtEvent = TRUE)
nNormal() computes a fixed design sample size for comparing 2 means
where variance is known. T The function allows computation of sample size
for a non-inferiority hypothesis. Note that you may wish to investigate
other R packages such as the pwr package which uses the t-distribution.
In the examples below we show how to set up a 2-arm group sequential design with a normal outcome.
nNormal() computes sample size for comparing two normal means when
the variance for observations in
nNormal( delta1 = 1, sd = 1.7, sd2 = NULL, alpha = 0.025, beta = 0.1, ratio = 1, sided = 1, n = NULL, delta0 = 0, outtype = 1 )nNormal( delta1 = 1, sd = 1.7, sd2 = NULL, alpha = 0.025, beta = 0.1, ratio = 1, sided = 1, n = NULL, delta0 = 0, outtype = 1 )
delta1 |
difference between sample means under the alternate hypothesis. |
sd |
Standard deviation for the control arm. |
sd2 |
Standard deviation of experimental arm; this will be set to be
the same as the control arm with the default of |
alpha |
type I error rate. Default is 0.025 since 1-sided testing is default. |
beta |
type II error rate. Default is 0.10 (90% power). Not needed if
|
ratio |
randomization ratio of experimental group compared to control. |
sided |
1 for 1-sided test (default), 2 for 2-sided test. |
n |
Sample size; may be input to compute power rather than sample size.
If |
delta0 |
difference between sample means under the null hypothesis; normally this will be left as the default of 0. |
outtype |
controls output; see value section below. |
This is more of a convenience routine than one recommended for broad use without careful considerations such as those outlined in Jennison and Turnbull (2000). For larger studies where a conservative estimate of within group standard deviations is available, it can be useful. A more detailed formulation is available in the vignette on two-sample normal sample size.
If n is NULL (default), total sample size (2 arms
combined) is computed. Otherwise, power is computed. If outtype=1
(default), the computed value (sample size or power) is returned in a scalar
or vector. If outtype=2, a data frame with sample sizes for each arm
(n1, n2)is returned; if n is not input as NULL,
a third variable, Power, is added to the output data frame. If
outtype=3, a data frame with is returned with the following columns:
n |
A vector with total samples size required for each event rate comparison specified |
n1 |
A vector of sample sizes for group 1 for each event rate comparison specified |
n2 |
A vector of sample sizes for group 2 for each event rate comparison specified |
alpha |
As input |
sided |
As input |
beta |
As input; if |
Power |
If |
sd |
As input |
sd2 |
As input |
delta1 |
As input |
delta0 |
As input |
se |
standard error for estimate of difference in treatment group means |
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Lachin JM (1981), Introduction to sample size determination and power analysis for clinical trials. Controlled Clinical Trials 2:93-113.
Snedecor GW and Cochran WG (1989), Statistical Methods. 8th ed. Ames, IA: Iowa State University Press.
See vignette("nNormal") for the full story, including the
derivation of the sample size formula, power checks via simulation,
and a group sequential design example.
# EXAMPLES # equal variances n=nNormal(delta1=.5,sd=1.1,alpha=.025,beta=.2) n x <- gsDesign(k = 3, n.fix = n, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(.5,.75), sfu = sfLDOF, sfl = sfHSD, sflpar = -1, delta1 = 0.5, endpoint = 'normal') gsBoundSummary(x) summary(x) # unequal variances, fixed design nNormal(delta1 = .5, sd = 1.1, sd2 = 2, alpha = .025, beta = .2) # unequal sample sizes nNormal(delta1 = .5, sd = 1.1, alpha = .025, beta = .2, ratio = 2) # non-inferiority assuming a better effect than null nNormal(delta1 = .5, delta0 = -.1, sd = 1.2)# EXAMPLES # equal variances n=nNormal(delta1=.5,sd=1.1,alpha=.025,beta=.2) n x <- gsDesign(k = 3, n.fix = n, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(.5,.75), sfu = sfLDOF, sfl = sfHSD, sflpar = -1, delta1 = 0.5, endpoint = 'normal') gsBoundSummary(x) summary(x) # unequal variances, fixed design nNormal(delta1 = .5, sd = 1.1, sd2 = 2, alpha = .025, beta = .2) # unequal sample sizes nNormal(delta1 = .5, sd = 1.1, alpha = .025, beta = .2, ratio = 2) # non-inferiority assuming a better effect than null nNormal(delta1 = .5, delta0 = -.1, sd = 1.2)
normalGrid() is intended to be used for computation of the expected value of a function of a normal random variable. The function produces grid points and weights to be used for numerical integration.
This is a utility function to provide a normal density function and a grid
to integrate over as described by Jennison and Turnbull (2000), Chapter 19.
While integration can be performed over the real line or over any portion of
it, the numerical integration does not extend beyond 6 standard deviations
from the mean. The grid used for integration uses equally spaced points over
the middle of the distribution function, and spreads points further apart in
the tails. The values returned in gridwgts may be used to integrate
any function over the given grid, although the user should take care that
the function integrated is not large in the tails of the grid where points
are spread further apart.
normalGrid(r = 18, bounds = c(0, 0), mu = 0, sigma = 1)normalGrid(r = 18, bounds = c(0, 0), mu = 0, sigma = 1)
r |
Control for grid points as in Jennison and Turnbull (2000), Chapter
19; default is 18. Range: 1 to 80. This might be changed by the user (e.g.,
|
bounds |
Range of integration. Real-valued vector of length 2. Default value of 0, 0 produces a range of + or - 6 standard deviations (6*sigma) from the mean (=mu). |
mu |
Mean of the desired normal distribution. |
sigma |
Standard deviation of the desired normal distribution. |
z |
Grid points for numerical integration. |
density |
The
standard normal density function evaluated at the values in |
gridwgts |
Simpson's rule weights for numerical integration
on the grid in |
wgts |
Weights to be used with
the grid in |
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
library(ggplot2) # standard normal distribution x <- normalGrid(r = 3) plot(x$z, x$wgts) # verify that numerical integration replicates sigma # get grid points and weights x <- normalGrid(mu = 2, sigma = 3) # compute squared deviation from mean for grid points dev <- (x$z - 2)^2 # multiply squared deviations by integration weights and sum sigma2 <- sum(dev * x$wgts) # square root of sigma2 should be sigma (3) sqrt(sigma2) # do it again with larger r to increase accuracy x <- normalGrid(r = 22, mu = 2, sigma = 3) sqrt(sum((x$z - 2)^2 * x$wgts)) # this can also be done by combining gridwgts and density sqrt(sum((x$z - 2)^2 * x$gridwgts * x$density)) # integrate normal density and compare to built-in function # to compute probability of being within 1 standard deviation # of the mean pnorm(1) - pnorm(-1) x <- normalGrid(bounds = c(-1, 1)) sum(x$wgts) sum(x$gridwgts * x$density) # find expected sample size for default design with # n.fix=1000 x <- gsDesign(n.fix = 1000) x # set a prior distribution for theta y <- normalGrid(r = 3, mu = x$theta[2], sigma = x$theta[2] / 1.5) z <- gsProbability( k = 3, theta = y$z, n.I = x$n.I, a = x$lower$bound, b = x$upper$bound ) z <- gsProbability(d = x, theta = y$z) cat( "Expected sample size averaged over normal\n prior distribution for theta with \n mu=", x$theta[2], "sigma=", x$theta[2] / 1.5, ":", round(sum(z$en * y$wgt), 1), "\n" ) plot(y$z, z$en, xlab = "theta", ylab = "E{N}", main = "Expected sample size for different theta values" ) lines(y$z, z$en)library(ggplot2) # standard normal distribution x <- normalGrid(r = 3) plot(x$z, x$wgts) # verify that numerical integration replicates sigma # get grid points and weights x <- normalGrid(mu = 2, sigma = 3) # compute squared deviation from mean for grid points dev <- (x$z - 2)^2 # multiply squared deviations by integration weights and sum sigma2 <- sum(dev * x$wgts) # square root of sigma2 should be sigma (3) sqrt(sigma2) # do it again with larger r to increase accuracy x <- normalGrid(r = 22, mu = 2, sigma = 3) sqrt(sum((x$z - 2)^2 * x$wgts)) # this can also be done by combining gridwgts and density sqrt(sum((x$z - 2)^2 * x$gridwgts * x$density)) # integrate normal density and compare to built-in function # to compute probability of being within 1 standard deviation # of the mean pnorm(1) - pnorm(-1) x <- normalGrid(bounds = c(-1, 1)) sum(x$wgts) sum(x$gridwgts * x$density) # find expected sample size for default design with # n.fix=1000 x <- gsDesign(n.fix = 1000) x # set a prior distribution for theta y <- normalGrid(r = 3, mu = x$theta[2], sigma = x$theta[2] / 1.5) z <- gsProbability( k = 3, theta = y$z, n.I = x$n.I, a = x$lower$bound, b = x$upper$bound ) z <- gsProbability(d = x, theta = y$z) cat( "Expected sample size averaged over normal\n prior distribution for theta with \n mu=", x$theta[2], "sigma=", x$theta[2] / 1.5, ":", round(sum(z$en * y$wgt), 1), "\n" ) plot(y$z, z$en, xlab = "theta", ylab = "E{N}", main = "Expected sample size for different theta values" ) lines(y$z, z$en)
The plot() function has been extended to work with objects returned
by gsDesign() and gsProbability(). For objects of type
gsDesign, seven types of plots are provided: z-values at boundaries
(default), power, approximate treatment effects at boundaries, conditional
power at boundaries, spending functions, expected sample size, and B-values
at boundaries. For objects of type gsProbability plots are available
for z-values at boundaries, power (default), approximate treatment effects at
boundaries, conditional power, expected sample size and B-values at
boundaries.
The intent is that many standard plot() parameters will function as
expected; exceptions to this rule exist. In particular, main, xlab,
ylab, lty, col, lwd, type, pch, cex have been tested and work for most
values of plottype; one exception is that type="l" cannot be
overridden when plottype=2. Default values for labels depend on
plottype and the class of x.
For endpoint = "info", information annotations on boundary,
treatment effect, B-value, and conditional power plots use I=
and round information to two decimal places (without integer rounding).
For test types 7 and 8, harm and futility probabilities stored on the
design are mutually exclusive stopping outcomes. In a type 2 plot, the
futility threshold curve is inclusive: it uses the sum of futility-only and
harm crossing probabilities. The separate harm curve continues to show
harm crossings only. This plotting convention does not modify the
probabilities stored on x.
Power plots show cumulative crossing probabilities through each analysis.
By default, a curve is shown only when its boundary is active at that
analysis; the original analysis numbers are retained. Skipped boundaries
still contribute their (zero) increments to the cumulative calculation.
For types 7 and 8, the combined futility or harm curve is shown when either
boundary is active. Use showSkipped = TRUE to also display curves
at skipped looks, which may duplicate earlier curves or be constant.
Note that there is some special behavior for values plotted and returned for
power and expected sample size (ASN) plots for a gsDesign object. A
call to x<-gsDesign() produces power and expected sample size for
only two theta values: 0 and x$delta. The call plot(x,
plottype="Power") (or plot(x,plottype="ASN") for a gsDesign
object produces power (expected sample size) curves and returns a
gsDesign object with theta values determined as follows. If
theta is non-null on input, the input value(s) are used. Otherwise,
for a gsProbability object, the theta values from that object
are used. For a gsDesign object where theta is input as
NULL (the default), theta=seq(0,2,.05)*x$delta) is used. For
a gsDesign object, the x-axis values are rescaled to
theta/x$delta and the label for the x-axis . For a
gsProbability object, the values of theta are plotted and are
labeled as . See examples below.
Approximate treatment effects at boundaries are computed dividing the Z-values
at the boundaries by the square root of n.I at that analysis.
Spending functions are plotted for a continuous set of values from 0 to 1.
This option should not be used if a boundary is used or a pointwise spending
function is used (sfu or sfl="WT", "OF", "Pocock" or
sfPoints).
Conditional power is computed using the function gsBoundCP(). The
default input for this routine is theta="thetahat" which will compute
the conditional power at each bound using the approximate treatment effect at
that bound. Otherwise, if the input is gsDesign object conditional
power is computed assuming theta=x$delta, the original effect size
for which the trial was planned.
Average sample number/expected sample size is computed using n.I at
each analysis times the probability of crossing a boundary at that analysis.
If no boundary is crossed at any analysis, this is counted as stopping at
the final analysis.
B-values are Z-values multiplied by sqrt(t)=sqrt(x$n.I/x$n.I[x$k]).
Thus, the expected value of a B-value at an analysis is the true value of
multiplied by the proportion of total planned observations at
that time. See Proschan, Lan and Wittes (2006).
## S3 method for class 'gsDesign' plot(x, plottype = 1, base = FALSE, ...) ## S3 method for class 'gsProbability' plot(x, plottype = 2, base = FALSE, ...)## S3 method for class 'gsDesign' plot(x, plottype = 1, base = FALSE, ...) ## S3 method for class 'gsProbability' plot(x, plottype = 2, base = FALSE, ...)
x |
Object of class
|
plottype |
1=boundary plot (default for 2=power plot (default for 3=approximate treatment effect at boundaries, 4=conditional power at boundaries, 5=spending function plot (only available if 6=expected sample size plot, and 7=B-values at boundaries. Character values for |
base |
Default is FALSE, which means ggplot2 graphics are used. If true, base graphics are used for plotting. |
... |
This allows many optional arguments that are standard when
calling Other arguments include:
|
An object of class(x); in many cases this is the input value
of x, while in others x$theta is replaced and corresponding
characteristics computed; see details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Proschan, MA, Lan, KKG, Wittes, JT (2006), Statistical Monitoring of Clinical Trials. A Unified Approach. New York: Springer.
library(ggplot2) # symmetric, 2-sided design with O'Brien-Fleming-like boundaries # lower bound is non-binding (ignored in Type I error computation) # sample size is computed based on a fixed design requiring n=100 x <- gsDesign(k = 5, test.type = 2, n.fix = 100) x # the following translate to calls to plot.gsDesign since x was # returned by gsDesign; run these commands one at a time plot(x) plot(x, plottype = 2) plot(x, plottype = 3) plot(x, plottype = 4) plot(x, plottype = 5) plot(x, plottype = 6) plot(x, plottype = 7) # Futility testing only at the first look: omit its skipped-look curves skipped <- gsDesign(testLower = c(TRUE, FALSE, FALSE)) plot(skipped, plottype = "power") # Include all looks, including coincident cumulative curves plot(skipped, plottype = "power", showSkipped = TRUE) # choose different parameter values for power plot # start with design in x from above y <- gsProbability( k = 5, theta = seq(0, .5, .025), x$n.I, x$lower$bound, x$upper$bound ) # the following translates to a call to plot.gsProbability since # y has that type plot(y)library(ggplot2) # symmetric, 2-sided design with O'Brien-Fleming-like boundaries # lower bound is non-binding (ignored in Type I error computation) # sample size is computed based on a fixed design requiring n=100 x <- gsDesign(k = 5, test.type = 2, n.fix = 100) x # the following translate to calls to plot.gsDesign since x was # returned by gsDesign; run these commands one at a time plot(x) plot(x, plottype = 2) plot(x, plottype = 3) plot(x, plottype = 4) plot(x, plottype = 5) plot(x, plottype = 6) plot(x, plottype = 7) # Futility testing only at the first look: omit its skipped-look curves skipped <- gsDesign(testLower = c(TRUE, FALSE, FALSE)) plot(skipped, plottype = "power") # Include all looks, including coincident cumulative curves plot(skipped, plottype = "power", showSkipped = TRUE) # choose different parameter values for power plot # start with design in x from above y <- gsProbability( k = 5, theta = seq(0, .5, .025), x$n.I, x$lower$bound, x$upper$bound ) # the following translates to a call to plot.gsProbability since # y has that type plot(y)
Plot the forward calculation from medianFollowUp(), optionally
marking a design's analysis times. Dropout and event stopping conventions,
input overrides and the planned population definition are shared with that
function. The historical plot name is retained.
plotMinMedianFollowUp( x = NULL, calendarTime = NULL, showAnalysisTimes = TRUE, timename = "Months", gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 )plotMinMedianFollowUp( x = NULL, calendarTime = NULL, showAnalysisTimes = TRUE, timename = "Months", gamma = NULL, R = NULL, eta = NULL, etaE = NULL, lambdaC = NULL, hr = NULL, S = NULL, ratio = NULL, stopAtEvent = FALSE, tol = 1e-08 )
x |
Optional |
calendarTime |
Nonnegative calendar cutoffs. With |
showAnalysisTimes |
Whether to mark the analysis times in |
timename |
Nonempty time unit label. Month(s) and year(s) use axis breaks every 6 months and 0.5 years, respectively. |
gamma |
Enrollment rates, using the control arm rate convention of
|
R |
Positive enrollment period durations. Enrollment stops at
|
eta |
Control dropout hazards; finite and nonnegative. Rows are participant time hazard intervals, columns are strata. A scalar is constant over intervals and strata; a vector specifies intervals. Standalone default is zero. |
etaE |
Experimental dropout hazards, in the same form as |
lambdaC |
Control event hazards, in the same form as |
hr |
Positive experimental/control event hazard ratio, required when
event stopping is enabled. Experimental event hazards are
|
S |
Positive durations of event/dropout hazard intervals, excluding
the final interval, which extends indefinitely. These intervals measure
time since each participant enrolled, not calendar time. With |
ratio |
Positive experimental/control allocation ratio; scalar or one value per stratum. Standalone default is 1. |
stopAtEvent |
Nonmissing logical scalar. If |
tol |
Positive absolute numerical tolerance in follow-up time units,
default |
A ggplot object.
x <- gsSurv(gamma = 10, R = 12, T = 30, minfup = 18) plotMinMedianFollowUp(x) plotMinMedianFollowUp(x, stopAtEvent = TRUE)x <- gsSurv(gamma = 10, R = 12, T = 30, minfup = 18) plotMinMedianFollowUp(x) plotMinMedianFollowUp(x, stopAtEvent = TRUE)
nSurvival() is used to calculate the sample size for a clinical trial
with a time-to-event endpoint. The Lachin and Foulkes (1986) method is used.
nEvents uses the Schoenfeld (1981) approximation to provide sample
size and power in terms of the underlying hazard ratio and the number of
events observed in a survival analysis. The functions hrz2n(),
hrn2z() and zn2hr() also use the Schoenfeld approximation to
provide simple translations between hazard ratios, z-values and the number
of events in an analysis; input variables can be given as vectors.
nSurvival() produces an object of class "nSurvival" with the number
of subjects and events for a set of pre-specified trial parameters, such as
accrual duration and follow-up period. The calculation is based on Lachin
and Foulkes (1986) method and can be used for risk ratio or risk difference.
The function also consider non-uniform (exponential) entry as well as
uniform entry.
If the logical approx is TRUE, the variance under alternative
hypothesis is used to replace the variance under null hypothesis. For
non-uniform entry, a non-zero value of gamma for exponential entry
must be supplied. For positive gamma, the entry distribution is
convex, whereas for negative gamma, the entry distribution is
concave.
nEvents() uses the Schoenfeld (1981) method to approximate the number
of events n (given beta) or the power (given n).
Arguments may be vectors or scalars, but any vectors must have the same
length.
The functions hrz2n, hrn2z and zn2hr also all apply the
Schoenfeld approximation for proportional hazards modeling. This
approximation is based on the asymptotic normal distribtuion of the logrank
statistic as well as related statistics are asymptotically normal. Let
denote the underlying hazard ratio (lambda1/lambda2 in
terms of the arguments to nSurvival). Further, let denote the
number of events observed when computing the statistic of interest and
the ratio of the sample size in an experimental group relative to a
control. The estimated natural logarithm of the hazard ratio from a
proportional hazards ratio is approximately normal with a mean of
and variance . Let denote a
logrank statistic (or a Wald statistic or score statistic from a
proportional hazards regression model). The same asymptotic theory implies
is asymptotically equivalent to a normalized estimate of the hazard
ratio and thus is asymptotically normal with variance
1 and mean
Plugging the estimated hazard ratio into the above equation allows approximating any one of the following based on the other two: the estimate hazard ratio, the number of events and the z-statistic. That is,
hrz2n() translates an observed interim hazard ratio and interim
z-value into the number of events required for the Z-value and hazard ratio
to correspond to each other. hrn2z() translates a hazard ratio and
number of events into an approximate corresponding Z-value. zn2hr()
translates a Z-value and number of events into an approximate corresponding
hazard ratio. Each of these functions has a default assumption of an
underlying hazard ratio of 1 which can be changed using the argument
hr0. hrn2z() and zn2hr() also have an argument
hr1 which is only used to compute the sign of the computed Z-value in
the case of hrn2z() and whether or not a z-value > 0 corresponds to a
hazard ratio > or < the null hazard ratio hr0.
## S3 method for class 'nSurvival' print(x, ...) nSurvival( lambda1 = 1/12, lambda2 = 1/24, Ts = 24, Tr = 12, eta = 0, ratio = 1, alpha = 0.025, beta = 0.1, sided = 1, approx = FALSE, type = c("rr", "rd"), entry = c("unif", "expo"), gamma = NA ) nEvents( hr = 0.6, alpha = 0.025, beta = 0.1, ratio = 1, sided = 1, hr0 = 1, n = 0, tbl = FALSE ) zn2hr(z, n, ratio = 1, hr0 = 1, hr1 = 0.7) hrn2z(hr, n, ratio = 1, hr0 = 1, hr1 = 0.7) hrz2n(hr, z, ratio = 1, hr0 = 1)## S3 method for class 'nSurvival' print(x, ...) nSurvival( lambda1 = 1/12, lambda2 = 1/24, Ts = 24, Tr = 12, eta = 0, ratio = 1, alpha = 0.025, beta = 0.1, sided = 1, approx = FALSE, type = c("rr", "rd"), entry = c("unif", "expo"), gamma = NA ) nEvents( hr = 0.6, alpha = 0.025, beta = 0.1, ratio = 1, sided = 1, hr0 = 1, n = 0, tbl = FALSE ) zn2hr(z, n, ratio = 1, hr0 = 1, hr1 = 0.7) hrn2z(hr, n, ratio = 1, hr0 = 1, hr1 = 0.7) hrz2n(hr, z, ratio = 1, hr0 = 1)
x |
An object of class "nSurvival" returned by |
... |
Allows additional arguments for |
lambda1, lambda2
|
event hazard rate for placebo and treatment group respectively. |
Ts |
maximum study duration. |
Tr |
accrual (recruitment) duration. |
eta |
equal dropout hazard rate for both groups. |
ratio |
randomization ratio between placebo and treatment group. Default is balanced design, i.e., randomization ratio is 1. |
alpha |
type I error rate. Default is 0.025 since 1-sided testing is default. |
beta |
type II error rate. Default is 0.10 (90% power). Not needed for
|
sided |
one or two-sided test? Default is one-sided test. |
approx |
logical. If |
type |
type of sample size calculation: risk ratio (“rr”) or risk difference (“rd”). |
entry |
patient entry type: uniform entry ( |
gamma |
rate parameter for exponential entry. |
hr |
Hazard ratio. For |
hr0 |
Hazard ratio under the null hypothesis (> 0, |
n |
Number of events. For |
tbl |
Indicator of whether or not scalar (vector) or tabular output is
desired for |
z |
A z-statistic. |
hr1 |
Hazard ratio under the alternate hypothesis for |
nSurvival produces a list with the following component
returned:
type |
As input. |
entry |
As input. |
n |
Sample size required (computed). |
nEvents |
Number of events required (computed). |
lambda1 |
As input. |
lambda2 |
As input. |
eta |
As input. |
ratio |
As input. |
gamma |
As input. |
alpha |
As input. |
beta |
As input. |
sided |
As input. |
Ts |
As input. |
Tr |
As input. |
nEvents produces a scalar or vector of sample sizes (or powers) when
tbl=FALSE or, when tbl=TRUE a data frame of values with the
following columns:
hr |
As input. |
n |
If |
alpha |
As input. |
beta |
If |
Power |
One minus the
output |
delta |
Standardized effect size represented by input difference between null and alternative hypothesis hazard ratios. |
ratio |
Ratio of experimental to control sample size where 'experimental' is the same as the group with hazard represented in the numerator of the hazard ratio. |
se |
Estimated standard error for the observed log(hazard ratio) with the given sample size. |
hrz2n outputs a number of events required to approximately have the
input hazard ratio, z-statistic and sample size correspond. hrn2z
outputs an approximate z-statistic corresponding to an input hazard ratio
and number of events. zn2hr outputs an approximate hazard ratio
corresponding to an input z-statistic and number of events.
Shanhong Guan [email protected], Keaven Anderson [email protected]
Lachin JM and Foulkes MA (1986), Evaluation of Sample Size and Power for Analyses of Survival with Allowance for Nonuniform Patient Entry, Losses to Follow-Up, Noncompliance, and Stratification. Biometrics, 42, 507-519.
Schoenfeld D (1981), The Asymptotic Properties of Nonparametric Tests for Comparing Survival Distributions. Biometrika, 68, 316-319.
vignette("gsDesignPackageOverview"), plot.gsDesign,
gsDesign, gsHR
library(ggplot2) # consider a trial with # 2 year maximum follow-up # 6 month uniform enrollment # Treatment/placebo hazards = 0.1/0.2 per 1 person-year # drop out hazard 0.1 per 1 person-year # alpha = 0.025 (1-sided) # power = 0.9 (default beta=.1) ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025 ) # group sequential translation with default bounds # note that delta1 is log hazard ratio; used later in gsBoundSummary summary x <- gsDesign( k = 5, test.type = 2, n.fix = ss$nEvents, nFixSurv = ss$n, delta1 = log(ss$lambda2 / ss$lambda1) ) # boundary plot plot(x) # effect size plot plot(x, plottype = "hr") # total sample size x$nSurv # number of events at analyses x$n.I # print the design x # overall design summary cat(summary(x)) # tabular summary of bounds gsBoundSummary(x, deltaname = "HR", Nname = "Events", logdelta = TRUE) # approximate number of events required using Schoenfeld's method # for 2 different hazard ratios nEvents(hr = c(.5, .6), tbl = TRUE) # vector output nEvents(hr = c(.5, .6)) # approximate power using Schoenfeld's method # given 2 sample sizes and hr=.6 nEvents(hr = .6, n = c(50, 100), tbl = TRUE) # vector output nEvents(hr = .6, n = c(50, 100)) # approximate hazard ratio corresponding to 100 events and z-statistic of 2 zn2hr(n = 100, z = 2) # same when hr0 is 1.1 zn2hr(n = 100, z = 2, hr0 = 1.1) # same when hr0 is .9 and hr1 is greater than hr0 zn2hr(n = 100, z = 2, hr0 = .9, hr1 = 1) # approximate number of events corresponding to z-statistic of 2 and # estimated hazard ratio of .5 (or 2) hrz2n(hr = .5, z = 2) hrz2n(hr = 2, z = 2) # approximate z statistic corresponding to 75 events # and estimated hazard ratio of .6 (or 1/.6) # assuming 2-to-1 randomization of experimental to control hrn2z(hr = .6, n = 75, ratio = 2) hrn2z(hr = 1 / .6, n = 75, ratio = 2)library(ggplot2) # consider a trial with # 2 year maximum follow-up # 6 month uniform enrollment # Treatment/placebo hazards = 0.1/0.2 per 1 person-year # drop out hazard 0.1 per 1 person-year # alpha = 0.025 (1-sided) # power = 0.9 (default beta=.1) ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025 ) # group sequential translation with default bounds # note that delta1 is log hazard ratio; used later in gsBoundSummary summary x <- gsDesign( k = 5, test.type = 2, n.fix = ss$nEvents, nFixSurv = ss$n, delta1 = log(ss$lambda2 / ss$lambda1) ) # boundary plot plot(x) # effect size plot plot(x, plottype = "hr") # total sample size x$nSurv # number of events at analyses x$n.I # print the design x # overall design summary cat(summary(x)) # tabular summary of bounds gsBoundSummary(x, deltaname = "HR", Nname = "Events", logdelta = TRUE) # approximate number of events required using Schoenfeld's method # for 2 different hazard ratios nEvents(hr = c(.5, .6), tbl = TRUE) # vector output nEvents(hr = c(.5, .6)) # approximate power using Schoenfeld's method # given 2 sample sizes and hr=.6 nEvents(hr = .6, n = c(50, 100), tbl = TRUE) # vector output nEvents(hr = .6, n = c(50, 100)) # approximate hazard ratio corresponding to 100 events and z-statistic of 2 zn2hr(n = 100, z = 2) # same when hr0 is 1.1 zn2hr(n = 100, z = 2, hr0 = 1.1) # same when hr0 is .9 and hr1 is greater than hr0 zn2hr(n = 100, z = 2, hr0 = .9, hr1 = 1) # approximate number of events corresponding to z-statistic of 2 and # estimated hazard ratio of .5 (or 2) hrz2n(hr = .5, z = 2) hrz2n(hr = 2, z = 2) # approximate z statistic corresponding to 75 events # and estimated hazard ratio of .6 (or 1/.6) # assuming 2-to-1 randomization of experimental to control hrn2z(hr = .6, n = 75, ratio = 2) hrn2z(hr = 1 / .6, n = 75, ratio = 2)
Inverts exact binomial efficacy tests at each completed analysis to obtain repeated confidence intervals for vaccine or prevention efficacy.
repeatedCIBinomialExact( gsD, n.I = NULL, x = NULL, conf.level = 0.95, tol = 1e-08, maxiter = 100 )repeatedCIBinomialExact( gsD, n.I = NULL, x = NULL, conf.level = 0.95, tol = 1e-08, maxiter = 100 )
gsD |
A |
n.I |
Increasing integer total event counts at completed analyses. If
|
x |
Integer experimental arm event counts at the analyses in
|
conf.level |
Two-sided confidence level. |
tol |
Absolute tolerance for bisection on the conditional binomial event probability scale. |
maxiter |
Maximum bisection iterations for each confidence limit. |
A two-sided interval with confidence level 1 - alpha uses the same
spending function, spending times, and count path ordering in both
directions, with alpha / 2 in each tail. The lower efficacy limit inverts
the usual lower event count efficacy test. The upper efficacy limit inverts
its mirror image after exchanging experimental and control arm event
counts. This mirrored test is not the design's futility boundary.
Non-binding futility and harm are ignored in both directions. Coverage is generally conservative because the exact rejection regions are discrete. Spending time remains relative to the planned final event count.
This follows the repeated confidence interval construction of Jennison and Turnbull (1984), using exact Bernoulli ordering as in Coe and Tamhane (1993).
A data frame with one row per completed analysis containing the observed counts, efficacy estimate, repeated confidence limits, confidence level, and one-sided tail level.
Jennison, C. and Turnbull, B. W. (1984). Repeated confidence intervals for group sequential clinical trials. Controlled Clinical Trials, 5, 33–45.
Coe, P. R. and Tamhane, A. C. (1993). Exact repeated confidence intervals for Bernoulli parameters in a group sequential clinical trial. Controlled Clinical Trials, 14, 19–29.
ciBinomialExact,
sequentialCIBinomialExact,
repeatedPValueBinomialExact
design <- gsSurv( k = 3, test.type = 4, timing = c(.45, .7), ratio = 3, hr = .3, hr0 = .7 ) counts <- toBinomialExact(design)$n.I repeatedCIBinomialExact(design, counts, x = c(12, 23, 38))design <- gsSurv( k = 3, test.type = 4, timing = c(.45, .7), ratio = 3, hr = .3, hr0 = .7 ) counts <- toBinomialExact(design)$n.I repeatedCIBinomialExact(design, counts, x = c(12, 23, 38))
Computes repeated p-values for the exact binomial design implied by a [gsSurv()] object. The p-value at analysis 'j' is the smallest local one-sided alpha level for which the observed experimental arm event count crosses the exact lower efficacy bound at that analysis. Non-binding futility and harm bounds are ignored for the Type I error calculation.
repeatedPValueBinomialExact( gsD, n.I = NULL, x = NULL, interval = c(1e-20, 0.9999), tol = 1e-08, maxiter = 100, check = FALSE )repeatedPValueBinomialExact( gsD, n.I = NULL, x = NULL, interval = c(1e-20, 0.9999), tol = 1e-08, maxiter = 100, check = FALSE )
gsD |
A 'gsSurv' object with non-binding 'test.type' 1, 4, 6, or 8. |
n.I |
Increasing integer total event counts at completed analyses. If 'NULL', the planned integer event counts from 'toInteger(gsD)' are used. This must have at most 1 value greater than or equal to planned final events ('gsD$maxn.IPlan' if available, otherwise 'max(gsD$n.I)'). |
x |
Integer experimental arm event counts at the analyses in 'n.I'. |
interval |
Search interval for the p-values. As in [sequentialPValue()], values outside this interval are truncated to the nearest endpoint. |
tol |
Relative tolerance for the monotone bisection search on the alpha scale. |
maxiter |
Maximum number of bisection iterations for each analysis. |
check |
Logical. If 'TRUE', checks the monotonicity of the alpha-indexed integer efficacy bounds on a coarse grid and warns if it is violated. |
A data frame with one row per completed analysis containing:
Analysis index.
Total events at analysis.
Observed experimental arm events.
Repeated p-value for the analysis.
Integer efficacy bound at the repeated p-value.
[sequentialPValueBinomialExact()], [sequentialPValue()], [toBinomialExact()], [gsBinomialExact()]
x <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(0.45, 0.7), sfu = sfHSD, sfupar = -4, sfl = sfLDOF, sflpar = 0, lambdaC = 0.001, hr = 0.3, hr0 = 0.7, eta = 5e-04, gamma = 10, R = 16, T = 24, minfup = 8, ratio = 3 ) counts <- toBinomialExact(x)$n.I repeatedPValueBinomialExact(gsD = x, n.I = counts, x = c(12, 23, 38))x <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(0.45, 0.7), sfu = sfHSD, sfupar = -4, sfl = sfLDOF, sflpar = 0, lambdaC = 0.001, hr = 0.3, hr0 = 0.7, eta = 5e-04, gamma = 10, R = 16, T = 24, minfup = 8, ratio = 3 ) counts <- toBinomialExact(x)$n.I repeatedPValueBinomialExact(gsD = x, n.I = counts, x = c(12, 23, 38))
Forms a sequential confidence interval after each completed analysis by intersecting the exact repeated confidence intervals through that analysis.
sequentialCIBinomialExact( gsD, n.I = NULL, x = NULL, conf.level = 0.95, tol = 1e-08, maxiter = 100 )sequentialCIBinomialExact( gsD, n.I = NULL, x = NULL, conf.level = 0.95, tol = 1e-08, maxiter = 100 )
gsD |
A |
n.I |
Increasing integer total event counts at completed analyses. If
|
x |
Integer experimental arm event counts at the analyses in
|
conf.level |
Two-sided confidence level. |
tol |
Absolute tolerance for bisection on the conditional binomial event probability scale. |
maxiter |
Maximum bisection iterations for each confidence limit. |
The interval is the inversion of
sequentialPValueBinomialExact applied
in both directions: a candidate efficacy remains in the confidence set only
when neither one-sided repeated test has rejected it at any completed
analysis. Thus the sequential interval through analysis j is the
intersection of repeated intervals 1 through j.
A data frame with one row per completed analysis containing the observed counts, efficacy estimate, sequential confidence limits, confidence level, and one-sided tail level.
ciBinomialExact,
repeatedCIBinomialExact,
sequentialPValueBinomialExact
design <- gsSurv( k = 3, test.type = 4, timing = c(.45, .7), ratio = 3, hr = .3, hr0 = .7 ) counts <- toBinomialExact(design)$n.I sequentialCIBinomialExact(design, counts, x = c(12, 23, 38))design <- gsSurv( k = 3, test.type = 4, timing = c(.45, .7), ratio = 3, hr = .3, hr0 = .7 ) counts <- toBinomialExact(design)$n.I sequentialCIBinomialExact(design, counts, x = c(12, 23, 38))
sequentialPValue computes a sequential p-value for a group sequential design using a spending function as described in
Maurer and Bretz (2013) and previously defined by Liu and Anderson (2008).
It is the minimum of repeated p-values computed at each analysis (Jennison and Turnbull, 2000).
This is particularly useful for multiplicity methods such as the graphical method for group sequential designs
where sequential p-values for multiple hypotheses can be used as nominal p-values to plug into a multiplicity graph.
A sequential p-value is described as the minimum alpha level at which a one-sided group sequential bound would
be rejected given interim and final observed results.
It is meaningful for both one-sided designs and designs with non-binding
futility or harm bounds (test.type 1, 4, 6, or 8), but not for
2-sided designs with binding futility or harm bounds (test.type 2,
3, 5, or 7). For test type 8, both non-binding lower bounds are ignored in
the Type I error calculation.
Mild restrictions are required on spending functions used, but these are satisfied for commonly used spending functions
such as the Lan-DeMets spending function approximating an O'Brien-Fleming bound or a Hwang-Shih-DeCani spending function; see Maurer and Bretz (2013).
sequentialPValue( gsD = gsDesign(), n.I = NULL, Z = NULL, usTime = NULL, interval = c(1e-05, 0.9999) )sequentialPValue( gsD = gsDesign(), n.I = NULL, Z = NULL, usTime = NULL, interval = c(1e-05, 0.9999) )
gsD |
Group sequential design generated by |
n.I |
Event counts (for time-to-event outcomes) or sample size (for most other designs); numeric vector with increasing, positive values with at most one
value greater than or equal to largest value in |
Z |
Z-value tests corresponding to analyses in |
usTime |
Spending time for upper bound at specified analyses; specify default: |
interval |
Interval for search to derive p-value; Default: |
Solution is found with a search using uniroot.
This finds the maximum alpha-level for which an efficacy bound is crossed,
completely ignoring any futility bound.
Sequential p-value (single numeric one-sided p-value between 0 and 1). Note that if the sequential p-value is less than the lower end of the input interval, the lower of interval will be returned. Similarly, if the sequential p-value is greater than the upper end of the input interval, then the upper end of interval is returned.
Keaven Anderson
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Liu, Qing, and Keaven M. Anderson. "On adaptive extensions of group sequential trials for clinical investigations." Journal of the American Statistical Association 103.484 (2008): 1621-1630.
Maurer, Willi, and Frank Bretz. "Multiple testing in group sequential trials using graphical approaches." Statistics in Biopharmaceutical Research 5.4 (2013): 311-320.;
# Derive Group Sequential Design x <- gsSurv(k = 4, alpha = 0.025, beta = 0.1, timing = c(.5,.65,.8), sfu = sfLDOF, sfl = sfHSD, sflpar = 2, lambdaC = log(2)/6, hr = 0.6, eta = 0.01 , gamma = c(2.5,5,7.5,10), R = c( 2,2,2,6 ), T = 30 , minfup = 18) x$n.I # Analysis at IA2 sequentialPValue(gsD=x,n.I=c(100,160),Z=c(1.5,2)) # Use planned spending instead of information fraction; do final analysis seqp <- sequentialPValue(gsD=x,n.I=c(100,160,190,230),Z=c(1.5,2,2.5,3),usTime=x$timing) seqp # Check bounds for updated design to verify at least one was crossed xupdate <- gsDesign(maxn.IPlan=max(x$n.I),n.I=c(100,160,190,230),usTime=x$timing, delta=x$delta,delta1=x$delta1,k=4,alpha=x$alpha,test.type=1, sfu=x$upper$sf,sfupar=x$upper$param) gsBoundSummary(xupdate,logdelta=TRUE,Nname="Events",deltaname="HR") # Now update bound using sequential p-value as alpha level xupdate <- gsDesign(maxn.IPlan=max(x$n.I),n.I=c(100,160,190,230),usTime=x$timing, delta=x$delta,delta1=x$delta1,k=4,alpha=seqp,test.type=1, sfu=x$upper$sf,sfupar=x$upper$param) # Check that we are at bound for 1 (or more) analysis and did not exceed bound for others gsBoundSummary(xupdate,logdelta=TRUE,Nname="Events",deltaname="HR")# Derive Group Sequential Design x <- gsSurv(k = 4, alpha = 0.025, beta = 0.1, timing = c(.5,.65,.8), sfu = sfLDOF, sfl = sfHSD, sflpar = 2, lambdaC = log(2)/6, hr = 0.6, eta = 0.01 , gamma = c(2.5,5,7.5,10), R = c( 2,2,2,6 ), T = 30 , minfup = 18) x$n.I # Analysis at IA2 sequentialPValue(gsD=x,n.I=c(100,160),Z=c(1.5,2)) # Use planned spending instead of information fraction; do final analysis seqp <- sequentialPValue(gsD=x,n.I=c(100,160,190,230),Z=c(1.5,2,2.5,3),usTime=x$timing) seqp # Check bounds for updated design to verify at least one was crossed xupdate <- gsDesign(maxn.IPlan=max(x$n.I),n.I=c(100,160,190,230),usTime=x$timing, delta=x$delta,delta1=x$delta1,k=4,alpha=x$alpha,test.type=1, sfu=x$upper$sf,sfupar=x$upper$param) gsBoundSummary(xupdate,logdelta=TRUE,Nname="Events",deltaname="HR") # Now update bound using sequential p-value as alpha level xupdate <- gsDesign(maxn.IPlan=max(x$n.I),n.I=c(100,160,190,230),usTime=x$timing, delta=x$delta,delta1=x$delta1,k=4,alpha=seqp,test.type=1, sfu=x$upper$sf,sfupar=x$upper$param) # Check that we are at bound for 1 (or more) analysis and did not exceed bound for others gsBoundSummary(xupdate,logdelta=TRUE,Nname="Events",deltaname="HR")
Computes the sequential p-value as the minimum repeated p-value over completed analyses, using [repeatedPValueBinomialExact()].
sequentialPValueBinomialExact( gsD, n.I = NULL, x = NULL, interval = c(1e-20, 0.9999), tol = 1e-08, maxiter = 100, check = FALSE )sequentialPValueBinomialExact( gsD, n.I = NULL, x = NULL, interval = c(1e-20, 0.9999), tol = 1e-08, maxiter = 100, check = FALSE )
gsD |
A 'gsSurv' object with non-binding 'test.type' 1, 4, 6, or 8. |
n.I |
Increasing integer total event counts at completed analyses. If 'NULL', the planned integer event counts from 'toInteger(gsD)' are used. This must have at most 1 value greater than or equal to planned final events ('gsD$maxn.IPlan' if available, otherwise 'max(gsD$n.I)'). |
x |
Integer experimental arm event counts at the analyses in 'n.I'. |
interval |
Search interval for the p-values. As in [sequentialPValue()], values outside this interval are truncated to the nearest endpoint. |
tol |
Relative tolerance for the monotone bisection search on the alpha scale. |
maxiter |
Maximum number of bisection iterations for each analysis. |
check |
Logical. If 'TRUE', checks the monotonicity of the alpha-indexed integer efficacy bounds on a coarse grid and warns if it is violated. |
A single numeric one-sided sequential p-value.
[repeatedPValueBinomialExact()], [sequentialPValue()]
x <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(0.45, 0.7), sfu = sfHSD, sfupar = -4, sfl = sfLDOF, sflpar = 0, lambdaC = 0.001, hr = 0.3, hr0 = 0.7, eta = 5e-04, gamma = 10, R = 16, T = 24, minfup = 8, ratio = 3 ) counts <- toBinomialExact(x)$n.I sequentialPValueBinomialExact(gsD = x, n.I = counts, x = c(12, 23, 38))x <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(0.45, 0.7), sfu = sfHSD, sfupar = -4, sfl = sfLDOF, sflpar = 0, lambdaC = 0.001, hr = 0.3, hr0 = 0.7, eta = 5e-04, gamma = 10, R = 16, T = 24, minfup = 8, ratio = 3 ) counts <- toBinomialExact(x)$n.I sequentialPValueBinomialExact(gsD = x, n.I = counts, x = c(12, 23, 38))
The function sfExponential implements the exponential spending
function (Anderson and Clark, 2009). Normally sfExponential will be
passed to gsDesign in the parameter sfu for the upper bound or
sfl for the lower bound to specify a spending function family for a
design. In this case, the user does not need to know the calling sequence.
The calling sequence is useful, however, when the user wishes to plot a
spending function as demonstrated below in examples.
An exponential spending function is defined for any positive nu and
as
A value of nu=0.8 approximates an O'Brien-Fleming spending function
well.
The general class of spending functions this family is derived from requires
a continuously increasing cumulative distribution function defined for
and is defined as
The exponential spending function can be
derived by letting , the exponential cumulative
distribution function. This function was derived as a generalization of the
Lan-DeMets (1983) spending function used to approximate an O'Brien-Fleming
spending function (sfLDOF()),
sfExponential(alpha, t, param)sfExponential(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
A single positive value specifying the nu parameter for which the exponential spending is to be computed; allowable range is (0, 1.5]. |
An object of type spendfn.
The gsDesign technical manual shows how to use sfExponential()
to closely approximate an O'Brien-Fleming design. An example is given below.
The manual is available at <https://keaven.github.io/gsd-tech-manual/>.
Keaven Anderson [email protected]
Anderson KM and Clark JB (2009), Fitting spending functions. Statistics in Medicine; 29:321-327.
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Lan, KKG and DeMets, DL (1983), Discrete sequential boundaries for clinical trials. Biometrika; 70:659-663.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # use 'best' exponential approximation for k=6 to O'Brien-Fleming design # (see manual for details) gsDesign( k = 6, sfu = sfExponential, sfupar = 0.7849295, test.type = 2 )$upper$bound # show actual O'Brien-Fleming bound gsDesign(k = 6, sfu = "OF", test.type = 2)$upper$bound # show Lan-DeMets approximation # (not as close as sfExponential approximation) gsDesign(k = 6, sfu = sfLDOF, test.type = 2)$upper$bound # plot exponential spending function across a range of values of interest t <- 0:100 / 100 plot(t, sfExponential(0.025, t, 0.8)$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Exponential Spending Function Example", type = "l" ) lines(t, sfExponential(0.025, t, 0.5)$spend, lty = 2) lines(t, sfExponential(0.025, t, 0.3)$spend, lty = 3) lines(t, sfExponential(0.025, t, 0.2)$spend, lty = 4) lines(t, sfExponential(0.025, t, 0.15)$spend, lty = 5) legend( x = c(.0, .3), y = .025 * c(.7, 1), lty = 1:5, legend = c( "nu = 0.8", "nu = 0.5", "nu = 0.3", "nu = 0.2", "nu = 0.15" ) ) text(x = .59, y = .95 * .025, labels = "<--approximates O'Brien-Fleming")library(ggplot2) # use 'best' exponential approximation for k=6 to O'Brien-Fleming design # (see manual for details) gsDesign( k = 6, sfu = sfExponential, sfupar = 0.7849295, test.type = 2 )$upper$bound # show actual O'Brien-Fleming bound gsDesign(k = 6, sfu = "OF", test.type = 2)$upper$bound # show Lan-DeMets approximation # (not as close as sfExponential approximation) gsDesign(k = 6, sfu = sfLDOF, test.type = 2)$upper$bound # plot exponential spending function across a range of values of interest t <- 0:100 / 100 plot(t, sfExponential(0.025, t, 0.8)$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Exponential Spending Function Example", type = "l" ) lines(t, sfExponential(0.025, t, 0.5)$spend, lty = 2) lines(t, sfExponential(0.025, t, 0.3)$spend, lty = 3) lines(t, sfExponential(0.025, t, 0.2)$spend, lty = 4) lines(t, sfExponential(0.025, t, 0.15)$spend, lty = 5) legend( x = c(.0, .3), y = .025 * c(.7, 1), lty = 1:5, legend = c( "nu = 0.8", "nu = 0.5", "nu = 0.3", "nu = 0.2", "nu = 0.15" ) ) text(x = .59, y = .95 * .025, labels = "<--approximates O'Brien-Fleming")
The function sfHSD implements a Hwang-Shih-DeCani spending function.
This is the default spending function for gsDesign(). Normally it
will be passed to gsDesign in the parameter sfu for the upper
bound or sfl for the lower bound to specify a spending function
family for a design. In this case, the user does not need to know the
calling sequence. The calling sequence is useful, however, when the user
wishes to plot a spending function as demonstrated below in examples.
A Hwang-Shih-DeCani spending function takes the form
where is the
value passed in param. A value of is used
to approximate an O'Brien-Fleming design (see sfExponential
for a better fit), while a value of approximates a
Pocock design well.
sfHSD(alpha, t, param)sfHSD(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
A single real value specifying the gamma parameter for which Hwang-Shih-DeCani spending is to be computed; allowable range is [-40, 40] |
An object of type spendfn. See vignette("SpendingFunctionOverview") for further details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # design a 4-analysis trial using a Hwang-Shih-DeCani spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfHSD, sfupar = -2, sfl = sfHSD, sflpar = 1) # print the design x # since sfHSD is the default for both sfu and sfl, # this could have been written as x <- gsDesign(k = 4, sfupar = -2, sflpar = 1) # print again x # plot the spending function using many points to obtain a smooth curve # show default values of gamma to see how the spending function changes # also show gamma=1 which is supposed to approximate a Pocock design t <- 0:100 / 100 plot(t, sfHSD(0.025, t, -4)$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Hwang-Shih-DeCani Spending Function Example", type = "l" ) lines(t, sfHSD(0.025, t, -2)$spend, lty = 2) lines(t, sfHSD(0.025, t, 1)$spend, lty = 3) legend( x = c(.0, .375), y = .025 * c(.8, 1), lty = 1:3, legend = c("gamma= -4", "gamma= -2", "gamma= 1") )library(ggplot2) # design a 4-analysis trial using a Hwang-Shih-DeCani spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfHSD, sfupar = -2, sfl = sfHSD, sflpar = 1) # print the design x # since sfHSD is the default for both sfu and sfl, # this could have been written as x <- gsDesign(k = 4, sfupar = -2, sflpar = 1) # print again x # plot the spending function using many points to obtain a smooth curve # show default values of gamma to see how the spending function changes # also show gamma=1 which is supposed to approximate a Pocock design t <- 0:100 / 100 plot(t, sfHSD(0.025, t, -4)$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Hwang-Shih-DeCani Spending Function Example", type = "l" ) lines(t, sfHSD(0.025, t, -2)$spend, lty = 2) lines(t, sfHSD(0.025, t, 1)$spend, lty = 3) legend( x = c(.0, .375), y = .025 * c(.8, 1), lty = 1:3, legend = c("gamma= -4", "gamma= -2", "gamma= 1") )
Lan and DeMets (1983) first published the method of using spending functions to set boundaries for group sequential trials. In this publication they proposed two specific spending functions: one to approximate an O'Brien-Fleming design and the other to approximate a Pocock design. The spending function to approximate O'Brien-Fleming has been generalized as proposed by Liu, et al (2012)
With param=1=rho, the Lan-DeMets (1983) spending function to approximate an O'Brien-Fleming
bound is implemented in the function (sfLDOF()):
For rho otherwise in [.005,2], this is the generalized version of Liu et al (2012).
For param outside of [.005,2], rho is set to 1. The Lan-DeMets (1983)
spending function to approximate a Pocock design is implemented in the
function sfLDPocock():
As shown in
examples below, other spending functions can be used to ge t as good or
better approximations to Pocock and O'Brien-Fleming bounds. In particular,
O'Brien-Fleming bounds can be closely approximated using
sfExponential.
sfLDOF(alpha, t, param = NULL) sfLDPocock(alpha, t, param)sfLDOF(alpha, t, param = NULL) sfLDPocock(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
This parameter is not used for |
An object of type spendfn. See spending functions for further
details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Lan, KKG and DeMets, DL (1983), Discrete sequential boundaries for clinical trials. Biometrika;70: 659-663.
Liu, Q, Lim, P, Nuamah, I, and Li, Y (2012), On adaptive error spending approach for group sequential trials with random information levels. Journal of biopharmaceutical statistics; 22(4), 687-699.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # 2-sided, symmetric 6-analysis trial Pocock # spending function approximation gsDesign(k = 6, sfu = sfLDPocock, test.type = 2)$upper$bound # show actual Pocock design gsDesign(k = 6, sfu = "Pocock", test.type = 2)$upper$bound # approximate Pocock again using a standard # Hwang-Shih-DeCani approximation gsDesign(k = 6, sfu = sfHSD, sfupar = 1, test.type = 2)$upper$bound # use 'best' Hwang-Shih-DeCani approximation for Pocock, k=6; # see manual for details gsDesign(k = 6, sfu = sfHSD, sfupar = 1.3354376, test.type = 2)$upper$bound # 2-sided, symmetric 6-analysis trial # O'Brien-Fleming spending function approximation gsDesign(k = 6, sfu = sfLDOF, test.type = 2)$upper$bound # show actual O'Brien-Fleming bound gsDesign(k = 6, sfu = "OF", test.type = 2)$upper$bound # approximate again using a standard Hwang-Shih-DeCani # approximation to O'Brien-Fleming x <- gsDesign(k = 6, test.type = 2) x$upper$bound x$upper$param # use 'best' exponential approximation for k=6; see manual for details gsDesign( k = 6, sfu = sfExponential, sfupar = 0.7849295, test.type = 2 )$upper$bound # plot spending functions for generalized Lan-DeMets approximation of ti <-(0:100)/100 rho <- c(.05,.5,1,1.5,2,2.5,3:6,8,10,12.5,15,20,30,200)/10 df <- NULL for(r in rho){ df <- rbind(df,data.frame(t=ti,rho=r,alpha=.025,spend=sfLDOF(alpha=.025,t=ti,param=r)$spend)) } ggplot(df,aes(x=t,y=spend,col=as.factor(rho)))+ geom_line()+ guides(col=guide_legend(expression(rho)))+ ggtitle("Generalized Lan-DeMets O'Brien-Fleming Spending Function")library(ggplot2) # 2-sided, symmetric 6-analysis trial Pocock # spending function approximation gsDesign(k = 6, sfu = sfLDPocock, test.type = 2)$upper$bound # show actual Pocock design gsDesign(k = 6, sfu = "Pocock", test.type = 2)$upper$bound # approximate Pocock again using a standard # Hwang-Shih-DeCani approximation gsDesign(k = 6, sfu = sfHSD, sfupar = 1, test.type = 2)$upper$bound # use 'best' Hwang-Shih-DeCani approximation for Pocock, k=6; # see manual for details gsDesign(k = 6, sfu = sfHSD, sfupar = 1.3354376, test.type = 2)$upper$bound # 2-sided, symmetric 6-analysis trial # O'Brien-Fleming spending function approximation gsDesign(k = 6, sfu = sfLDOF, test.type = 2)$upper$bound # show actual O'Brien-Fleming bound gsDesign(k = 6, sfu = "OF", test.type = 2)$upper$bound # approximate again using a standard Hwang-Shih-DeCani # approximation to O'Brien-Fleming x <- gsDesign(k = 6, test.type = 2) x$upper$bound x$upper$param # use 'best' exponential approximation for k=6; see manual for details gsDesign( k = 6, sfu = sfExponential, sfupar = 0.7849295, test.type = 2 )$upper$bound # plot spending functions for generalized Lan-DeMets approximation of ti <-(0:100)/100 rho <- c(.05,.5,1,1.5,2,2.5,3:6,8,10,12.5,15,20,30,200)/10 df <- NULL for(r in rho){ df <- rbind(df,data.frame(t=ti,rho=r,alpha=.025,spend=sfLDOF(alpha=.025,t=ti,param=r)$spend)) } ggplot(df,aes(x=t,y=spend,col=as.factor(rho)))+ geom_line()+ guides(col=guide_legend(expression(rho)))+ ggtitle("Generalized Lan-DeMets O'Brien-Fleming Spending Function")
The function sfLinear() allows specification of a piecewise linear
spending function. The function sfStep() specifies a step function
spending function. Both functions provide complete flexibility in setting
spending at desired timepoints in a group sequential design. Normally these
function will be passed to gsDesign() in the parameter sfu for
the upper bound or sfl for the lower bound to specify a spending
function family for a design. When passed to gsDesign(), the value of
param would be passed to sfLinear() or sfStep() through
the gsDesign() arguments sfupar for the upper bound and
sflpar for the lower bound.
Note that sfStep() allows setting a particular level of spending when
the timing is not strictly known; an example shows how this can inflate Type
I error when timing of analyses are changed based on knowing the treatment
effect at an interim.
sfLinear(alpha, t, param) sfStep(alpha, t, param)sfLinear(alpha, t, param) sfStep(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size or information for which the spending function will be computed. |
param |
A vector with a positive, even length. Values must range from 0
to 1, inclusive. Letting |
An object of type spendfn. The cumulative spending returned
in sfLinear$spend is 0 for t <= 0 and alpha for
t>=1. For t between specified points, linear interpolation is
used to determine sfLinear$spend.
The cumulative spending returned in sfStep$spend is 0 for
t<param[1] and alpha for t>=1. Letting m <-
length(param/2), for i=1,2,...m-1 and param[i]<= t <
param[i+1], the cumulative spending is set at alpha * param[i+m]
(also for param[m]<=t<1).
Note that if param[2m] is 1, then the first time an analysis is
performed after the last proportion of final planned information
(param[m]) will be the final analysis, using any remaining error that
was not previously spent.
See vignette("SpendingFunctionOverview") for further details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # set up alpha spending and beta spending to be piecewise linear sfupar <- c(.2, .4, .05, .2) sflpar <- c(.3, .5, .65, .5, .75, .9) x <- gsDesign(sfu = sfLinear, sfl = sfLinear, sfupar = sfupar, sflpar = sflpar) plot(x, plottype = "sf") x # now do an example where there is no lower-spending at interim 1 # and no upper spending at interim 2 sflpar <- c(1 / 3, 2 / 3, 0, .25) sfupar <- c(1 / 3, 2 / 3, .1, .1) x <- gsDesign(sfu = sfLinear, sfl = sfLinear, sfupar = sfupar, sflpar = sflpar) plot(x, plottype = "sf") x # now do an example where timing of interims changes slightly, but error spending does not # also, spend all alpha when at least >=90 percent of final information is in the analysis sfupar <- c(.2, .4, .9, ((1:3) / 3)^3) x <- gsDesign(k = 3, n.fix = 100, sfu = sfStep, sfupar = sfupar, test.type = 1) plot(x, pl = "sf") # original planned sample sizes ceiling(x$n.I) # cumulative spending planned at original interims cumsum(x$upper$spend) # change timing of analyses; # note that cumulative spending "P(Cross) if delta=0" does not change from cumsum(x$upper$spend) # while full alpha is spent, power is reduced by reduced sample size y <- gsDesign( k = 3, sfu = sfStep, sfupar = sfupar, test.type = 1, maxn.IPlan = x$n.I[x$k], n.I = c(30, 70, 95), n.fix = x$n.fix ) # note that full alpha is used, but power is reduced due to lowered sample size gsBoundSummary(y) # now show how step function can be abused by 'adapting' stage 2 sample size based on interim result x <- gsDesign(k = 2, delta = .05, sfu = sfStep, sfupar = c(.02, .001), timing = .02, test.type = 1) # spending jumps from miniscule to full alpha at first analysis after interim 1 plot(x, pl = "sf") # sample sizes at analyses: ceiling(x$n.I) # simulate 1 million stage 1 sum of 178 Normal(0,1) random variables # Normal(0,Variance=178) under null hypothesis s1 <- rnorm(1000000, 0, sqrt(178)) # compute corresponding z-values z1 <- s1 / sqrt(178) # set stage 2 sample size to 1 if z1 is over final bound, otherwise full sample size n2 <- rep(1, 1000000) n2[z1 < 1.96] <- ceiling(x$n.I[2]) - ceiling(178) # now sample n2 observations for second stage s2 <- rnorm(1000000, 0, sqrt(n2)) # add sum and divide by standard deviation z2 <- (s1 + s2) / (sqrt(178 + n2)) # By allowing full spending when final analysis is either # early or late depending on observed interim z1, # Type I error is now almost twice the planned .025 sum(z1 >= x$upper$bound[1] | z2 >= x$upper$bound[2]) / 1000000 # if stage 2 sample size is random and independent of z1 with same frequency, # this is not a problem s1alt <- rnorm(1000000, 0, sqrt(178)) z1alt <- s1alt / sqrt(178) z2alt <- (s1alt + s2) / sqrt(178 + n2) sum(z1alt >= x$upper$bound[1] | z2alt >= x$upper$bound[2]) / 1000000library(ggplot2) # set up alpha spending and beta spending to be piecewise linear sfupar <- c(.2, .4, .05, .2) sflpar <- c(.3, .5, .65, .5, .75, .9) x <- gsDesign(sfu = sfLinear, sfl = sfLinear, sfupar = sfupar, sflpar = sflpar) plot(x, plottype = "sf") x # now do an example where there is no lower-spending at interim 1 # and no upper spending at interim 2 sflpar <- c(1 / 3, 2 / 3, 0, .25) sfupar <- c(1 / 3, 2 / 3, .1, .1) x <- gsDesign(sfu = sfLinear, sfl = sfLinear, sfupar = sfupar, sflpar = sflpar) plot(x, plottype = "sf") x # now do an example where timing of interims changes slightly, but error spending does not # also, spend all alpha when at least >=90 percent of final information is in the analysis sfupar <- c(.2, .4, .9, ((1:3) / 3)^3) x <- gsDesign(k = 3, n.fix = 100, sfu = sfStep, sfupar = sfupar, test.type = 1) plot(x, pl = "sf") # original planned sample sizes ceiling(x$n.I) # cumulative spending planned at original interims cumsum(x$upper$spend) # change timing of analyses; # note that cumulative spending "P(Cross) if delta=0" does not change from cumsum(x$upper$spend) # while full alpha is spent, power is reduced by reduced sample size y <- gsDesign( k = 3, sfu = sfStep, sfupar = sfupar, test.type = 1, maxn.IPlan = x$n.I[x$k], n.I = c(30, 70, 95), n.fix = x$n.fix ) # note that full alpha is used, but power is reduced due to lowered sample size gsBoundSummary(y) # now show how step function can be abused by 'adapting' stage 2 sample size based on interim result x <- gsDesign(k = 2, delta = .05, sfu = sfStep, sfupar = c(.02, .001), timing = .02, test.type = 1) # spending jumps from miniscule to full alpha at first analysis after interim 1 plot(x, pl = "sf") # sample sizes at analyses: ceiling(x$n.I) # simulate 1 million stage 1 sum of 178 Normal(0,1) random variables # Normal(0,Variance=178) under null hypothesis s1 <- rnorm(1000000, 0, sqrt(178)) # compute corresponding z-values z1 <- s1 / sqrt(178) # set stage 2 sample size to 1 if z1 is over final bound, otherwise full sample size n2 <- rep(1, 1000000) n2[z1 < 1.96] <- ceiling(x$n.I[2]) - ceiling(178) # now sample n2 observations for second stage s2 <- rnorm(1000000, 0, sqrt(n2)) # add sum and divide by standard deviation z2 <- (s1 + s2) / (sqrt(178 + n2)) # By allowing full spending when final analysis is either # early or late depending on observed interim z1, # Type I error is now almost twice the planned .025 sum(z1 >= x$upper$bound[1] | z2 >= x$upper$bound[2]) / 1000000 # if stage 2 sample size is random and independent of z1 with same frequency, # this is not a problem s1alt <- rnorm(1000000, 0, sqrt(178)) z1alt <- s1alt / sqrt(178) z2alt <- (s1alt + s2) / sqrt(178 + n2) sum(z1alt >= x$upper$bound[1] | z2alt >= x$upper$bound[2]) / 1000000
The functions sfLogistic(), sfNormal(),
sfExtremeValue(), sfExtremeValue2(), sfCauchy(), and
sfBetaDist() are all 2-parameter spending function families. These
provide increased flexibility in some situations where the flexibility of a
one-parameter spending function family is not sufficient. These functions
all allow fitting of two points on a cumulative spending function curve; in
this case, four parameters are specified indicating an x and a y coordinate
for each of 2 points. Normally each of these functions will be passed to
gsDesign() in the parameter sfu for the upper bound or
sfl for the lower bound to specify a spending function family for a
design. In this case, the user does not need to know the calling sequence.
The calling sequence is useful, however, when the user wishes to plot a
spending function as demonstrated in the examples; note, however, that an
automatic - and -spending function plot
is also available.
sfBetaDist(alpha,t,param) is simply alpha times the incomplete
beta cumulative distribution function with parameters and
passed in param evaluated at values passed in t.
The other spending functions take the form
where is a
cumulative distribution function with values on the real line
(logistic for sfLogistic(), normal for sfNormal(), extreme
value for sfExtremeValue() and Cauchy for sfCauchy()) and
is its inverse.
For the logistic spending function this simplifies to
For the extreme value distribution with
this simplifies to
Since the
extreme value distribution is not symmetric, there is also a version where
the standard distribution is flipped about 0. This is reflected in
sfExtremeValue2() where
sfLogistic(alpha, t, param) sfBetaDist(alpha, t, param) sfCauchy(alpha, t, param) sfExtremeValue(alpha, t, param) sfExtremeValue2(alpha, t, param) sfNormal(alpha, t, param)sfLogistic(alpha, t, param) sfBetaDist(alpha, t, param) sfCauchy(alpha, t, param) sfExtremeValue(alpha, t, param) sfExtremeValue2(alpha, t, param) sfNormal(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size or information for which the spending function will be computed. |
param |
In the two-parameter specification,
|
An object of type spendfn.
See vignette("SpendingFunctionOverview") for further details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
library(ggplot2) # design a 4-analysis trial using a Kim-DeMets spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfPower, sfupar = 3, sfl = sfPower, sflpar = 1.5) # print the design x # plot the alpha- and beta-spending functions plot(x, plottype = 5) # start by showing how to fit two points with sfLogistic # plot the spending function using many points to obtain a smooth curve # note that curve fits the points x=.1, y=.01 and x=.4, y=.1 # specified in the 3rd parameter of sfLogistic t <- 0:100 / 100 plot(t, sfLogistic(1, t, c(.1, .4, .01, .1))$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Logistic Spending Function Examples", type = "l", cex.main = .9 ) lines(t, sfLogistic(1, t, c(.01, .1, .1, .4))$spend, lty = 2) # now just give a=0 and b=1 as 3rd parameters for sfLogistic lines(t, sfLogistic(1, t, c(0, 1))$spend, lty = 3) # try a couple with unconventional shapes again using # the xy form in the 3rd parameter lines(t, sfLogistic(1, t, c(.4, .6, .1, .7))$spend, lty = 4) lines(t, sfLogistic(1, t, c(.1, .7, .4, .6))$spend, lty = 5) legend( x = c(.0, .475), y = c(.76, 1.03), lty = 1:5, legend = c( "Fit (.1, 01) and (.4, .1)", "Fit (.01, .1) and (.1, .4)", "a=0, b=1", "Fit (.4, .1) and (.6, .7)", "Fit (.1, .4) and (.7, .6)" ) ) # set up a function to plot comparsons of all # 2-parameter spending functions plotsf <- function(alpha, t, param) { plot(t, sfCauchy(alpha, t, param)$spend, xlab = "Proportion of enrollment", ylab = "Cumulative spending", type = "l", lty = 2 ) lines(t, sfExtremeValue(alpha, t, param)$spend, lty = 5) lines(t, sfLogistic(alpha, t, param)$spend, lty = 1) lines(t, sfNormal(alpha, t, param)$spend, lty = 3) lines(t, sfExtremeValue2(alpha, t, param)$spend, lty = 6, col = 2) lines(t, sfBetaDist(alpha, t, param)$spend, lty = 7, col = 3) legend( x = c(.05, .475), y = .025 * c(.55, .9), lty = c(1, 2, 3, 5, 6, 7), col = c(1, 1, 1, 1, 2, 3), legend = c( "Logistic", "Cauchy", "Normal", "Extreme value", "Extreme value 2", "Beta distribution" ) ) } # do comparison for a design with conservative early spending # note that Cauchy spending function is quite different # from the others param <- c(.25, .5, .05, .1) plotsf(.025, t, param)library(ggplot2) # design a 4-analysis trial using a Kim-DeMets spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfPower, sfupar = 3, sfl = sfPower, sflpar = 1.5) # print the design x # plot the alpha- and beta-spending functions plot(x, plottype = 5) # start by showing how to fit two points with sfLogistic # plot the spending function using many points to obtain a smooth curve # note that curve fits the points x=.1, y=.01 and x=.4, y=.1 # specified in the 3rd parameter of sfLogistic t <- 0:100 / 100 plot(t, sfLogistic(1, t, c(.1, .4, .01, .1))$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "Logistic Spending Function Examples", type = "l", cex.main = .9 ) lines(t, sfLogistic(1, t, c(.01, .1, .1, .4))$spend, lty = 2) # now just give a=0 and b=1 as 3rd parameters for sfLogistic lines(t, sfLogistic(1, t, c(0, 1))$spend, lty = 3) # try a couple with unconventional shapes again using # the xy form in the 3rd parameter lines(t, sfLogistic(1, t, c(.4, .6, .1, .7))$spend, lty = 4) lines(t, sfLogistic(1, t, c(.1, .7, .4, .6))$spend, lty = 5) legend( x = c(.0, .475), y = c(.76, 1.03), lty = 1:5, legend = c( "Fit (.1, 01) and (.4, .1)", "Fit (.01, .1) and (.1, .4)", "a=0, b=1", "Fit (.4, .1) and (.6, .7)", "Fit (.1, .4) and (.7, .6)" ) ) # set up a function to plot comparsons of all # 2-parameter spending functions plotsf <- function(alpha, t, param) { plot(t, sfCauchy(alpha, t, param)$spend, xlab = "Proportion of enrollment", ylab = "Cumulative spending", type = "l", lty = 2 ) lines(t, sfExtremeValue(alpha, t, param)$spend, lty = 5) lines(t, sfLogistic(alpha, t, param)$spend, lty = 1) lines(t, sfNormal(alpha, t, param)$spend, lty = 3) lines(t, sfExtremeValue2(alpha, t, param)$spend, lty = 6, col = 2) lines(t, sfBetaDist(alpha, t, param)$spend, lty = 7, col = 3) legend( x = c(.05, .475), y = .025 * c(.55, .9), lty = c(1, 2, 3, 5, 6, 7), col = c(1, 1, 1, 1, 2, 3), legend = c( "Logistic", "Cauchy", "Normal", "Extreme value", "Extreme value 2", "Beta distribution" ) ) } # do comparison for a design with conservative early spending # note that Cauchy spending function is quite different # from the others param <- c(.25, .5, .05, .1) plotsf(.025, t, param)
The function sfPoints implements a spending function with values
specified for an arbitrary set of specified points. It is now recommended to
use sfLinear rather than sfPoints. Normally sfPoints will be passed
to gsDesign in the parameter sfu for the upper bound or
sfl for the lower bound to specify a spending function family for a
design. In this case, the user does not need to know the calling sequence,
just the points they wish to specify. If using sfPoints() in a
design, it is recommended to specify how to interpolate between the
specified points (e.g,, linear interpolation); also consider fitting smooth
spending functions; see vignette("SpendingFunctionOverview").
sfPoints(alpha, t, param)sfPoints(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from >0 and <=1. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
A vector of the same length as |
An object of type spendfn. See spending functions for further
details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview"), sfLogistic
library(ggplot2) # example to specify spending on a pointwise basis x <- gsDesign( k = 6, sfu = sfPoints, sfupar = c(.01, .05, .1, .25, .5, 1), test.type = 2 ) x # get proportion of upper spending under null hypothesis # at each analysis y <- x$upper$prob[, 1] / .025 # change to cumulative proportion of spending for (i in 2:length(y)) y[i] <- y[i - 1] + y[i] # this should correspond to input sfupar round(y, 6) # plot these cumulative spending points plot(1:6 / 6, y, main = "Pointwise spending function example", xlab = "Proportion of final sample size", ylab = "Cumulative proportion of spending", type = "p" ) # approximate this with a t-distribution spending function # by fitting 3 points tx <- 0:100 / 100 lines(tx, sfTDist(1, tx, c(c(1, 3, 5) / 6, .01, .1, .5))$spend) text(x = .6, y = .9, labels = "Pointwise Spending Approximated by") text(x = .6, y = .83, "t-Distribution Spending with 3-point interpolation") # example without lower spending at initial interim or # upper spending at last interim x <- gsDesign( k = 3, sfu = sfPoints, sfupar = c(.25, .25), sfl = sfPoints, sflpar = c(0, .25) ) xlibrary(ggplot2) # example to specify spending on a pointwise basis x <- gsDesign( k = 6, sfu = sfPoints, sfupar = c(.01, .05, .1, .25, .5, 1), test.type = 2 ) x # get proportion of upper spending under null hypothesis # at each analysis y <- x$upper$prob[, 1] / .025 # change to cumulative proportion of spending for (i in 2:length(y)) y[i] <- y[i - 1] + y[i] # this should correspond to input sfupar round(y, 6) # plot these cumulative spending points plot(1:6 / 6, y, main = "Pointwise spending function example", xlab = "Proportion of final sample size", ylab = "Cumulative proportion of spending", type = "p" ) # approximate this with a t-distribution spending function # by fitting 3 points tx <- 0:100 / 100 lines(tx, sfTDist(1, tx, c(c(1, 3, 5) / 6, .01, .1, .5))$spend) text(x = .6, y = .9, labels = "Pointwise Spending Approximated by") text(x = .6, y = .83, "t-Distribution Spending with 3-point interpolation") # example without lower spending at initial interim or # upper spending at last interim x <- gsDesign( k = 3, sfu = sfPoints, sfupar = c(.25, .25), sfl = sfPoints, sflpar = c(0, .25) ) x
The function sfPower() implements a Kim-DeMets (power) spending
function. This is a flexible, one-parameter spending function recommended by
Jennison and Turnbull (2000). Normally it will be passed to
gsDesign() in the parameter sfu for the upper bound or
sfl for the lower bound to specify a spending function family for a
design. In this case, the user does not need to know the calling sequence.
The calling sequence is useful, however, when the user wishes to plot a
spending function as demonstrated below in examples.
A Kim-DeMets spending function takes the form
where is the value
passed in param. See examples below for a range of values of
that may be of interest (param=0.75 to 3 are
documented there).
sfPower(alpha, t, param)sfPower(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
A single, positive value specifying the |
An object of type spendfn. See vignette("SpendingFunctionOverview") for further details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # design a 4-analysis trial using a Kim-DeMets spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfPower, sfupar = 3, sfl = sfPower, sflpar = 1.5) # print the design x # plot the spending function using many points to obtain a smooth curve # show rho=3 for approximation to O'Brien-Fleming and rho=.75 for # approximation to Pocock design. # Also show rho=2 for an intermediate spending. # Compare these to Hwang-Shih-DeCani spending with gamma=-4, -2, 1 t <- 0:100 / 100 plot(t, sfPower(0.025, t, 3)$spend, xlab = "Proportion of sample size", ylab = "Cumulative Type I error spending", main = "Kim-DeMets (rho) versus Hwang-Shih-DeCani (gamma) Spending", type = "l", cex.main = .9 ) lines(t, sfPower(0.025, t, 2)$spend, lty = 2) lines(t, sfPower(0.025, t, 0.75)$spend, lty = 3) lines(t, sfHSD(0.025, t, 1)$spend, lty = 3, col = 2) lines(t, sfHSD(0.025, t, -2)$spend, lty = 2, col = 2) lines(t, sfHSD(0.025, t, -4)$spend, lty = 1, col = 2) legend( x = c(.0, .375), y = .025 * c(.65, 1), lty = 1:3, legend = c("rho= 3", "rho= 2", "rho= 0.75") ) legend( x = c(.0, .357), y = .025 * c(.65, .85), lty = 1:3, bty = "n", col = 2, legend = c("gamma= -4", "gamma= -2", "gamma=1") )library(ggplot2) # design a 4-analysis trial using a Kim-DeMets spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfPower, sfupar = 3, sfl = sfPower, sflpar = 1.5) # print the design x # plot the spending function using many points to obtain a smooth curve # show rho=3 for approximation to O'Brien-Fleming and rho=.75 for # approximation to Pocock design. # Also show rho=2 for an intermediate spending. # Compare these to Hwang-Shih-DeCani spending with gamma=-4, -2, 1 t <- 0:100 / 100 plot(t, sfPower(0.025, t, 3)$spend, xlab = "Proportion of sample size", ylab = "Cumulative Type I error spending", main = "Kim-DeMets (rho) versus Hwang-Shih-DeCani (gamma) Spending", type = "l", cex.main = .9 ) lines(t, sfPower(0.025, t, 2)$spend, lty = 2) lines(t, sfPower(0.025, t, 0.75)$spend, lty = 3) lines(t, sfHSD(0.025, t, 1)$spend, lty = 3, col = 2) lines(t, sfHSD(0.025, t, -2)$spend, lty = 2, col = 2) lines(t, sfHSD(0.025, t, -4)$spend, lty = 1, col = 2) legend( x = c(.0, .375), y = .025 * c(.65, 1), lty = 1:3, legend = c("rho= 3", "rho= 2", "rho= 0.75") ) legend( x = c(.0, .357), y = .025 * c(.65, .85), lty = 1:3, bty = "n", col = 2, legend = c("gamma= -4", "gamma= -2", "gamma=1") )
The function sfTDist() provides perhaps the maximum flexibility among
spending functions provided in the gsDesign package. This function
allows fitting of three points on a cumulative spending function curve; in
this case, six parameters are specified indicating an x and a y coordinate
for each of 3 points. Normally this function will be passed to
gsDesign() in the parameter sfu for the upper bound or
sfl for the lower bound to specify a spending function family for a
design. In this case, the user does not need to know the calling sequence.
The calling sequence is useful, however, when the user wishes to plot a
spending function as demonstrated below in examples.
The t-distribution spending function takes the form
where is a cumulative t-distribution function
with df degrees of freedom and is its inverse.
sfTDist(alpha, t, param)sfTDist(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
In the three-parameter specification, the first paramater (a)
may be any real value, the second (b) any positive value, and the third
parameter (df=degrees of freedom) any real value 1 or greater. When
|
An object of type spendfn. See spending functions for further
details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
library(ggplot2) # 3-parameter specification: a, b, df sfTDist(1, 1:5 / 6, c(-1, 1.5, 4))$spend # 5-parameter specification fits 2 points, in this case # the 1st 2 interims are at 25% and 50% of observations with # cumulative error spending of 10% and 20%, respectively # final parameter is df sfTDist(1, 1:3 / 4, c(.25, .5, .1, .2, 4))$spend # 6-parameter specification fits 3 points # Interims are at 25%. 50% and 75% of observations # with cumulative spending of 10%, 20% and 50%, respectively # Note: not all 3 point combinations can be fit sfTDist(1, 1:3 / 4, c(.25, .5, .75, .1, .2, .5))$spend # Example of error message when the 3-points specified # in the 6-parameter version cannot be fit try(sfTDist(1, 1:3 / 4, c(.25, .5, .75, .1, .2, .3))$errmsg) # sfCauchy (sfTDist with 1 df) and sfNormal (sfTDist with infinite df) # show the limits of what sfTdist can fit # for the third point are u3 from 0.344 to 0.6 when t3=0.75 sfNormal(1, 1:3 / 4, c(.25, .5, .1, .2))$spend[3] sfCauchy(1, 1:3 / 4, c(.25, .5, .1, .2))$spend[3] # plot a few t-distribution spending functions fitting # t=0.25, .5 and u=0.1, 0.2 # to demonstrate the range of flexibility t <- 0:100 / 100 plot(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 1))$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "t-Distribution Spending Function Examples", type = "l" ) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 1.5))$spend, lty = 2) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 3))$spend, lty = 3) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 10))$spend, lty = 4) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 100))$spend, lty = 5) legend( x = c(.0, .3), y = .025 * c(.7, 1), lty = 1:5, legend = c("df = 1", "df = 1.5", "df = 3", "df = 10", "df = 100") )library(ggplot2) # 3-parameter specification: a, b, df sfTDist(1, 1:5 / 6, c(-1, 1.5, 4))$spend # 5-parameter specification fits 2 points, in this case # the 1st 2 interims are at 25% and 50% of observations with # cumulative error spending of 10% and 20%, respectively # final parameter is df sfTDist(1, 1:3 / 4, c(.25, .5, .1, .2, 4))$spend # 6-parameter specification fits 3 points # Interims are at 25%. 50% and 75% of observations # with cumulative spending of 10%, 20% and 50%, respectively # Note: not all 3 point combinations can be fit sfTDist(1, 1:3 / 4, c(.25, .5, .75, .1, .2, .5))$spend # Example of error message when the 3-points specified # in the 6-parameter version cannot be fit try(sfTDist(1, 1:3 / 4, c(.25, .5, .75, .1, .2, .3))$errmsg) # sfCauchy (sfTDist with 1 df) and sfNormal (sfTDist with infinite df) # show the limits of what sfTdist can fit # for the third point are u3 from 0.344 to 0.6 when t3=0.75 sfNormal(1, 1:3 / 4, c(.25, .5, .1, .2))$spend[3] sfCauchy(1, 1:3 / 4, c(.25, .5, .1, .2))$spend[3] # plot a few t-distribution spending functions fitting # t=0.25, .5 and u=0.1, 0.2 # to demonstrate the range of flexibility t <- 0:100 / 100 plot(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 1))$spend, xlab = "Proportion of final sample size", ylab = "Cumulative Type I error spending", main = "t-Distribution Spending Function Examples", type = "l" ) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 1.5))$spend, lty = 2) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 3))$spend, lty = 3) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 10))$spend, lty = 4) lines(t, sfTDist(0.025, t, c(.25, .5, .1, .2, 100))$spend, lty = 5) legend( x = c(.0, .3), y = .025 * c(.7, 1), lty = 1:5, legend = c("df = 1", "df = 1.5", "df = 3", "df = 10", "df = 100") )
The functions sfTruncated() and sfTrimmed apply any other
spending function over a restricted range. This allows eliminating spending
for early interim analyses when you desire not to stop for the bound being
specified; this is usually applied to eliminate early tests for a positive
efficacy finding. The truncation can come late in the trial if you desire to
stop a trial any time after, say, 90 percent of information is available and
an analysis is performed. This allows full Type I error spending if the
final analysis occurs early. Both functions set cumulative spending to 0
below a 'spending interval' in the interval [0,1], and set cumulative
spending to 1 above this range. sfTrimmed() otherwise does not change
an input spending function that is specified; probably the preferred and
more intuitive method in most cases. sfTruncated() resets the time
scale on which the input spending function is computed to the 'spending
interval.'
sfGapped() allows elimination of analyses after some time point in
the trial; see details and examples.
sfTrimmed simply computes the value of the input spending function
and parameters in the sub-range of [0,1], sets spending to 0 below this
range and sets spending to 1 above this range.
sfGapped spends outside of the range provided in trange. Below
trange, the input spending function is used. Above trange, full spending is
used; i.e., the first analysis performed above the interval in trange is the
final analysis. As long as the input spending function is strictly
increasing, this means that the first interim in the interval trange is the
final interim analysis for the bound being specified.
sfTruncated compresses spending into a sub-range of [0,1]. The
parameter param$trange specifies the range over which spending is to
occur. Within this range, spending is spent according to the spending
function specified in param$sf along with the corresponding spending
function parameter(s) in param$param. See example using
sfLinear that spends uniformly over specified range.
sfTruncated(alpha, t, param) sfTrimmed(alpha, t, param) sfGapped(alpha, t, param)sfTruncated(alpha, t, param) sfTrimmed(alpha, t, param) sfGapped(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size or information for which the spending function will be computed. |
param |
a list containing the elements sf (a spendfn object such as sfHSD), trange (the range over which the spending function increases from 0 to 1; 0 <= trange[1]<trange[2] <=1; for sfGapped, trange[1] must be > 0), and param (null for a spending function with no parameters or a scalar or vector of parameters needed to fully specify the spending function in sf). |
An object of type spendfn. See vignette("SpendingFunctionOverview")
for further details.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
# Eliminate efficacy spending forany interim at or before 20 percent of information. # Complete spending at first interim at or after 80 percent of information. tx <- (0:100) / 100 s <- sfHSD(alpha = .05, t = tx, param = 1)$spend x <- data.frame(t = tx, Spending = s, sf = "Original spending") param <- list(trange = c(.2, .8), sf = sfHSD, param = 1) s <- sfTruncated(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Truncated")) s <- sfTrimmed(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Trimmed")) s <- sfGapped(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Gapped")) ggplot2::ggplot(x, ggplot2::aes(x = t, y = Spending, col = sf)) + ggplot2::geom_line() # now apply the sfTrimmed version in gsDesign # initially, eliminate the early efficacy analysis # note: final spend must occur at > next to last interim x <- gsDesign( k = 4, n.fix = 100, sfu = sfTrimmed, sfupar = list(sf = sfHSD, param = 1, trange = c(.3, .9)) ) # first upper bound=Inf means no testing there gsBoundSummary(x) # now, do not eliminate early efficacy analysis param <- list(sf = sfHSD, param = 1, trange = c(0, .9)) x <- gsDesign(k = 4, n.fix = 100, sfu = sfTrimmed, sfupar = param) # The above means if final analysis is done a little early, all spending can occur # Suppose we set calendar date for final analysis based on # estimated full information, but come up with only 97 pct of plan xA <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], .97 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfTrimmed, sfupar = param ) # now accelerate without the trimmed spending function xNT <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], .97 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfHSD, sfupar = 1 ) # Check last bound if analysis done at early time x$upper$bound[4] # Now look at last bound if done at early time with trimmed spending function # that allows capture of full alpha xA$upper$bound[4] # With original spending function, we don't get full alpha and therefore have # unnecessarily stringent bound at final analysis xNT$upper$bound[4] # note that if the last analysis is LATE, all 3 approaches should give the same # final bound that has a little larger z-value xlate <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], 1.25 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfHSD, sfupar = 1 ) xlate$upper$bound[4] # eliminate futility after the first interim analysis # note that by setting trange[1] to .2, the spend at t=.2 is used for the first # interim at or after 20 percent of information x <- gsDesign(n.fix = 100, sfl = sfGapped, sflpar = list(trange = c(.2, .9), sf = sfHSD, param = 1))# Eliminate efficacy spending forany interim at or before 20 percent of information. # Complete spending at first interim at or after 80 percent of information. tx <- (0:100) / 100 s <- sfHSD(alpha = .05, t = tx, param = 1)$spend x <- data.frame(t = tx, Spending = s, sf = "Original spending") param <- list(trange = c(.2, .8), sf = sfHSD, param = 1) s <- sfTruncated(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Truncated")) s <- sfTrimmed(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Trimmed")) s <- sfGapped(alpha = .05, t = tx, param = param)$spend x <- rbind(x, data.frame(t = tx, Spending = s, sf = "Gapped")) ggplot2::ggplot(x, ggplot2::aes(x = t, y = Spending, col = sf)) + ggplot2::geom_line() # now apply the sfTrimmed version in gsDesign # initially, eliminate the early efficacy analysis # note: final spend must occur at > next to last interim x <- gsDesign( k = 4, n.fix = 100, sfu = sfTrimmed, sfupar = list(sf = sfHSD, param = 1, trange = c(.3, .9)) ) # first upper bound=Inf means no testing there gsBoundSummary(x) # now, do not eliminate early efficacy analysis param <- list(sf = sfHSD, param = 1, trange = c(0, .9)) x <- gsDesign(k = 4, n.fix = 100, sfu = sfTrimmed, sfupar = param) # The above means if final analysis is done a little early, all spending can occur # Suppose we set calendar date for final analysis based on # estimated full information, but come up with only 97 pct of plan xA <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], .97 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfTrimmed, sfupar = param ) # now accelerate without the trimmed spending function xNT <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], .97 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfHSD, sfupar = 1 ) # Check last bound if analysis done at early time x$upper$bound[4] # Now look at last bound if done at early time with trimmed spending function # that allows capture of full alpha xA$upper$bound[4] # With original spending function, we don't get full alpha and therefore have # unnecessarily stringent bound at final analysis xNT$upper$bound[4] # note that if the last analysis is LATE, all 3 approaches should give the same # final bound that has a little larger z-value xlate <- gsDesign( k = x$k, n.fix = 100, n.I = c(x$n.I[1:3], 1.25 * x$n.I[4]), test.type = x$test.type, maxn.IPlan = x$n.I[x$k], sfu = sfHSD, sfupar = 1 ) xlate$upper$bound[4] # eliminate futility after the first interim analysis # note that by setting trange[1] to .2, the spend at t=.2 is used for the first # interim at or after 20 percent of information x <- gsDesign(n.fix = 100, sfl = sfGapped, sflpar = list(trange = c(.2, .9), sf = sfHSD, param = 1))
Error spending functions based on Xi and Gallo (2019).
The intention of these spending functions is to provide bounds where the
conditional error at an efficacy bound is approximately equal to the
conditional error rate for crossing the final analysis bound.
This is explained in greater detail in
vignette("ConditionalErrorSpending").
sfXG1(alpha, t, param) sfXG2(alpha, t, param) sfXG3(alpha, t, param)sfXG1(alpha, t, param) sfXG2(alpha, t, param) sfXG3(alpha, t, param)
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
This is the gamma parameter in the Xi and Gallo spending function paper, distinct for each function. See the details section for functional forms and range of param acceptable for each spending function. |
Xi and Gallo use an additive boundary for group sequential designs with
connection to conditional error.
Three spending functions are defined: sfXG1(), sfXG2(),
and sfXG3().
Method 1 is defined for as
Method 2 is defined for as
Method 3 is defined as for as
An object of type spendfn. See spending functions for
further details.
Keaven Anderson [email protected]
vignette("SpendingFunctionOverview"), gsDesign,
vignette("gsDesignPackageOverview")
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
Xi D and Gallo P (2019), An additive boundary for group sequential designs with connection to conditional error. Statistics in Medicine; 38 (23), 4656–4669.
# Plot conditional error spending spending functions across # a range of values of interest pts <- seq(0, 1.2, 0.01) pal <- palette() plot( pts, sfXG1(0.025, pts, 0.5)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 1" ) lines(pts, sfXG1(0.025, pts, 0.6)$spend, col = pal[2]) lines(pts, sfXG1(0.025, pts, 0.75)$spend, col = pal[3]) lines(pts, sfXG1(0.025, pts, 0.9)$spend, col = pal[4]) legend( "topleft", legend = c("gamma=0.5", "gamma=0.6", "gamma=0.75", "gamma=0.9"), col = pal[1:4], lty = 1 ) plot( pts, sfXG2(0.025, pts, 0.14)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 2" ) lines(pts, sfXG2(0.025, pts, 0.25)$spend, col = pal[2]) lines(pts, sfXG2(0.025, pts, 0.5)$spend, col = pal[3]) lines(pts, sfXG2(0.025, pts, 0.75)$spend, col = pal[4]) lines(pts, sfXG2(0.025, pts, 0.9)$spend, col = pal[5]) legend( "topleft", legend = c("gamma=0.14", "gamma=0.25", "gamma=0.5", "gamma=0.75", "gamma=0.9"), col = pal[1:5], lty = 1 ) plot( pts, sfXG3(0.025, pts, 0.013)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 3" ) lines(pts, sfXG3(0.025, pts, 0.02)$spend, col = pal[2]) lines(pts, sfXG3(0.025, pts, 0.05)$spend, col = pal[3]) lines(pts, sfXG3(0.025, pts, 0.1)$spend, col = pal[4]) lines(pts, sfXG3(0.025, pts, 0.25)$spend, col = pal[5]) lines(pts, sfXG3(0.025, pts, 0.5)$spend, col = pal[6]) lines(pts, sfXG3(0.025, pts, 0.75)$spend, col = pal[7]) lines(pts, sfXG3(0.025, pts, 0.9)$spend, col = pal[8]) legend( "bottomright", legend = c( "gamma=0.013", "gamma=0.02", "gamma=0.05", "gamma=0.1", "gamma=0.25", "gamma=0.5", "gamma=0.75", "gamma=0.9" ), col = pal[1:8], lty = 1 )# Plot conditional error spending spending functions across # a range of values of interest pts <- seq(0, 1.2, 0.01) pal <- palette() plot( pts, sfXG1(0.025, pts, 0.5)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 1" ) lines(pts, sfXG1(0.025, pts, 0.6)$spend, col = pal[2]) lines(pts, sfXG1(0.025, pts, 0.75)$spend, col = pal[3]) lines(pts, sfXG1(0.025, pts, 0.9)$spend, col = pal[4]) legend( "topleft", legend = c("gamma=0.5", "gamma=0.6", "gamma=0.75", "gamma=0.9"), col = pal[1:4], lty = 1 ) plot( pts, sfXG2(0.025, pts, 0.14)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 2" ) lines(pts, sfXG2(0.025, pts, 0.25)$spend, col = pal[2]) lines(pts, sfXG2(0.025, pts, 0.5)$spend, col = pal[3]) lines(pts, sfXG2(0.025, pts, 0.75)$spend, col = pal[4]) lines(pts, sfXG2(0.025, pts, 0.9)$spend, col = pal[5]) legend( "topleft", legend = c("gamma=0.14", "gamma=0.25", "gamma=0.5", "gamma=0.75", "gamma=0.9"), col = pal[1:5], lty = 1 ) plot( pts, sfXG3(0.025, pts, 0.013)$spend, type = "l", col = pal[1], xlab = "t", ylab = "Spending", main = "Xi-Gallo, Method 3" ) lines(pts, sfXG3(0.025, pts, 0.02)$spend, col = pal[2]) lines(pts, sfXG3(0.025, pts, 0.05)$spend, col = pal[3]) lines(pts, sfXG3(0.025, pts, 0.1)$spend, col = pal[4]) lines(pts, sfXG3(0.025, pts, 0.25)$spend, col = pal[5]) lines(pts, sfXG3(0.025, pts, 0.5)$spend, col = pal[6]) lines(pts, sfXG3(0.025, pts, 0.75)$spend, col = pal[7]) lines(pts, sfXG3(0.025, pts, 0.9)$spend, col = pal[8]) legend( "bottomright", legend = c( "gamma=0.013", "gamma=0.02", "gamma=0.05", "gamma=0.1", "gamma=0.25", "gamma=0.5", "gamma=0.75", "gamma=0.9" ), col = pal[1:8], lty = 1 )
Simulate seasonal rare-event trials monitored with exact binomial efficacy bounds derived from a 'gsSurv' design. This helper supports fixed enrollment and a simple blinded information-adaptive enrollment rule while keeping the spending framework fixed through the original 'gsSurv' design object.
The function summarizes empirical rejection rates (Type I error or power), futility stopping rates (binding interpretation), Monte Carlo standard errors, average final events, average total enrollment, and average number of informative looks.
simBinomialSeasonalExact( gsD, ve = c(0.3, 0.8), nsim = c(600, 600), control_event_rate = c(0.003, 0.003), season_length = 0.5, dropout_rate = 0.1, planned_counts = NULL, timing = NULL, enroll_control_per_look = NULL, enroll_experimental_per_look = NULL, adaptive = c(FALSE, TRUE), adapt_looks = NULL, max_multiplier = 2, usTime = NULL, lsTime = NULL, final_full_spending = FALSE, seed = NULL, return_trials = FALSE )simBinomialSeasonalExact( gsD, ve = c(0.3, 0.8), nsim = c(600, 600), control_event_rate = c(0.003, 0.003), season_length = 0.5, dropout_rate = 0.1, planned_counts = NULL, timing = NULL, enroll_control_per_look = NULL, enroll_experimental_per_look = NULL, adaptive = c(FALSE, TRUE), adapt_looks = NULL, max_multiplier = 2, usTime = NULL, lsTime = NULL, final_full_spending = FALSE, seed = NULL, return_trials = FALSE )
gsD |
A 'gsSurv' object with 'test.type' 1 or 4. |
ve |
Numeric vector of vaccine efficacy (or prevention efficacy) scenarios to simulate. Each value must be finite and less than 1. 've = 0' corresponds to equal event rates (superiority null); 've < 0' corresponds to experimental arm event rates above control (non-inferiority margin or harmful scenarios). |
nsim |
Integer scalar or vector giving the number of simulations per element of 've'. |
control_event_rate |
Numeric scalar or vector with control seasonal event probabilities corresponding to 've'. |
season_length |
Numeric scalar > 0 giving season duration in years. |
dropout_rate |
Seasonal dropout probability in '[0, 1)'. |
planned_counts |
Optional increasing integer vector of planned cumulative events at analyses. If 'NULL', these are derived from 'timing * toInteger(gsD)$n.I[k]'. |
timing |
Optional increasing cumulative spending time vector ending at 1 used to derive 'planned_counts' when 'planned_counts = NULL'. |
enroll_control_per_look |
Optional control arm enrollment by look (scalar or length 'k' integer vector). If both enrollment vectors are 'NULL', defaults are derived from the seasonal accrual pattern in 'gsD'. |
enroll_experimental_per_look |
Optional experimental arm enrollment by look (scalar or length 'k' integer vector). If 'NULL' and 'enroll_control_per_look' is supplied, this is set using 'gsD$ratio'. |
adaptive |
Logical vector specifying whether to simulate fixed and/or adaptive enrollment scenarios. |
adapt_looks |
Integer vector of look indices after which adaptation can be applied (default: all interim looks). |
max_multiplier |
Maximum multiplicative enrollment increase at a look when adaptation is enabled. |
usTime |
Optional upper spending time override passed to [toBinomialExact()]. If 'NULL', spending time defaults to '1 / k, 2 / k, ..., 1'. |
lsTime |
Optional lower spending time override for 'test.type = 4'. If 'NULL', this defaults to 'usTime'. |
final_full_spending |
Logical scalar. If 'TRUE', force full alpha spending at the final analysis even when the final observed total event count is below planned final events. |
seed |
Optional integer seed for reproducibility. |
return_trials |
Logical. If 'TRUE', return trial-level simulation outcomes. |
A list with:
Data frame with scenario-level summaries.
List with planned counts, exact design object, and planned/calibrated enrollment by look.
List of simulation inputs used.
Optional trial-level data frame ('NULL' unless 'return_trials = TRUE').
[toBinomialExact()], [repeatedPValueBinomialExact()], [sequentialPValueBinomialExact()]
x <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(1 / 3, 2 / 3), sfu = sfHSD, sfupar = 1, sfl = sfHSD, sflpar = -2, lambdaC = -log(1 - 0.003) / 0.5, hr = 0.2, hr0 = 0.7, eta = -log(1 - 0.1) / 0.5, gamma = c(1, 0, 1, 0, 1, 0), R = c(2, 10, 2, 10, 2, 10), T = 42, minfup = 6, ratio = 3 ) |> toInteger() simBinomialSeasonalExact( gsD = x, ve = c(0.3, 0.8), nsim = c(50, 50), control_event_rate = c(0.003, 0.003), seed = 123 )$summaryx <- gsSurv( k = 3, test.type = 4, alpha = 0.025, beta = 0.1, timing = c(1 / 3, 2 / 3), sfu = sfHSD, sfupar = 1, sfl = sfHSD, sflpar = -2, lambdaC = -log(1 - 0.003) / 0.5, hr = 0.2, hr0 = 0.7, eta = -log(1 - 0.1) / 0.5, gamma = c(1, 0, 1, 0, 1, 0), R = c(2, 10, 2, 10, 2, 10), T = 42, minfup = 6, ratio = 3 ) |> toInteger() simBinomialSeasonalExact( gsD = x, ve = c(0.3, 0.8), nsim = c(50, 50), control_event_rate = c(0.003, 0.003), seed = 123 )$summary
A tabular summary of a group sequential design's bounds and their properties
are often useful. The 'vintage' print.gsDesign() function provides a
complete but minimally formatted summary of a group sequential design
derived by gsDesign(). A brief description of the overall design can
also be useful (summary.gsDesign(). A tabular summary of boundary
characteristics oriented only towards LaTeX output is produced by
xtable.gsSurv. More flexibility is provided by
gsBoundSummary() which produces a tabular summary of a
user-specifiable set of package-provided boundary properties in a data
frame. This can also be used to along with functions such as
print.data.frame(), write.table(),
write.csv(), write.csv2() or, from the RTF
package, addTable.RTF() (from the rtf package) to produce console or
R Markdown output or output to a variety of file types. xprint() is
provided for LaTeX output by setting default options for
print.xtable when producing tables summarizing design
bounds.
Single-analysis fixed designs are supported; interim-only characteristics
such as conditional and predictive power are omitted when k = 1.
Individual transformation of z-value test statistics for interim and final
analyses are obtained from gsBValue(), gsDelta(),
gsHR() and gsCPz() for B-values, approximate treatment effect
(see details), approximate hazard ratio and conditional power, respectively.
The print.gsDesign function is intended to provide an easier output
to review than is available from a simple list of all the output components.
The gsBoundSummary function is intended to provide a summary of
boundary characteristics that is often useful for evaluating boundary
selection; this outputs an extension of the data.frame class that
sets up default printing without row names using
print.gsBoundSummary. summary.gsDesign, on the other hand,
provides a summary of the overall design at a higher level; this provides
characteristics not included in the gsBoundSummary summary and no
detail concerning interim analysis bounds.
In brief, the computed descriptions of group sequential design bounds are as
follows: Z: Standardized normal test statistic at design bound.
p (1-sided): 1-sided p-value for Z. This will be computed as
the probability of a greater EXCEPT for lower bound when a 2-sided design is
being summarized.
delta at bound: Approximate value of the natural parameter at the
bound. The approximate standardized effect size at the bound is generally
computed as Z/sqrt(n). Calling this theta, this is translated
to the delta using the values delta0 and delta1 from
the input x by the formula delta0 +
(delta1-delta0)/theta1*theta where theta1 is the alternate
hypothesis value of the standardized parameter. Note that this value will be
exponentiated in the case of relative risks, hazard ratios or when the user
specifies logdelta=TRUE. In the case of hazard ratios, the value is
computed instead by gsHR() to be consistent with
plot.gsDesign(). Similarly, the value is computed by gsRR()
when the relative risk is the natural parameter.
For HR summaries with logdelta = TRUE (or class gsSurv),
missing x$hr0 and x$hr are recovered from
exp(x$delta0) and exp(x$delta1), respectively, when finite
and positive. This preserves the null HR and effect direction when a
survival design is reconstructed with gsDesign(). Explicit HR
fields take precedence. If the null HR cannot be recovered, a warning
is issued and HR at bound calculations assume hr0 = 1.
Spending: Incremental error spending at each given analysis. For
asymmetric designs, futility bound will have beta-spending summarized.
Efficacy bound always has alpha-spending summarized.
For types 7 and 8, harm spending is calibrated under the null ignoring
futility stopping (but retaining efficacy stopping); capping at an active
futility bound can reduce attained spending. It need not equal the
increment in the reported harm crossing probability.
B-value: sqrt(t)*Z where t is the proportion of
information at the analysis divided by the final analysis planned
information. The expected value for B-values is directly proportional to
t.
CP: Conditional power under the estimated treatment difference
assuming the interim Z-statistic is at the study bound
CP H1: Conditional power under the alternate hypothesis treatment
effect assuming the interim test statistic is at the study bound.
PP: Predictive power assuming the interim test statistic is at the
study bound and the input prior distribution for the standardized effect
size. This is the conditional power averaged across the posterior
distribution for the treatment effect given the interim test statistic
value. P{Cross if delta=xx}: For each of the parameter values in
x, the probability of crossing either bound given that treatment
effect is computed. This value is cumulative for each bound. For example,
the probability of crossing the efficacy bound at or before the analysis of
interest. For test types 7 and 8, harm, futility, and efficacy crossing
probabilities are mutually exclusive and assume all active stopping rules
are followed, including futility even for a non-binding design. Harm
crossing probabilities can therefore be below the harm spending targets.
Every characteristic for a bound,
including its cumulative crossing probability, is shown as NA at an
analysis where that bound is inactive. The underlying probability arrays on
x retain the cumulative crossing information.
## S3 method for class 'gsDesign' summary(object, information = FALSE, timeunit = "months", ...) ## S3 method for class 'gsDesign' print(x, ...) gsBoundSummary( x, deltaname = NULL, logdelta = FALSE, Nname = NULL, digits = 4, ddigits = 2, tdigits = 0, timename = "Month", prior = normalGrid(mu = x$delta/2, sigma = 10/sqrt(x$n.fix)), POS = FALSE, ratio = NULL, exclude = c("B-value", "Spending", "CP", "CP H1", "PP"), r = 18, alpha = NULL, ... ) xprint( x, include.rownames = FALSE, hline.after = c(-1, which(x$Value == x[1, ]$Value) - 1, nrow(x)), ... ) ## S3 method for class 'gsBoundSummary' print(x, row.names = FALSE, digits = 4, ...) gsBValue(z, i, x, ylab = "B-value", ...) gsDelta(z, i, x, ylab = NULL, ...) gsRR(z, i, x, ratio = 1, ylab = "Approximate risk ratio", ...) gsHR(z, i, x, ratio = 1, ylab = "Approximate hazard ratio", ...) gsCPz(z, i, x, theta = NULL, ylab = NULL, ...)## S3 method for class 'gsDesign' summary(object, information = FALSE, timeunit = "months", ...) ## S3 method for class 'gsDesign' print(x, ...) gsBoundSummary( x, deltaname = NULL, logdelta = FALSE, Nname = NULL, digits = 4, ddigits = 2, tdigits = 0, timename = "Month", prior = normalGrid(mu = x$delta/2, sigma = 10/sqrt(x$n.fix)), POS = FALSE, ratio = NULL, exclude = c("B-value", "Spending", "CP", "CP H1", "PP"), r = 18, alpha = NULL, ... ) xprint( x, include.rownames = FALSE, hline.after = c(-1, which(x$Value == x[1, ]$Value) - 1, nrow(x)), ... ) ## S3 method for class 'gsBoundSummary' print(x, row.names = FALSE, digits = 4, ...) gsBValue(z, i, x, ylab = "B-value", ...) gsDelta(z, i, x, ylab = NULL, ...) gsRR(z, i, x, ratio = 1, ylab = "Approximate risk ratio", ...) gsHR(z, i, x, ratio = 1, ylab = "Approximate hazard ratio", ...) gsCPz(z, i, x, theta = NULL, ylab = NULL, ...)
object |
An item of class |
information |
indicator of whether |
timeunit |
Text string with time units used for time-to-event designs
created with |
... |
This allows many optional arguments that are standard when
calling |
x |
An item of class |
deltaname |
Natural parameter name. If default |
logdelta |
Indicates whether natural parameter is the natural logarithm
of the actual parameter. For example, the relative risk or odds-ratio would
be put on the logarithmic scale since the asymptotic behavior is 'more
normal' than a non-transformed value. As with |
Nname |
This will normally be changed to |
digits |
Number of digits past the decimal to be printed in the body of the table. |
ddigits |
Number of digits past the decimal to be printed for the natural parameter delta. |
tdigits |
Number of digits past the decimal point to be shown for estimated timing of each analysis. |
timename |
Text string indicating time unit. |
prior |
A prior distribution for the standardized effect size. Must be
of the format produced by |
POS |
This is an indicator of whether or not probability of success
(POS) should be estimated at baseline or at each interim based on the prior
distribution input in |
ratio |
Sample size ratio assumed for experimental to control treatment
group sample sizes. This only matters when |
exclude |
A list of test statistics to be excluded from design boundary
summary produced; see details or examples for a list of all possible output
values. A value of |
r |
See |
alpha |
If used, a vector of alternate alpha-levels to print boundaries
for. Only works with non-binding test types 1, 4, 6, and 8. If specified,
efficacy bound
columns are headed by individual alpha levels. The alpha level of the input
design is always included as the first column. Alternate alpha levels retain
the efficacy testing schedule in |
include.rownames |
indicator of whether or not to include row names in output. |
hline.after |
table lines after which horizontal separation lines should be set; default is to put lines between each analysis as well as at the top and bottom of the table. |
row.names |
indicator of whether or not to print row names |
z |
A vector of z-statistics |
i |
A vector containing the analysis for each element in |
ylab |
Used when functions are passed to |
theta |
A scalar value representing the standardized effect size used
for conditional power calculations; see |
For survival designs, summary() identifies the stored
sample size/power calculation method, when available. The method is not
displayed by gsBoundSummary(). Each analysis block accommodates
both its annotations and the selected boundary statistics, padding the
shorter side. Padding has an empty Value and missing numeric values;
the print method displays these cells as blanks.
gsBValue(), gsDelta(), gsHR() and
gsCPz() each returns a vector containing the B-values, approximate
treatment effect (see details), approximate hazard ratio and conditional
power, respectively, for each value specified by the interim test statistics
in z at interim analyses specified in i.
summary returns a text string summarizing the design at a high level.
This may be used with gsBoundSummary for a nicely formatted, concise
group sequential design description.
gsBoundSummary returns a table in a data frame providing a variety of
boundary characteristics. The tabular format makes formatting particularly
amenable to place in documents either through direct creation of readable by
Word (see the rtf package) or to a csv format readable by spreadsheet
software using write.csv.
print.gsDesign prints an overall summary a group sequential design.
While the design description is complete, the format is not as 'document
friendly' as gsBoundSummary.
print.gsBoundSummary is a simple extension of print.data.frame
intended for objects created with gsBoundSummary. The only extension
is to make the default to not print row names. This is probably 'not good R
style' but may be helpful for many lazy R programmers like the author.
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
gsDesign, plot.gsDesign,
gsProbability, xtable.gsSurv
library(ggplot2) # survival endpoint using gsSurv # generally preferred over nSurv since time computations are shown xgs <- gsSurv(lambdaC = .2, hr = .5, eta = .1, T = 2, minfup = 1.5) gsBoundSummary(xgs, timename = "Year", tdigits = 1) summary(xgs) # survival endpoint using nSurvival # NOTE: generally recommend gsSurv above for this! ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025, ratio = 2 ) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) # generate some of the above summary statistics for the upper bound z <- xs$upper$bound # B-values gsBValue(z = z, i = 1:3, x = xs) # hazard ratio gsHR(z = z, i = 1:3, x = xs) # conditional power at observed treatment effect gsCPz(z = z[1:2], i = 1:2, x = xs) # conditional power at H1 treatment effect gsCPz(z = z[1:2], i = 1:2, x = xs, theta = xs$delta) # information-based design xinfo <- gsDesign(delta = .3, delta1 = .3) gsBoundSummary(xinfo, Nname = "Information") # show all available boundary descriptions gsBoundSummary(xinfo, Nname = "Information", exclude = NULL) # add intermediate parameter value xinfo <- gsProbability(d = xinfo, theta = c(0, .15, .3)) class(xinfo) # note this is still as gsDesign class object gsBoundSummary(xinfo, Nname = "Information") # now look at a binomial endpoint; specify H0 treatment difference as p1-p2=.05 # now treatment effect at bound (say, thetahat) is transformed to # xp$delta0 + xp$delta1*(thetahat-xp$delta0)/xp$delta np <- nBinomial(p1 = .15, p2 = .10) xp <- gsDesign(n.fix = np, endpoint = "Binomial", delta1 = .05) summary(xp) gsBoundSummary(xp, deltaname = "p[C]-p[E]") # estimate treatment effect at lower bound # by setting delta0=0 (default) and delta1 above in gsDesign # treatment effect at bounds is scaled to these differences # in this case, this is the difference in event rates gsDelta(z = xp$lower$bound, i = 1:3, xp) # binomial endpoint with risk ratio estimates n.fix <- nBinomial(p1 = .3, p2 = .15, scale = "RR") xrr <- gsDesign(k = 2, n.fix = n.fix, delta1 = log(.15 / .3), endpoint = "Binomial") gsBoundSummary(xrr, deltaname = "RR", logdelta = TRUE) gsRR(z = xp$lower$bound, i = 1:3, xrr) plot(xrr, plottype = "RR") # delta is odds-ratio: sample size slightly smaller than for relative risk or risk difference n.fix <- nBinomial(p1 = .3, p2 = .15, scale = "OR") xOR <- gsDesign(k = 2, n.fix = n.fix, delta1 = log(.15 / .3 / .85 * .7), endpoint = "Binomial") gsBoundSummary(xOR, deltaname = "OR", logdelta = TRUE) # for nice LaTeX table output, use xprint xprint(xtable::xtable(gsBoundSummary(xOR, deltaname = "OR", logdelta = TRUE), caption = "Table caption." ))library(ggplot2) # survival endpoint using gsSurv # generally preferred over nSurv since time computations are shown xgs <- gsSurv(lambdaC = .2, hr = .5, eta = .1, T = 2, minfup = 1.5) gsBoundSummary(xgs, timename = "Year", tdigits = 1) summary(xgs) # survival endpoint using nSurvival # NOTE: generally recommend gsSurv above for this! ss <- nSurvival( lambda1 = .2, lambda2 = .1, eta = .1, Ts = 2, Tr = .5, sided = 1, alpha = .025, ratio = 2 ) xs <- gsDesign(nFixSurv = ss$n, n.fix = ss$nEvents, delta1 = log(ss$lambda2 / ss$lambda1)) gsBoundSummary(xs, logdelta = TRUE, ratio = ss$ratio) # generate some of the above summary statistics for the upper bound z <- xs$upper$bound # B-values gsBValue(z = z, i = 1:3, x = xs) # hazard ratio gsHR(z = z, i = 1:3, x = xs) # conditional power at observed treatment effect gsCPz(z = z[1:2], i = 1:2, x = xs) # conditional power at H1 treatment effect gsCPz(z = z[1:2], i = 1:2, x = xs, theta = xs$delta) # information-based design xinfo <- gsDesign(delta = .3, delta1 = .3) gsBoundSummary(xinfo, Nname = "Information") # show all available boundary descriptions gsBoundSummary(xinfo, Nname = "Information", exclude = NULL) # add intermediate parameter value xinfo <- gsProbability(d = xinfo, theta = c(0, .15, .3)) class(xinfo) # note this is still as gsDesign class object gsBoundSummary(xinfo, Nname = "Information") # now look at a binomial endpoint; specify H0 treatment difference as p1-p2=.05 # now treatment effect at bound (say, thetahat) is transformed to # xp$delta0 + xp$delta1*(thetahat-xp$delta0)/xp$delta np <- nBinomial(p1 = .15, p2 = .10) xp <- gsDesign(n.fix = np, endpoint = "Binomial", delta1 = .05) summary(xp) gsBoundSummary(xp, deltaname = "p[C]-p[E]") # estimate treatment effect at lower bound # by setting delta0=0 (default) and delta1 above in gsDesign # treatment effect at bounds is scaled to these differences # in this case, this is the difference in event rates gsDelta(z = xp$lower$bound, i = 1:3, xp) # binomial endpoint with risk ratio estimates n.fix <- nBinomial(p1 = .3, p2 = .15, scale = "RR") xrr <- gsDesign(k = 2, n.fix = n.fix, delta1 = log(.15 / .3), endpoint = "Binomial") gsBoundSummary(xrr, deltaname = "RR", logdelta = TRUE) gsRR(z = xp$lower$bound, i = 1:3, xrr) plot(xrr, plottype = "RR") # delta is odds-ratio: sample size slightly smaller than for relative risk or risk difference n.fix <- nBinomial(p1 = .3, p2 = .15, scale = "OR") xOR <- gsDesign(k = 2, n.fix = n.fix, delta1 = log(.15 / .3 / .85 * .7), endpoint = "Binomial") gsBoundSummary(xOR, deltaname = "OR", logdelta = TRUE) # for nice LaTeX table output, use xprint xprint(xtable::xtable(gsBoundSummary(xOR, deltaname = "OR", logdelta = TRUE), caption = "Table caption." ))
Spending Function
## S3 method for class 'spendfn' summary(object, ...) spendingFunction(alpha, t, param)## S3 method for class 'spendfn' summary(object, ...) spendingFunction(alpha, t, param)
object |
A spendfn object to be summarized. |
... |
Not currently used. |
alpha |
Real value |
t |
A vector of points with increasing values from 0 to 1, inclusive. Values of the proportion of sample size/information for which the spending function will be computed. |
param |
A single real value or a vector of real values specifying the spending function parameter(s); this must be appropriately matched to the spending function specified. |
spendingFunction and spending functions in general produce an
object of type spendfn.
name |
A character string with the name of the spending function. |
param |
any parameters used for the spending function. |
parname |
a character string or strings with the name(s) of
the parameter(s) in |
sf |
the spending function specified. |
spend |
a vector of cumulative spending values corresponding to
the input values in |
bound |
this is null when returned from the spending function,
but is set in |
prob |
this is null when returned from the spending function,
but is set in |
The gsDesign technical manual is available at https://keaven.github.io/gsd-tech-manual/.
Keaven Anderson [email protected]
Jennison C and Turnbull BW (2000), Group Sequential Methods with Applications to Clinical Trials. Boca Raton: Chapman and Hall.
gsDesign, sfHSD, sfPower,
sfLogistic, sfExponential,
sfTruncated, vignette("gsDesignPackageOverview")
# Example 1: simple example showing what most users need to know # Design a 4-analysis trial using a Hwang-Shih-DeCani spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfHSD, sfupar = -2, sfl = sfHSD, sflpar = 1) # Print the design x # Summarize the spending functions summary(x$upper) summary(x$lower) # Plot the alpha- and beta-spending functions plot(x, plottype = 5) # What happens to summary if we used a boundary function design x <- gsDesign(test.type = 2, sfu = "OF") y <- gsDesign(test.type = 1, sfu = "WT", sfupar = .25) summary(x$upper) summary(y$upper) # Example 2: advanced example: writing a new spending function # Most users may ignore this! # Implementation of 2-parameter version of # beta distribution spending function # assumes t and alpha are appropriately specified (does not check!) sfbdist <- function(alpha, t, param) { # Check inputs checkVector(param, "numeric", c(0, Inf), c(FALSE, TRUE)) if (length(param) != 2) { stop( "b-dist example spending function parameter must be of length 2" ) } # Set spending using cumulative beta distribution and return x <- list( name = "B-dist example", param = param, parname = c("a", "b"), sf = sfbdist, spend = alpha * pbeta(t, param[1], param[2]), bound = NULL, prob = NULL ) class(x) <- "spendfn" x } # Now try it out! # Plot some example beta (lower bound) spending functions using # the beta distribution spending function t <- 0:100 / 100 plot( t, sfbdist(1, t, c(2, 1))$spend, type = "l", xlab = "Proportion of information", ylab = "Cumulative proportion of total spending", main = "Beta distribution Spending Function Example" ) lines(t, sfbdist(1, t, c(6, 4))$spend, lty = 2) lines(t, sfbdist(1, t, c(.5, .5))$spend, lty = 3) lines(t, sfbdist(1, t, c(.6, 2))$spend, lty = 4) legend( x = c(.65, 1), y = 1 * c(0, .25), lty = 1:4, legend = c("a=2, b=1", "a=6, b=4", "a=0.5, b=0.5", "a=0.6, b=2") )# Example 1: simple example showing what most users need to know # Design a 4-analysis trial using a Hwang-Shih-DeCani spending function # for both lower and upper bounds x <- gsDesign(k = 4, sfu = sfHSD, sfupar = -2, sfl = sfHSD, sflpar = 1) # Print the design x # Summarize the spending functions summary(x$upper) summary(x$lower) # Plot the alpha- and beta-spending functions plot(x, plottype = 5) # What happens to summary if we used a boundary function design x <- gsDesign(test.type = 2, sfu = "OF") y <- gsDesign(test.type = 1, sfu = "WT", sfupar = .25) summary(x$upper) summary(y$upper) # Example 2: advanced example: writing a new spending function # Most users may ignore this! # Implementation of 2-parameter version of # beta distribution spending function # assumes t and alpha are appropriately specified (does not check!) sfbdist <- function(alpha, t, param) { # Check inputs checkVector(param, "numeric", c(0, Inf), c(FALSE, TRUE)) if (length(param) != 2) { stop( "b-dist example spending function parameter must be of length 2" ) } # Set spending using cumulative beta distribution and return x <- list( name = "B-dist example", param = param, parname = c("a", "b"), sf = sfbdist, spend = alpha * pbeta(t, param[1], param[2]), bound = NULL, prob = NULL ) class(x) <- "spendfn" x } # Now try it out! # Plot some example beta (lower bound) spending functions using # the beta distribution spending function t <- 0:100 / 100 plot( t, sfbdist(1, t, c(2, 1))$spend, type = "l", xlab = "Proportion of information", ylab = "Cumulative proportion of total spending", main = "Beta distribution Spending Function Example" ) lines(t, sfbdist(1, t, c(6, 4))$spend, lty = 2) lines(t, sfbdist(1, t, c(.5, .5))$spend, lty = 3) lines(t, sfbdist(1, t, c(.6, 2))$spend, lty = 4) legend( x = c(.65, 1), y = 1 * c(0, .25), lty = 1:4, legend = c("a=2, b=1", "a=6, b=4", "a=0.5, b=0.5", "a=0.6, b=2") )
nSurv() is used to calculate the sample size for a two-arm clinical trial
with a time-to-event endpoint under the assumption of proportional hazards.
The default method assumes a fixed enrollment duration and fixed trial duration;
in this case the required sample size is achieved by increasing enrollment rates.
nSurv() implements the Lachin and Foulkes (1986) method as default.
Schoenfeld (1981), Freedman (1982), and Bernstein and Lagakos (1989) methods
are also supported; see Details.
gsSurv() combines nSurv() with gsDesign() to derive a
group sequential design for a study with a time-to-event endpoint.
When k = 1, gsSurv() uses the fixed design calculations from
nSurv() directly and returns a normalized single-analysis
gsSurv object for use with functions such as
toInteger and gsBoundSummary.
tEventsIA(x, timing = 0.25, tol = .Machine$double.eps^0.25) nEventsIA(tIA = 5, x = NULL, target = 0, simple = TRUE) nSurv( lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, T = 18, minfup = 6, ratio = 1, alpha = 0.025, beta = 0.1, sided = 1, tol = .Machine$double.eps^0.25, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") ) ## S3 method for class 'nSurv' print(x, digits = 3, show_strata = TRUE, ...) ## S3 method for class 'gsSurv' xtable( x, caption = NULL, label = NULL, align = NULL, digits = NULL, display = NULL, auto = FALSE, footnote = NULL, fnwid = "9cm", timename = "months", ... ) gsSurv( k = 3, test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, astar = 0, timing = 1, sfu = sfHSD, sfupar = -4, sfl = sfHSD, sflpar = -2, sfharm = sfLDPocock, sfharmparam = -2, r = 18, lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, T = 18, minfup = 6, ratio = 1, tol = .Machine$double.eps^0.25, usTime = NULL, lsTime = NULL, testUpper = TRUE, testLower = TRUE, testHarm = TRUE, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") ) ## S3 method for class 'gsSurv' print(x, digits = 3, show_gsDesign = FALSE, show_strata = TRUE, ...)tEventsIA(x, timing = 0.25, tol = .Machine$double.eps^0.25) nEventsIA(tIA = 5, x = NULL, target = 0, simple = TRUE) nSurv( lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, T = 18, minfup = 6, ratio = 1, alpha = 0.025, beta = 0.1, sided = 1, tol = .Machine$double.eps^0.25, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") ) ## S3 method for class 'nSurv' print(x, digits = 3, show_strata = TRUE, ...) ## S3 method for class 'gsSurv' xtable( x, caption = NULL, label = NULL, align = NULL, digits = NULL, display = NULL, auto = FALSE, footnote = NULL, fnwid = "9cm", timename = "months", ... ) gsSurv( k = 3, test.type = 4, alpha = 0.025, sided = 1, beta = 0.1, astar = 0, timing = 1, sfu = sfHSD, sfupar = -4, sfl = sfHSD, sflpar = -2, sfharm = sfLDPocock, sfharmparam = -2, r = 18, lambdaC = log(2)/6, hr = 0.6, hr0 = 1, eta = 0, etaE = NULL, gamma = 1, R = 12, S = NULL, T = 18, minfup = 6, ratio = 1, tol = .Machine$double.eps^0.25, usTime = NULL, lsTime = NULL, testUpper = TRUE, testLower = TRUE, testHarm = TRUE, method = c("LachinFoulkes", "Schoenfeld", "Freedman", "BernsteinLagakos") ) ## S3 method for class 'gsSurv' print(x, digits = 3, show_gsDesign = FALSE, show_strata = TRUE, ...)
x |
An object of class |
timing |
Sets relative timing of interim analyses in |
tol |
For cases when |
tIA |
Timing of an interim analysis; should be between 0 and
|
target |
The targeted proportion of events at an interim analysis. This is used for root-finding will be 0 for normal use. |
simple |
See output specification for |
lambdaC |
Scalar, vector or matrix of event hazard rates for the control group; rows represent time periods while columns represent strata; a vector implies a single stratum. Note that rates corresponding the final time period are extended indefinitely. |
hr |
Hazard ratio (experimental/control) under the alternate hypothesis
(scalar, > 0, must differ from |
hr0 |
Hazard ratio (experimental/control) under the null hypothesis
(scalar, > 0, must differ from |
eta |
Scalar, vector or matrix of dropout hazard rates for the control group; rows represent time periods while columns represent strata; if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
etaE |
Matrix dropout hazard rates for the experimental group specified
in like form as |
gamma |
A scalar, vector or matrix of rates of entry by time period (rows) and strata (columns); if entered as a scalar, rate is constant across strata and time periods; if entered as a vector, rates are constant across strata. |
R |
A scalar or vector of durations of time periods for recruitment
rates specified in rows of |
S |
A scalar or vector of durations of piecewise constant event rates
specified in rows of |
T |
Study duration; if |
minfup |
Follow-up of last patient enrolled; if |
ratio |
Randomization ratio of experimental treatment divided by control; normally a scalar, but may be a vector with length equal to number of strata. |
alpha |
Type I error rate. Default is 0.025 since 1-sided testing is default. |
beta |
Type II error rate. Default is 0.10 (90% power); |
sided |
|
method |
One of |
digits |
Number of digits past the decimal place to print
( |
show_strata |
Logical; for |
... |
Other arguments that may be passed to generic functions underlying the methods here. |
caption |
Passed through to generic |
label |
Passed through to generic |
align |
Passed through to generic |
display |
Passed through to generic |
auto |
Passed through to generic |
footnote |
Footnote for xtable output; may be useful for describing some of the design parameters. |
fnwid |
A text string controlling the width of footnote text at the bottom of the xtable output. |
timename |
Character string with plural of time units (e.g., "months") |
k |
Number of analyses planned, including interim and final. |
test.type |
|
astar |
Total spending for the lower (test.type 5 or 6) or harm
(test.type 7 or 8) bound under the null hypothesis. Default is 0.
For |
sfu |
A spending function or a character string indicating a boundary
type (that is, “WT” for Wang-Tsiatis bounds, “OF” for
O'Brien-Fleming bounds and “Pocock” for Pocock bounds). For
one-sided and symmetric two-sided testing is used to completely specify
spending ( |
sfupar |
Real value, default is |
sfl |
Specifies the spending function for lower boundary crossing
probabilities when asymmetric, two-sided testing is performed
( |
sflpar |
Real value, default is |
sfharm |
A spending function for the harm bound, used with
|
sfharmparam |
Real value, default is |
r |
Integer value (>= 1 and <= 80) controlling the number of numerical
integration grid points. Default is 18, as recommended by Jennison and
Turnbull (2000). Grid points are spread out in the tails for accurate
probability calculations. Larger values provide more grid points and greater
accuracy but slow down computation. Jennison and Turnbull (p. 350) note an
accuracy of |
usTime |
Default is NULL in which case upper bound spending time is
determined by |
lsTime |
Default is NULL in which case lower bound spending time is
determined by |
testUpper |
Indicator of which analyses should include an upper
(efficacy) bound.
A single value of |
testLower |
Indicator of which analyses should include a lower
(futility) bound.
A single value of |
testHarm |
Indicator of which analyses should include a harm bound.
A single value of |
show_gsDesign |
Logical; for |
The Lachin and Foulkes method uses both null and alternate hypothesis
variances to derive sample size and is extended here to support
non-inferiority, super-superiority, and stratified designs.
As an alternative, the Kim and Tsiatis (1990) method can be used with fixed
enrollment rates and either fixed enrollment duration or fixed minimum
follow-up.
The Schoenfeld approach uses the asymptotic distribution of the log-rank
statistic under the assumption of proportional hazards and local alternatives
(i.e., is small). The Freedman approach uses the same
asymptotic distribution and, like the Schoenfeld approach, uses just the
null hypothesis variance to derive sample size.
The Bernstein and Lagakos (1989) approach was derived to compute sample size
for a stratified model with a common proportional hazards assumption across
strata. Like the Lachin and Foulkes (1986) method, it uses both null and
alternate hypothesis variances to compute sample size; however, the null
hypothesis variance is computed differently. The Bernstein and Lagakos (1989)
approach uses the alternate hypothesis failure rate assumptions for both the
control and experimental groups, while the Lachin and Foulkes method uses
null hypothesis rates that average the alternate hypothesis failure rates to
get similar numbers of expected events under the null and alternate
hypotheses. Since the Lachin and Foulkes method has fewer events under the
null hypothesis (less statistical information), it calculates less power
than the Bernstein and Lagakos method.
Piecewise exponential survival is supported as well as piecewise constant
enrollment and dropout rates. The methods are for a 2-arm trial with
treatment groups referred to as experimental and control. A stratified
population is allowed as in Lachin and Foulkes (1986); this method has been
extended to derive non-inferiority as well as superiority trials.
Stratification also allows power calculation for meta-analyses.
print(), xtable() and summary() methods are provided to
operate on the returned value from gsSurv(), an object of class
gsSurv. print() is also extended to nSurv objects. The
functions gsBoundSummary (data frame for tabular output),
xprint (application of xtable for tabular output) and
summary.gsSurv (textual summary of gsDesign or gsSurv
object) may be preferred summary functions; see example in vignettes. See
also gsBoundSummary for output
of tabular summaries of bounds for designs produced by gsSurv().
Both nEventsIA and tEventsIA require a group sequential design
for a time-to-event endpoint of class gsSurv as input.
nEventsIA calculates the expected number of events under the
alternate hypothesis at a given interim time. tEventsIA calculates
the time that the expected number of events under the alternate hypothesis
is a given proportion of the total events planned for the final analysis.
nSurv() produces an object of class nSurv with the number of
subjects and events for a set of pre-specified trial parameters, such as
accrual duration and follow-up period. The underlying power calculation is
based on Lachin and Foulkes (1986) method for proportional hazards assuming
a fixed underlying hazard ratio between 2 treatment groups. The method has
been extended here to enable designs to test non-inferiority. Piecewise
constant enrollment and failure rates are assumed and a stratified
population is allowed. See also nSurvival for other Lachin and
Foulkes (1986) methods assuming a constant hazard difference or exponential
enrollment rate.
When study duration (T) and follow-up duration (minfup) are
fixed, nSurv applies exactly the Lachin and Foulkes (1986) method of
computing sample size under the proportional hazards assumption. For this
computation, enrollment rates are altered proportionately to those input in
gamma to achieve the power of interest.
Given the specified enrollment rate(s) input in gamma, nSurv
may also be used to derive enrollment duration required for a trial to have
defined power if T is input as NULL; in this case, both
R (enrollment duration for each specified enrollment rate) and
T (study duration) will be computed on output.
Alternatively and also using the fixed enrollment rate(s) in gamma,
if minimum follow-up minfup is specified as NULL, then the
enrollment duration(s) specified in R are considered fixed and
minfup and T are computed to derive the desired power. This
method will fail if the specified enrollment rates and durations either
over-powers the trial with no additional follow-up or underpowers the trial
with infinite follow-up. This method produces a corresponding error message
in such cases. For methods other than Lachin and Foulkes, these fixed rate
duration solves use the selected method for the fixed design event
calculation.
The input to gsSurv is a combination of the input to nSurv()
and gsDesign().
The original call is stored in call. The inputs component
retains evaluated survival model inputs used for printing and the applicable
testUpper, testLower, and testHarm arguments. The
normalized testing schedules used by the design are stored directly in the
corresponding top-level components.
When T = NULL and minfup is specified, gsSurv()
preserves the input accrual rate and minimum follow-up, applies the group
sequential design, and solves the accrual duration needed for the final
planned number of events.
When both T and minfup are NULL, gsSurv()
preserves the input accrual rate and duration, applies the group sequential
design, and solves the follow-up duration needed for the final planned
number of events.
nEventsIA() is provided to compute the expected number of events at a
given point in time given enrollment, event and censoring rates. The routine
is used with a root finding routine to approximate the approximate timing of
an interim analysis. It is also used to extend enrollment or follow-up of a
fixed design to obtain a sufficient number of events to power a group
sequential design.
nSurv() returns an object of type nSurv with the
following components:
alpha |
As input. |
sided |
As input. |
beta |
Type II error; if missing, this is computed. |
power |
Power corresponding to input |
lambdaC |
As input. |
etaC |
As input. |
etaE |
As input. |
gamma |
As input unless none of the following are |
ratio |
As input. |
R |
As input unless |
S |
As input. |
T |
As input. |
minfup |
As input. |
hr |
As input. |
hr0 |
As input. |
n |
Total expected sample size corresponding to output accrual rates and durations. |
N |
Identical to |
d |
Total expected number of events under the alternate hypothesis. |
tol |
As input, except when not used in computations in
which case this is returned as |
eDC |
A vector of expected number of events by stratum in the control group under the alternate hypothesis. |
eDE |
A vector of expected number of events by stratum in the experimental group under the alternate hypothesis. |
eDC0 |
A vector of expected number of events by stratum in the control group under the null hypothesis. |
eDE0 |
A vector of expected number of events by stratum in the experimental group under the null hypothesis. |
eNC |
A vector of the expected accrual in each stratum in the control group. |
eNE |
A vector of the expected accrual in each stratum in the experimental group. |
variable |
A text string equal to "Accrual rate" if a design was
derived by varying the accrual rate, "Accrual duration" if a design was
derived by varying the accrual duration, "Follow-up duration" if a design
was derived by varying follow-up duration, or "Power" if accrual rates and
duration as well as follow-up duration was specified and
|
gsSurv() returns much of the above plus an object of class
gsDesign in a variable named gs; see gsDesign
for general documentation on what is returned in gs. The value of
gs$n.I represents the number of endpoints required at each analysis
to adequately power the trial. Other items returned by gsSurv() are:
gs |
A group sequential design ( |
lambdaC |
As input. |
etaC |
As input. |
etaE |
As input. |
gamma |
As input unless none of the following are |
ratio |
As input. |
R |
As input unless |
S |
As input. |
T |
As input. |
minfup |
As input. |
hr |
As input. |
hr0 |
As input. |
N |
A vector containing cumulative total expected enrollment at each analysis. |
eNC |
Total expected sample size corresponding to output accrual rates and durations. |
eNE |
Total expected sample size corresponding to output accrual rates and durations. |
eDC |
Total expected number of events under the alternate hypothesis. |
eDE |
Total expected number of events under the alternate hypothesis. |
tol |
As input, except when not used in computations in which case
this is returned as |
eDC |
A vector of expected number of events by stratum in the control group under the alternate hypothesis. |
eDE |
A vector of expected number of events by stratum in the experimental group under the alternate hypothesis. |
eDC0 |
A vector of expected number of events by stratum in the control group under the null hypothesis. |
eDE0 |
A vector of expected number of events by stratum in the experimental group under the null hypothesis. |
eNC |
A vector of the expected accrual in each stratum in the control group. |
eNE |
A vector of the expected accrual in each stratum in the experimental group. |
variable |
A text string equal to "Accrual rate" if a design was
derived by varying the accrual rate, "Accrual duration" if a design was
derived by varying the accrual duration, "Follow-up duration" if a design
was derived by varying follow-up duration, or "Power" if accrual rates and
duration as well as follow-up duration was specified and |
nEventsIA() returns the expected proportion of the final planned
events observed at the input analysis time minus target when
simple=TRUE. When simple=FALSE, nEventsIA returns a
list with following components:
T |
The input value |
eDC |
The expected number of events in the control group at time the
output time |
eDE |
The expected number of events in the experimental group at the
output time |
eNC |
The expected enrollment in the control group at the
output time |
eNE |
The expected enrollment in the experimental group at the
output time |
tEventsIA() returns the same structure as nEventsIA(..., simple=TRUE) when
Keaven Anderson [email protected]
Kim KM and Tsiatis AA (1990), Study duration for clinical trials with survival response and early stopping rule. Biometrics, 46, 81-92
Lachin JM and Foulkes MA (1986), Evaluation of Sample Size and Power for Analyses of Survival with Allowance for Nonuniform Patient Entry, Losses to Follow-Up, Noncompliance, and Stratification. Biometrics, 42, 507-519.
Schoenfeld D (1981), The Asymptotic Properties of Nonparametric Tests for Comparing Survival Distributions. Biometrika, 68, 316-319.
Freedman LS (1982), Tables of the Number of Patients Required in Clinical Trials Using the Logrank Test. Statistics in Medicine, 1, 121-129.
vignette("SurvivalEnrollmentPlanning", package = "gsDesign")
for enrollment ramp-up and duration planning,
vignette("gsSurvBasicExamples", package = "gsDesign") for basic
survival sample size examples, vignette("SurvivalOverview",
package = "gsDesign") for method background, and
vignette("SeqDesignSurvival", package = "gsDesign") for a SAS PROC
SEQDESIGN translation example.
gsBoundSummary, xprint,
gsSurvCalendar, gsSurvPower,
gsDesign-package, plot.gsDesign, gsDesign,
gsHR, nSurvival
# Vary accrual rate gamma to obtain power # T, minfup and R all specified, although R will be adjusted on output # gamma as input will be multiplied in output to achieve desired power # Default method is Lachin and Foulkes x_nsurv <- nSurv( lambdaC = log(2) / 6, R = 10, hr = .5, eta = .001, gamma = 1, alpha = 0.02, beta = .15, T = 36, minfup = 12, method = "LachinFoulkes" ) # Demonstrate print method print(x_nsurv) # Same assumptions for group sequential design x_gs <- gsSurv( k = 4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 1, T = 36, minfup = 12, method = "LachinFoulkes" ) print(x_gs) # Demonstrate xtable method for gsSurv print(xtable::xtable(x_gs, footnote = "This is a footnote; note that it can be wide.", caption = "Caption example for xtable output." )) # Demonstrate nEventsIA method # find expected number of events at time 12 in the above trial nEventsIA(x = x_gs, tIA = 10) # find time at which 1/4 of events are expected tEventsIA(x = x_gs, timing = .25) # Adjust accrual duration R to achieve desired power # Trial duration T input as NULL and will be computed based on # accrual duration R and minimum follow-up duration minfup # Minimum follow-up duration minfup is specified # We use the Schoenfeld method to compute accrual duration R # Control median survival time is 6 nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, alpha = .025, beta = .1, minfup = 12, T = NULL, method = "Schoenfeld" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, T = 36, minfup = 12, method = "Schoenfeld" ) |> print() # Common four-period enrollment ramp-up. With T and minfup fixed, the # relative gamma pattern is scaled to power the trial, and the final R # period is extended so enrollment lasts T - minfup. ramp_rate <- gsSurv( T = 26, minfup = 12, gamma = 1:4, R = rep(1, 4) ) # With T = NULL and minfup fixed, gamma stays fixed and the final # enrollment period is extended to obtain the required sample size. ramp_duration <- gsSurv( T = NULL, minfup = 12, gamma = 1:4, R = rep(1, 4) ) # Vary minimum follow-up duration minfup to obtain power # Accrual duration R rate gamma are fixed and will not change on output. # Trial duration T and minimum follow-up minfup are input as NULL # and will be computed on output. # We will use the Freedman method to compute sample size # Control median survival time is 6 # Often this method will fail as the accrual duration and rate provide too # little or too much sample size. nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, R = 25, alpha = .025, beta = .1, minfup = NULL, T = NULL, method = "Freedman" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, T = 36, minfup = 12, method = "Freedman" ) |> print() # piecewise constant enrollment rates (vary accrual rate to achieve power) # also piecewise constant failure rates # will specify annualized enrollment and failure rates nSurv( lambdaC = -log(c(.95, .97, .98)), # 5%, 3% and 2% annual failure rates S = c(1, 1), # 1 year duration for first 2 failure rates, 3rd continues indefinitely R = c(.25, .25, 1.5), # 2-year enrollment with ramp-up over first 1/2 year gamma = c(1, 3, 6), # 1, 3 and 6 annualized enrollment rates will be # multiplied by ratio to achieve desired power hr = .5, eta = -log(1 - .01), # 1% annual censoring rate minfup = 3, T = 5, # 5-year trial duration with 3-year minimum follow-up alpha = .025, beta = .1, method = "LachinFoulkes" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = -log(c(.95, .97, .98)), # 5%, 3% and 2% annual failure rates S = c(1, 1), # 1 year duration for first 2 failure rates, 3rd continues indefinitely R = c(.25, .25, 1.5), # 2-year enrollment with ramp-up over first 1/2 year gamma = c(1, 3, 6), # 1, 3 and 6 annualized enrollment rates will be # multiplied by ratio to achieve desired power hr = .5, eta = -log(1 - .01), # 1% annual censoring rate minfup = 3, T = 5, # 5-year trial duration with 3-year minimum follow-up alpha = .025, beta = .1, method = "LachinFoulkes" ) |> print() # combine it all: 2 strata, 2 failure rate periods # Note that method = "LachinFoulkes" may be preferred here nSurv( lambdaC = matrix(log(2) / c(6, 12, 18, 24), ncol = 2), hr = .5, eta = matrix(log(2) / c(40, 50, 45, 55), ncol = 2), S = 3, gamma = matrix(c(3, 6, 5, 7), ncol = 2), R = c(5, 10), T = 27, minfup = 12, alpha = .025, beta = .1, method = "BernsteinLagakos" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = matrix(log(2) / c(6, 12, 18, 24), ncol = 2), hr = .5, eta = matrix(log(2) / c(40, 50, 45, 55), ncol = 2), S = 3, gamma = matrix(c(3, 6, 5, 7), ncol = 2), R = c(5, 10), T = 27, minfup = 12, alpha = .025, beta = .1, method = "BernsteinLagakos" ) |> print() # Example to compute power for a fixed design. # Trial duration T, minimum follow-up minfup and accrual duration R are all # specified and will not change on output. # beta=NULL will compute power and output will be the same as if beta were specified. # This option is not available for group sequential designs. nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, R = 25, alpha = .025, beta = NULL, minfup = 12, T = 36, method = "LachinFoulkes" ) |> print()# Vary accrual rate gamma to obtain power # T, minfup and R all specified, although R will be adjusted on output # gamma as input will be multiplied in output to achieve desired power # Default method is Lachin and Foulkes x_nsurv <- nSurv( lambdaC = log(2) / 6, R = 10, hr = .5, eta = .001, gamma = 1, alpha = 0.02, beta = .15, T = 36, minfup = 12, method = "LachinFoulkes" ) # Demonstrate print method print(x_nsurv) # Same assumptions for group sequential design x_gs <- gsSurv( k = 4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 1, T = 36, minfup = 12, method = "LachinFoulkes" ) print(x_gs) # Demonstrate xtable method for gsSurv print(xtable::xtable(x_gs, footnote = "This is a footnote; note that it can be wide.", caption = "Caption example for xtable output." )) # Demonstrate nEventsIA method # find expected number of events at time 12 in the above trial nEventsIA(x = x_gs, tIA = 10) # find time at which 1/4 of events are expected tEventsIA(x = x_gs, timing = .25) # Adjust accrual duration R to achieve desired power # Trial duration T input as NULL and will be computed based on # accrual duration R and minimum follow-up duration minfup # Minimum follow-up duration minfup is specified # We use the Schoenfeld method to compute accrual duration R # Control median survival time is 6 nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, alpha = .025, beta = .1, minfup = 12, T = NULL, method = "Schoenfeld" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, T = 36, minfup = 12, method = "Schoenfeld" ) |> print() # Common four-period enrollment ramp-up. With T and minfup fixed, the # relative gamma pattern is scaled to power the trial, and the final R # period is extended so enrollment lasts T - minfup. ramp_rate <- gsSurv( T = 26, minfup = 12, gamma = 1:4, R = rep(1, 4) ) # With T = NULL and minfup fixed, gamma stays fixed and the final # enrollment period is extended to obtain the required sample size. ramp_duration <- gsSurv( T = NULL, minfup = 12, gamma = 1:4, R = rep(1, 4) ) # Vary minimum follow-up duration minfup to obtain power # Accrual duration R rate gamma are fixed and will not change on output. # Trial duration T and minimum follow-up minfup are input as NULL # and will be computed on output. # We will use the Freedman method to compute sample size # Control median survival time is 6 # Often this method will fail as the accrual duration and rate provide too # little or too much sample size. nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, R = 25, alpha = .025, beta = .1, minfup = NULL, T = NULL, method = "Freedman" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, T = 36, minfup = 12, method = "Freedman" ) |> print() # piecewise constant enrollment rates (vary accrual rate to achieve power) # also piecewise constant failure rates # will specify annualized enrollment and failure rates nSurv( lambdaC = -log(c(.95, .97, .98)), # 5%, 3% and 2% annual failure rates S = c(1, 1), # 1 year duration for first 2 failure rates, 3rd continues indefinitely R = c(.25, .25, 1.5), # 2-year enrollment with ramp-up over first 1/2 year gamma = c(1, 3, 6), # 1, 3 and 6 annualized enrollment rates will be # multiplied by ratio to achieve desired power hr = .5, eta = -log(1 - .01), # 1% annual censoring rate minfup = 3, T = 5, # 5-year trial duration with 3-year minimum follow-up alpha = .025, beta = .1, method = "LachinFoulkes" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = -log(c(.95, .97, .98)), # 5%, 3% and 2% annual failure rates S = c(1, 1), # 1 year duration for first 2 failure rates, 3rd continues indefinitely R = c(.25, .25, 1.5), # 2-year enrollment with ramp-up over first 1/2 year gamma = c(1, 3, 6), # 1, 3 and 6 annualized enrollment rates will be # multiplied by ratio to achieve desired power hr = .5, eta = -log(1 - .01), # 1% annual censoring rate minfup = 3, T = 5, # 5-year trial duration with 3-year minimum follow-up alpha = .025, beta = .1, method = "LachinFoulkes" ) |> print() # combine it all: 2 strata, 2 failure rate periods # Note that method = "LachinFoulkes" may be preferred here nSurv( lambdaC = matrix(log(2) / c(6, 12, 18, 24), ncol = 2), hr = .5, eta = matrix(log(2) / c(40, 50, 45, 55), ncol = 2), S = 3, gamma = matrix(c(3, 6, 5, 7), ncol = 2), R = c(5, 10), T = 27, minfup = 12, alpha = .025, beta = .1, method = "BernsteinLagakos" ) # Same assumptions for group sequential design gsSurv( k = 4, sfu = gsDesign::sfHSD, sfupar = -4, sfl = gsDesign::sfPower, sflpar = .5, lambdaC = matrix(log(2) / c(6, 12, 18, 24), ncol = 2), hr = .5, eta = matrix(log(2) / c(40, 50, 45, 55), ncol = 2), S = 3, gamma = matrix(c(3, 6, 5, 7), ncol = 2), R = c(5, 10), T = 27, minfup = 12, alpha = .025, beta = .1, method = "BernsteinLagakos" ) |> print() # Example to compute power for a fixed design. # Trial duration T, minimum follow-up minfup and accrual duration R are all # specified and will not change on output. # beta=NULL will compute power and output will be the same as if beta were specified. # This option is not available for group sequential designs. nSurv( lambdaC = log(2) / 6, hr = .5, eta = .001, gamma = 6, R = 25, alpha = .025, beta = NULL, minfup = 12, T = 36, method = "LachinFoulkes" ) |> print()
Translate survival design bounds to exact binomial bounds
toBinomialExact( x, observedEvents = NULL, alpha = NULL, usTime = NULL, lsTime = NULL, maxSpend = FALSE )toBinomialExact( x, observedEvents = NULL, alpha = NULL, usTime = NULL, lsTime = NULL, maxSpend = FALSE )
x |
An object of class |
observedEvents |
If NULL (default), targeted timing of analyses will come from |
alpha |
Optional alpha level for deriving updated exact efficacy bounds.
If |
usTime |
Optional upper spending time override (length |
lsTime |
Optional lower spending time override for |
maxSpend |
Logical scalar. If 'TRUE', force full alpha spending (and, for 'test.type = 4' or '8', full beta spending; for 'test.type = 6', full lower bound spending under the null; and, for 'test.type = 8', full harm spending under the null) at the final analysis even when 'observedEvents[k] < x$maxn.IPlan'. This keeps earlier analysis spending unchanged and applies the override only at the last look. |
Fixed survival designs (gsSurv(k = 1)) are converted using the exact
lower binomial tail under hr0. The largest integer efficacy cutoff
with probability no greater than the available alpha spending is selected;
discreteness may reduce achieved power. A single observedEvents value
can update a fixed design. Spending still uses the original planned final
event count as denominator, including under- and over-runs; maxSpend
can request full spending for an under-run. No artificial interim is added.
Test types 1 (one-sided), 4 (non-binding beta spending futility), 6
(non-binding lower bound spending under the null), and 8 (non-binding
futility and harm) are supported for full conversion. For Type 8, the exact
upper event count stopping probability is partitioned into mutually
exclusive futility and harm components. Harm spending is calibrated under
the null ignoring futility stopping, but retaining efficacy stopping.
Reported harm probabilities assume futility is followed and can be smaller
than spending targets. Discreteness and the requirement that the harm region
be nested within the active futility region can also reduce spending. Binding
designs (types 2, 3, 5, and 7) are outside the non-binding exact efficacy
framework. Exact repeated and sequential efficacy
p-values can nevertheless be computed for non-binding types 1, 4, 6, and 8
with repeatedPValueBinomialExact() and
sequentialPValueBinomialExact(), which intentionally ignore
non-binding lower and harm bounds.
The exact binomial routine gsBinomialExact has requirements that may not be satisfied
by the initial asymptotic approximation.
Thus, the approximations are updated to satisfy the following requirements of gsBinomialExact:
a (the efficacy bound) must be positive, non-decreasing, and strictly less than n.I
b (the upper event count stopping bound for futility, harm, or the
Type 6 lower bound) must be positive, non-decreasing, and strictly greater than a
n.I - b must be non-decreasing and >= 0
With 'observedEvents', spending times are based on
observedEvents / x$maxn.IPlan. If maxSpend = TRUE, the final
spending time is set to 1 so all remaining spending is used at the last look.
If x$testLower is present (for example from gsSurv() with
selective lower bound looks), lower bound spending is flattened at analyses
where testLower = FALSE.
For Type 4, the exact futility bound at a skipped look carries forward
n.I - b from the preceding look (or uses b = n.I + 1
at a skipped first look). It cannot be crossed by a continuing path and
spends no additional beta. Efficacy calibration ignores these non-binding
futility bounds.
An object with primary class gsBinomialExactSpending,
inheriting from gsBinomialExact and gsProbability. In
addition to the exact bounds and crossing probabilities, the returned
object records 'test.type', 'alpha', 'beta', 'ratio', applicable 'astar',
analysis-specific testing indicators, spending times, planned final event
count, 'maxSpend', and the evaluated conversion call. The retained
randomization ratio can be used by VEtable. For
‘test.type = 6', the exact object’s upper event count bound represents the
non-binding lower stopping bound, with its first probability column
calibrated under the null hypothesis. For 'test.type = 8', 'upper'
represents all upper event count stops, while 'futility' and 'harm'
partition those stops into mutually exclusive components.
# Fixed survival design: exact probabilities, without an artificial interim fixed <- gsSurv(k = 1, hr = .3, hr0 = .7, ratio = 1) toBinomialExact(fixed) # The following code derives the group sequential design using the method # of Lachin and Foulkes x <- gsSurv( k = 3, # 3 analyses test.type = 4, # Non-binding futility bound 1 (no futility bound) and 4 are allowable alpha = .025, # 1-sided Type I error beta = .1, # Type II error (1 - power) timing = c(0.45, 0.7), # Proportion of final planned events at interims sfu = sfHSD, # Efficacy spending function sfupar = -4, # Parameter for efficacy spending function sfl = sfLDOF, # Futility spending function; not needed for test.type = 1 sflpar = 0, # Parameter for futility spending function lambdaC = .001, # Exponential failure rate hr = 0.3, # Assumed proportional hazard ratio (1 - vaccine efficacy = 1 - VE) hr0 = 0.7, # Null hypothesis VE eta = 5e-04, # Exponential dropout rate gamma = 10, # Piecewise exponential enrollment rates R = 16, # Time period durations for enrollment rates in gamma T = 24, # Planned trial duration minfup = 8, # Planned minimum follow-up ratio = 3 # Randomization ratio (experimental:control) ) # Convert bounds to exact binomial bounds toBinomialExact(x) # Update bounds at time of analysis toBinomialExact(x, observedEvents = c(20,55,80)) # Update exact efficacy bounds using a different alpha level toBinomialExact(x, observedEvents = c(20,55,80), alpha = 0.01) # Explicit spending time override toBinomialExact(x, observedEvents = c(20, 55, 80), usTime = c(.25, .65, 1)) # Optionally force full spending at final look when final events are below plan toBinomialExact(x, observedEvents = c(20, 55, 75), maxSpend = TRUE)# Fixed survival design: exact probabilities, without an artificial interim fixed <- gsSurv(k = 1, hr = .3, hr0 = .7, ratio = 1) toBinomialExact(fixed) # The following code derives the group sequential design using the method # of Lachin and Foulkes x <- gsSurv( k = 3, # 3 analyses test.type = 4, # Non-binding futility bound 1 (no futility bound) and 4 are allowable alpha = .025, # 1-sided Type I error beta = .1, # Type II error (1 - power) timing = c(0.45, 0.7), # Proportion of final planned events at interims sfu = sfHSD, # Efficacy spending function sfupar = -4, # Parameter for efficacy spending function sfl = sfLDOF, # Futility spending function; not needed for test.type = 1 sflpar = 0, # Parameter for futility spending function lambdaC = .001, # Exponential failure rate hr = 0.3, # Assumed proportional hazard ratio (1 - vaccine efficacy = 1 - VE) hr0 = 0.7, # Null hypothesis VE eta = 5e-04, # Exponential dropout rate gamma = 10, # Piecewise exponential enrollment rates R = 16, # Time period durations for enrollment rates in gamma T = 24, # Planned trial duration minfup = 8, # Planned minimum follow-up ratio = 3 # Randomization ratio (experimental:control) ) # Convert bounds to exact binomial bounds toBinomialExact(x) # Update bounds at time of analysis toBinomialExact(x, observedEvents = c(20,55,80)) # Update exact efficacy bounds using a different alpha level toBinomialExact(x, observedEvents = c(20,55,80), alpha = 0.01) # Explicit spending time override toBinomialExact(x, observedEvents = c(20, 55, 80), usTime = c(.25, .65, 1)) # Optionally force full spending at final look when final events are below plan toBinomialExact(x, observedEvents = c(20, 55, 75), maxSpend = TRUE)
Translate group sequential design to integer events (survival designs) or sample size (other designs)
toInteger(x, ratio = x$ratio, roundUpFinal = TRUE)toInteger(x, ratio = x$ratio, roundUpFinal = TRUE)
x |
An object of class |
ratio |
Usually corresponds to experimental:control sample size ratio.
If an integer is provided, rounding is done to a multiple of
|
roundUpFinal |
For non-survival designs, final sample size is rounded
up to a multiple of |
It is useful to explicitly provide the argument ratio when a
gsDesign object is input since gsDesign() does not have a
ratio in return.
ratio = 0, roundUpFinal = TRUE will just round up the sample size
for non-survival designs.
Since x <- gsSurv(ratio = M) returns a value for ratio,
toInteger(x) will round to a multiple of M + 1 if M
is a non-negative integer; otherwise, just rounding will occur.
For 1:1 randomization, ratio = 1 gives an even final sample size.
For 2:1 randomization, ratio = 2 gives a final sample size that is
a multiple of 3.
To just round without concern for randomization ratio, set ratio = 0.
If toInteger(x, ratio = 3), rounding for final sample size is done
to a multiple of 3 + 1 = 4; this could represent a 3:1 or 1:3
randomization ratio.
For 3:2 randomization, ratio = 4 would ensure rounding sample size
to a multiple of 5.
An nSurv object is converted through the corresponding
single-analysis gsSurv representation and returned as an
nSurv object. Its required event count d is rounded in the
same way as the final event count for a gsSurv object, and its total
sample size n is rounded according to ratio.
For a gsSurv object, x$n.I is an event count schedule.
toInteger() rounds the final planned event count (up when
roundUpFinal = TRUE; otherwise to nearest integer, with a 0.01
tolerance), then derives interim integer event targets from
x$timing * final_events. Interim counts are constrained to be positive
and strictly increasing. Group sequential boundaries and spending are
recomputed with gsDesign() at the integer event counts.
For a single-analysis survival design, the fixed efficacy boundary is
retained and its power is recomputed at the integer final event count
without invoking multi-look group sequential calculations.
Total sample size for a survival design is then updated under a fixed
calendar plan (same enrollment periods, study duration, and minimum
follow-up). Enrollment rates are scaled proportionally to the final event
inflation factor and rounded to the nearest allocation multiple
ratio + 1 (or rounded up when roundUpFinal = TRUE), with
additional allocation step adjustment only if needed to make the integer
final event target achievable.
If fixed calendar enrollment rate inflation cannot make the integer final
event target feasible, toInteger() falls back to a variable-duration
solve and issues a warning.
For a complete seasonal exact binomial monitoring workflow, see
vignette("MultiSeasonRareEvents", package = "gsDesign").
Selective bound settings (testUpper, testLower, testHarm,
and harm spending for test.type 7 or 8) are carried from the input
design into the internal gsDesign() recomputation so skipped looks stay
skipped after integer rounding.
Output is an object of the same class as input x; i.e.,
gsDesign with integer vector for n.I, gsSurv
with integer vector n.I and integer total sample size, or
nSurv with integer d and integer total sample size n.
See details.
gsSurv, toBinomialExact,
vignette("MultiSeasonRareEvents", package = "gsDesign")
# The following code derives the group sequential design using the method # of Lachin and Foulkes x <- gsSurv( k = 3, # 3 analyses test.type = 4, # Non-binding futility bound 1 (no futility bound) and 4 are allowable alpha = .025, # 1-sided Type I error beta = .1, # Type II error (1 - power) timing = c(0.45, 0.7), # Proportion of final planned events at interims sfu = sfHSD, # Efficacy spending function sfupar = -4, # Parameter for efficacy spending function sfl = sfLDOF, # Futility spending function; not needed for test.type = 1 sflpar = 0, # Parameter for futility spending function lambdaC = .001, # Exponential failure rate hr = 0.3, # Assumed proportional hazard ratio (1 - vaccine efficacy = 1 - VE) hr0 = 0.7, # Null hypothesis VE eta = 5e-04, # Exponential dropout rate gamma = 10, # Piecewise exponential enrollment rates R = 16, # Time period durations for enrollment rates in gamma T = 24, # Planned trial duration minfup = 8, # Planned minimum follow-up ratio = 3 # Randomization ratio (experimental:control) ) # Convert sample size to multiple of ratio + 1 = 4, # with final event count rounded up by default. toInteger(x)# The following code derives the group sequential design using the method # of Lachin and Foulkes x <- gsSurv( k = 3, # 3 analyses test.type = 4, # Non-binding futility bound 1 (no futility bound) and 4 are allowable alpha = .025, # 1-sided Type I error beta = .1, # Type II error (1 - power) timing = c(0.45, 0.7), # Proportion of final planned events at interims sfu = sfHSD, # Efficacy spending function sfupar = -4, # Parameter for efficacy spending function sfl = sfLDOF, # Futility spending function; not needed for test.type = 1 sflpar = 0, # Parameter for futility spending function lambdaC = .001, # Exponential failure rate hr = 0.3, # Assumed proportional hazard ratio (1 - vaccine efficacy = 1 - VE) hr0 = 0.7, # Null hypothesis VE eta = 5e-04, # Exponential dropout rate gamma = 10, # Piecewise exponential enrollment rates R = 16, # Time period durations for enrollment rates in gamma T = 24, # Planned trial duration minfup = 8, # Planned minimum follow-up ratio = 3 # Randomization ratio (experimental:control) ) # Convert sample size to multiple of ratio + 1 = 4, # with final event count rounded up by default. toInteger(x)
Creates a vaccine or prevention efficacy summary table from an exact binomial design. The table includes event count bounds, efficacy at each bound, cumulative error spending, and cumulative efficacy crossing probabilities for selected efficacy assumptions. Optional time-to-event design information adds planned analysis times and expected enrollment.
VEtable(x, ve, tteDesign = NULL, ratio = NULL)VEtable(x, ve, tteDesign = NULL, ratio = NULL)
x |
An object of class |
ve |
Numeric vector of vaccine or prevention efficacy assumptions strictly between 0 and 1. |
tteDesign |
Optional |
ratio |
Experimental-to-control randomization ratio. By default, this
is taken from |
Vaccine efficacy (VE), also termed prevention efficacy (PE) for non-vaccine preventive interventions, is translated to the exact binomial probability that an event is in the experimental group using the specified randomization ratio. The argument is named 've' because the motivating application is a vaccine trial; the same calculation applies to PE. Cumulative alpha is calculated while ignoring non-binding futility, as is required for exact efficacy Type I error control.
A tibble of class gsVETable with one row per analysis. The
columns contain analysis number, optional timing and enrollment, total
cases, exact efficacy and futility bounds, efficacy at each bound,
cumulative alpha and beta spending, and cumulative efficacy crossing
probability under each value in ve. Pass the result to
lt() for a formatted table with explanatory footnotes.
toBinomialExact,
vignette("VaccineEfficacy")
x <- gsSurv( k = 2, test.type = 4, timing = .6, ratio = 3, hr = .3, hr0 = .7, lambdaC = .002, eta = .0001, gamma = 10, R = 8, T = 24, minfup = 16 ) exact <- toBinomialExact(x) VEtable(exact, ve = c(.5, .7), tteDesign = x)x <- gsSurv( k = 2, test.type = 4, timing = .6, ratio = 3, hr = .3, hr0 = .7, lambdaC = .002, eta = .0001, gamma = 10, R = 8, T = 24, minfup = 16 ) exact <- toBinomialExact(x) VEtable(exact, ve = c(.5, .7), tteDesign = x)