setup_trial {adaptr}R Documentation

Setup a generic trial specification

Description

Specifies the design of an adaptive trial with any type of outcome and validates all inputs. Use calibrate_trial() to calibrate the trial specification to obtain a specific value for a certain performance metric (e.g., the Bayesian type 1 error rate). Use run_trial() or run_trials() to conduct single/multiple simulations of the specified trial, respectively.
See setup_trial_binom() and setup_trial_norm() for simplified setup of trial designs for common outcome types. For additional trial specification examples, see the the Basic examples vignette (vignette("Basic-examples", package = "adaptr")) and the Advanced example vignette (vignette("Advanced-example", package = "adaptr")).

Usage

setup_trial(
  arms,
  true_ys,
  fun_y_gen = NULL,
  fun_draws = NULL,
  start_probs = NULL,
  fixed_probs = NULL,
  min_probs = rep(NA, length(arms)),
  max_probs = rep(NA, length(arms)),
  data_looks = NULL,
  max_n = NULL,
  look_after_every = NULL,
  randomised_at_looks = NULL,
  control = NULL,
  control_prob_fixed = NULL,
  inferiority = 0.01,
  superiority = 0.99,
  equivalence_prob = NULL,
  equivalence_diff = NULL,
  equivalence_only_first = NULL,
  futility_prob = NULL,
  futility_diff = NULL,
  futility_only_first = NULL,
  highest_is_best = FALSE,
  soften_power = 1,
  fun_raw_est = mean,
  cri_width = 0.95,
  n_draws = 5000,
  robust = TRUE,
  description = NULL,
  add_info = NULL
)

Arguments

arms

character vector with unique names for the trial arms.

true_ys

numeric vector specifying true outcomes (e.g., event probabilities, mean values, etc.) for all trial arms.

fun_y_gen

function, generates outcomes. See setup_trial() Details for information on how to specify this function.
Note: this function is called once during setup to validate its output (with the global random seed restored afterwards).

fun_draws

function, generates posterior draws. See setup_trial() Details for information on how to specify this function.
Note: this function is called up to three times during setup to validate its output (with the global random seed restored afterwards).

start_probs

numeric vector, allocation probabilities for each arm at the beginning of the trial. The default (NULL) automatically generates equal randomisation probabilities for each arm.

fixed_probs

numeric vector, fixed allocation probabilities for each arm. Must be either a numeric vector with NA for arms without fixed probabilities and values between 0 and 1 for the other arms or NULL (default), if adaptive randomisation is used for all arms or if one of the special settings ("sqrt-based", "sqrt-based start", "sqrt-based fixed", or "match") is specified for control_prob_fixed (described below).

min_probs

numeric vector, lower threshold for adaptive allocation probabilities; lower probabilities will be rounded up to these values. Must be NA (default for all arms) if no lower threshold is wanted and for arms using fixed allocation probabilities.

max_probs

numeric vector, upper threshold for adaptive allocation probabilities; higher probabilities will be rounded down to these values. Must be NA (default for all arms) if no threshold is wanted and for arms using fixed allocation probabilities.

data_looks

vector of increasing integers, specifies when to conduct adaptive analyses (= the total number of patients with available outcome data at each adaptive analysis). The last number in the vector represents the final adaptive analysis, i.e., the final analysis where superiority, inferiority, practical equivalence, or futility can be claimed. Instead of specifying data_looks, the max_n and look_after_every arguments can be used in combination (in which case data_looks must be NULL, the default value).

max_n

single integer, number of patients with available outcome data at the last possible adaptive analysis (defaults to NULL). Must only be specified if data_looks is NULL. Requires specification of the look_after_every argument.

look_after_every

single integer, specified together with max_n. Adaptive analyses will be conducted after every look_after_every patients have available outcome data, and at the total sample size as specified by max_n (max_n does not need to be a multiple of look_after_every). If specified, data_looks must be NULL (default).

randomised_at_looks

vector of increasing integers or NULL, specifying the number of patients randomised at the time of each adaptive analysis, with new patients randomised using the current allocation probabilities at said analysis. If NULL (the default), the number of patients randomised at each analysis will match the number of patients with available outcome data at said analysis, as specified by data_looks or max_n and look_after_every, i.e., outcome data will be available immediately after randomisation for all patients.
If not NULL, the vector must be of the same length as the number of adaptive analyses specified by data_looks or max_n and look_after_every, and all values must be larger than or equal to the number of patients with available outcome data at each analysis.

control

single character string, name of one of the arms or NULL (default). If specified, this arm will serve as a common control arm, to which all other arms will be compared and the inferiority/superiority/equivalence thresholds (see below) will be for those comparisons. See setup_trial() Details for information on behaviour with respect to these comparisons.

control_prob_fixed

if a common control arm is specified, this can be set NULL (the default), in which case the control arm allocation probability will not be fixed if control arms change (the allocation probability for the first control arm may still be fixed using fixed_probs). If not NULL, a vector of probabilities of either length 1 or ⁠number of arms - 1⁠ can be provided, or one of the special arguments "sqrt-based", "sqrt-based start", "sqrt-based fixed" or "match". See setup_trial() Details for details on how this affects trial behaviour.

inferiority

single numeric value or vector of numeric values of the same length as the maximum number of possible adaptive analyses, specifying the probability threshold(s) for inferiority (default is 0.01). All values must be ⁠>= 0⁠ and ⁠<= 1⁠, and if multiple values are supplied, no values may be lower than the preceding value. If a common controlis not used, all values must be ⁠< 1 / number of arms⁠. An arm will be considered inferior and dropped if the probability that it is best (when comparing all arms) or better than the control arm (when a common control is used) drops below the inferiority threshold at an adaptive analysis.

superiority

single numeric value or vector of numeric values of the same length as the maximum number of possible adaptive analyses, specifying the probability threshold(s) for superiority (default is 0.99). All values must be ⁠>= 0⁠ and ⁠<= 1⁠, and if multiple values are supplied, no values may be higher than the preceding value. If the probability that an arm is best (when comparing all arms) or better than the control arm (when a common control is used) exceeds the superiority threshold at an adaptive analysis, said arm will be declared the winner and the trial will be stopped (if no common control is used or if the last comparator is dropped in a design with a common control) or become the new control and the trial will continue (if a common control is specified).

equivalence_prob

single numeric value, vector of numeric values of the same length as the maximum number of possible adaptive analyses or NULL (default, corresponding to no equivalence assessment), specifying the probability threshold(s) for equivalence. If not NULL, all values must be ⁠> 0⁠ and ⁠<= 1⁠, and if multiple values are supplied, no value may be higher than the preceding value. If not NULL, arms will be dropped for equivalence if the probability of either (a) equivalence compared to a common control or (b) equivalence between all arms remaining (designs without a common control) exceeds the equivalence threshold at an adaptive analysis. Requires specification of equivalence_diff and equivalence_only_first.

equivalence_diff

single numeric value (⁠> 0⁠) or NULL (default, corresponding to no equivalence assessment). If a numeric value is specified, estimated absolute differences smaller than this threshold will be considered equivalent. For designs with a common control arm, the differences between each non-control arm and the control arm is used, and for trials without a common control arm, the difference between the highest and lowest estimated outcome rates are used and the trial is only stopped for equivalence if all remaining arms are equivalent.

equivalence_only_first

single logical in trial specifications where equivalence_prob and equivalence_diff are specified and a common control arm is included, otherwise NULL (default). If a common control arm is used, this specifies whether equivalence will only be assessed for the first control (if TRUE) or also for subsequent control arms (if FALSE) if one arm is superior to the first control and becomes the new control.

futility_prob

single numeric value, vector of numeric values of the same length as the maximum number of possible adaptive analyses or NULL (default, corresponding to no futility assessment), specifying the probability threshold(s) for futility. All values must be ⁠> 0⁠ and ⁠<= 1⁠, and if multiple values are supplied, no value may be higher than the preceding value. If not NULL, arms will be dropped for futility if the probability for futility compared to the common control exceeds the futility threshold at an adaptive analysis. Requires a common control arm (otherwise this argument must be NULL), specification of futility_diff, and futility_only_first.

futility_diff

single numeric value (⁠> 0⁠) or NULL (default, corresponding to no futility assessment). If a numeric value is specified, estimated differences below this threshold in the beneficial direction (as specified in highest_is_best) will be considered futile when assessing futility in designs with a common control arm. If only 1 arm remains after dropping arms for futility, the trial will be stopped without declaring the last arm superior.

futility_only_first

single logical in trial specifications designs where futility_prob and futility_diff are specified, otherwise NULL (default and required in designs without a common control arm). Specifies whether futility will only be assessed against the first control (if TRUE) or also for subsequent control arms (if FALSE) if one arm is superior to the first control and becomes the new control.

highest_is_best

single logical, specifies whether larger estimates of the outcome are favourable or not; defaults to FALSE, corresponding to, e.g., an undesirable binary outcomes (e.g., mortality) or a continuous outcome where lower numbers are preferred (e.g., hospital length of stay).

soften_power

either a single numeric value or a numeric vector of exactly the same length as the maximum number of looks/adaptive analyses. Values must be between 0 and 1 (default); if ⁠< 1⁠, then re-allocated non-fixed allocation probabilities are all raised to this power (followed by rescaling to sum to 1) to make adaptive allocation probabilities less extreme, in turn used to redistribute remaining probability while respecting limits when defined by min_probs and/or max_probs. If 1, then no softening is applied.

fun_raw_est

function that takes a numeric vector and returns a single numeric value, used to calculate a raw summary estimate of the outcomes in each arm. Defaults to mean(), which is always used in the setup_trial_binom() and setup_trial_norm() functions.
Note: the function is called one time per arm during setup to validate the output structure.

cri_width

single numeric ⁠>= 0⁠ and ⁠< 1⁠, the width of the percentile-based credible intervals used when summarising individual trial results. Defaults to 0.95, corresponding to 95% credible intervals.

n_draws

single integer, the number of draws from the posterior distributions for each arm used when running the trial. Defaults to 5000; can be reduced for a speed gain (at the potential loss of stability of results if too low) or increased for increased precision (increasing simulation time). Values ⁠< 100⁠ are not allowed and values ⁠< 1000⁠ are not recommended and warned against.

robust

single logical, if TRUE (default) the medians and median absolute deviations (scaled to be comparable to the standard deviation for normal distributions; MAD_SDs, see stats::mad()) are used to summarise the posterior distributions; if FALSE, the means and standard deviations (SDs) are used instead (slightly faster, but may be less appropriate for posteriors skewed on the natural scale).

description

optional single character string describing the trial design, will only be used in print functions if not NULL (the default).

add_info

optional single string containing additional information regarding the trial design or specifications, will only be used in print functions if not NULL (the default).

Details

How to specify the fun_y_gen function

The function must take the following arguments:

The function must return a single numeric vector, corresponding to the outcomes for all patients allocated since the last adaptive analysis, in the same order as allocs.
See the Advanced example vignette (vignette("Advanced-example", package = "adaptr")) for an example with further details.

How to specify the fun_draws function

The function must take the following arguments:

The function must return a matrix (containing numeric values) with arms named columns and n_draws rows. The matrix must have columns only for currently active arms (when called). Each row should contain a single posterior draw for each arm on the original outcome scale: if they are estimated as, e.g., the log(odds), these estimates must be transformed to probabilities and similarly for other measures.
Important: the matrix cannot contain NAs, even if no patients have been randomised to an arm yet. See the provided example for one way to alleviate this.
See the Advanced examples vignette (vignette("Advanced-example", package = "adaptr")) for an example with further details.

Notes

Using additional custom or functions from loaded packages in the custom functions

If the fun_y_gen, fun_draws, or fun_raw_est functions calls other user-specified functions (or uses objects defined by the user outside these functions or the setup_trial()-call) or functions from external packages and simulations are conducted on multiple cores, these objects or functions must be exported or prefixed with their namespaces, respectively, as described in setup_cluster() and run_trials().

More information on arguments

Superiority and inferiority

In trial designs without a common control arm, superiority and inferiority are assessed by comparing all currently active groups. This means that if a "final" analysis of a trial without a common control and ⁠> 2 arms⁠ is conducted including all arms (as will often be done in practice) after an adaptive trial has stopped, the final probabilities of the best arm being superior may differ slightly.
For example, in a trial with three arms and no common control arm, one arm may be dropped early for inferiority defined as ⁠< 1%⁠ probability of being the overall best arm. The trial may then continue with the two remaining arms, and stopped when one is declared superior to the other defined as ⁠> 99%⁠ probability of being the overall best arm. If a final analysis is then conducted including all arms, the final probability of the best arm being overall superior will generally be slightly lower as the probability of the first dropped arm being the best will often be ⁠> 0%⁠, even if very low and below the inferiority threshold.
This is less relevant trial designs with a common control, as pairwise assessments of superiority/inferiority compared to the common control will not be influenced similarly by previously dropped arms (and previously dropped arms may be included in the analyses, even if posterior distributions are not returned for those). Similarly, in actual clinical trials and when randomised_at_looks is specified with numbers higher than the number of patients with available outcome data at each analysis, final probabilities may change somewhat when the all patients are have completed follow-up and are included in a final analysis.

Equivalence

Equivalence is assessed after both inferiority and superiority have been assessed (and in case of superiority, it will be assessed against the new control arm in designs with a common control, if specified - see above).

Futility

Futility is assessed after inferiority, superiority, and equivalence have been assessed (and in case of superiority, it will be assessed against the new control arm in designs with a common control, if specified - see above). Arms will thus be dropped for equivalence before futility.

Varying probability thresholds

Different probability thresholds (for superiority, inferiority, equivalence, and futility) may be specified for different adaptive analyses. This may be used, e.g., to apply more strict probability thresholds at earlier analyses (or make one or more stopping rules not apply at earlier analyses), similar to the use of monitoring boundaries with different thresholds used for interim analyses in conventional, frequentist group sequential trial designs. See the Basic examples vignette (vignette("Basic-examples", package = "adaptr")) for an example.

Value

A trial_spec object used to run simulations by run_trial() or run_trials(). The output is essentially a list containing the input values (some combined in a data.frame called trial_arms), but its class signals that these inputs have been validated and inappropriate combinations and settings have been ruled out. Also contains best_arm, holding the arm(s) with the best value(s) in true_ys. Use str() to peruse the actual content of the returned object.

Examples

# Setup a custom trial specification with right-skewed, log-normally
# distributed continuous outcomes (higher values are worse)

# Define the function that will generate the outcomes in each arm
# Notice: contents should match arms/true_ys in the setup_trial() call below
get_ys_lognorm <- function(allocs) {
  y <- numeric(length(allocs))
  # arms (names and order) and values (except for exponentiation) should match
  # those used in setup_trial (below)
  means <- c("Control" = 2.2, "Experimental A" = 2.1, "Experimental B" = 2.3)
  for (arm in names(means)) {
    ii <- which(allocs == arm)
    y[ii] <- rlnorm(length(ii), means[arm], 1.5)
  }
  y
}

# Define the function that will generate posterior draws
# In this example, the function uses no priors (corresponding to improper
# flat priors) and calculates results on the log-scale, before exponentiating
# back to the natural scale, which is required for assessments of
# equivalence, futility and general interpretation
get_draws_lognorm <- function(arms, allocs, ys, control, n_draws) {
  draws <- list()
  logys <- log(ys)
  for (arm in arms){
    ii <- which(allocs == arm)
    n <- length(ii)
    if (n > 1) {
      # Necessary to avoid errors if too few patients randomised to this arm
      draws[[arm]] <- exp(rnorm(n_draws, mean = mean(logys[ii]), sd = sd(logys[ii])/sqrt(n - 1)))
    } else {
      # Too few patients randomised to this arm - extreme uncertainty
      draws[[arm]] <- exp(rnorm(n_draws, mean = mean(logys), sd = 1000 * (max(logys) - min(logys))))
    }
  }
  do.call(cbind, draws)
}

# The actual trial specification is then defined
lognorm_trial <- setup_trial(
  # arms should match those above
  arms = c("Control", "Experimental A", "Experimental B"),
  # true_ys should match those above
  true_ys = exp(c(2.2, 2.1, 2.3)),
  fun_y_gen = get_ys_lognorm, # as specified above
  fun_draws = get_draws_lognorm, # as specified above
  max_n = 5000,
  look_after_every = 200,
  control = "Control",
  # Square-root-based, fixed control group allocation ratio
  # and response-adaptive randomisation for other arms
  control_prob_fixed = "sqrt-based",
  # Equivalence assessment
  equivalence_prob = 0.9,
  equivalence_diff = 0.5,
  equivalence_only_first = TRUE,
  highest_is_best = FALSE,
  # Summarise raw results by taking the mean on the
  # log scale and back-transforming
  fun_raw_est = function(x) exp(mean(log(x))) ,
  # Summarise posteriors using medians with MAD-SDs,
  # as distributions will not be normal on the actual scale
  robust = TRUE,
  # Description/additional info used when printing
  description = "continuous, log-normally distributed outcome",
  add_info = "SD on the log scale for all arms: 1.5"
)

# Print trial specification with 3 digits for all probabilities
print(lognorm_trial, prob_digits = 3)


[Package adaptr version 1.3.2 Index]