| Type: | Package |
| Title: | Optimal Two-Stage Designs for Ordered Categorical Outcomes |
| Version: | 1.1.0 |
| Date: | 2026-08-28 |
| Description: | Functions to design and simulate optimal two-stage randomized controlled trials (RCTs) with ordered categorical outcomes, using rank-based tests and interim decision rules for futility and superiority, following Park (2025) <doi:10.1371/journal.pone.0318211>. The functions 'rule()', 'op()' and 'design_table()' provide a single entry point for constructing designs, evaluating their operating characteristics, and tabulating several designs at once. The separate functions provided for each combination of test statistic and stopping rule up to version 1.0.2 were deprecated in 1.0.3 and are removed here. Please see the package reference manual and the vignette for details. |
| License: | GPL-3 |
| Encoding: | UTF-8 |
| Depends: | R (≥ 4.0) |
| Imports: | stats |
| Suggests: | knitr, rmarkdown, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| RoxygenNote: | 7.3.2 |
| NeedsCompilation: | no |
| Author: | Yeonhee Park [aut, cre], Yudi Wang [aut], Zhanpeng Xu [aut] |
| Maintainer: | Yeonhee Park <yeonheepark@skku.edu> |
| Packaged: | 2026-09-17 14:50:51 UTC; ypark56 |
| Repository: | CRAN |
| Date/Publication: | 2026-09-23 02:30:02 UTC |
Optimal Two-Stage Designs for Ordered Categorical Outcomes
Description
Functions to design and simulate optimal two-stage randomized controlled trials (RCTs) with ordered categorical outcomes, using rank-based tests and interim decision rules for futility and superiority, following Park (2025) <doi:10.1371/journal.pone.0318211>. The functions 'rule()', 'op()' and 'design_table()' provide a single entry point for constructing designs, evaluating their operating characteristics, and tabulating several designs at once. The separate functions provided for each combination of test statistic and stopping rule up to version 1.0.2 were deprecated in 1.0.3 and are removed here. Please see the package reference manual and the vignette for details.
Details
The workflow runs through two functions. rule constructs a
decision rule: the test statistic, the monitoring scheme and the optimality
criterion are chosen by argument, and the result is a named object with a
print method. op takes that object and estimates the
operating characteristics by simulation, reporting the type I error rate,
the power, the interim stopping probabilities and the expected sample size
under both hypotheses in a single call.
design_table sweeps a set of tests and criteria and returns a
data frame with one row per design. Proportional_odds_assumption
checks whether the score test is appropriate, p2_fun constructs
the experimental-group probabilities from a specified odds ratio, and
theta returns the win probability.
For advanced calculations, V_S.over.nk, QR_fun,
and W_W evaluate the asymptotic variance quantities for the
score, Mann–Whitney–Wilcoxon, and win odds statistics, respectively. These
helpers reproduce the Appendix calculations in the accompanying article and
permit boundary calculations at a prespecified sample size.
Versions up to 1.0.2 provided a separate function for each combination of test
and stopping rule. Those functions were deprecated in 1.0.3 and removed in
1.1.0; NEWS.md and the vignette tabulate the correspondence.
Author(s)
Yeonhee Park [aut, cre], Yudi Wang [aut], Zhanpeng Xu [aut]
Maintainer: Yeonhee Park <yeonheepark@skku.edu>
References
Park, Y. (2025). Optimal two-stage group sequential designs based on Mann-Whitney-Wilcoxon test. PloS one, 20(2), e0318211.
Checking proportional odds assumption
Description
This is the function to check the proportional odds assumption for the score test.
Usage
Proportional_odds_assumption(p1, p2, verbose = TRUE)
Arguments
p1 |
A vector containing the probabilities of the outcome falling into each level of the control arm. |
p2 |
A vector containing the probabilities of the outcome falling into each level of the experimental arm. |
verbose |
Logical. If |
Validated as for rule: numeric, equal length, at least two
categories, no missing or negative values, and summing to one within
1e-6.
Value
Indicates whether the proportional odds assumption holds. If the assumption holds, the
function returns the log-odds ratio from the score test. If the assumption does not
hold, the function
returns NA and additionally emits a message and a warning explaining
that the score test is not advisable and suggesting the Mann-Whitney-Wilcoxon
or win odds test, so that the condition is not missed in scripted use.
Examples
## The assumption holds when p2 is built from p1 by a common odds ratio
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
p2 <- p2_fun(p1, log(3.06))
Proportional_odds_assumption(p1, p2)
## and fails otherwise, with a message and a warning rather than a bare NA
q1 <- c(1/3, 1/3, 1/3)
q2 <- c(1/2, 1/3, 1/6)
try(Proportional_odds_assumption(q1, q2))
Variance component of the Mann-Whitney-Wilcoxon statistic
Description
Returns the quantity Q (equivalently R, by symmetry of the
arguments) appearing in the asymptotic variance of the Mann-Whitney-Wilcoxon
statistic, \mathrm{Var}(T_k^{(M)}) \approx Q/n_{1 \cdot k} + R/n_{2 \cdot k}.
Usage
QR_fun(p1, p2)
Arguments
p1 |
A vector of outcome-category probabilities for the control arm. |
p2 |
A vector of outcome-category probabilities for the experimental
arm; supply |
Value
A single number.
See Also
Examples
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
lambda <- 1
## Null variance of the statistic at a total size of 28, and the threshold it
## implies for a nominal one-sided level of 0.05
v <- ((1 + lambda) * QR_fun(p1, p1) + (1 + 1/lambda) * QR_fun(p1, p1)) / 28
qnorm(0.05, lower.tail = FALSE) * sqrt(v)
Variance factor of the score statistic
Description
Returns the asymptotic variance of the score test statistic per patient, that
is \mathrm{Var}(T_k^{(S)}) / n_{\cdot\cdot k}, under the proportional
odds model. Because the factor does not depend on the sample size, the variance
at any total size n is obtained by multiplying by n, which is what
makes the sample-size equations solvable in closed form.
Usage
V_S.over.nk(p1, p2, lambda = 1)
Arguments
p1 |
A vector of outcome-category probabilities for the control arm. |
p2 |
A vector of outcome-category probabilities for the experimental
arm; supply |
lambda |
A finite positive allocation ratio, experimental to control (default 1). |
Value
A single number, the variance per patient.
See Also
QR_fun and W_W for the rank-based tests.
Examples
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
p2 <- p2_fun(p1, log(3.06))
## Null variance factor, and the threshold a fixed total size n implies for a
## nominal one-sided level of 0.05
v <- V_S.over.nk(p1, p1)
qnorm(0.05, lower.tail = FALSE) * sqrt(v * 28)
## Under the alternative the factor differs
V_S.over.nk(p1, p2)
Variance factor of the win odds statistic
Description
Returns the variance factor W_k^{(W)} of the win odds statistic, so that
its asymptotic variance at total size n is W_k^{(W)}/n.
Usage
W_W(p1, p2, lambda = 1)
Arguments
p1 |
A vector of outcome-category probabilities for the control arm. |
p2 |
A vector of outcome-category probabilities for the experimental
arm; supply |
lambda |
A finite positive allocation ratio, experimental to control (default 1). |
Value
A single number.
See Also
theta for the win probability itself,
V_S.over.nk and QR_fun for the other tests.
Examples
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
## The win odds statistic is centred at 0.5 under the null; the threshold at a
## total size of 28 for a nominal one-sided level of 0.05 is
0.5 + qnorm(0.05, lower.tail = FALSE) * sqrt(W_W(p1, p1) / 28)
Table of designs and operating characteristics
Description
Builds a data frame of decision rules and simulated operating characteristics
over a set of tests and optimality criteria, replacing the
expand.grid() and apply() idiom that required positional
indexing of returned vectors. The random number generator is re-seeded for
every row, so each row is reproducible on its own.
Usage
design_table(alpha, beta, p1, p2, tests, criteria,
stopping = c("F", "FS"), lambda = 1, nsim = 10000,
seed = 1234, on_degenerate = c("fallback", "warn", "error", "none"),
digits = NULL)
Arguments
alpha, beta, p1, p2, lambda |
As in |
tests |
Character vector of tests, a subset of |
criteria |
Numeric vector of optimality criteria, a subset of 1 to 5. |
stopping |
|
nsim |
Number of simulation replicates per row. |
seed |
Seed applied before each scenario of each row. |
on_degenerate |
Passed to |
digits |
Optional number of digits for rounding the estimated
probabilities. When supplied, EN0 and ENa are rounded to two decimals and
EN is recomputed from the rounded values, so that a reader checking
EN = (EN0 + ENa)/2 against the printed table reproduces the printed EN
exactly. The default |
Value
A data frame with one row per (test, criterion) combination, containing the
decision rule, the estimated type I error rate and power with Monte Carlo
standard errors, the futility and superiority stopping probabilities under each
hypothesis, the expected sample sizes, and a degenerate flag.
See Also
Examples
p1 <- c(1/3, 1/3, 1/3)
p2 <- c(1/2, 1/3, 1/6)
design_table(0.05, 0.2, p1, p2, tests = "M", criteria = 1,
stopping = "FS", nsim = 500)
Operating characteristics of a design
Description
Estimates the operating characteristics of a design produced by
rule by Monte Carlo simulation. Both hypotheses are evaluated in
a single call, and every returned quantity is named, so the meaning of the
output does not depend on how the function was called.
Usage
op(design, nsim = 10000, seed = NULL, p1 = NULL, p2 = NULL,
allow_degenerate = FALSE)
Arguments
design |
An object returned by |
nsim |
Number of simulation replicates (default 10000). |
seed |
Optional integer. If supplied, the random number generator is
seeded immediately before each scenario, so the result is reproducible
independently of the state of the stream when |
p1, p2 |
Optional category probabilities overriding those stored in
|
allow_degenerate |
Logical. A degenerate two-stage optimum that has
not been replaced by its single-stage substitute cannot be simulated
meaningfully, because its stage-1 size is too small to allocate
patients to both arms; such a design is refused by default. Set to
|
Value
An object of class "OptOTrialsOC" containing alpha and
power with their Monte Carlo standard errors se_alpha and
se_power, the expected sample sizes EN0, ENa and
EN, and a data frame table reporting, under each hypothesis, the
rejection probability, the probability of stopping early for futility, the
probability of stopping early for superiority, the probability of continuing to
the second stage, and the expected sample size. Futility and superiority
stopping are reported separately rather than pooled.
See Also
Examples
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
p2 <- p2_fun(p1, log(3.06))
d <- rule(0.05, 0.2, p1, p2, test = "M", stopping = "F", criterion = 1)
## Printing gives an aligned table: both hypotheses in one call, with the
## futility and superiority stopping probabilities reported separately.
o <- op(d, nsim = 1000, seed = 1234)
o # same as print(o)
## Every quantity is named, so which is which does not depend on how the
## function was called.
o$alpha # type I error rate, with
o$se_alpha # its Monte Carlo standard error
o$power
o$se_power
c(o$EN0, o$ENa, o$EN) # expected sample sizes
o$table # the full table, one row per hypothesis
names(o)
## The seed is applied to each hypothesis separately, so a call reproduces
## on its own whatever the state of the random number stream.
runif(100)
identical(op(d, nsim = 1000, seed = 1234)$power, o$power)
## Evaluating the same design under a different alternative
op(d, nsim = 1000, seed = 1234, p2 = p2_fun(p1, log(2)))$power
Experimental-group probabilities from a specified odds ratio
Description
Constructs the category probabilities for the experimental arm from those of the control arm and a specified log odds ratio, under the proportional odds model. It is a convenience for setting up an alternative hypothesis in terms of a clinically meaningful effect size rather than by writing out a second probability vector.
Usage
p2_fun(p1, theta)
Arguments
p1 |
A vector containing the probabilities of the outcome falling into
each level for the control arm. Validated on every call: numeric, at least two categories, no missing or
negative values, and summing to one within |
theta |
The log odds ratio representing the expected effect of the experimental treatment. A single finite number. |
Value
A numeric vector of the same length as p1, giving the probability of
each outcome level for the experimental arm.
See Also
Proportional_odds_assumption to check that a pair of
vectors is consistent with a common odds ratio.
Examples
## Example 1 of the article: six ordered categories, odds ratio 3.06
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
p2 <- p2_fun(p1, log(3.06))
p2
sum(p2)
## The construction is consistent by definition, so the diagnostic recovers
## the odds ratio it was given
exp(Proportional_odds_assumption(p1, p2, verbose = FALSE))
Optimal design for a trial with an ordered categorical outcome
Description
Constructs the decision rule for a one- or two-stage randomised trial with an ordered categorical outcome. This is the recommended entry point to the package: the test statistic and the monitoring scheme are selected by argument, so a single function replaces the eleven separate decision-rule functions provided up to version 1.0.2.
Usage
rule(alpha, beta, p1, p2, test = c("S", "M", "W"),
stopping = c("F", "FS", "none"), criterion = 1, lambda = 1,
on_degenerate = c("fallback", "warn", "error", "none"), min_n1 = 10)
Arguments
alpha |
Target type I error rate. |
beta |
Target type II error rate; power is |
p1 |
Numeric vector of outcome category probabilities for the control group. Must be non-negative and sum to 1. |
p2 |
Numeric vector of outcome category probabilities for the
experimental group. Must be the same length as Both vectors are validated on every call: each must be numeric, cover at
least two outcome categories, contain no missing or negative values, and
sum to one within a tolerance of |
test |
Test statistic: |
stopping |
Monitoring scheme: |
criterion |
Optimality criterion, 1 to 5. 1: minimise the expected total
sample size under the null; 2: under the alternative; 3: assuming
|
lambda |
Ratio of experimental to control sample sizes (default 1). |
on_degenerate |
How to handle a degenerate optimum (see Details).
|
min_n1 |
Smallest stage-1 sample size regarded as usable when detecting degeneracy (default 10). |
Details
Minimising the expected sample size under the alternative (criterion = 2)
can drive the optimum to a design with a stage-1 sample size of 1 and an
interim boundary so far into the lower tail that the interim analysis cannot
change any decision: the design is then two-stage in name only. Intuitively,
the criterion rewards designs under which continuation to the final analysis is
almost certain when the treatment works, so the optimiser commits essentially
all information to the final analysis. rule() detects this and, by
default, substitutes the corresponding single-stage design, recording the
substitution in the returned object rather than making it silently.
Value
An object of class "OptOTrialsRule": a list with components test,
stopping, criterion, alpha, beta, lambda,
p1, p2, the stage-1 size n1, the interim futility and
superiority boundaries t1f and t1s, the maximum size n2,
the final boundary t2, a data frame effect giving each boundary
on a clinically interpretable effect-size scale (odds ratio for the score test,
win odds for the rank-based tests), and the flags degenerate,
degenerate_reason and substituted. A print method displays
these with labels.
Note
The name rule is also used by the cli package, which MAMS
and several other packages attach. If cli is attached after
OptOTrials, cli::rule masks this function for the rest of the
session and a call to rule() will fail with an error about unused
arguments. Attach OptOTrials last, or call OptOTrials::rule()
explicitly.
See Also
Examples
p1 <- c(0.075, 0.182, 0.319, 0.243, 0.015, 0.166)
p2 <- p2_fun(p1, log(3.06))
## Printing the object gives a labelled summary of the decision rule,
## including each boundary on the effect-size scale.
d <- rule(0.05, 0.2, p1, p2, test = "M", stopping = "F", criterion = 1)
d # same as print(d)
## The components are named, so nothing has to be indexed by position.
d$n1 # stage-1 size
d$t1f # interim futility boundary
d$n2 # cumulative size at the final analysis
d$t2 # final boundary
d$effect # boundaries as win odds (or odds ratios)
names(d)
## The object is passed straight to op(); the sample sizes and boundaries
## never have to be transcribed between the two calls.
op(d, nsim = 1000, seed = 1234)
## An FS design adds an interim superiority boundary.
rule(0.05, 0.2, p1, p2, test = "M", stopping = "FS", criterion = 1)
## Minimising E(N | Ha) can return a degenerate optimum. By default it is
## detected and replaced by the single-stage design, with a warning.
d2 <- rule(0.05, 0.2, p1, p2, test = "M", stopping = "F", criterion = 2)
d2$substituted
Win probability for two ordered categorical distributions
Description
Computes the win probability
\theta = \Pr(\eta < \xi) + 0.5\Pr(\eta = \xi), the expectation of the win
odds statistic, where \xi is a draw from the control distribution and
\eta a draw from the experimental distribution. Categories are ordered
from most to least desirable, so \theta > 0.5 favours the experimental
treatment. The win odds are \theta / (1 - \theta).
Usage
theta(p1, p2)
Arguments
p1 |
A vector containing the probabilities of the outcome falling into
each level for the control arm. It must be numeric, contain at least two
categories and no missing or negative values, and sum to one within
|
p2 |
A vector containing the probabilities of the outcome falling into
each level for the experimental arm. It is validated as for |
Value
A single number, the win probability \theta.
See Also
rule for designs based on the win odds test.
Examples
p1 <- c(1/3, 1/3, 1/3)
p2 <- c(1/2, 1/3, 1/6)
## Win probability and the corresponding win odds
th <- theta(p1, p2)
th
th / (1 - th)
## Under the null the two distributions coincide and theta is 0.5
theta(p1, p1)