diff --git a/R/ahr_blinded.R b/R/ahr_blinded.R index 644167e4..691afe59 100644 --- a/R/ahr_blinded.R +++ b/R/ahr_blinded.R @@ -84,14 +84,14 @@ ahr_blinded <- function( hr = c(1, .6), ratio = 1) { # Input checking - if (!is.vector(hr, mode = "numeric") || min(hr) <= 0) { - stop("ahr_blinded: hr must be a vector of positive numbers.") + if (!is.numeric(hr) || min(hr) <= 0) { + stop("'hr' must be a vector of positive numbers.") } - if (!is.vector(intervals, mode = "numeric") || min(intervals) <= 0) { - stop("ahr_blinded: intervals must be a vector of positive numbers.") + if (!is.numeric(intervals) || min(intervals) <= 0) { + stop("'intervals' must be a vector of positive numbers.") } if (length(intervals) != length(hr)) { - stop("ahr_blinded: the piecewise model specified hr and intervals are not aligned.") + stop("the piecewise model specified 'hr' and 'intervals' differ in length.") } # Set final element of "intervals" to Inf diff --git a/R/as_gt.R b/R/as_gt.R index 59e0fe20..c31e3880 100644 --- a/R/as_gt.R +++ b/R/as_gt.R @@ -79,110 +79,57 @@ as_gt <- function(x, ...) { #' summary() %>% #' as_gt() as_gt.fixed_design <- function(x, title = NULL, footnote = NULL, ...) { - # get the design method - if ("ahr" %in% class(x)) { - design_mtd <- "ahr" - } else if ("fh" %in% class(x)) { - design_mtd <- "fh" - } else if ("mb" %in% class(x)) { - design_mtd <- "mb" - } else if ("lf" %in% class(x)) { - design_mtd <- "lf" - } else if ("rd" %in% class(x)) { - design_mtd <- "rd" - } else if ("maxcombo" %in% class(x)) { - design_mtd <- "maxcombo" - } else if ("milestone" %in% class(x)) { - design_mtd <- "milestone" - } else if ("rmst" %in% class(x)) { - design_mtd <- "rmst" - } else if ("rd" %in% class(x)) { - design_mtd <- "rd" - } - - - # set the default title - if (is.null(title)) { - title <- switch(design_mtd, - "ahr" = { - "Fixed Design under AHR Method" - }, - "fh" = { - "Fixed Design under Fleming-Harrington Method" - }, - "mb" = { - "Fixed Design under Magirr-Burman Method" - }, - "lf" = { - "Fixed Design under Lachin and Foulkes Method" - }, - "rd" = { - "Fixed Design of Risk Difference under Farrington-Manning Method" - }, - "maxcombo" = { - "Fixed Design under MaxCombo Method" - }, - "milestone" = { - "Fixed Design under Milestone Method" - }, - "rmst" = { - "Fixed Design under Restricted Mean Survival Time Method" - }, - "rd" = { - "Fixed Design of Risk Difference" - } + method <- fd_method(x) + ans <- gt::gt(x) %>% + gt::tab_header(title = title %||% fd_title(method)) %>% + gt::tab_footnote( + footnote = footnote %||% fd_footnote(x, method), + locations = gt::cells_title(group = "title") ) - } - + return(ans) +} - # set the default footnote - if (is.null(footnote)) { - footnote <- switch(design_mtd, - "ahr" = { - "Power computed with average hazard ratio method." - }, - "fh" = { - paste0( - "Power for Fleming-Harrington test ", - substr(x$Design, 19, nchar(x$Design)), - " using method of Yung and Liu." - ) - }, - "mb" = { - paste0( - "Power for ", - x$Design, - " computed with method of Yung and Liu." - ) - }, - "lf" = { - "Power using Lachin and Foulkes method applied - using expected average hazard ratio (AHR) at time of planned analysis." - }, - "rd" = { - "Risk difference power without continuity correction using method of Farrington and Manning." - }, - "maxcombo" = { - paste0( - "Power for MaxCombo test with Fleming-Harrington tests", - substr(x$Design, 9, nchar(x$Design)), "." - ) - }, - "milestone" = { - paste0("Power for ", x$Design, " computed with method of Yung and Liu.") - }, - "rmst" = { - paste0("Power for ", x$Design, " computed with method of Yung and Liu.") - } - ) - } +# get the fixed design method +fd_method <- function(x) { + methods <- c("ahr", "fh", "mb", "lf", "rd", "maxcombo", "milestone", "rmst") + intersect(methods, class(x))[1] +} - ans <- x %>% - gt::gt() %>% - gt::tab_header(title = title) %>% - gt::tab_footnote(footnote = footnote, locations = gt::cells_title(group = "title")) +# get the default title +fd_title <- function(method) { + sprintf("Fixed Design %s Method", switch( + method, + ahr = "under AHR", fh = "under Fleming-Harrington", mb = "under Magirr-Burman", + lf = "under Lachin and Foulkes", maxcombo = "under MaxCombo", + milestone = "under Milestone", rmst = "under Restricted Mean Survival Time", + rd = "of Risk Difference under Farrington-Manning" + )) +} - return(ans) +# get the default footnote +fd_footnote <- function(x, method) { + switch( + method, + ahr = "Power computed with average hazard ratio method.", + fh = paste( + "Power for Fleming-Harrington test", substring(x$Design, 19), + "using method of Yung and Liu." + ), + lf = paste( + "Power using Lachin and Foulkes method applied using expected", + "average hazard ratio (AHR) at time of planned analysis." + ), + rd = paste( + "Risk difference power without continuity correction using method of", + "Farrington and Manning." + ), + maxcombo = paste0( + "Power for MaxCombo test with Fleming-Harrington tests ", + substring(x$Design, 9), "." + ), + # for mb, milestone, and rmst + paste("Power for", x$Design, "computed with method of Yung and Liu.") + ) } #' @rdname as_gt @@ -303,259 +250,167 @@ as_gt.gs_design <- function( display_inf_bound = FALSE, ...) { - method <- class(x)[class(x) %in% c("ahr", "wlr", "combo", "rd")] - full_alpha <- attributes(x)$full_alpha - - x_alpha <- max((x %>% dplyr::filter(Bound == display_bound[1]))[[colname_spannersub[2]]]) - - x_non_binding <- "non_binding" %in% class(x) - - x_k <- lapply(x$Analysis, function(x) { - return(as.numeric(substring(x, 11, 11))) - }) %>% unlist() - - if (!display_inf_bound) { - x <- x %>% filter(!is.infinite(Z)) - } - x_old <- x + full_alpha <- attr(x, "full_alpha") + parts <- gsd_parts( + x, title, subtitle, colname_spannersub, footnote, + display_bound, display_columns, display_inf_bound + ) - # Set defaults ---- - # set different default title to different methods - if (method == "ahr" && is.null(title)) { - title <- "Bound summary for AHR design" - } - if (method == "wlr" && is.null(title)) { - title <- "Bound summary for WLR design" - } - if (method == "combo" && is.null(title)) { - title <- "Bound summary for MaxCombo design" - } + x <- parts$x %>% + dplyr::group_by(Analysis) %>% + gt::gt() %>% + gt::tab_spanner( + columns = dplyr::all_of(colname_spannersub), + label = colname_spanner + ) %>% + gt::tab_header(title = parts$title, subtitle = parts$subtitle) - if (method == "rd" && is.null(title)) { - title <- "Bound summary of Binary Endpoint" + # Add footnotes ---- + footnote <- parts$footnote + for (i in seq_along(footnote$content)) { + att <- footnote$attr[i] + loc <- if (att == "colname") { + # footnotes are added on the colnames + gt::cells_column_labels(columns = footnote$location[i]) + } else if (att %in% c("title", "subtitle")) { + # on the title/subtitle + gt::cells_title(group = att) + } else if (att == "analysis") { + # on the analysis summary row, which is a grouping variable, i.e., Analysis + gt::cells_row_groups(groups = dplyr::starts_with("Analysis")) + } else if (att == "spanner") { + # on the column spanner + gt::cells_column_spanners(spanners = colname_spanner) + } + if (!is.null(loc)) + x <- gt::tab_footnote(x, footnote = footnote$content[i], locations = loc) } - # set different default subtitle to different methods - if (method == "ahr" && is.null(subtitle)) { - subtitle <- "AHR approximations of ~HR at bound" - } - if (method == "wlr" && is.null(subtitle)) { - subtitle <- "WLR approximation of ~wHR at bound" - } - if (method == "combo" && is.null(subtitle)) { - subtitle <- "MaxCombo approximation" - } - if (method == "rd" && is.null(subtitle)) { - subtitle <- "measured by risk difference" - } + # add footnote for non-binding design + footnote_nb <- gsd_footnote_nb(x_old, parts$alpha, full_alpha) + if (!is.null(footnote_nb)) x <- gt::tab_footnote( + x, + footnote = footnote_nb, + locations = gt::cells_body( + columns = colname_spannersub[2], + rows = gsd_footnote_row(parts$x, display_bound[1]) + ) + ) + return(x) +} + +# get different default columns to display +gsd_columns <- function(columns, method, x) { # set different default columns to display - if (is.null(display_columns)) { - if (method == "ahr") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~HR at bound", "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "wlr") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~wHR at bound", "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "combo") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "rd") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~Risk difference at bound", "Alternate hypothesis", "Null hypothesis" - ) - } - } - # filter the columns to display as the output - ## if `Probability` is selected to output, then transform it to `c("Alternate hypothesis", "Null hypothesis")` - if ("Probability" %in% display_columns) { - display_columns <- display_columns[!display_columns == "Probability"] - display_columns <- c(display_columns, "Alternate hypothesis", "Null hypothesis") - } + if (is.null(columns)) columns <- c( + "Analysis", "Bound", "Z", "Nominal p", + sprintf("%s at bound", switch(method, ahr = "~HR", wlr = "~wHR", rd = "~Risk difference")), + "Alternate hypothesis", "Null hypothesis" + ) + # filter the columns to display as the output: if `Probability` is selected to + # output, transform it to `c("Alternate hypothesis", "Null hypothesis")` + if (any(i <- columns == "Probability")) + columns <- c(columns[!i], "Alternate hypothesis", "Null hypothesis") ## check if the `display_columns` are included in `x` output - if (sum(!(display_columns %in% names(x))) >= 1) { - stop("as_gt: the variable names in display_columns is not outputted in the summary_bound object!") - } else { - x <- x %>% dplyr::select(dplyr::all_of(display_columns)) - } + if (!all(columns %in% names(x))) stop( + "not all variable names in 'display_columns' are in the summary_bound object!" + ) + columns +} - # set different default footnotes to different methods - if (method == "ahr" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse("~HR at bound" %in% display_columns, - "Approximate hazard ratio to cross bound.", NA - ), - ifelse("Nominal p" %in% display_columns, - "One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.", NA - ) - ), - location = c( - ifelse("~HR at bound" %in% display_columns, "~HR at bound", NA), - ifelse("Nominal p" %in% display_columns, "Nominal p", NA) - ), - attr = c( - ifelse("~HR at bound" %in% display_columns, "colname", NA), - ifelse("Nominal p" %in% display_columns, "colname", NA) - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "wlr" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse("~wHR at bound" %in% display_columns, - "Approximate hazard ratio to cross bound.", NA - ), - ifelse("Nominal p" %in% display_columns, - "One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.", NA - ), - "wAHR is the weighted AHR." - ), - location = c( - ifelse("~wHR at bound" %in% display_columns, "~wHR at bound", NA), - ifelse("Nominal p" %in% display_columns, "Nominal p", NA), - NA - ), - attr = c( - ifelse("~wHR at bound" %in% display_columns, "colname", NA), - ifelse("Nominal p" %in% display_columns, "colname", NA), - "analysis" - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "combo" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse("Nominal p" %in% display_columns, - "One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.", NA - ), - "EF is event fraction. AHR is under regular weighted log rank test." - ), - location = c( - ifelse("Nominal p" %in% display_columns, "Nominal p", NA), - NA - ), - attr = c( - ifelse("Nominal p" %in% display_columns, "colname", NA), - "analysis" - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "rd" && is.null(footnote)) { - footnote <- list( - content = c(ifelse("Nominal p" %in% display_columns, - "One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.", NA - )), - location = c(ifelse("Nominal p" %in% display_columns, "Nominal p", NA)), - attr = c(ifelse("Nominal p" %in% display_columns, "colname", NA)) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) +# default footnotes for 'gs_design' tables +gsd_footnote <- function(method, columns) { + n <- c("Nominal p", "~HR at bound", "~wHR at bound") + i <- n %in% columns + res <- if (i[1]) list( + content = paste( + "One-sided p-value for experimental vs control treatment.", + "Value < 0.5 favors experimental, > 0.5 favors control." + ), + location = n[1], attr = "colname" + ) else { + list(content = NULL, location = NULL, attr = NULL) } + x <- "Approximate hazard ratio to cross bound." + switch( + method, + ahr = res %+% if (i[2]) list(x, n[2], "colname"), + wlr = res %+% (if (i[3]) list(x, n[3], "colname")) %+% + list("wAHR is the weighted AHR.", NULL, "analysis"), + combo = res %+% list( + "EF is event fraction. AHR is under regular weighted log rank test.", + NULL, "analysis" + ), + rd = res + ) +} - # Filter out inf bound ---- - x <- x %>% - subset(!is.na(`Alternate hypothesis`)) %>% - subset(!is.na(`Null hypothesis`)) +# footnote for non-binding designs +gsd_footnote_nb <- function(x, x_alpha, full_alpha) { + if (!inherits(x, "non_binding") || x_alpha >= full_alpha) return() + a1 <- format(x_alpha, scientific = FALSE) + a2 <- format(full_alpha, scientific = FALSE) + a3 <- format(full_alpha - x_alpha, scientific = FALSE) + paste0( + "Cumulative alpha for final analysis ", + "(", a1, ") ", "is less than the full alpha ", "(", a2, ") ", + "when the futility bound is non-binding. ", + "The smaller value subtracts the probability of crossing a futility bound ", + "before crossing an efficacy bound at a later analysis ", + "(", a2, " - ", a3, " = ", a1, ") ", "under the null hypothesis." + ) +} - # Add spanner ---- - names(x)[names(x) == "Alternate hypothesis"] <- colname_spannersub[1] - names(x)[names(x) == "Null hypothesis"] <- colname_spannersub[2] +# where to add the non-binding design footnote +gsd_footnote_row <- function(x, bound) { + # for a vector of "Analysis: N", get a logical vector `i`, in which `TRUE` + # indicates the position of the largest `N` + a <- x$Analysis + r <- "^Analysis: ([0-9]+).*" + i <- grepl(r, a) + k <- as.numeric(sub(r, '\\1', a[i])) + i[i] <- k == max(k) + i & x$Bound == bound +} - x <- x %>% - subset(Bound %in% display_bound) %>% - dplyr::arrange(Analysis) %>% - dplyr::group_by(Analysis) %>% - gt::gt() %>% - gt::tab_spanner( - columns = dplyr::all_of(colname_spannersub), - label = colname_spanner - ) %>% - gt::tab_header(title = title, subtitle = subtitle) +# a list of information for `as_[gt|rtf].gs_design()` methods: the transformed +# data, title, and footnote, etc. +gsd_parts <- function( + x, title, subtitle, spannersub, footnote, bound, columns, inf_bound, + alpha_column = spannersub[2], transform = identity +) { + method <- intersect(c("ahr", "wlr", "combo", "rd"), class(x))[1] + if (!inf_bound) x <- filter(x, !is.infinite(Z)) + # `x` needs a custom transformation in as_rtf() + x2 <- transform(x) - # Add footnotes ---- - if (!is.null(footnote$content)) { - if (length(footnote$content) != 0) { - for (i in seq_along(footnote$content)) { - # if the footnotes is added on the colnames - if (footnote$attr[i] == "colname") { - x <- x %>% - gt::tab_footnote( - footnote = footnote$content[i], - locations = gt::cells_column_labels(columns = footnote$location[i]) - ) - } - # if the footnotes is added on the title/subtitle - if (footnote$attr[i] == "title" || footnote$attr[i] == "subtitle") { - x <- x %>% - gt::tab_footnote( - footnote = footnote$content[i], - locations = gt::cells_title(group = footnote$attr[i]) - ) - } - # if the footnotes is added on the analysis summary row, which is a grouping variable, i.e., Analysis - if (footnote$attr[i] == "analysis") { - x <- x %>% - gt::tab_footnote( - footnote = footnote$content[i], - locations = gt::cells_row_groups(groups = dplyr::starts_with("Analysis")) - ) - } - # if the footnotes is added on the column spanner - if (footnote$attr[i] == "spanner") { - x <- x %>% - gt::tab_footnote( - footnote = footnote$content[i], - locations = gt::cells_column_spanners(spanners = colname_spanner) - ) - } - } - } - } + columns <- gsd_columns(columns, method, x2) + x2 <- x2[, columns] + x2 <- subset(x2, !is.na(`Alternate hypothesis`) & !is.na(`Null hypothesis`)) + x2 <- subset(x2, Bound %in% bound) - ## if it is non-binding design - if (x_non_binding && (x_alpha < full_alpha)) { - x <- x %>% - gt::tab_footnote( - footnote = paste0( - "Cumulative alpha for final analysis ", - "(", format(x_alpha, scientific = FALSE), ") ", - "is less than the full alpha ", - "(", format(full_alpha, scientific = FALSE), ") ", - "when the futility bound is non-binding. ", - "The smaller value subtracts the probability of ", - "crossing a futility bound before ", - "crossing an efficacy bound at a later analysis ", - "(", - format(full_alpha, scientific = FALSE), - " - ", - format(full_alpha - x_alpha, scientific = FALSE), - " = ", - format(x_alpha, scientific = FALSE), - ") ", - "under the null hypothesis." - ), - locations = gt::cells_body( - columns = colname_spannersub[2], - rows = (substring(x_old$Analysis, 1, 11) == paste0("Analysis: ", max(x_k))) & - (x_old$Bound == display_bound[1]) - ) - ) - } + i <- match(c("Alternate hypothesis", "Null hypothesis"), names(x2)) + names(x2)[i] <- spannersub - return(x) + title <- title %||% paste("Bound summary", switch( + method, + ahr = "for AHR design", wlr = "for WLR design", + combo = "for MaxCombo design", rd = "of Binary Endpoint" + )) + subtitle <- subtitle %||% switch( + method, + ahr = "AHR approximations of ~HR at bound", + wlr = "WLR approximation of ~wHR at bound", + combo = "MaxCombo approximation", + rd = "measured by risk difference" + ) + + list( + x = dplyr::arrange(x2, Analysis), + title = title, subtitle = subtitle, + footnote = footnote %||% gsd_footnote(method, columns), + alpha = max(filter(x, Bound == bound[1])[[alpha_column]]) + ) } diff --git a/R/as_rtf.R b/R/as_rtf.R index 8e009d3e..e317aff4 100644 --- a/R/as_rtf.R +++ b/R/as_rtf.R @@ -101,140 +101,23 @@ as_rtf.fixed_design <- function( file, ...) { orientation <- match.arg(orientation) - - # get the design method - if ("ahr" %in% class(x)) { - design_mtd <- "ahr" - } else if ("fh" %in% class(x)) { - design_mtd <- "fh" - } else if ("mb" %in% class(x)) { - design_mtd <- "mb" - } else if ("lf" %in% class(x)) { - design_mtd <- "lf" - } else if ("rd" %in% class(x)) { - design_mtd <- "rd" - } else if ("maxcombo" %in% class(x)) { - design_mtd <- "maxcombo" - } else if ("milestone" %in% class(x)) { - design_mtd <- "milestone" - } else if ("rmst" %in% class(x)) { - design_mtd <- "rmst" - } else if ("rd" %in% class(x)) { - design_mtd <- "rd" - } - - # set the default title - if (is.null(title)) { - title <- switch(design_mtd, - "ahr" = { - paste0("Fixed Design under AHR Method", " {^a}") - }, - "fh" = { - paste0("Fixed Design under Fleming-Harrington Method", " {^a}") - }, - "mb" = { - paste0("Fixed Design under Magirr-Burman Method", " {^a}") - }, - "lf" = { - paste0("Fixed Design under Lachin and Foulkes Method", " {^a}") - }, - "rd" = { - paste0("Fixed Design of Risk Difference under Farrington-Manning Method", " {^a}") - }, - "maxcombo" = { - paste0("Fixed Design under MaxCombo Method", " {^a}") - }, - "milestone" = { - paste0("Fixed Design under Milestone Method", " {^a}") - }, - "rmst" = { - paste0("Fixed Design under Restricted Mean Survival Time Method", " {^a}") - }, - "rd" = { - paste0("Fixed Design of Risk Difference", " {^a}") - } - ) - } - - - # set the default footnote - if (is.null(footnote)) { - footnote <- switch(design_mtd, - "ahr" = { - paste0("{^a} ", "Power computed with average hazard ratio method.") - }, - "fh" = { - paste0( - "{^a} ", - "Power for Fleming-Harrington test ", - substr(x$Design, 19, nchar(x$Design)), - " using method of Yung and Liu." - ) - }, - "mb" = { - paste0( - "{^a} ", - "Power for ", - x$Design, - " computed with method of Yung and Liu." - ) - }, - "lf" = { - paste0( - "{^a} ", - "Power using Lachin and Foulkes method applied - using expected average hazard ratio (AHR) at time of planned analysis." - ) - }, - "rd" = { - paste0( - "{^a} ", - "Risk difference power without continuity correction using method of Farrington and Manning." - ) - }, - "maxcombo" = { - paste0( - "{^a} ", - "Power for MaxCombo test with Fleming-Harrington tests", - substr(x$Design, 9, nchar(x$Design)), "." - ) - }, - "milestone" = { - paste0("{^a} ", "Power for ", x$Design, " computed with method of Yung and Liu.") - }, - "rmst" = { - paste0("{^a} ", "Power for ", x$Design, " computed with method of Yung and Liu.") - } - ) - } + method <- fd_method(x) + title <- title %||% paste(fd_title(method), "{^a}") + footnote <- footnote %||% paste("{^a}", fd_footnote(x, method)) # set default column width n_row <- nrow(x) n_col <- ncol(x) - if (!is.null(col_rel_width) && !(n_col == length(col_rel_width))) { - stop( - "col_rel_width must have the same length (has ", - length(col_rel_width), - ") as `x` has number of columns (has ", - n_col, ").", - call. = FALSE - ) - } + check_rel_width(col_rel_width, n_col) # set column header - colheader <- - paste0(paste(names(x), collapse = " | ")) + colheader <- paste(names(x), collapse = " | ") # set relative width - if (is.null(col_rel_width)) { - rel_width <- c(2, rep(1, (n_col - 1))) - } else { - rel_width <- col_rel_width - } + rel_width <- col_rel_width %||% c(2, rep(1, n_col - 1)) # Column boarder - border_top <- rep("single", n_col) - border_left <- rep("single", n_col) + border_top <- border_left <- rep("single", n_col) # Using order number to customize row format text_justification <- c("l", rep("c", n_col - 1)) @@ -245,13 +128,9 @@ as_rtf.fixed_design <- function( ans <- x %>% r2rtf::rtf_page(orientation = orientation) %>% r2rtf::rtf_title(title) %>% - r2rtf::rtf_colheader( - colheader = colheader, - col_rel_width = rel_width, - text_font_size = text_font_size - ) %>% + r2rtf::rtf_colheader(colheader, rel_width, text_font_size = text_font_size) %>% r2rtf::rtf_body( - col_rel_width = rel_width, + rel_width, border_left = border_left, border_top = border_top, text_justification = text_justification, @@ -261,21 +140,19 @@ as_rtf.fixed_design <- function( text_font_size = text_font_size ) - if (!is.null(footnote)) { - ans <- ans %>% - r2rtf::rtf_footnote(footnote, - text_font_size = text_font_size - ) - } - # Prepare output - ans %>% - r2rtf::rtf_encode() %>% - r2rtf::write_rtf(file = file) + rtf_write(ans, file, footnote, text_font_size) invisible(x) } +check_rel_width <- function(width, n_col) { + if (!is.null(width) && n_col != length(width)) stop( + "The length of 'col_rel_width' (", length(width), ") differs with ", + "the number of columns in 'x' (", n_col, ")." + ) +} + #' @rdname as_rtf #' #' @param title A string to specify the title of the RTF table. @@ -413,242 +290,43 @@ as_rtf.gs_design <- function( file, ...) { orientation <- match.arg(orientation) - - method <- class(x)[class(x) %in% c("ahr", "wlr", "combo", "rd")] - x_alpha <- max((x %>% dplyr::filter(Bound == display_bound[1]))[["Null hypothesis"]]) - x_non_binding <- "non_binding" %in% class(x) - x_k <- lapply(x$Analysis, function(x) { - return(as.numeric(substring(x, 11, 11))) - }) %>% unlist() x_old <- x - x <- data.frame(lapply(x, function(x) trimws(formatC(x, flag = "-"), "r"))) - names(x) <- names(x_old) - - # Set defaults ---- - # set different default title to different methods - if (method == "ahr" && is.null(title)) { - title <- "Bound summary for AHR design" - } - if (method == "wlr" && is.null(title)) { - title <- "Bound summary for WLR design" - } - if (method == "combo" && is.null(title)) { - title <- "Bound summary for MaxCombo design" - } - - if (method == "rd" && is.null(title)) { - title <- "Bound summary of Binary Endpoint" - } - - # set different default subtitle to different methods - if (method == "ahr" && is.null(subtitle)) { - subtitle <- "AHR approximations of ~HR at bound" - } - if (method == "wlr" && is.null(subtitle)) { - subtitle <- "WLR approximation of ~wHR at bound" - } - if (method == "combo" && is.null(subtitle)) { - subtitle <- "MaxCombo approximation" - } - if (method == "rd" && is.null(subtitle)) { - subtitle <- "measured by risk difference" - } - - # set different default columns to display - if (is.null(display_columns)) { - if (method == "ahr") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~HR at bound", "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "wlr") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~wHR at bound", "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "combo") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "Alternate hypothesis", "Null hypothesis" - ) - } else if (method == "rd") { - display_columns <- c( - "Analysis", "Bound", "Z", "Nominal p", - "~Risk difference at bound", "Alternate hypothesis", "Null hypothesis" - ) + parts <- gsd_parts( + x, title, subtitle, colname_spannersub, footnote, + display_bound, display_columns, display_inf_bound, "Null hypothesis", + function(x) { + x2 <- data.frame(lapply(x, function(x) trimws(formatC(x, flag = "-"), "r"))) + names(x2) <- names(x) + x2 } - } - # filter the columns to display as the output - ## if `Probability` is selected to output, then transform it to `c("Alternate hypothesis", "Null hypothesis")` - if ("Probability" %in% display_columns) { - display_columns <- display_columns[!display_columns == "Probability"] - display_columns <- c(display_columns, "Alternate hypothesis", "Null hypothesis") - } - ## check if the `display_columns` are included in `x` output - if (sum(!(display_columns %in% names(x))) >= 1) { - stop("as_rtf: the variable names in display_columns is not outputted in the summary_bound object!") - } else { - x <- x %>% dplyr::select(dplyr::all_of(display_columns)) - } - - # set different default footnotes to different methods - if (method == "ahr" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse( - "Nominal p" %in% display_columns, - paste( - "One-sided p-value for experimental vs control treatment.", - "Value < 0.5 favors experimental, > 0.5 favors control." - ), - NA - ), - ifelse( - "~HR at bound" %in% display_columns, - "Approximate hazard ratio to cross bound.", - NA - ) - ), - location = c( - ifelse("Nominal p" %in% display_columns, "Nominal p", NA), - ifelse("~HR at bound" %in% display_columns, "~HR at bound", NA) - ), - attr = c( - ifelse("Nominal p" %in% display_columns, "colname", NA), - ifelse("~HR at bound" %in% display_columns, "colname", NA) - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "wlr" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse( - "Nominal p" %in% display_columns, - paste( - "One-sided p-value for experimental vs control treatment.", - "Value < 0.5 favors experimental, > 0.5 favors control." - ), - NA - ), - ifelse( - "~wHR at bound" %in% display_columns, - "Approximate hazard ratio to cross bound.", - NA - ), - "wAHR is the weighted AHR." - ), - location = c( - ifelse("Nominal p" %in% display_columns, "Nominal p", NA), - ifelse("~wHR at bound" %in% display_columns, "~wHR at bound", NA), - NA - ), - attr = c( - ifelse("Nominal p" %in% display_columns, "colname", NA), - ifelse("~wHR at bound" %in% display_columns, "colname", NA), - "analysis" - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "combo" && is.null(footnote)) { - footnote <- list( - content = c( - ifelse( - "Nominal p" %in% display_columns, - paste( - "One-sided p-value for experimental vs control treatment.", - "Value < 0.5 favors experimental, > 0.5 favors control." - ), - NA - ), - "EF is event fraction. AHR is under regular weighted log rank test." - ), - location = c( - ifelse("Nominal p" %in% display_columns, "Nominal p", NA), - NA - ), - attr = c( - ifelse("Nominal p" %in% display_columns, "colname", NA), - "analysis" - ) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - if (method == "rd" && is.null(footnote)) { - footnote <- list( - content = c(ifelse( - "Nominal p" %in% display_columns, - paste( - "One-sided p-value for experimental vs control treatment.", - "Value < 0.5 favors experimental, > 0.5 favors control." - ), - NA - )), - location = c(ifelse("Nominal p" %in% display_columns, "Nominal p", NA)), - attr = c(ifelse("Nominal p" %in% display_columns, "colname", NA)) - ) - footnote <- lapply(footnote, function(x) x[!is.na(x)]) - } - - # Filter out inf bound ---- - x <- x %>% - subset(!is.na(`Alternate hypothesis`)) %>% - subset(!is.na(`Null hypothesis`)) - - # organize data - x <- x %>% - subset(Bound %in% display_bound) %>% - dplyr::arrange(Analysis) + ) + x <- parts$x + title <- parts$title + subtitle <- parts$subtitle # Set rtf parameters ---- n_col <- ncol(x) n_row <- nrow(x) - if (!is.null(col_rel_width) && !(n_col == length(col_rel_width))) { - stop( - "col_rel_width must have the same length (has ", - length(col_rel_width), - ") as `x` has number of columns (has ", - n_col, ").", - call. = FALSE - ) - } - - # set column header - names(x)[names(x) == "Alternate hypothesis"] <- colname_spannersub[1] - names(x)[names(x) == "Null hypothesis"] <- colname_spannersub[2] - + check_rel_width(col_rel_width, n_col) colheader <- c( paste0(" | ", colname_spanner), paste(names(x)[-1], collapse = " | ") ) # set relative width - if (is.null(col_rel_width)) { - rel_width_body <- rep(1, n_col) - } else { - rel_width_body <- col_rel_width - } + rel_width_body <- col_rel_width %||% rep(1, n_col) - rel_width_head <- rel_width_body[2:length(rel_width_body)] + rel_width_head <- rel_width_body[-1] rel_width_head <- list( - c( - sum(rel_width_head[2:(n_col - 2)]), - sum(tail(rel_width_head, n = 2)) - ), + c(sum(rel_width_head[2:(n_col - 2)]), sum(tail(rel_width_head, 2))), rel_width_head ) # column boarder - border_top_head <- rep("single", (n_col - 1)) - border_top_body <- rep("single", n_col) - border_bottom <- rep("single", n_col) - border_left_head <- list( - c("single", "single"), - rep("single", n_col - 1) - ) - border_left_body <- c("single", border_left_head[[2]]) + border_top_body <- border_bottom <- border_left_body <- rep("single", n_col) + border_top_head <- border_top_body[-1] + border_left_head <- list(rep("single", 2), border_top_head) # Using order number to customize row format text_justification <- c("l", "l", rep("c", n_col - 2)) @@ -658,108 +336,62 @@ as_rtf.gs_design <- function( # Add footnotes ---- # initialization for footnote footnotes <- NULL - alpha_utf_int <- 96 - - if (!is.null(footnote$content)) { - if (length(footnote$content) > 0) { - for (i in seq_along(footnote$content)) { - alpha_utf_int <- alpha_utf_int + 1 - if (footnote$attr[i] == "colname") { - colheader[2] <- sub( - footnote$location[i], - paste0(footnote$location[i], " {^", intToUtf8(alpha_utf_int), "}"), - colheader[2] - ) - } else if (footnote$attr[i] == "title") { - title <- paste0(title, " \\super ", intToUtf8(alpha_utf_int)) - } else if (footnote$attr[i] == "subtitle") { - subtitle <- paste0(subtitle, " {\\super ", intToUtf8(alpha_utf_int), "}") - } else if (footnote$attr[i] == "analysis") { - x["Analysis"] <- lapply(x["Analysis"], function(z) paste0(z, " {^", intToUtf8(alpha_utf_int), "}")) - } else if (footnote$attr[i] == "spanner") { - colheader[1] <- sub( - colname_spanner, - paste0(colname_spanner, " {^", intToUtf8(alpha_utf_int), "}"), - colheader[1] - ) - } - marked_footnote <- paste0("{\\super ", intToUtf8(alpha_utf_int), "} ", footnote$content[i]) - if (!is.null(footnotes)) { - footnotes <- paste0(footnotes, "\\line", marked_footnote) - } else { - footnotes <- marked_footnote - } - } + footnote <- parts$footnote + # footnote markers (a, b, c, ... from letters[idx]) + idx <- 0L + marker <- function() letters[idx <<- idx + 1L] + + for (i in seq_along(footnote$content)) { + att <- footnote$attr[i] + mkr <- marker() + if (att == "colname") { + colheader[2] <- sub( + footnote$location[i], + paste0(footnote$location[i], " {^", mkr, "}"), + colheader[2] + ) + } else if (att == "title") { + title <- paste0(title, " \\super ", mkr) + } else if (att == "subtitle") { + subtitle <- paste0(subtitle, " {\\super ", mkr, "}") + } else if (att == "analysis") { + x["Analysis"] <- lapply(x["Analysis"], function(z) paste0(z, " {^", mkr, "}")) + } else if (att == "spanner") { + colheader[1] <- sub( + colname_spanner, + paste0(colname_spanner, " {^", mkr, "}"), + colheader[1] + ) } + marked_footnote <- paste0("{\\super ", mkr, "} ", footnote$content[i]) + footnotes <- c(footnotes, marked_footnote) } - ## if it is non-binding design - if (x_non_binding && (x_alpha < full_alpha)) { - alpha_utf_int <- alpha_utf_int + 1 - - x[ - (substring(x$Analysis, 1, 11) == paste0("Analysis: ", max(x_k))) & - x$Bound == display_bound[1], colname_spannersub[2] - ] <- paste0( - x[ - (substring(x$Analysis, 1, 11) == paste0("Analysis: ", max(x_k))) & - x$Bound == display_bound[1], colname_spannersub[2] - ], - " {^", intToUtf8(alpha_utf_int), "}" - ) - - footnote_non_binding <- paste0( - "{\\super ", intToUtf8(alpha_utf_int), "} ", - "Cumulative alpha for final analysis ", - "(", format(x_alpha, scientific = FALSE), ") ", - "is less than the full alpha ", - "(", format(full_alpha, scientific = FALSE), ") ", - "when the futility bound is non-binding. ", - "The smaller value subtracts the probability of ", - "crossing a futility bound before ", - "crossing an efficacy bound at a later analysis ", - "(", - format(full_alpha, scientific = FALSE), - " - ", - format(full_alpha - x_alpha, scientific = FALSE), - " = ", - format(x_alpha, scientific = FALSE), - ") ", - "under the null hypothesis." - ) - - if (!is.null(footnotes)) { - footnotes <- paste0(footnotes, "\\line", footnote_non_binding) - } else { - footnotes <- footnote_non_binding - } + # add footnote for non-binding design + footnote_nb <- gsd_footnote_nb(x_old, parts$alpha, full_alpha) + if (!is.null(footnote_nb)) { + mkr <- marker() + i <- gsd_footnote_row(x, display_bound[1]) + j <- colname_spannersub[2] + x[i, j] <- paste0(x[i, j], " {^", mkr, "}") + footnotes <- c(footnotes, paste0("{\\super ", mkr, "} ", footnote_nb)) } # Output ---- # use r2rtf ans <- x %>% r2rtf::rtf_page(orientation = orientation) %>% - r2rtf::rtf_title( - title = title, - subtitle = subtitle, - text_convert = FALSE - ) %>% + r2rtf::rtf_title(title, subtitle, text_convert = FALSE) %>% r2rtf::rtf_colheader( - colheader = colheader[1], - col_rel_width = rel_width_head[[1]], - text_font_size = text_font_size, - border_left = border_left_head[[1]] + colheader[1], rel_width_head[[1]], border_left_head[[1]], + text_font_size = text_font_size ) %>% r2rtf::rtf_colheader( - colheader = colheader[2], - border_top = border_top_head, - border_left = border_left_head[[2]], - col_rel_width = rel_width_head[[2]], - text_font_size = text_font_size + colheader[2], rel_width_head[[2]], border_left_head[[2]], + border_top = border_top_head, text_font_size = text_font_size ) %>% r2rtf::rtf_body( - page_by = "Analysis", - col_rel_width = rel_width_body, + rel_width_body, page_by = "Analysis", border_left = border_left_body, border_top = border_top_body, border_bottom = border_bottom, @@ -770,18 +402,17 @@ as_rtf.gs_design <- function( text_font_size = text_font_size ) - if (!is.null(footnotes)) { - ans <- ans %>% - r2rtf::rtf_footnote(footnotes, - text_font_size = text_font_size, - text_convert = FALSE - ) - } - # Prepare output - ans %>% - r2rtf::rtf_encode() %>% - r2rtf::write_rtf(file = file) + rtf_write(ans, file, footnotes, text_font_size, text_convert = FALSE) invisible(x_old) } + +# write RTF with (optional) footnotes +rtf_write <- function(x, file, footnote = NULL, size, ...) { + if (!is.null(footnote)) { + footnote <- paste(footnote, collapse = "\\line") + x <- r2rtf::rtf_footnote(x, footnote, text_font_size = size, ...) + } + r2rtf::write_rtf(r2rtf::rtf_encode(x), file) +} diff --git a/R/fixed_design_mb.R b/R/fixed_design_mb.R index db90d001..9c27bf0f 100644 --- a/R/fixed_design_mb.R +++ b/R/fixed_design_mb.R @@ -23,7 +23,7 @@ #' @param ratio Experimental:Control randomization ratio. #' @param study_duration Study duration. #' @param tau Test parameter of Magirr-Burman method. -#' +#' @param w_max Test parameter of Magirr-Burman method. #' @export #' #' @rdname fixed_design @@ -43,7 +43,8 @@ #' dropout_rate = .001 #' ), #' study_duration = 36, -#' tau = 4 +#' tau = 4, +#' w_max = 2 #' ) #' x %>% summary() #' @@ -58,7 +59,8 @@ #' dropout_rate = .001 #' ), #' study_duration = 36, -#' tau = 4 +#' tau = 4, +#' w_max = 2 #' ) #' x %>% summary() #' @@ -69,7 +71,8 @@ fixed_design_mb <- function( study_duration = 36, enroll_rate, fail_rate, - tau = 6) { + tau = 6, + w_max = Inf) { # Check inputs ---- check_enroll_rate(enroll_rate) check_fail_rate(fail_rate) @@ -88,7 +91,7 @@ fixed_design_mb <- function( # Generate design ---- weight <- function(x, arm0, arm1) { - wlr_weight_fh(x, arm0, arm1, rho = -1, gamma = 0, tau = tau) + wlr_weight_mb(x, arm0, arm1, tau = tau, w_max = w_max) } if (is.null(power)) { d <- gs_power_wlr( diff --git a/R/gs_spending_combo.R b/R/gs_spending_combo.R index 34518f50..617b9c30 100644 --- a/R/gs_spending_combo.R +++ b/R/gs_spending_combo.R @@ -78,13 +78,16 @@ #' gs_spending_combo(par, info = 1:3 / 3) #' #' # Truncated, trimmed and gapped spending functions -#' par <- list(sf = gsDesign::sfTruncated, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +#' par <- list(sf = gsDesign::sfTruncated, total_spend = 0.025, +#' param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) #' gs_spending_combo(par, info = 1:3 / 3) #' -#' par <- list(sf = gsDesign::sfTrimmed, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +#' par <- list(sf = gsDesign::sfTrimmed, total_spend = 0.025, +#' param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) #' gs_spending_combo(par, info = 1:3 / 3) #' -#' par <- list(sf = gsDesign::sfGapped, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +#' par <- list(sf = gsDesign::sfGapped, total_spend = 0.025, +#' param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) #' gs_spending_combo(par, info = 1:3 / 3) #' #' # Xi and Gallo conditional error spending functions diff --git a/R/pw_info.R b/R/pw_info.R index 90f55339..0c5dd6b9 100644 --- a/R/pw_info.R +++ b/R/pw_info.R @@ -239,7 +239,7 @@ pw_info <- function( ans <- ans[order(t), .SD, by = .(time, stratum)] # filter out the rows with 0 events - ans <- ans[!is_almost_k(event, 0L)] + ans <- ans[!almost_equal(event, 0L)] setcolorder(ans, neworder = c("time", "stratum", "t", "hr", "n", "event", "info", "info0")) setDF(ans) diff --git a/R/to_integer.R b/R/to_integer.R index 9c16ac7f..f358c6be 100644 --- a/R/to_integer.R +++ b/R/to_integer.R @@ -98,7 +98,7 @@ to_integer.fixed_design <- function(x, sample_size = TRUE, ...) { mutate(rate = rate * ceiling(output_n / multiply_factor) * multiply_factor / output_n) # Round up the FA events - event_ceiling <- ceiling(x$analysis$event) |> as.integer() + event_ceiling <- ceiling(x$analysis$event) if ((x$design == "ahr") && (input_n != output_n)) { x_new <- gs_power_ahr( @@ -200,13 +200,8 @@ to_integer.fixed_design <- function(x, sample_size = TRUE, ...) { # Make n and event of ans$analysis exactly integers if ("fixed_design" %in% class(ans)) { - if (is_almost_k(x = ans$analysis$n, k = round(ans$analysis$n))) { - ans$analysis$n <- round(ans$analysis$n) - } - - if (is_almost_k(x = ans$analysis$event, k = round(ans$analysis$event))) { - ans$analysis$event <- round(ans$analysis$event) - } + ans$analysis$n <- round(ans$analysis$n) + ans$analysis$event <- round(ans$analysis$event) } return(ans) @@ -270,77 +265,24 @@ to_integer.fixed_design <- function(x, sample_size = TRUE, ...) { #' gsDesign::sfLDOF(alpha = 0.025, t = 18 / 30)$spend #' } to_integer.gs_design <- function(x, sample_size = TRUE, ...) { + is_ahr <- inherits(x, "ahr") + is_wlr <- inherits(x, "wlr") + is_rd <- inherits(x, "rd") + if (!(is_ahr || is_wlr || is_rd)) { + message("The input object is not applicable to get an integer sample size.") + return(x) + } + n_analysis <- length(x$analysis$analysis) multiply_factor <- x$input$ratio + 1 - if ("ahr" %in% class(x)) { - # Updated events - event <- x$analysis$event - if (n_analysis == 1) { - event_new <- ceiling(event) %>% as.integer() - } else { - event_new <- c(floor(event[1:(n_analysis - 1)]), ceiling(event[n_analysis])) %>% as.integer() - } - - # Updated sample size and enroll rate - sample_size_new <- (ceiling(x$analysis$n[n_analysis] / multiply_factor) * multiply_factor) %>% as.integer() - enroll_rate <- x$enroll_rate - enroll_rate_new <- enroll_rate %>% - mutate(rate = rate * sample_size_new / x$analysis$n[n_analysis]) - - # Updated upar - # If it is spending bounds - # Scenario 1: information-based spending - # Scenario 2: calendar-based spending - if (identical(x$input$upper, gs_b)) { - upar_new <- x$input$upar - } else if (identical(x$input$upper, gs_spending_bound)) { - upar_new <- x$input$upar - if (!("timing" %in% names(x$input$upar))) { - info_with_new_event <- gs_info_ahr( - enroll_rate = enroll_rate_new, - fail_rate = x$input$fail_rate, - ratio = x$input$ratio, - event = event_new, - analysis_time = NULL - ) - - upar_new$timing <- info_with_new_event$info / max(info_with_new_event$info) - } - } - - # Updated lpar - if (identical(x$input$lower, gs_b)) { - lpar_new <- x$input$lpar - } else if (identical(x$input$lower, gs_spending_bound)) { - lpar_new <- x$input$lpar - if (!("timing" %in% names(x$input$lpar))) { - lpar_new$timing <- upar_new$timing - } - } - - # Updated design with integer events and sample size - x_new <- gs_power_ahr( - enroll_rate = enroll_rate_new, - fail_rate = x$input$fail_rate, - event = event_new, - analysis_time = NULL, - ratio = x$input$ratio, - upper = x$input$upper, upar = upar_new, - lower = x$input$lower, lpar = lpar_new, - test_upper = x$input$test_upper, - test_lower = x$input$test_lower, - binding = x$input$binding, - info_scale = x$input$info_scale, r = x$input$r, tol = x$input$tol, - interval = c(0.01, max(x$analysis$time) + 100) - ) - } else if ("wlr" %in% class(x)) { + if (!is_rd) { # Updated events to integer event <- x$analysis$event if (n_analysis == 1) { - event_new <- ceiling(event) %>% as.integer() + event_new <- ceiling(event) } else { - event_new <- c(floor(event[1:(n_analysis - 1)]), ceiling(event[n_analysis])) %>% as.integer() + event_new <- c(floor(event[1:(n_analysis - 1)]), ceiling(event[n_analysis])) } # Updated sample size to integer and enroll rates @@ -384,7 +326,7 @@ to_integer.gs_design <- function(x, sample_size = TRUE, ...) { } # Updated design with integer events and sample size - x_new <- gs_power_wlr( + power_args <- list( enroll_rate = enroll_rate_new, fail_rate = x$input$fail_rate, event = event_new, @@ -396,11 +338,11 @@ to_integer.gs_design <- function(x, sample_size = TRUE, ...) { test_lower = x$input$test_lower, binding = x$input$binding, info_scale = x$input$info_scale, r = x$input$r, tol = x$input$tol, - weight = x$input$weight, - approx = x$input$approx, interval = c(0.01, max(x$analysis$time) + 100) ) - } else if ("rd" %in% class(x)) { + if (is_wlr) power_args[c("weight", "approx")] <- x$input[c("weight", "approx")] + x_new <- do.call(if (is_wlr) gs_power_wlr else gs_power_ahr, power_args) + } else { n_stratum <- length(x$input$p_c$stratum) # Update unstratified sample size to integer @@ -413,27 +355,23 @@ to_integer.gs_design <- function(x, sample_size = TRUE, ...) { ) # Update sample size per stratum - if (n_stratum == 1) { - suppressMessages( - tbl_n <- tibble::tibble( - analysis = rep(1:n_analysis, each = n_stratum), - stratum = rep(x$input$p_c$stratum, n_analysis) - ) %>% - left_join(sample_size_new) + suppressMessages({ + tbl_n <- tibble::tibble( + analysis = rep(1:n_analysis, each = n_stratum), + stratum = rep(x$input$p_c$stratum, n_analysis) ) - } else { - suppressMessages( - tbl_n <- tibble::tibble( - analysis = rep(1:n_analysis, each = n_stratum), - stratum = rep(x$input$p_c$stratum, n_analysis) - ) %>% + tbl_n <- if (n_stratum == 1) { + tbl_n %>% + left_join(sample_size_new) + } else { + tbl_n %>% left_join(x$input$stratum_prev) %>% left_join(sample_size_new) %>% mutate(n_new = prevalence * n) %>% select(-c(n, prevalence)) %>% dplyr::rename(n = n_new) - ) - } + } + }) # If it is spending bounds # Scenario 1: information-based spending @@ -485,29 +423,11 @@ to_integer.gs_design <- function(x, sample_size = TRUE, ...) { r = x$input$r, tol = x$input$tol ) - } else { - message("The input object is not applicable to get an integer sample size.") - x_new <- x } # Make n and event of x_new$analysis exactly integers - if ("ahr" %in% class(x) || "wlr" %in% class(x)) { - for (i in seq_along(n_analysis)) { - if (is_almost_k(x = x_new$analysis$n[i], k = round(x_new$analysis$n[i]))) { - x_new$analysis$n[i] <- round(x_new$analysis$n[i]) - } - - if (is_almost_k(x = x_new$analysis$event[i], k = round(x_new$analysis$event[i]))) { - x_new$analysis$event[i] <- round(x_new$analysis$event[i]) - } - } - } else if ("rd" %in% class(x)) { - for (i in seq_along(n_analysis)) { - if (is_almost_k(x = x_new$analysis$n[i], k = round(x_new$analysis$n[i]))) { - x_new$analysis$n[i] <- round(x_new$analysis$n[i]) - } - } - } + x_new$analysis$n <- round(x_new$analysis$n) + if (!is_rd) x_new$analysis$event <- round(x_new$analysis$event) return(x_new) } diff --git a/R/utility_wlr.R b/R/utility_wlr.R index 56c7c0f8..5622f29e 100644 --- a/R/utility_wlr.R +++ b/R/utility_wlr.R @@ -273,6 +273,6 @@ gs_sigma2_wlr <- function(arm0, } #' @noRd -is_almost_k <- function(x, k, tol = .Machine$double.eps^0.5) { +almost_equal <- function(x, k, tol = .Machine$double.eps^0.5) { abs(x - k) < tol -} \ No newline at end of file +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 00000000..bca42fde --- /dev/null +++ b/R/utils.R @@ -0,0 +1,12 @@ +# %||% was introduced in base R 4.4.0 +if (!exists('%||%', baseenv(), inherits = FALSE)) `%||%` <- function(x, y) { + if (is.null(x)) y else x +} + +# append elements from y to x +`%+%` <- function(x, y) { + # append by names if available, otherwise append by integer indices + idx <- names(y) %||% seq_along(y) + for (i in idx) x[[i]] <- c(x[[i]], y[[i]]) + x +} diff --git a/R/wlr_weight.R b/R/wlr_weight.R index 14592cb5..4cc8f7fd 100644 --- a/R/wlr_weight.R +++ b/R/wlr_weight.R @@ -29,7 +29,7 @@ #' @param rho A scalar parameter that controls the type of test. #' @param gamma A scalar parameter that controls the type of test. #' @param tau A scalar parameter of the cut-off time for modest weighted log rank test. -#' @param wmax A scalar parameter of the cut-off weight for modest weighted log rank test. +#' @param w_max A scalar parameter of the cut-off weight for modest weighted log rank test. #' @param power A scalar parameter that controls the power of the weight function. #' #' @section Specification: @@ -171,7 +171,7 @@ wlr_weight_n <- function(x, arm0, arm1, power = 1) { #' arm0 <- gs_arm$arm0 #' arm1 <- gs_arm$arm1 #' -#' wlr_weight_mb(1:3, arm0, arm1, tau = -1, wmax = 1.2) -wlr_weight_mb <- function(x, arm0, arm1, tau = NULL, wmax = Inf) { - pmin(wmax, wlr_weight_fh(x, arm0, arm1, rho = -1, gamma = 0, tau = tau)) +#' wlr_weight_mb(1:3, arm0, arm1, tau = -1, w_max = 1.2) +wlr_weight_mb <- function(x, arm0, arm1, tau = NULL, w_max = Inf) { + pmin(w_max, wlr_weight_fh(x, arm0, arm1, rho = -1, gamma = 0, tau = tau)) } diff --git a/man/fixed_design.Rd b/man/fixed_design.Rd index cd61ddba..113dd28c 100644 --- a/man/fixed_design.Rd +++ b/man/fixed_design.Rd @@ -62,7 +62,8 @@ fixed_design_mb( study_duration = 36, enroll_rate, fail_rate, - tau = 6 + tau = 6, + w_max = Inf ) fixed_design_milestone( @@ -117,6 +118,8 @@ and \code{1 - alpha} otherwise).} \item{tau}{Test parameter in RMST.} +\item{w_max}{Test parameter of Magirr-Burman method.} + \item{p_c}{A numerical value of the control arm rate.} \item{p_e}{A numerical value of the experimental arm rate.} @@ -289,7 +292,8 @@ x <- fixed_design_mb( dropout_rate = .001 ), study_duration = 36, - tau = 4 + tau = 4, + w_max = 2 ) x \%>\% summary() @@ -304,7 +308,8 @@ x <- fixed_design_mb( dropout_rate = .001 ), study_duration = 36, - tau = 4 + tau = 4, + w_max = 2 ) x \%>\% summary() diff --git a/man/gs_spending_combo.Rd b/man/gs_spending_combo.Rd index 38287a0c..b1c8cedc 100644 --- a/man/gs_spending_combo.Rd +++ b/man/gs_spending_combo.Rd @@ -80,13 +80,16 @@ par <- list(sf = gsDesign::sfPoints, total_spend = 0.025, param = c(.25, .25)) gs_spending_combo(par, info = 1:3 / 3) # Truncated, trimmed and gapped spending functions -par <- list(sf = gsDesign::sfTruncated, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +par <- list(sf = gsDesign::sfTruncated, total_spend = 0.025, + param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) gs_spending_combo(par, info = 1:3 / 3) -par <- list(sf = gsDesign::sfTrimmed, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +par <- list(sf = gsDesign::sfTrimmed, total_spend = 0.025, + param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) gs_spending_combo(par, info = 1:3 / 3) -par <- list(sf = gsDesign::sfGapped, total_spend = 0.025, param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) +par <- list(sf = gsDesign::sfGapped, total_spend = 0.025, + param = list(trange = c(.2, .8), sf = gsDesign::sfHSD, param = 1)) gs_spending_combo(par, info = 1:3 / 3) # Xi and Gallo conditional error spending functions diff --git a/man/wlr_weight.Rd b/man/wlr_weight.Rd index e1bcc5ca..c4a8bd0e 100644 --- a/man/wlr_weight.Rd +++ b/man/wlr_weight.Rd @@ -14,7 +14,7 @@ wlr_weight_1(x, arm0, arm1) wlr_weight_n(x, arm0, arm1, power = 1) -wlr_weight_mb(x, arm0, arm1, tau = NULL, wmax = Inf) +wlr_weight_mb(x, arm0, arm1, tau = NULL, w_max = Inf) } \arguments{ \item{x}{A vector of numeric values.} @@ -31,7 +31,7 @@ wlr_weight_mb(x, arm0, arm1, tau = NULL, wmax = Inf) \item{power}{A scalar parameter that controls the power of the weight function.} -\item{wmax}{A scalar parameter of the cut-off weight for modest weighted log rank test.} +\item{w_max}{A scalar parameter of the cut-off weight for modest weighted log rank test.} } \value{ A vector of weights. @@ -132,5 +132,5 @@ gs_arm <- gs_create_arm(enroll_rate, fail_rate, ratio = 1) arm0 <- gs_arm$arm0 arm1 <- gs_arm$arm1 -wlr_weight_mb(1:3, arm0, arm1, tau = -1, wmax = 1.2) +wlr_weight_mb(1:3, arm0, arm1, tau = -1, w_max = 1.2) } diff --git a/tests/testthat/_snaps/independent_as_gt.md b/tests/testthat/_snaps/independent_as_gt.md index 4568c13e..1ee9b113 100644 --- a/tests/testthat/_snaps/independent_as_gt.md +++ b/tests/testthat/_snaps/independent_as_gt.md @@ -79,8 +79,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \end{minipage} \endgroup @@ -117,8 +116,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \end{minipage} \endgroup @@ -144,8 +142,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \textsuperscript{\textit{3}}wAHR is the weighted AHR.\\ \end{minipage} @@ -222,9 +219,8 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ - \textsuperscript{\textit{2}}EF is event fraction. AHR is under regular weighted log rank test.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{2}}EF is event fraction. AHR is under regular weighted log rank test.\\ \end{minipage} \endgroup @@ -249,8 +245,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \end{minipage} \endgroup @@ -284,8 +279,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Cumulative alpha for final analysis (0.0238) is less than the full alpha (0.025) when the futility bound is non-binding. The smaller value subtracts the probability of crossing a futility bound before crossing an efficacy bound at a later analysis (0.025 - 0.0012 = 0.0238) under the null hypothesis.\\ \end{minipage} \endgroup @@ -322,8 +316,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \textsuperscript{\textit{3}}wAHR is the weighted AHR.\\ \end{minipage} @@ -361,8 +354,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \textsuperscript{\textit{3}}wAHR is the weighted AHR.\\ \textsuperscript{\textit{4}}Cumulative alpha for final analysis (-Inf) is less than the full alpha (0.025) when the futility bound is non-binding. The smaller value subtracts the probability of crossing a futility bound before crossing an efficacy bound at a later analysis (0.025 - Inf = -Inf) under the null hypothesis.\\ @@ -437,8 +429,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}Approximate hazard ratio to cross bound.\\ \textsuperscript{\textit{3}}wAHR is the weighted AHR.\\ \end{minipage} @@ -476,8 +467,7 @@ \bottomrule \end{longtable} \begin{minipage}{\linewidth} - \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. - Value < 0.5 favors experimental, > 0.5 favors control.\\ + \textsuperscript{\textit{1}}One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\\ \textsuperscript{\textit{2}}wAHR is the weighted AHR.\\ \end{minipage} \endgroup diff --git a/tests/testthat/test-independent-fixed_design.R b/tests/testthat/test-independent-fixed_design.R index 25794cdf..848c655c 100644 --- a/tests/testthat/test-independent-fixed_design.R +++ b/tests/testthat/test-independent-fixed_design.R @@ -138,7 +138,7 @@ test_that("MaxCombo", { tau = c(-1, 4, 6) ) - expect_equal(y$analysis$power, 0.9, tolerance = testthat_tolerance() * 1000) + expect_equal(y$analysis$power, 0.9, tolerance = 1e-4) }) test_that("RMST", {