Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions R/gs_power_npe.R
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta
upper_prob <- rep(NA, n_analysis)
lower_prob <- rep(NA, n_analysis)
harm_prob <- rep(NA, n_analysis)
# Close the continuation region at the final analysis for beta-spending designs
close_final_bound <- test_upper[n_analysis] && test_lower[n_analysis] &&
identical(lower, gs_spending_bound) && theta1[n_analysis] != theta0[n_analysis]

# Calculate crossing prob under H1 ----
for (k in 1:n_analysis) {
Expand All @@ -359,6 +362,10 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta
theta = theta1, efficacy = FALSE
)
b[k] <- upper(k = k, par = upar, hgm1 = hgm1_0, info = info0, r = r, tol = tol, test_bound = test_upper)
# Set the final futility bound equal to the efficacy bound
if (k == n_analysis && close_final_bound) {
a[k] <- b[k]
}
harm_z[k] <- harm(
k = k, par = hpar, hgm1 = hgm1_harm0, info = info0, r = r, tol = tol, test_bound = test_harm,
theta = theta0, efficacy = FALSE
Expand Down Expand Up @@ -460,6 +467,11 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta
)
}
}
# Assign the remaining probability mass to futility when no final continuation region remains
if (k == n_analysis && close_final_bound) {
previous_lower_prob <- if (k > 1) sum(lower_prob[seq_len(k - 1)]) else 0
lower_prob[k] <- 1 - sum(upper_prob) - previous_lower_prob
}
}

if (all(!test_harm)) {
Expand Down
107 changes: 97 additions & 10 deletions tests/testit/test-developer-gs_design_ahr.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ assert("Multiple analysisTimes", {
(x1$analysis$theta %==% u2$theta)
(x1$analysis$info %==% u2$info)
(x1$analysis$info0 %==% u2$info0)
(l1$z %==% l2$Z)
(l1$probability %==% l2$Probability)
(head(l1$z, -1) %==% head(l2$Z, -1))
})

assert("Specified information fraction", {
Expand All @@ -62,8 +61,7 @@ assert("Specified information fraction", {
(all.equal(x1$analysis$theta, u2$theta, tolerance = 1e-7))
(all.equal(x1$analysis$info, u2$info, tolerance = 4e-8))
(all.equal(x1$analysis$info0, u2$info0, tolerance = 4e-8))
(all.equal(l1$z, l2$Z, tolerance = 2e-7))
(all.equal(l1$probability, l2$Probability))
(all.equal(head(l1$z, -1), head(l2$Z, -1), tolerance = 2e-7))
})

assert("Multiple analysis times & IF and driven by times", {
Expand All @@ -81,8 +79,7 @@ assert("Multiple analysis times & IF and driven by times", {
(x1$analysis$theta %==% u2$theta)
(x1$analysis$info %==% u2$info)
(x1$analysis$info0 %==% u2$info0)
(l1$z %==% l2$Z)
(l1$probability %==% l2$Probability)
(head(l1$z, -1) %==% head(l2$Z, -1))
})

assert("Multiple analysis times & IF and driven by IF", {
Expand All @@ -100,8 +97,7 @@ assert("Multiple analysis times & IF and driven by IF", {
(x1$analysis$theta %==% u2$theta)
(x1$analysis$info %==% u2$info)
(x1$analysis$info0 %==% u2$info0)
(l1$z %==% l2$Z)
(l1$probability %==% l2$Probability)
(head(l1$z, -1) %==% head(l2$Z, -1))
})

assert("2-sided symmetric design with O'Brien-Fleming spending", {
Expand Down Expand Up @@ -166,8 +162,99 @@ assert("Pocock lower spending under H1 (NPH)", {
(x1$analysis$theta %==% u2$theta)
(x1$analysis$info %==% u2$info)
(x1$analysis$info0 %==% u2$info0)
(l1$z %==% l2$Z)
(l1$probability %==% l2$Probability)
(head(l1$z, -1) %==% head(l2$Z, -1))
})

assert("Final futility and efficacy bounds match with lower spending under beta-spending, binding/non-binding when it is PH", {
enroll_rate <- define_enroll_rate(
duration = c(2, 2, 10),
rate = (1:3) / 3
)
fail_rate <- define_fail_rate(
duration = Inf,
fail_rate = log(2) / 9,
hr = 0.6,
dropout_rate = 0.0001
)

# binding
x <- gs_design_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
alpha = 0.025, beta = 0.1,
info_scale = "h0_h1_info",
info_frac = 1:3 / 3, analysis_time = 36,
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 3),
binding = TRUE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

# non-binding
x <- gs_design_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
alpha = 0.025, beta = 0.1,
info_scale = "h0_h1_info",
info_frac = 1:3 / 3, analysis_time = 36,
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 3),
binding = FALSE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

})

assert("Final futility and efficacy bounds match with lower spending under beta-spending, binding/non-binding when it is NPH", {
enroll_rate <- define_enroll_rate(
duration = c(2, 2, 10),
rate = (1:3) / 3
)
fail_rate <- define_fail_rate(
duration = c(3, Inf),
fail_rate = log(2) / 9,
hr = c(1, 0.6),
dropout_rate = 0.0001
)

# binding
x <- gs_design_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
alpha = 0.025, beta = 0.1,
info_scale = "h0_h1_info",
info_frac = 1:3 / 3, analysis_time = 36,
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 3),
binding = TRUE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

# non-binding
x <- gs_design_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
alpha = 0.025, beta = 0.1,
info_scale = "h0_h1_info",
info_frac = 1:3 / 3, analysis_time = 36,
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 3),
binding = FALSE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

})

assert("Spending time when both efficacy and futility bound are fixed", {
Expand Down
10 changes: 8 additions & 2 deletions tests/testit/test-developer-gs_design_npe.R
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ assert("spending bounds", {
dplyr::mutate(bound = tolower(bound)) |>
dplyr::select(analysis, bound, z, probability, theta, info, info0, info1) |>
dplyr::arrange(analysis, bound)
(as.data.frame(x1_c) %==% as.data.frame(x2))
legacy_rows <- x1_c$analysis < 3 | x1_c$bound != "lower"
(as.data.frame(x1_c[legacy_rows, ]) %==% as.data.frame(x2[legacy_rows, ]))
(x1_c$z[x1_c$analysis == 3 & x1_c$bound == "lower"] ==
x1_c$z[x1_c$analysis == 3 & x1_c$bound == "upper"])
})

assert("2-sided symmetric spend", {
Expand Down Expand Up @@ -266,7 +269,10 @@ assert("2-sided symmetric spend", {
dplyr::mutate(bound = tolower(bound)) |>
dplyr::select(analysis, bound, z, probability, theta, info, info0, info1) |>
dplyr::arrange(analysis, bound)
(as.data.frame(x1_c) %==% as.data.frame(x2))
legacy_rows <- x1_c$analysis < 3 | x1_c$bound != "lower"
(as.data.frame(x1_c[legacy_rows, ]) %==% as.data.frame(x2[legacy_rows, ]))
(x1_c$z[x1_c$analysis == 3 & x1_c$bound == "lower"] ==
x1_c$z[x1_c$analysis == 3 & x1_c$bound == "upper"])
})

assert("Harm bound is not provided for fixed designs", {
Expand Down
56 changes: 50 additions & 6 deletions tests/testit/test-developer-gs_power_ahr.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ assert("calendar based cut", {
(x1$analysis$info0 %==% u2$info0)
(x1$analysis$time %==% l2$Time)
(x1$analysis$event %==% l2$Events)
(l1$z %==% l2$Z)
(l1$probability %==% l2$Probability)
(head(l1$z, -1) %==% head(l2$Z, -1))
(head(l1$probability, -1) %==% head(l2$Probability, -1))
(x1$analysis$ahr %==% l2$AHR)
(x1$analysis$theta %==% l2$theta)
(x1$analysis$info %==% l2$info)
Expand Down Expand Up @@ -81,8 +81,8 @@ assert("event based cut", {
(all.equal(x1$analysis$info0, x2$info0[x2$Bound == "Upper"], tolerance = 2e-7))
(all.equal(x1$analysis$time, x2$Time[x2$Bound == "Lower"], tolerance = 2e-7))
(all.equal(x1$analysis$event, x2$Events[x2$Bound == "Lower"], tolerance = 2e-7))
(all.equal(x1$bound$z[x1$bound$bound == "lower"], x2$Z[x2$Bound == "Lower"], tolerance = 2e-7))
(all.equal(x1$bound$probability[x1$bound$bound == "lower"], x2$Probability[x2$Bound == "Lower"], tolerance = 3e-7))
(all.equal(head(x1$bound$z[x1$bound$bound == "lower"], -1), head(x2$Z[x2$Bound == "Lower"], -1), tolerance = 1e-6, scale = 1))
(all.equal(head(x1$bound$probability[x1$bound$bound == "lower"], -1), head(x2$Probability[x2$Bound == "Lower"], -1), tolerance = 3e-7, scale = 1))
(all.equal(x1$analysis$ahr, x2$AHR[x2$Bound == "Lower"], tolerance = 3e-7))
(all.equal(x1$analysis$theta, x2$theta[x2$Bound == "Lower"], tolerance = 1e-7))
(all.equal(x1$analysis$info, x2$info[x2$Bound == "Lower"], tolerance = 3e-6))
Expand Down Expand Up @@ -118,8 +118,8 @@ assert("calendar + event based cut", {
(all.equal(x1$analysis$info0, x2$info0[x2$Bound == "Upper"], tolerance = 3e-6))
(all.equal(x1$analysis$time, x2$Time[x2$Bound == "Lower"], tolerance = 2e-6))
(all.equal(x1$analysis$event, x2$Events[x2$Bound == "Lower"], tolerance = 3e-6))
(all.equal(x1$bound$z[x1$bound$bound == "lower"], x2$Z[x2$Bound == "Lower"], tolerance = 2e-6))
(all.equal(x1$bound$probability[x1$bound$bound == "lower"], x2$Probability[x2$Bound == "Lower"], tolerance = 5e-7))
(all.equal(head(x1$bound$z[x1$bound$bound == "lower"], -1), head(x2$Z[x2$Bound == "Lower"], -1), tolerance = 2e-6))
(all.equal(head(x1$bound$probability[x1$bound$bound == "lower"], -1), head(x2$Probability[x2$Bound == "Lower"], -1), tolerance = 5e-7, scale = 1))
(all.equal(x1$analysis$ahr, x2$AHR[x2$Bound == "Lower"], tolerance = 3e-7))
(all.equal(x1$analysis$theta, x2$theta[x2$Bound == "Lower"], tolerance = 3e-7, scale = 1))
(all.equal(x1$analysis$info, x2$info[x2$Bound == "Lower"], tolerance = 3e-6))
Expand Down Expand Up @@ -156,3 +156,47 @@ assert("Validate the boundary is symmetric in symmetric designs.", {
lower_z <- x$bound$z[x$bound$bound == "lower"]
(all.equal(upper_z, -lower_z))
})

assert("Final futility and efficacy bounds match with lower spending under beta-spending, binding/non-binding when it is NPH", {
enroll_rate <- define_enroll_rate(
duration = 12,
rate = 600/12
)
fail_rate <- define_fail_rate(
duration = c(3, Inf),
fail_rate = log(2) / 9,
hr = c(1, 0.6),
dropout_rate = 0.0001
)

# binding
x <- gs_power_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
info_scale = "h0_h1_info",
event = c(100, 200, 300),
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -3),
binding = TRUE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

# non-binding
x <- gs_power_ahr(
enroll_rate = enroll_rate, fail_rate = fail_rate,
info_scale = "h0_h1_info",
analysis_time = c(24, 30, 36),
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
lower = gs_spending_bound,
lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 3),
binding = FALSE, h1_spending = TRUE
)
fa_efficacy_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "upper", ]
fa_futility_bound <- x$bound[x$bound$analysis == 3 & x$bound$bound == "lower", ]
(fa_efficacy_bound$z == fa_futility_bound$z)

})
10 changes: 8 additions & 2 deletions tests/testit/test-developer-gs_power_npe.R
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ assert("Spending function bounds - Lower spending based on non-zero effect", {
) |>
dplyr::rename(analysis = Analysis, bound = Bound, z = Z, probability = Probability) |>
dplyr::mutate(bound = tolower(bound))
(x1 %==% as.data.frame(x2))
legacy_rows <- x1$analysis < 3 | x1$bound != "lower"
(x1[legacy_rows, ] %==% as.data.frame(x2[legacy_rows, ]))
(x1$z[x1$analysis == 3 & x1$bound == "lower"] ==
x1$z[x1$analysis == 3 & x1$bound == "upper"])
})

assert("Same bounds, but power under different theta", {
Expand All @@ -117,7 +120,10 @@ assert("Same bounds, but power under different theta", {
) |>
dplyr::rename(analysis = Analysis, bound = Bound, z = Z, probability = Probability) |>
dplyr::mutate(bound = tolower(bound))
(x1 %==% as.data.frame(x2))
legacy_rows <- x1$analysis < 3 | x1$bound != "lower"
(x1[legacy_rows, ] %==% as.data.frame(x2[legacy_rows, ]))
(x1$z[x1$analysis == 3 & x1$bound == "lower"] ==
x1$z[x1$analysis == 3 & x1$bound == "upper"])
})

assert("Two-sided symmetric spend, O'Brien-Fleming spending", {
Expand Down
12 changes: 6 additions & 6 deletions tests/testit/test-independent-as_gt.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Futility & -0.66 & 0.7462 & 1.2331 & 0.0668 & 0.2655 \\
\multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 2.03 & 0.0211 & 0.5631 & 0.2070 & 0.0250 \\
Futility & -0.23 & 0.5897 & 1.0662 & 0.1008 & 0.4303 \\
Futility & 2.03 & 0.0211 & 0.5631 & 0.7930 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down Expand Up @@ -294,7 +294,7 @@ Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\
\multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{4}}} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\
Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\
Futility & 2.03 & 0.0212 & 0.5631 & 0.7929 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down Expand Up @@ -480,7 +480,7 @@ Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\
\multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{3}}} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\
Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\
Futility & 2.03 & 0.0212 & 0.5631 & 0.7929 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down Expand Up @@ -530,7 +530,7 @@ Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\
\multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{3}}} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\
Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\
Futility & 2.03 & 0.0212 & 0.5631 & 0.7929 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down Expand Up @@ -588,7 +588,7 @@ Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\
\multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{4}}} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\
Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\
Futility & 2.03 & 0.0212 & 0.5631 & 0.7929 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down Expand Up @@ -680,7 +680,7 @@ Futility & 0.7452 & -0.66 & 0.0664 & 0.2664 \\
\multicolumn{5}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{2}}} \\[2.5pt]
\midrule\addlinespace[2.5pt]
Efficacy & 0.0212 & 2.03 & 0.2071 & 0.0250 \\
Futility & 0.5881 & -0.22 & 0.1002 & 0.4319 \\
Futility & 0.0212 & 2.03 & 0.7929 & 0.9750 \\
\bottomrule
\end{tabular*}
\begin{minipage}{\linewidth}
Expand Down
Loading