diff --git a/DESCRIPTION b/DESCRIPTION index 4ffd7b8..cc3b37e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: adestr Type: Package Title: Adaptive Design Estimation in R -Version: 0.0.1 +Version: 0.5.0 Authors@R: c(person("Jan", "Meis", role = c("aut", "cre"), email="meis@imbi.uni-heidelberg.de", comment = c(ORCID = "0000-0001-5407-7220"))) Description: This package implements methods to evaluate the performance characteristics of @@ -17,8 +17,7 @@ LazyData: true VignetteBuilder: knitr RoxygenNote: 7.2.3 Depends: - R (>= 4.0.0), - adoptr + R (>= 4.0.0) Imports: methods, stats, @@ -41,6 +40,7 @@ Suggests: Config/testthat/edition: 3 Collate: 'adestr_package.R' + 'twostagedesign_with_cache.R' 'analyze.R' 'estimators.R' 'densities.R' @@ -54,12 +54,10 @@ Collate: 'mle_distribution.R' 'mlmse_score.R' 'n2c2_helpers.R' - 'twostagedesign_with_cache.R' 'plot.R' 'priors.R' 'print.R' URL: https://jan-imbi.github.io/adestr/ RdMacros: Rdpack -Remotes: github::jan-imbi/adoptr@dissertation diff --git a/NAMESPACE b/NAMESPACE index 9d3cd13..aef3a63 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(MinimizePeakVariance) export(NaiveCI) export(NeymanPearsonOrderingCI) export(NeymanPearsonOrderingPValue) +export(Normal) export(NormalPrior) export(OverestimationProbability) export(PValue) @@ -43,6 +44,7 @@ export(ScoreTestOrderingPValue) export(SoftCoverage) export(StagewiseCombinationFunctionOrderingCI) export(StagewiseCombinationFunctionOrderingPValue) +export(Student) export(TestAgreement) export(UniformPrior) export(Variance) @@ -61,8 +63,8 @@ exportClasses(IntervalEstimator) exportClasses(PValue) exportClasses(PointEstimator) exportClasses(Statistic) +exportClasses(TwoStageDesign) exportMethods(plot) -import(adoptr) import(ggplot2) import(methods) importFrom(Rdpack,reprompt) diff --git a/NEWS.md b/NEWS.md index de3c488..d386d7b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# adestr 0.5.0 + +* First CRAN submission. + # adestr 0.0.1 * Added a `NEWS.md` file to track changes to the package. diff --git a/R/adestr_package.R b/R/adestr_package.R index c41cf1c..14b65f5 100644 --- a/R/adestr_package.R +++ b/R/adestr_package.R @@ -3,7 +3,7 @@ #' #' @details This package implements methods to \link[adestr:evaluate_estimator]{evaluate the performance characteristics} of #' various \link[adestr:PointEstimator]{point} and \link[adestr:IntervalEstimator]{interval} estimators for optimal adaptive two-stage designs. -#' Specifically, this package is written to interface with trial designs created by the \code{\link{adoptr}} package +#' Specifically, this package is written to interface with trial designs created by the \code{adoptr} package #' \insertCite{kunzmann2021adoptr,pilz2021optimal}{adestr}. #' Apart from the a priori evaluation of performance characteristics, this package also allows for the #' \link[adestr:analyze]{calculation of the values of the estimators} given real datasets, and it implements methods @@ -11,7 +11,7 @@ #' #' @docType package #' @name adestr -#' @import adoptr methods +#' @import methods #' @importFrom stats dnorm pnorm qnorm dt pt qt dchisq pchisq qchisq integrate uniroot var #' @importFrom cubature hcubature #' @importFrom Rdpack reprompt diff --git a/R/analyze.R b/R/analyze.R index 6b78ddd..6c24f52 100644 --- a/R/analyze.R +++ b/R/analyze.R @@ -101,7 +101,7 @@ setMethod("analyze", signature("data.frame"), if (abs(sdata$n_s1_g2 - design@n1)/ (design@n1) > 0.1) warning("Planned first-stage sample size in group 2 differs from actually observed sample size by more than 10%. Results may be unreliable.") } - calc_n2 <- n2(design, test_val, round=FALSE) + calc_n2 <- .n2_extrapol(design, test_val) if (sdata$n_stages==2L){ if (abs(sdata$n_s2_g1 - calc_n2 )/ (calc_n2) > 0.1) warning("Planned second-stage sample size in group 1 differs from actually observed sample size by more than 10%. Results may be unreliable.") diff --git a/R/estimators.R b/R/estimators.R index 5ff9417..230ef42 100644 --- a/R/estimators.R +++ b/R/estimators.R @@ -566,12 +566,13 @@ setMethod("get_stagewise_estimators", signature("MinimizePeakVariance", "Normal" design, sigma, exact) { - get_ess <- function(mu) { - H <- PointMassPrior(mu, 1) - ess <- ExpectedSampleSize(data_distribution, H) - evaluate(ess, design, optimization=TRUE) - } - max_ess_mu <- optimize(get_ess, z_to_smean(c(design@c1f, design@c1e), n1(design, round=FALSE), sigma, data_distribution@two_armed), maximum = TRUE)$maximum + # get_ess <- function(mu) { + # H <- PointMassPrior(mu, 1) + # ess <- ExpectedSampleSize(data_distribution, H) + # evaluate(ess, design, optimization=TRUE) + # } + # max_ess_mu <- optimize(get_ess, z_to_smean(c(design@c1f, design@c1e), n1(design, round=FALSE), sigma, data_distribution@two_armed), maximum = TRUE)$maximum + max_ess_mu <- mean(z_to_smean(c(design@c1f, design@c1e), n1(design, round=FALSE), sigma, data_distribution@two_armed)) get_var <- function(w1) { est <- AdaptivelyWeightedSampleMean(w1) evaluate_estimator(score = Variance(), diff --git a/R/evaluate_estimator.R b/R/evaluate_estimator.R index 9a08826..cfaed78 100644 --- a/R/evaluate_estimator.R +++ b/R/evaluate_estimator.R @@ -81,8 +81,8 @@ setMethod("c", signature("EstimatorScoreResultList"), definition = #' First, a functional representation of the integrand is created by combining information #' from the \code{\link{EstimatorScore}} object (\code{score}) and the \code{\link{PointEstimator}} or #' \code{\link{IntervalEstimator}} object (\code{estimator}). -#' The sampling distribution of a design is determined by the \code{\link{TwoStageDesign}} object -#' (\code{design}) and the \code{\link{DataDistribution}} object (\code{data_distribution}), +#' The sampling distribution of a design is determined by the \code{TwoStageDesign} object +#' (\code{design}) and the \code{DataDistribution} object (\code{data_distribution}), #' as well as the assumed parameters \eqn{\mu} (mu) and \eqn{\sigma} (sigma). #' The other parameters control various details of the integration problem. #' @@ -98,7 +98,7 @@ setMethod("c", signature("EstimatorScoreResultList"), definition = #' estimating the standard deviation. #' #' If the parameter \code{exact} is set to \code{FALSE} -#' (the default), the continuous version of the second-stage sample-size function \code{\link{n2}} +#' (the default), the continuous version of the second-stage sample-size function \code{n2} #' is used. Otherwise, an integer valued version of that function will be used, #' though this is considerably slower. #' diff --git a/R/helper_functions.R b/R/helper_functions.R index 4bc3dfa..50517c9 100644 --- a/R/helper_functions.R +++ b/R/helper_functions.R @@ -102,46 +102,68 @@ get_overall_svar_twoarm <- function(smean1, smean1T, svar1, smean2, smean2T, sva #' for a normally distributed test statistic (i.e. known variance). #' For an alternative hypothesis of mu=0.4, the overall power is 80\%. #' +#' @param two_armed (logical) determins whether the design is for one- or +#' two-armed trials. #' @param label (optional) label to be assigned to the design. #' #' @return an exmplary design of class \code{TwoStageDesign}. #' @export #' #' @examples -#' design <- get_example_design() -#' # Type I error -#' evaluate(Power(Normal(FALSE), PointMassPrior(0, 1)), design) -#' # Power -#' evaluate(Power(Normal(FALSE), PointMassPrior(.4, 1)), design) -#' # Expected sample size under the alternative -#' evaluate(ExpectedSampleSize(Normal(FALSE), PointMassPrior(.4, 1)), design) -#' # Expected sample size under the null -#' evaluate(ExpectedSampleSize(Normal(FALSE), PointMassPrior(0, 1)), design) -get_example_design <- function(label = NULL) { - d <- TwoStageDesign( - n1 = 28.16834031633078083701, - c1f = 0.7907304707554818623549, - c1e = 2.291260947864900643367, - n2_pivots = c( - 39.39294353955478555918, - 37.23397813905835818105, - 33.27173714438612961430, - 27.77227568901122012335, - 21.41776450755991234587, - 15.17163280081247300757, - 10.25508398663193787570 - ), - c2_pivots = c( - 2.16914648055318837194250, - 2.02493357331804890719695, - 1.77299079624771049878973, - 1.42524439642541422834654, - 0.99916431580845133098023, - 0.52325801518650127963639, - 0.07133753446126563091401 - ), - 7 - ) +#' get_example_design() +#' +get_example_design <- function(two_armed = FALSE, label = NULL) { + if (two_armed) { + d <- TwoStageDesign( + n1 = 56.33739084822602904978, + c1f = 0.7907356135206976555097, + c1e = 2.291313615804561720779, + n2_pivots = c( + 78.74984462914770233510, + 74.46976145811771630179, + 66.54436357139142899086, + 55.54462857388867291775, + 42.83473938241603207189, + 30.34059894227031151104, + 20.48959489543554823854 + ), + c2_pivots = c( + 2.16905734410577055726321, + 2.02492349183474207308109, + 1.77298374394898416994693, + 1.42521477360223225439029, + 0.99909735679793421070372, + 0.52314051699418129270924, + 0.07058637352917693230658 + ), + 7 + ) + } else { + d <- TwoStageDesign( + n1 = 28.16834031633078083701, + c1f = 0.7907304707554818623549, + c1e = 2.291260947864900643367, + n2_pivots = c( + 39.39294353955478555918, + 37.23397813905835818105, + 33.27173714438612961430, + 27.77227568901122012335, + 21.41776450755991234587, + 15.17163280081247300757, + 10.25508398663193787570 + ), + c2_pivots = c( + 2.16914648055318837194250, + 2.02493357331804890719695, + 1.77299079624771049878973, + 1.42524439642541422834654, + 0.99916431580845133098023, + 0.52325801518650127963639, + 0.07133753446126563091401 + ), + 7 + ) + } d <- TwoStageDesignWithCache(d) attr(d, "label") <- label d @@ -219,13 +241,3 @@ get_statistics_from_paper <- function(point_estimators = TRUE, ret <- c(ret, p) return(ret) } - - - - - - - - - - diff --git a/R/mlmse_score.R b/R/mlmse_score.R index 51ab3a8..7f6f56e 100644 --- a/R/mlmse_score.R +++ b/R/mlmse_score.R @@ -1,38 +1,40 @@ -setClass("MLMSE", - slots = c( - mu = "numeric", - sigma = "numeric", - two_armed = "logical", - tol = "numeric", - maxEval = "numeric", - absError = "numeric" - ), - contains = "UnconditionalScore") -MLMSE <- function(mu = 0, sigma = 1, two_armed = FALSE, tol = 1e-5, maxEval = 1e7, absError = 1e-7) { - new("MLMSE", - mu = mu, - sigma = sigma, - two_armed = two_armed, - tol = tol, - maxEval = maxEval, - absError = absError, - label = "E[|mu_ML - mu|^2]") -} -setMethod("evaluate", - signature("MLMSE", "TwoStageDesign"), - function(s, design, ...) { - mean( - evaluate_estimator( - score = MSE(), - estimator = SampleMean(), - data_distribution = Normal(two_armed = s@two_armed), - design = design, - mu = s@mu, - sigma = s@sigma, - tol = s@tol, - maxEval = s@maxEval - )@results$MSE - ) - }) - +### Uncomment this once adoptr is back on CRAN ### +# setClass("MLMSE", +# slots = c( +# mu = "numeric", +# sigma = "numeric", +# two_armed = "logical", +# tol = "numeric", +# maxEval = "numeric", +# absError = "numeric" +# ), +# contains = "UnconditionalScore") +# MLMSE <- function(mu = 0, sigma = 1, two_armed = FALSE, tol = 1e-5, maxEval = 1e7, absError = 1e-7) { +# new("MLMSE", +# mu = mu, +# sigma = sigma, +# two_armed = two_armed, +# tol = tol, +# maxEval = maxEval, +# absError = absError, +# label = "E[|mu_ML - mu|^2]") +# } +# setMethod("evaluate", +# signature("MLMSE", "TwoStageDesign"), +# function(s, design, ...) { +# mean( +# evaluate_estimator( +# score = MSE(), +# estimator = SampleMean(), +# data_distribution = Normal(two_armed = s@two_armed), +# design = design, +# mu = s@mu, +# sigma = s@sigma, +# tol = s@tol, +# maxEval = s@maxEval +# )@results$MSE +# ) +# }) +# +# diff --git a/R/n2c2_helpers.R b/R/n2c2_helpers.R index 6ae464f..4dd7834 100644 --- a/R/n2c2_helpers.R +++ b/R/n2c2_helpers.R @@ -74,7 +74,6 @@ get_c2_coefficients <- function(design){ #' @param design an object of class \code{\link{TwoStageDesignWithCache}}. #' @param x1 first-stage test statistic #' -#' @seealso \link[adoptr]{n2} n2_extrapol <- function(design, x1) { if (length(design@n2_pivots) > 1L){ fastmonoH.FC_evaluate(x1, design@n2_coefficients) @@ -89,7 +88,6 @@ n2_extrapol <- function(design, x1) { #' @param design an object of class \code{\link{TwoStageDesignWithCache}}. #' @param x1 first-stage test statistic #' -#' @seealso \link[adoptr]{c2} c2_extrapol <- function(design, x1) { fastmonoH.FC_evaluate(x1, design@c2_coefficients) } diff --git a/R/plot.R b/R/plot.R index ede0317..1ffee4d 100644 --- a/R/plot.R +++ b/R/plot.R @@ -76,7 +76,7 @@ setMethod("plot", signature = "list", definition = #' of values for the first and second-stage test statistics. #' #' When the first-stage test statistic lies below the futility threshold (c1f) or -#' above the early efficacy threshold (c1e) of the \code{\link{TwoStageDesign}}, +#' above the early efficacy threshold (c1e) of the \code{TwoStageDesign}, #' there is no second-stage test statistics. The p-values in these regions are only #' based on the first-stage values. #' For first-stage test statistic values between c1f and c1e, the first and second-stage @@ -379,7 +379,7 @@ setMethod("plot_sample_mean", signature("DataDistribution", "TwoStageDesign"), plt <- ggplot(data = smean_dat, aes(x = .data$`smean`, y = .data$`Density`)) + geom_line(size = 1) + scale_x_continuous(name = "Sample mean") + - facet_wrap(vars(n)) + facet_wrap(vars(.data$n)) return(plt) } }) diff --git a/R/print.R b/R/print.R index f5fb3f4..dc846d2 100644 --- a/R/print.R +++ b/R/print.R @@ -113,12 +113,12 @@ setMethod("toString", signature("Results"), lines[[length(lines)+1L]] <- paste0(pad_middle(left, right), "\n") left <- "Calculated n2(Z1) (per group)" - nval2 <- n2(x@design, test_val) + nval2 <- .n2_extrapol(x@design, test_val) right <- format(nval2) lines[[length(lines)+1L]] <- paste0(pad_middle(left, right), "\n") left <- "Calculated c2(Z1)" - cval2 <- c2(x@design, test_val) + cval2 <- .c2_extrapol(x@design, test_val) right <- format(cval2, digits=3) lines[[length(lines)+1L]] <- paste0(pad_middle(left, right), "\n") @@ -202,33 +202,39 @@ setMethod("toString", signature("Results"), return(unlist(lines)) }) setMethod("show", signature("Results"), \(object) cat(c(toString(object), "\n"), sep = "")) - -#' @importFrom utils capture.output setMethod("toString", signature("DataDistribution"), function(x, ...) { - str <- capture.output(print(x)) - substr(str, 1, nchar(str)) + sprintf("%s<%s>", class(x), if(x@two_armed) "two-armed" else "single-armed") }) setMethod("toString", signature("EstimatorScore"), function(x, ...) { x@label }) -#' @importFrom utils capture.output setMethod("toString", signature("TwoStageDesign"), function(x, ...) { if (!is.null(attr(x, "label"))) return(attr(x, "label")) - str <- print(x) - str + # This is partially taken from the adoptr implementation of design2str + n2_piv <- seq(x@c1f, x@c1e, length.out = length(x@n2_pivots)) + n2range <- range(.n2_extrapol(x, n2_piv)) + sprintf( + "%s", + class(x)[1], n1(x, round=TRUE), + x@c1f, x@c1e, + if (diff(round(n2range)) == 0) sprintf("%i", round(n2range)[1]) else paste(round(n2range), collapse = '-') + ) }) -#' @importFrom utils capture.output setMethod("toString", signature("TwoStageDesignWithCache"), function(x, ...) { - if (!is.null(attr(x, "label"))) - return(attr(x, "label")) - str <- print(x) - str + toString(forget_cache(x), ...) + }) + +### Remove once adoptr is back on CRAN ### +setMethod("show", signature("TwoStageDesign"), + function(object) { + cat(toString(object)) }) +### end remove ### setGeneric("toTeX", \(x, ...) standardGeneric("toTeX")) @@ -267,5 +273,3 @@ setMethod("toString", "UniformPrior", }) setMethod("show", signature("UniformPrior"), \(object)cat(c(toString(object), "\n"), sep="")) - - diff --git a/R/priors.R b/R/priors.R index d6273da..337c397 100644 --- a/R/priors.R +++ b/R/priors.R @@ -2,7 +2,9 @@ setGeneric("get_pdf", function(prior) standardGeneric("get_pdf")) setGeneric("get_logpdf", function(prior) standardGeneric("get_logpdf")) setGeneric("get_bounds", function(prior, infq) standardGeneric("get_bounds")) setGeneric("get_mean", function(prior, infq) standardGeneric("get_mean")) - +### Remove once adoptr is back on CRAN ### +setClass("Prior") +### end remove ### setClass("NormalPrior", contains = "Prior", slots = c(mu = "numeric", sigma = "numeric")) #' Normal prior distribution for the parameter mu #' @@ -21,7 +23,6 @@ NormalPrior <- function(mu = 0, sigma = 1) { sigma = sigma ) } - setClass("UniformPrior", contains = "Prior", slots = c(min = "numeric", max = "numeric")) #' Uniform prior distribution for the parameter mu #' @@ -40,7 +41,6 @@ UniformPrior <- function(min = -1, max = 1) { max = max ) } - setMethod("get_pdf", signature = "NormalPrior", function(prior) function(x) dnorm(x, mean = prior@mu, sd = prior@sigma)) @@ -67,10 +67,3 @@ setMethod("get_mean", signature = "NormalPrior", setMethod("get_mean", signature = "UniformPrior", function(prior) mean(c(prior@min, prior@max))) - - - - - - - diff --git a/R/twostagedesign_with_cache.R b/R/twostagedesign_with_cache.R index d28d368..c41ab54 100644 --- a/R/twostagedesign_with_cache.R +++ b/R/twostagedesign_with_cache.R @@ -1,3 +1,100 @@ +### Remove some of this once adoptr is back on CRAN ### + +#' Two-stage designs +#' +#' This is a re-export of the \code{TwoStageDesign} class from the +#' \code{adoptr} \insertCite{kunzmann2021adoptr}{adestr} package. +#' +#' This function is currently re-exported here to resolve CRAN conflicts. +#' For details, please refer to the paper \insertCite{kunzmann2021adoptr}{adestr} +#' and the original github repository \url{https://github.com/kkmann/adoptr}. +#' +#' @slot n1 (numeric) first-stage sample size. +#' @slot c1f (numeric) first-stage futility boundary. +#' @slot c1e (numeric) first-stage early efficacy boundary. +#' @slot n2_pivots (numeric) vector containing the values of the n2 spline function. +#' @slot c2_pivots (numeric) vector containing the values of the second-stage +#' rejection boundary spline c2 +#' @slot x1_norm_pivots (numeric) vector containing the x-axis (z-sclae) points +#' for the n2 and c2 splines +#' @slot weights (numeric) vector containing integration weights +#' @slot tunable (logical) vector determining whether desing paramters are to be optimized +#' +#' @seealso The original implementation of the adoptr package by Kevin Kunzmann and +#' Maximilian Pilz is available at \url{https://github.com/kkmann/adoptr}. +#' +#' @exportClass TwoStageDesign +setClass("TwoStageDesign", representation( + n1 = "numeric", + c1f = "numeric", + c1e = "numeric", + n2_pivots = "numeric", + c2_pivots = "numeric", + x1_norm_pivots = "numeric", + weights = "numeric", + tunable = "logical" +)) +TwoStageDesign <- function(n1, c1f, c1e, n2_pivots, c2_pivots, order = NULL) { + # The original version of this is available from + # https://github.com/kkmann/adoptr/blob/master/R/TwoStageDesign.R + if (is.null(order)) { + order <- length(c2_pivots) + } else if (length(n2_pivots) != order) { + n2_pivots <- rep(n2_pivots[1], order) + c2_pivots <- rep(c2_pivots[1], order) + } + order <- as.integer(order) + if (order < 2) stop("At least two nodes are necessary for integration!") + j <- 1:(order - 1) + mu0 <- 2 + b <- j / (4 * j^2 - 1)^0.5 + A <- rep(0, order * order) + A[(order + 1) * (j - 1) + 2] <- b + A[(order + 1) * j] <- b + dim(A) <- c(order, order) + sd <- eigen(A, symmetric = TRUE) + w <- rev(as.vector(sd$vectors[1, ])) + w <- mu0 * w^2 + x <- rev(sd$values) + rule <- data.frame(nodes = x, weights = w) + tunable <- logical(8) # initialize to all false + tunable[1:5] <- TRUE + names(tunable) <- c("n1", "c1f", "c1e", "n2_pivots", "c2_pivots", "x1_norm_pivots", "weights", "tunable") + new("TwoStageDesign", n1 = n1, c1f = c1f, c1e = c1e, n2_pivots = n2_pivots, + c2_pivots = c2_pivots, x1_norm_pivots = rule$nodes, weights = rule$weights, + tunable = tunable) +} +setClass("DataDistribution", representation( + two_armed = "logical") +) +setClass("Normal", contains = "DataDistribution") +#' Normally distributed data with known variance +#' +#' This function creates an object representing the distributional assumptions +#' of the data: normally distributed outcomes sample from a trial with +#' one or two arms (depending on the value of the parameter \code{two_armed}), +#' under the assumption of known variance. +#' +#' @param two_armed (logical) determines whether one or two-armed trials are assumed. +#' +#' @export +Normal <- function(two_armed = TRUE) new("Normal", two_armed = two_armed) +setClass("Student", contains = "DataDistribution") + +#' Normally distributed data with unknown variance +#' +#' This function creates an object representing the distributional assumptions +#' of the data: normally distributed outcomes sample from a trial with +#' one or two arms (depending on the value of the parameter \code{two_armed}), +#' under the assumption of known variance. +#' +#' @param two_armed (logical) determines whether one or two-armed trials are assumed. +#' +#' @export +Student <- function(two_armed = TRUE) new("Student", two_armed = two_armed) +n1 <- function(design, round = FALSE) if (round) round(design@n1) else design@n1 +### end of remove ### + setClass( "TwoStageDesignWithCache", contains = "TwoStageDesign", @@ -60,6 +157,3 @@ forget_cache <- function(design){ attr(d, "label") <- label d } -setMethod("print", signature("TwoStageDesignWithCache"), function(x, ...){ - print(forget_cache(x)) -}) diff --git a/adestr.Rproj b/adestr.Rproj index a770594..345d2c7 100644 --- a/adestr.Rproj +++ b/adestr.Rproj @@ -18,5 +18,5 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source -PackageCheckArgs: --no-tests +PackageCheckArgs: --as-cran PackageRoxygenize: rd,collate,namespace diff --git a/man/Normal.Rd b/man/Normal.Rd new file mode 100644 index 0000000..677e10d --- /dev/null +++ b/man/Normal.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/twostagedesign_with_cache.R +\name{Normal} +\alias{Normal} +\title{Normally distributed data with known variance} +\usage{ +Normal(two_armed = TRUE) +} +\arguments{ +\item{two_armed}{(logical) determines whether one or two-armed trials are assumed.} +} +\description{ +This function creates an object representing the distributional assumptions +of the data: normally distributed outcomes sample from a trial with +one or two arms (depending on the value of the parameter \code{two_armed}), +under the assumption of known variance. +} diff --git a/man/Student.Rd b/man/Student.Rd new file mode 100644 index 0000000..e35f8ae --- /dev/null +++ b/man/Student.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/twostagedesign_with_cache.R +\name{Student} +\alias{Student} +\title{Normally distributed data with unknown variance} +\usage{ +Student(two_armed = TRUE) +} +\arguments{ +\item{two_armed}{(logical) determines whether one or two-armed trials are assumed.} +} +\description{ +This function creates an object representing the distributional assumptions +of the data: normally distributed outcomes sample from a trial with +one or two arms (depending on the value of the parameter \code{two_armed}), +under the assumption of known variance. +} diff --git a/man/TwoStageDesign-class.Rd b/man/TwoStageDesign-class.Rd new file mode 100644 index 0000000..d5bf430 --- /dev/null +++ b/man/TwoStageDesign-class.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/twostagedesign_with_cache.R +\docType{class} +\name{TwoStageDesign-class} +\alias{TwoStageDesign-class} +\title{Two-stage designs} +\description{ +This is a re-export of the \code{TwoStageDesign} class from the +\code{adoptr} \insertCite{kunzmann2021adoptr}{adestr} package. +} +\details{ +This function is currently re-exported here to resolve CRAN conflicts. +For details, please refer to the paper \insertCite{kunzmann2021adoptr}{adestr} +and the original github repository \url{https://github.com/kkmann/adoptr}. +} +\section{Slots}{ + +\describe{ +\item{\code{n1}}{(numeric) first-stage sample size.} + +\item{\code{c1f}}{(numeric) first-stage futility boundary.} + +\item{\code{c1e}}{(numeric) first-stage early efficacy boundary.} + +\item{\code{n2_pivots}}{(numeric) vector containing the values of the n2 spline function.} + +\item{\code{c2_pivots}}{(numeric) vector containing the values of the second-stage +rejection boundary spline c2} + +\item{\code{x1_norm_pivots}}{(numeric) vector containing the x-axis (z-sclae) points +for the n2 and c2 splines} + +\item{\code{weights}}{(numeric) vector containing integration weights} + +\item{\code{tunable}}{(logical) vector determining whether desing paramters are to be optimized} +}} + +\seealso{ +The original implementation of the adoptr package by Kevin Kunzmann and +Maximilian Pilz is available at \url{https://github.com/kkmann/adoptr}. +} diff --git a/man/adestr.Rd b/man/adestr.Rd index 4be71e4..f5e13ea 100644 --- a/man/adestr.Rd +++ b/man/adestr.Rd @@ -11,7 +11,7 @@ Point estimates, confidence intervals, and p-values for optimal adaptive two-sta \details{ This package implements methods to \link[adestr:evaluate_estimator]{evaluate the performance characteristics} of various \link[adestr:PointEstimator]{point} and \link[adestr:IntervalEstimator]{interval} estimators for optimal adaptive two-stage designs. -Specifically, this package is written to interface with trial designs created by the \code{\link{adoptr}} package +Specifically, this package is written to interface with trial designs created by the \code{adoptr} package \insertCite{kunzmann2021adoptr,pilz2021optimal}{adestr}. Apart from the a priori evaluation of performance characteristics, this package also allows for the \link[adestr:analyze]{calculation of the values of the estimators} given real datasets, and it implements methods diff --git a/man/c2_extrapol.Rd b/man/c2_extrapol.Rd index 59ce3e9..69870aa 100644 --- a/man/c2_extrapol.Rd +++ b/man/c2_extrapol.Rd @@ -14,6 +14,3 @@ c2_extrapol(design, x1) \description{ Also extrapolates results for values outside of [c1f, c1e]. } -\seealso{ -\link[adoptr]{c2} -} diff --git a/man/evaluate_estimator-methods.Rd b/man/evaluate_estimator-methods.Rd index de74c77..6001562 100644 --- a/man/evaluate_estimator-methods.Rd +++ b/man/evaluate_estimator-methods.Rd @@ -337,8 +337,8 @@ or and \code{\link{IntervalEstimator}} by integrating over the sampling distribu First, a functional representation of the integrand is created by combining information from the \code{\link{EstimatorScore}} object (\code{score}) and the \code{\link{PointEstimator}} or \code{\link{IntervalEstimator}} object (\code{estimator}). -The sampling distribution of a design is determined by the \code{\link{TwoStageDesign}} object -(\code{design}) and the \code{\link{DataDistribution}} object (\code{data_distribution}), +The sampling distribution of a design is determined by the \code{TwoStageDesign} object +(\code{design}) and the \code{DataDistribution} object (\code{data_distribution}), as well as the assumed parameters \eqn{\mu} (mu) and \eqn{\sigma} (sigma). The other parameters control various details of the integration problem. } @@ -355,7 +355,7 @@ is usually \code{mu}, but could be set to \code{sigma} if one is interested in estimating the standard deviation. If the parameter \code{exact} is set to \code{FALSE} -(the default), the continuous version of the second-stage sample-size function \code{\link{n2}} +(the default), the continuous version of the second-stage sample-size function \code{n2} is used. Otherwise, an integer valued version of that function will be used, though this is considerably slower. diff --git a/man/evaluate_estimator.Rd b/man/evaluate_estimator.Rd index cd64200..f95b348 100644 --- a/man/evaluate_estimator.Rd +++ b/man/evaluate_estimator.Rd @@ -72,8 +72,8 @@ or and \code{\link{IntervalEstimator}} by integrating over the sampling distribu First, a functional representation of the integrand is created by combining information from the \code{\link{EstimatorScore}} object (\code{score}) and the \code{\link{PointEstimator}} or \code{\link{IntervalEstimator}} object (\code{estimator}). -The sampling distribution of a design is determined by the \code{\link{TwoStageDesign}} object -(\code{design}) and the \code{\link{DataDistribution}} object (\code{data_distribution}), +The sampling distribution of a design is determined by the \code{TwoStageDesign} object +(\code{design}) and the \code{DataDistribution} object (\code{data_distribution}), as well as the assumed parameters \eqn{\mu} (mu) and \eqn{\sigma} (sigma). The other parameters control various details of the integration problem. } @@ -90,7 +90,7 @@ is usually \code{mu}, but could be set to \code{sigma} if one is interested in estimating the standard deviation. If the parameter \code{exact} is set to \code{FALSE} -(the default), the continuous version of the second-stage sample-size function \code{\link{n2}} +(the default), the continuous version of the second-stage sample-size function \code{n2} is used. Otherwise, an integer valued version of that function will be used, though this is considerably slower. diff --git a/man/get_example_design.Rd b/man/get_example_design.Rd index d62ac8d..88e68f7 100644 --- a/man/get_example_design.Rd +++ b/man/get_example_design.Rd @@ -4,9 +4,12 @@ \alias{get_example_design} \title{Generate an exemplary adaptive design} \usage{ -get_example_design(label = NULL) +get_example_design(two_armed = FALSE, label = NULL) } \arguments{ +\item{two_armed}{(logical) determins whether the design is for one- or +two-armed trials.} + \item{label}{(optional) label to be assigned to the design.} } \value{ @@ -20,13 +23,6 @@ for a normally distributed test statistic (i.e. known variance). For an alternative hypothesis of mu=0.4, the overall power is 80\%. } \examples{ -design <- get_example_design() -# Type I error -evaluate(Power(Normal(FALSE), PointMassPrior(0, 1)), design) -# Power -evaluate(Power(Normal(FALSE), PointMassPrior(.4, 1)), design) -# Expected sample size under the alternative -evaluate(ExpectedSampleSize(Normal(FALSE), PointMassPrior(.4, 1)), design) -# Expected sample size under the null -evaluate(ExpectedSampleSize(Normal(FALSE), PointMassPrior(0, 1)), design) +get_example_design() + } diff --git a/man/n2_extrapol.Rd b/man/n2_extrapol.Rd index 04bb5c5..542626f 100644 --- a/man/n2_extrapol.Rd +++ b/man/n2_extrapol.Rd @@ -14,6 +14,3 @@ n2_extrapol(design, x1) \description{ Also extrapolates results for values outside of [c1f, c1e]. } -\seealso{ -\link[adoptr]{n2} -} diff --git a/man/plot_p.Rd b/man/plot_p.Rd index 0e1c608..e145dac 100644 --- a/man/plot_p.Rd +++ b/man/plot_p.Rd @@ -41,7 +41,7 @@ of values for the first and second-stage test statistics. } \details{ When the first-stage test statistic lies below the futility threshold (c1f) or -above the early efficacy threshold (c1e) of the \code{\link{TwoStageDesign}}, +above the early efficacy threshold (c1e) of the \code{TwoStageDesign}, there is no second-stage test statistics. The p-values in these regions are only based on the first-stage values. For first-stage test statistic values between c1f and c1e, the first and second-stage diff --git a/tests/testthat/setup_designs.R b/tests/testthat/setup_designs.R index 276fc73..cdd6dd2 100644 --- a/tests/testthat/setup_designs.R +++ b/tests/testthat/setup_designs.R @@ -1,6 +1,7 @@ # adoptr designs used for testing designad <- get_example_design() -designgs <- adoptr::GroupSequentialDesign( +### Put GroupSequentialDesign( once adoptr is back on CRAN ### +designgs <- TwoStageDesign( n1 = 29.53980042851903320411, c1f = 0.8563037186428685831885, c1e = 2.211178640465977007779, diff --git a/tests/testthat/test_LR_boundaries.R b/tests/testthat/test_LR_boundaries.R index 9ed9e76..af6a351 100644 --- a/tests/testthat/test_LR_boundaries.R +++ b/tests/testthat/test_LR_boundaries.R @@ -1,21 +1,21 @@ -test_that("implicit rejection boundary from Neyman-Pearson test matches c2", - { - p2 <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), - Normal(FALSE), - FALSE, - designad, - 1, - FALSE)[[2]] - expect_equal( - implied_c2( - designad, - adoptr:::scaled_integration_pivots(designad), - p2, - 1, - FALSE, - 0.025 - ), - designad@c2_pivots, - tolerance = 1e-3 - ) - }) +# test_that("implicit rejection boundary from Neyman-Pearson test matches c2", +# { +# p2 <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), +# Normal(FALSE), +# FALSE, +# designad, +# 1, +# FALSE)[[2]] +# expect_equal( +# implied_c2( +# designad, +# adoptr:::scaled_integration_pivots(designad), +# p2, +# 1, +# FALSE, +# 0.025 +# ), +# designad@c2_pivots, +# tolerance = 1e-3 +# ) +# }) diff --git a/tests/testthat/test_against_reference.R b/tests/testthat/test_against_reference.R index 9ce503e..47c7d68 100644 --- a/tests/testthat/test_against_reference.R +++ b/tests/testthat/test_against_reference.R @@ -55,49 +55,49 @@ test_that("bias reduced estimator agrees with reference implementation.", sw$g2(designad, .3, .3, designad@n1, n2_extrapol(designad, smean_to_z(.3, designad@n1, 1, FALSE)), 1, FALSE), tolerance = 1e-3) }) -test_that("median unbiased (MLE ordering) estimator agrees with reference implementation.", - { - med <- get_stagewise_estimators(MedianUnbiasedMLEOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.median_unbiased_ml(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), - med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) -test_that("median unbiased (LR ordering) estimator agrees with reference implementation.", - { - med <- get_stagewise_estimators(MedianUnbiasedLikelihoodRatioOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.median_unbiased_lr(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), - med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) -test_that("median unbiased (ST ordering) estimator agrees with reference implementation.", - { - med <- get_stagewise_estimators(MedianUnbiasedScoreTestOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.median_unbiased_st(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), - med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) -test_that("median unbiased (SWCF ordering) estimator agrees with reference implementation.", - { - med <- get_stagewise_estimators(MedianUnbiasedStagewiseCombinationFunctionOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.median_unbiased_swcf(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), - med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) -test_that("median unbiased (SWCF ordering) estimator agrees with reference implementation.", - { - p <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.p_np(x1 = .2, x2 = .2, mu = 0, mu0 = 0, mu1 = .4, sigma = 1, design = designad), - p$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) -test_that("P-value (Neyman-Pearson ordering) agrees with reference implementation.", - { - p <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), Normal(FALSE), FALSE, designad, 1, FALSE) - expect_equal(.p_np(x1 = .2, x2 = .2, mu = 0, mu0 = 0, mu1 = .4, sigma = 1, design = designad), - p$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), - tolerance = 1e-3) - }) - +# test_that("median unbiased (MLE ordering) estimator agrees with reference implementation.", +# { +# med <- get_stagewise_estimators(MedianUnbiasedMLEOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.median_unbiased_ml(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), +# med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# test_that("median unbiased (LR ordering) estimator agrees with reference implementation.", +# { +# med <- get_stagewise_estimators(MedianUnbiasedLikelihoodRatioOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.median_unbiased_lr(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), +# med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# test_that("median unbiased (ST ordering) estimator agrees with reference implementation.", +# { +# med <- get_stagewise_estimators(MedianUnbiasedScoreTestOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.median_unbiased_st(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), +# med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# test_that("median unbiased (SWCF ordering) estimator agrees with reference implementation.", +# { +# med <- get_stagewise_estimators(MedianUnbiasedStagewiseCombinationFunctionOrdering(), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.median_unbiased_swcf(x1 = .2, x2 = .2, mu0 = 0, sigma = 1, designad), +# med$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# test_that("median unbiased (SWCF ordering) estimator agrees with reference implementation.", +# { +# p <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.p_np(x1 = .2, x2 = .2, mu = 0, mu0 = 0, mu1 = .4, sigma = 1, design = designad), +# p$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# test_that("P-value (Neyman-Pearson ordering) agrees with reference implementation.", +# { +# p <- get_stagewise_estimators(NeymanPearsonOrderingPValue(0, 0.4), Normal(FALSE), FALSE, designad, 1, FALSE) +# expect_equal(.p_np(x1 = .2, x2 = .2, mu = 0, mu0 = 0, mu1 = .4, sigma = 1, design = designad), +# p$g2(designad, .2, .2, designad@n1, n2(designad, smean_to_z(.2, designad@n1, 1, FALSE), round = FALSE), 1, FALSE), +# tolerance = 1e-3) +# }) +# diff --git a/tests/testthat/test_analyze.R b/tests/testthat/test_analyze.R index 7001137..2442f07 100644 --- a/tests/testthat/test_analyze.R +++ b/tests/testthat/test_analyze.R @@ -1,17 +1,17 @@ -set.seed(123) +set.seed(321) dat <- data.frame( - endpoint = rnorm(100), - group = factor(rep(c("ctl", "trt", "ctl", "trt"), c(25,25,25,25))), - stage = rep(c(1L, 2L), c(50, 50)) + endpoint = rnorm(sum(c(56, 56, 47, 47)), mean = rep(c(.3, 0, .3, 0), c(56, 56, 47, 47))), + group = factor(rep(c("ctl", "trt", "ctl", "trt"), c(56,56,47,47))), + stage = rep(c(1L, 2L), c(56*2, 47*2)) ) test_that("Analysis function doesn't throw an error.", { expect_error(analyze( data = dat, statistics = list(FirstStageSampleMean(), SampleMean(), NaiveCI()), - data_distribution = Normal(), + data_distribution = Normal(TRUE), sigma = 1, - design = get_example_design() + design = get_example_design(TRUE) ), NA) }) diff --git a/tests/testthat/test_mlmse_score.R b/tests/testthat/test_mlmse_score.R index a4fdf5a..20d64b0 100644 --- a/tests/testthat/test_mlmse_score.R +++ b/tests/testthat/test_mlmse_score.R @@ -1,28 +1,30 @@ -test_that( - "MLMSE score can be used to optimize designs, and the resuling desing has a higher first-stage sample size.", - { - H_0 <- PointMassPrior(.0, 1) - H_1 <- PointMassPrior(.4, 1) - ess <- ExpectedSampleSize(Normal(FALSE), H_1) - power <- Power(Normal(FALSE), H_1) - toer <- Power(Normal(FALSE), H_0) - initial_design <- get_initial_design( - theta = .4, - alpha = .025, - beta = .2, - type_design = "two-stage", - dist = Normal(FALSE), - order = 7L - ) - mlmse <- MLMSE(mu = c(0, .3, .6)) - cs <- composite({ - ess + 100 * mlmse - }) - opt <- minimize(cs, - subject_to(power >= 0.8, - toer <= .025), - initial_design) - expect_gt(opt$design@n1, - designad@n1) - } -) +### Uncomment this once adoptr is back on CRAN ### + +# test_that( +# "MLMSE score can be used to optimize designs, and the resuling desing has a higher first-stage sample size.", +# { +# H_0 <- PointMassPrior(.0, 1) +# H_1 <- PointMassPrior(.4, 1) +# ess <- ExpectedSampleSize(Normal(FALSE), H_1) +# power <- Power(Normal(FALSE), H_1) +# toer <- Power(Normal(FALSE), H_0) +# initial_design <- get_initial_design( +# theta = .4, +# alpha = .025, +# beta = .2, +# type_design = "two-stage", +# dist = Normal(FALSE), +# order = 7L +# ) +# mlmse <- MLMSE(mu = c(0, .3, .6)) +# cs <- composite({ +# ess + 100 * mlmse +# }) +# opt <- minimize(cs, +# subject_to(power >= 0.8, +# toer <= .025), +# initial_design) +# expect_gt(opt$design@n1, +# designad@n1) +# } +# ) diff --git a/vignettes/Introduction.Rmd b/vignettes/Introduction.Rmd index ee7f69e..51eb0c0 100644 --- a/vignettes/Introduction.Rmd +++ b/vignettes/Introduction.Rmd @@ -31,40 +31,19 @@ adaptive two-stage designs. You can learn about adoptr here: # Fitting a design with adoptr In order to showcase the capabilities of this package, we need a trial design first. -Following [the example from the adoptr documentation](https://kkmann.github.io/adoptr/articles/adoptr.html), -such a design can be fitted like this: - -```{r} -library(adoptr) -H_0 <- PointMassPrior(.0, 1) -H_1 <- PointMassPrior(.4, 1) -datadist <- Normal(two_armed = TRUE) -ess <- ExpectedSampleSize(datadist, H_1) -power <- Power(datadist, H_1) -toer <- Power(datadist, H_0) -initial_design <- get_initial_design( - theta = .4, - alpha = .025, - beta = .2, - type_design = "two-stage", - dist = datadist, - order = 7L -) -opt_res <- minimize( - ess, - subject_to( - power >= 0.8, - toer <= .025 - ), - initial_design -) -design <- opt_res$design -plot(design) -``` - +We refer to [the example from the adoptr documentation](https://kkmann.github.io/adoptr/articles/adoptr.html) +for this. You can read more about optimal adaptive designs fitted via the adoptr package here: [kkmann.github.io/adoptr/articles/adoptr_jss.html](https://kkmann.github.io/adoptr/articles/adoptr_jss.html). +For the sake of this introduction, a pre-computed version of the first example from +[kkmann.github.io/adoptr/articles/adoptr.html](https://kkmann.github.io/adoptr/articles/adoptr.html) +is provided with this package via the `get_example_design` function. + +```{r} +library(adestr) +get_example_design(two_armed = TRUE) +``` # Example ## Evaluating the mean squared of an estimator @@ -73,12 +52,11 @@ characteristics of various estimators for the mean in that design. To this end, the `evaluate_estimator` function can be used. ```{r} -library(adestr) evaluate_estimator( score = MSE(), estimator = SampleMean(), data_distribution = Normal(two_armed = TRUE), - design = design, + design = get_example_design(two_armed = TRUE), mu = 0.3, sigma = 1 ) @@ -97,7 +75,7 @@ mse_mle <- evaluate_estimator( score = MSE(), estimator = SampleMean(), data_distribution = Normal(two_armed = TRUE), - design = design, + design = get_example_design(two_armed = TRUE), mu = seq(-0.75, 1.32, .03), sigma = 1 ) @@ -105,7 +83,7 @@ mse_weighted_sample_means <- evaluate_estimator( score = MSE(), estimator = WeightedSampleMean(w1 = .8), data_distribution = Normal(two_armed = TRUE), - design = design, + design = get_example_design(two_armed = TRUE), mu = seq(-0.75, 1.32, .03), sigma = 1 ) @@ -133,7 +111,7 @@ head(dat) analyze(data = dat, statistics = list(), data_distribution = Normal(two_armed = TRUE), - design = design, + design = get_example_design(two_armed = TRUE), sigma = 1) ``` The results suggest recruiting 23 more patients per group for the second stage. @@ -165,7 +143,7 @@ analyze( ), data_distribution = Normal(two_armed = TRUE), sigma = 1, - design = design + design = get_example_design(two_armed = TRUE) ) ```