Skip to content

Commit 9fbc3ba

Browse files
audreyyeoCHdependabot-preview[bot]danielinteractive
authored
92_boundsPostprob (#104)
Co-authored-by: 27856297+dependabot-preview[bot]@users.noreply.github.com <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Daniel Sabanes Bove <[email protected]>
1 parent ef3fbc6 commit 9fbc3ba

12 files changed

+329
-112
lines changed

R/boundsPostprob.R

+54-48
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,78 @@
11
#' Decision cutpoints for boundary (based on posterior probability)
22
#'
33
#' This function is used to identify the efficacy and futility
4-
#' boundaries based on posterior probabilities, i.e.:
5-
#' Efficacy boundary: find minimum x (xU) where Pr(P>p0|x,n,a,b) >= tU and
6-
#' Futility boundary: find maximum x (xL) where Pr(P>p1|x,n,a,b) <= tL
4+
#' boundaries based on the following rules:
5+
#' Efficacy boundary: find minimum x (xU) where Pr(RR > p1 |x, n, a, b) >= tU and
6+
#' Futility boundary: find maximum x (xL) where Pr(RR < p0 | x, n, a, b) >= tL
77
#'
8-
#' @param nvec a vector of number of patients
9-
#' @param p0 the efficacy threshold parameter in the postprob function
10-
#' @param p1 the futility threshold parameter in the postprob function
11-
#' (default = p0)
12-
#' @param tL futility boundary probability threshold
13-
#' @param tU efficacy boundary probability threshold
14-
#' @param a the alpha parameter of the beta prior of treatment group
15-
#' @param b the beta parameter of the beta prior of treatment group
16-
#' @return A matrix where for each sample size in \code{nvec}, this function
17-
#' returns the maximum number of responses that meet the futility
18-
#' threshold (xL), its corresponding response rate (pL), posterior probability
19-
#' (postL), upper bound of one sided 95% CI for the response rate based on an
20-
#' exact binomial test (UciL), and the same boundary parameters for efficacy:
21-
#' the minimal number of responses that meet the efficacy threshold (xU),
22-
#' the corresponding response rate (pU), posterior probability (postU) and
23-
#' the lower bound of one sided 95% CI for the response rate based on exact
24-
#' binomial test (LciU).
25-
#'
26-
#' @importFrom stats binom.test
8+
#' @inheritParams postprob
9+
#' @inheritParams ocPostprob
10+
#' @typed looks : numeric
11+
#' A vector of number of patients in each look.
12+
#' @return A matrix for each same size in `looks`. For each sample size, the following is returned:
13+
#' - `xL` : the maximum number of responses that meet the futility threshold.
14+
#' - `pL` : response rate corresponding to `xL`.
15+
#' - `postL`: posterior probability corresponding to `xL`, i.e. Pr(RR < p0 | xL, n, a, b).
16+
#' - `pL_upper_ci` : upper bound of one sided 95% CI for the response rate `pL` based on an
17+
#' exact binomial test.
18+
#' - `xU` : the minimal number of responses that meet the efficacy threshold.
19+
#' - `pU` : response rate corresponding to `xU`.
20+
#' - `postU` : posterior probability corresponding to `xU`, i.e. Pr(RR > p1 |xU, n, a, b).
21+
#' - `pU_lower_ci` : lower bound of one sided 95% CI for the response rate `pU` based on exact
22+
#' binomial test.
2723
#'
2824
#' @example examples/boundsPostprob.R
2925
#' @export
30-
#' @keywords graphics
31-
boundsPostprob <- function(nvec, p0, p1 = p0, tL, tU, a, b) {
32-
z <- matrix(NA, length(nvec), 6)
33-
dimnames(z) <- list(nvec, c(
34-
"xL", "pL", "postL",
35-
"xU", "pU", "postU"
36-
))
26+
boundsPostprob <- function(looks, p0, p1 = p0, tL, tU, parE = c(1, 1), weights) {
27+
assert_numeric(looks)
28+
assert_number(p0, lower = 0, upper = 1)
29+
assert_number(p1, lower = 0, upper = 1)
30+
assert_number(tL, lower = 0, upper = 1)
31+
assert_number(tU, lower = 0, upper = 1)
32+
assert_numeric(parE, min.len = 2, any.missing = FALSE)
33+
z <- matrix(NA, nrow = length(looks), ncol = 8)
3734
znames <- c(
38-
"xL", "pL", "postL", "UciL",
39-
"xU", "pU", "postU", "LciU"
35+
"xL", "pL", "postL", "pL_upper_ci",
36+
"xU", "pU", "postU", "pU_lower_ci"
4037
)
41-
z <- matrix(NA, length(nvec), length(znames))
42-
dimnames(z) <- list(nvec, znames)
38+
dimnames(z) <- list(looks, znames)
4339
k <- 0
44-
for (n in nvec) {
40+
parE <- t(parE)
41+
if (missing(weights)) {
42+
weights <- rep(1, nrow(parE))
43+
}
44+
assert_numeric(weights, min.len = 0, len = nrow(par), finite = TRUE)
45+
for (n in looks) {
4546
k <- k + 1
4647
# initialize so will return NA if 0 or n in "continue" region
4748
xL <- NA
4849
xU <- NA
4950
for (x in 0:n) {
50-
postp <- postprob(x, n, p1, parE = c(a, b))
51-
if (postp <= tL) {
51+
postp_fut <- 1 - postprob(x, n, p0, parE, weights) # futility look
52+
if (postp_fut >= tL) { # Rule is P(RR < p0) > tL
53+
postL <- postp_fut
5254
xL <- x
5355
}
54-
if (p0 != p1) {
55-
postp <- postprob(x, n, p0, parE = c(a, b))
56-
}
57-
if (postp >= tU) {
56+
postp_eff <- postprob(x, n, p1, parE, weights) # efficacy look
57+
if (postp_eff >= tU) { # Rule is P(RR > p1) > tU
58+
postU <- postp_eff
5859
xU <- x
59-
# done: leave innermost for loop
6060
break
6161
}
6262
}
63-
# calculate posterior probabilities at boundaries
64-
postL <- postprob(xL, n, p1, parE = c(a, b))
65-
postU <- postprob(xU, n, p0, parE = c(a, b))
6663
# calculate lower CI at boundaries
67-
UciL <- ifelse(!is.na(xL), stats::binom.test(xL, n, alt = "less")$conf.int[2], NA)
68-
LciU <- ifelse(!is.na(xU), stats::binom.test(xU, n, alt = "greater")$conf.int[1], NA)
69-
z[k, ] <- c(xL, xL / n, postL, UciL, xU, xU / n, postU, LciU)
64+
pL_upper_ci <- ifelse(!is.na(xL), stats::binom.test(xL, n, alt = "less")$conf.int[2], NA)
65+
pU_lower_ci <- ifelse(!is.na(xU), stats::binom.test(xU, n, alt = "greater")$conf.int[1], NA)
66+
z[k, ] <- c(
67+
xL,
68+
xL / n,
69+
postL,
70+
pL_upper_ci,
71+
xU,
72+
xU / n,
73+
postU,
74+
pU_lower_ci
75+
)
7076
}
71-
return(round(data.frame(nvec, z), 4))
77+
round(data.frame(looks, z), 4)
7278
}

R/ocPostprob.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ h_get_oc <- function(all_sizes, Nmax, decision) {
187187
#'
188188
#' Stop criteria for Efficacy :
189189
#'
190-
#' `Pr(truep > p1) > tU`
190+
#' `Pr(RR > p1) > tU`
191191
#'
192192
#' Stop criteria for Futility :
193193
#'
194-
#' `Pr(truep < p0) > tL`
194+
#' `Pr(RR < p0) > tL`
195195
#'
196196
#' Resulting operating characteristics include the following:
197197
#'

examples/boundsPostprob.R

+24-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
## 40 pts trial with interim looks after each 10 pts.,
2-
## efficacy decision if more than 90% probability to be above 20% ORR,
3-
## futility decision if less than 10% probability to be above 20% ORR,
4-
## with uniform prior (i.e. beta(1, 1)) on the ORR:
1+
# 40 pts trial with interim looks after each 10 pts.,
2+
# Efficacy decision if more than 80% probability to be above 20% ORR,
3+
# Futility decision if more than 60% probability to be below 15% ORR,
4+
# with uniform prior (i.e. beta(1, 1)) on the ORR:
55
boundsPostprob(
6-
nvec = c(10, 20, 30, 40), p0 = 0.20,
7-
tL = 0.10, tU = 0.90, a = 1, b = 1
6+
looks = c(10, 20, 30, 40),
7+
p0 = 0.15,
8+
p1 = 0.20,
9+
tL = 0.60,
10+
tU = 0.80,
11+
parE = c(1, 1)
12+
)
13+
14+
# 40 pts trial with interim looks at 7 and 20 pts.
15+
# Efficacy decision if more than 80% probability to be above 20% ORR,
16+
# Futility decision if more than 60% probability to be below 15% ORR,
17+
# with mixed prior and weights:
18+
boundsPostprob(
19+
looks = c(7, 20, 40),
20+
p0 = 0.15,
21+
p1 = 0.20,
22+
tL = 0.60,
23+
tU = 0.80,
24+
parE = rbind(c(1, 19), c(2, 10)),
25+
weights = c(0.2, 0.8)
826
)
9-
## From this we see e.g. that at the third IA at 30 pts, we would stop for futility
10-
## if 5 or less patients responded, and for efficacy if 9 or more pts responded.
File renamed without changes.

examples/ocPredprobDist.R

+8-4
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,14 @@ result$oc
7474
# True response rate or truep of the treatment group = 40%
7575
# Desired difference to Standard of Care for Efficacy and Futility = 50%
7676
# Delta calculation is absolute case. The following are the Final Stop rules respectively :
77-
# - Final look for Efficacy: Pr( response rate + deltaE > 25% ) > 60% or P(response rate + deltaE > p0) > tT
78-
# - Final look for Futility: Pr( response rate + deltaF < 25% ) < 60% or P(response rate + deltaF > p0) < tT
79-
# - Interim look for Efficacy: Pr( success at final ) > 80% or P(success at final) > phiU
80-
# - Interim look for Futility: Pr( failure at final ) < 20% or P(success at final) < phiL
77+
# - Final look for Efficacy: Pr( response rate + deltaE > 25% ) > 60% or
78+
# P(response rate + deltaE > p0) > tT
79+
# - Final look for Futility: Pr( response rate + deltaF < 25% ) < 60% or
80+
# P(response rate + deltaF > p0) < tT
81+
# - Interim look for Efficacy: Pr( success at final ) > 80% or
82+
# P(success at final) > phiU
83+
# - Interim look for Futility: Pr( failure at final ) < 20% or
84+
# P(success at final) < phiL
8185
# We assume a prior of treatment arm parE = Beta(1,1), unless otherwise indicated.
8286

8387
set.seed(20)

examples/plotBounds.R

-7
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,3 @@ plotBounds(boundsPredprob(
77
nvec = c(10, 20, 30, 40), p = 0.20, tT = 0.80,
88
phiL = 0.10, phiU = 0.90, a = 1, b = 1
99
), yt = "p")
10-
plotBounds(
11-
boundsPostprob(
12-
nvec = c(10, 20, 30, 40), p0 = 0.20,
13-
tL = 0.10, tU = 0.90, a = 1, b = 1
14-
),
15-
yt = "p", add = TRUE
16-
)

man/boundsPostprob.Rd

+49-30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ocPostprob.Rd

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)