-
Notifications
You must be signed in to change notification settings - Fork 10
Add harm bound to AHR designs #640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
489ed2a
5e415a4
568d492
399cd2a
f2bcae3
f585c45
9b59c45
b577e09
6d1efe7
ec13144
4e081f7
fa84635
b561720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| #' Bound summary table | ||
| #' | ||
| #' Summarizes the efficacy and futility bounds for each analysis. | ||
| #' Summarizes the efficacy, futility, and harm bounds for each analysis. | ||
| #' | ||
| #' @param x Design object. | ||
| #' @param alpha Vector of alpha values to compute additional efficacy columns. | ||
|
|
@@ -52,14 +52,25 @@ gs_bound_summary <- function(x, digits = 4, ddigits = 2, tdigits = 0, timename = | |
| } | ||
| } | ||
| out <- Reduce(cbind, outlist) | ||
| # Use of union() allows placement of column "Futility" at the far right, but | ||
| # only if it is returned by gs_bound_summary_single(). This is because | ||
| # one-sided designs do not produce a Futility column. | ||
| # Use of union() allows placement of columns "Futility" and "Harm" at the far | ||
| # right, but only if they are returned by gs_bound_summary_single(). This is | ||
| # because one-sided designs do not produce a Futility column, and designs | ||
| # without harm bounds do not produce a Harm column. | ||
| column_order <- union(c("Analysis", "Value", col_efficacy_name), colnames(out)) | ||
| out <- out[, column_order] | ||
| return(out) | ||
| } | ||
|
|
||
| gs_bound_summary_values <- function(bound, analysis, bound_name, columns) { | ||
| row_bound <- bound[ | ||
| bound$analysis == analysis & bound$bound == bound_name, | ||
| columns, | ||
| drop = FALSE | ||
| ] | ||
| if (nrow(row_bound) == 0) return(rep(NA_real_, length(columns))) | ||
| as.numeric(unlist(row_bound[1, columns], use.names = FALSE)) | ||
| } | ||
|
|
||
| gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, | ||
| ddigits, tdigits, timename) { | ||
| # Input | ||
|
|
@@ -72,6 +83,8 @@ gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, | |
| col_value <- character() | ||
| col_efficacy <- numeric() | ||
| col_futility <- numeric() | ||
| col_harm <- numeric() | ||
| bound_columns <- c("z", "nominal p", "~hr at bound", "probability0", "probability") | ||
|
|
||
| for (i in seq_len(nrow(analysis))) { | ||
|
|
||
|
|
@@ -113,33 +126,31 @@ gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, | |
| ) | ||
|
|
||
| # Efficacy column | ||
| row_efficacy <- bound[ | ||
| bound$analysis == i & bound$bound == "upper", | ||
| c("z", "nominal p", "~hr at bound", "probability0", "probability") | ||
| ] | ||
| col_efficacy <- c(col_efficacy, as.numeric(row_efficacy)) | ||
| col_efficacy <- c(col_efficacy, gs_bound_summary_values(bound, i, "upper", bound_columns)) | ||
|
|
||
| # Futility column | ||
| row_futility <- bound[ | ||
| bound$analysis == i & bound$bound == "lower", | ||
| c("z", "nominal p", "~hr at bound", "probability0", "probability") | ||
| ] | ||
| col_futility <- c(col_futility, as.numeric(row_futility)) | ||
| col_futility <- c(col_futility, gs_bound_summary_values(bound, i, "lower", bound_columns)) | ||
|
|
||
| # Harm column | ||
| col_harm <- c(col_harm, gs_bound_summary_values(bound, i, "harm", bound_columns)) | ||
| } | ||
|
|
||
| col_efficacy <- round(col_efficacy, digits) | ||
| col_futility <- round(col_futility, digits) | ||
| col_harm <- round(col_harm, digits) | ||
|
|
||
| out <- data.frame( | ||
| Analysis = col_analysis, | ||
| Value = col_value, | ||
| Efficacy = col_efficacy, | ||
| Futility = col_futility | ||
| Futility = col_futility, | ||
| Harm = col_harm | ||
| ) | ||
| colnames(out)[3] <- col_efficacy_name | ||
|
|
||
| # One-sided design should not include Futility column | ||
| if (all(is.na(out[["Futility"]]))) out[["Futility"]] <- NULL | ||
| if (all(is.na(out[["Harm"]]))) out[["Harm"]] <- NULL | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changes look good when I tested locally, but this needs some unit tests for long-term maintenance. For example, confirm that a one-sided design does not return the column |
||
|
|
||
| return(out) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -98,7 +98,13 @@ | |
| #' # Example 2 ---- | ||
| #' # Single analysis | ||
| #' gs_design_ahr(analysis_time = 40) | ||
| #' | ||
| #' gs_design_ahr( | ||
| #' analysis_time = 40, | ||
| #' upper = gs_b, upar = -qnorm(0.025), test_upper = TRUE, | ||
| #' lower = gs_b, lpar = -1, test_lower = TRUE, | ||
| #' harm = gs_b, hpar = -2, test_harm = TRUE | ||
| #' ) | ||
| #' | ||
| #' # Example 3 ---- | ||
| #' # Multiple analysis_time | ||
| #' gs_design_ahr(analysis_time = c(12, 24, 36)) | ||
|
|
@@ -168,6 +174,22 @@ | |
| #' lpar = rep(-Inf, 3) | ||
| #' ) | ||
| #' } | ||
| #' | ||
| #' # Example 8 ---- | ||
| #' # Design with an additional harm bound | ||
| #' \donttest{ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self: we use xref: #147 |
||
| #' gs_design_ahr( | ||
| #' analysis_time = c(12, 24, 36), | ||
| #' upper = gs_spending_bound, | ||
| #' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), | ||
| #' lower = gs_spending_bound, | ||
| #' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), | ||
| #' test_lower = c(TRUE, TRUE, FALSE), | ||
| #' harm = gs_spending_bound, | ||
| #' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), | ||
| #' test_harm = c(TRUE, TRUE, FALSE) | ||
| #' ) | ||
| #' } | ||
| gs_design_ahr <- function( | ||
| enroll_rate = define_enroll_rate( | ||
| duration = c(2, 2, 10), | ||
|
|
@@ -186,9 +208,12 @@ gs_design_ahr <- function( | |
| upar = list(sf = gsDesign::sfLDOF, total_spend = alpha), | ||
| lower = gs_spending_bound, | ||
| lpar = list(sf = gsDesign::sfLDOF, total_spend = beta), | ||
| harm = gs_b, | ||
| hpar = -Inf, | ||
| h1_spending = TRUE, | ||
| test_upper = TRUE, | ||
| test_lower = TRUE, | ||
| test_harm = FALSE, | ||
| info_scale = c("h0_h1_info", "h0_info", "h1_info"), | ||
| r = 18, | ||
| tol = 1e-6, | ||
|
|
@@ -200,6 +225,7 @@ gs_design_ahr <- function( | |
| info_scale <- match.arg(info_scale) | ||
| upper <- match.fun(upper) | ||
| lower <- match.fun(lower) | ||
| harm <- match.fun(harm) | ||
|
|
||
| # Check inputs ---- | ||
| check_analysis_time(analysis_time) | ||
|
|
@@ -309,6 +335,7 @@ gs_design_ahr <- function( | |
| alpha = alpha, beta = beta, binding = binding, | ||
| upper = upper, upar = upar, test_upper = test_upper, | ||
| lower = lower, lpar = lpar, test_lower = test_lower, | ||
| harm = harm, hpar = hpar, test_harm = test_harm, | ||
| r = r, tol = tol | ||
| ) | ||
| ) | ||
|
|
@@ -378,6 +405,16 @@ gs_design_ahr <- function( | |
| bound$spending_time[which(bound$bound == "lower")] <- spending_time_lower | ||
| } | ||
|
|
||
| if (identical(harm, gs_spending_bound)) { | ||
| if (!is.null(hpar$timing)) { | ||
| spending_time_harm <- hpar$timing | ||
| } else { | ||
| spending_time_harm <- info0 / info0_final | ||
| } | ||
|
|
||
| bound$spending_time[which(bound$bound == "harm")] <- spending_time_harm | ||
| } | ||
|
|
||
| if (all(is.na(bound$spending_time))){ | ||
| bound$spending_time <- NULL | ||
| } | ||
|
|
@@ -397,7 +434,8 @@ gs_design_ahr <- function( | |
| info_scale = info_scale, | ||
| upper = upper, upar = upar, | ||
| lower = lower, lpar = lpar, | ||
| test_upper = test_upper, test_lower = test_lower, | ||
| harm = harm, hpar = hpar, | ||
| test_upper = test_upper, test_lower = test_lower, test_harm = test_harm, | ||
| h1_spending = h1_spending, binding = binding, | ||
| info_scale = info_scale, r = r, tol = tol | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This refactoring looks good