From 6cb828e1a1fb51bf86cee5189b603c8ce8abf6ae Mon Sep 17 00:00:00 2001 From: Robin Denz Date: Mon, 1 Apr 2024 19:41:34 +0200 Subject: [PATCH] refactor: put together difference/ratio arguments of adjusted_surv_quantile() into one argument (contrast) --- R/adjusted_surv_quantile.r | 15 +++++------- R/input_checks.r | 20 ++++++---------- R/risk_tables.r | 5 ++-- man/adjusted_surv_quantile.Rd | 23 ++++++++----------- tests/testthat/test_MI_adjustedsurv.r | 8 +++---- tests/testthat/test_adjusted_surv_quantile.r | 24 ++++++++++---------- vignettes/comparing_groups.rmd | 4 ++-- 7 files changed, 43 insertions(+), 56 deletions(-) diff --git a/R/adjusted_surv_quantile.r b/R/adjusted_surv_quantile.r index 0aeabda..8833bdf 100644 --- a/R/adjusted_surv_quantile.r +++ b/R/adjusted_surv_quantile.r @@ -212,14 +212,13 @@ get_surv_quantile_contrast <- function(plotdata, q_surv, contrast, adjusted_surv_quantile <- function(adjsurv, p=0.5, conf_int=FALSE, conf_level=adjsurv$conf_level, use_boot=FALSE, interpolation="steps", - difference=FALSE, ratio=FALSE, - group_1=NULL, group_2=NULL) { + contrast="none", group_1=NULL, + group_2=NULL) { check_inputs_surv_q(adjsurv=adjsurv, conf_int=conf_int, p=p, use_boot=use_boot, interpolation=interpolation, - difference=difference, ratio=ratio, - conf_level=conf_level, group_1=group_1, - group_2=group_2) + contrast=contrast, conf_level=conf_level, + group_1=group_1, group_2=group_2) if (use_boot) { plotdata <- adjsurv$boot_adj @@ -227,7 +226,7 @@ adjusted_surv_quantile <- function(adjsurv, p=0.5, conf_int=FALSE, plotdata <- adjsurv$adj } - if (difference | ratio) { + if (contrast %in% c("diff", "ratio")) { conf_int_main <- FALSE } else if (conf_int) { conf_int_main <- TRUE @@ -242,9 +241,7 @@ adjusted_surv_quantile <- function(adjsurv, p=0.5, conf_int=FALSE, method=adjsurv$method, boot_data=adjsurv$boot_data, use_boot=use_boot) - if (difference | ratio) { - contrast <- ifelse(difference, "diff", "ratio") - + if (contrast %in% c("diff", "ratio")) { if (conf_int) { if (is.null(adjsurv$mids_analyses)) { diff --git a/R/input_checks.r b/R/input_checks.r index 2fdebb3..d4719da 100644 --- a/R/input_checks.r +++ b/R/input_checks.r @@ -1120,7 +1120,7 @@ check_inputs_adj_diff <- function(adj, group_1, group_2, conf_int, use_boot) { ## check inputs for adjusted_surv_quantile function check_inputs_surv_q <- function(adjsurv, p, conf_int, use_boot, - interpolation, difference, ratio, + interpolation, contrast, conf_level, group_1, group_2) { if (!inherits(adjsurv, "adjustedsurv")) { stop("'adjsurv' must be an adjustedsurv object created using the", @@ -1130,7 +1130,7 @@ check_inputs_surv_q <- function(adjsurv, p, conf_int, use_boot, " numbers <= 1 & >= 0.") } else if (conf_int && !use_boot && !"ci_lower" %in% colnames(adjsurv$adj) && - !(difference | ratio)) { + !contrast %in% c("diff", "ratio")) { stop("There are no approximate confidence intervals to use.", " Either set 'use_boot=TRUE' or rerun the adjustedsurv function", " with 'conf_int=TRUE' if possible.") @@ -1145,10 +1145,9 @@ check_inputs_surv_q <- function(adjsurv, p, conf_int, use_boot, } else if (!(length(conf_level)==1 && is.numeric(conf_level) && conf_level < 1 && conf_level > 0)) { stop("'conf_level' must be a single number < 1 and > 0.") - } else if (!(is.logical(difference) && length(difference)==1)) { - stop("'difference' must be either TRUE or FALSE.") - } else if (!(is.logical(ratio) && length(ratio)==1)) { - stop("'ratio' must be either TRUE or FALSE.") + } else if (!(length(contrast)==1 && is.character(contrast) && + contrast %in% c("diff", "ratio", "none"))) { + stop("'contrast' must be one of c('none', 'diff', 'ratio').") } else if (!(is.null(group_1) || (length(group_1)==1 && is.character(group_1) && group_1 %in% levels(adjsurv$adj$group)))) { @@ -1161,16 +1160,11 @@ check_inputs_surv_q <- function(adjsurv, p, conf_int, use_boot, " of the 'variable' column.") } - if (difference & ratio) { - stop("Cannot use both 'ratio=TRUE' and 'difference=TRUE' at the", - " same time. Set one to FALSE.") - } - - if ((difference | ratio) && conf_int && + if (contrast %in% c("diff", "ratio") && conf_int && ((is.null(adjsurv$mids_analyses) && is.null(adjsurv$boot_data)) || (!is.null(adjsurv$mids_analyses) && is.null(adjsurv$mids_analyses[[1]]$boot_data)))) { - stop("Cannot calculate confidence intervals for differences if", + stop("Cannot calculate confidence intervals for differences/ratios if", " bootstrapping was not performed in the original adjustedsurv()", " function call. Run adjustedsurv() again with bootstrap=TRUE", " to continue.") diff --git a/R/risk_tables.r b/R/risk_tables.r index 0965691..4b7c803 100644 --- a/R/risk_tables.r +++ b/R/risk_tables.r @@ -125,7 +125,7 @@ get_risk_table <- function(times, data, ev_time, variable=NULL, event=NULL, # remove NA values out <- stats::na.omit(out) - # round values (only relevant if weighted) + # round values (only relevant if weighted or MI) out$est <- round(out$est, digits=digits) return(out) @@ -140,8 +140,7 @@ plot_risk_table.all <- function(plotdata, breaks, reverse_order=FALSE, custom_colors=NULL) { p <- ggplot2::ggplot(data=plotdata, - ggplot2::aes(x=.data$time, y=1, - label=.data$est)) + + ggplot2::aes(x=.data$time, y=1, label=.data$est)) + ggplot2::geom_text(size=text_size, alpha=text_alpha, color=text_color, family=text_family, fontface=text_fontface) + gg_theme + diff --git a/man/adjusted_surv_quantile.Rd b/man/adjusted_surv_quantile.Rd index cb14950..525e8c3 100644 --- a/man/adjusted_surv_quantile.Rd +++ b/man/adjusted_surv_quantile.Rd @@ -11,8 +11,8 @@ This function can be utilized to estimate confounder-adjusted survival time quan adjusted_surv_quantile(adjsurv, p=0.5, conf_int=FALSE, conf_level=adjsurv$conf_level, use_boot=FALSE, interpolation="steps", - difference=FALSE, ratio=FALSE, - group_1=NULL, group_2=NULL) + contrast="none", group_1=NULL, + group_2=NULL) } \arguments{ @@ -34,17 +34,14 @@ Whether to use the bootstrap confidence interval estimates of the survival curve \item{interpolation}{ Either \code{"steps"} (default) or \code{"linear"}. This parameter controls how interpolation is performed. If this argument is set to \code{"steps"}, the curves will be treated as step functions. If it is set to \code{"linear"}, the curves wil be treated as if there are straight lines between the point estimates instead. Points that lie between estimated points will be interpolated accordingly. } - \item{difference}{ -Whether to estimate the difference between two adjusted survival time quantiles instead. When \code{conf_int=TRUE} is also specified and bootstrapping was performed in the original \code{adjustedsurv} call, this function will also estimate the corresponding standard error, the confidence interval and a p-value testing whether the difference is equal to 0. To specify which difference should be calculated, the \code{group_1} and \code{group_2} arguments can be used. By default, the difference between the first and second level in \code{variable} is computed. - } - \item{ratio}{ -Whether to estimate the ratio of two adjusted survival time quantiles instead. When \code{conf_int=TRUE} is also specified and bootstrapping was performed in the original \code{adjustedsurv} call, this function will also estimate the corresponding standard error, the confidence interval and a p-value testing whether the ratio is equal to 1. To specify which ratio should be calculated, the \code{group_1} and \code{group_2} arguments can be used. By default, the ratio between the first and second level in \code{variable} is computed. + \item{contrast}{ +A single character string, specifying which contrast should be estimated. Needs to be one of \code{"none"} (estimate no contrasts, just return the adjusted survival time quantile, the default), \code{"diff"} (estimate the difference) or \code{"ratio"} (estimate the ratio). When \code{conf_int=TRUE} is also specified and bootstrapping was performed in the original \code{adjustedsurv} call, this function will also estimate the corresponding standard error, the confidence interval and a p-value testing whether the difference is equal to 0 (or the ratio is equal to 1). To specify which difference/ratio should be calculated, the \code{group_1} and \code{group_2} arguments can be used. By default, the difference/ratio between the first and second level in \code{variable} is computed. } \item{group_1}{ -Optional argument to get a specific difference or ratio. This argument takes a single character string specifying one of the levels of the \code{variable} used in the original \code{adjustedsurv} function call. This group will be subtracted from. For example if \code{group_1="A"} and \code{group_2="B"} and \code{difference=TRUE} the difference \code{A - B} will be used. If \code{NULL}, the order of the factor levels in the original data determines the order. Ignored if \code{difference=FALSE} and \code{ratio=FALSE}. +Optional argument to get a specific difference or ratio. This argument takes a single character string specifying one of the levels of the \code{variable} used in the original \code{adjustedsurv} function call. This group will be subtracted from. For example if \code{group_1="A"} and \code{group_2="B"} and \code{contrast=="diff"} the difference \code{A - B} will be used. If \code{NULL}, the order of the factor levels in the original data determines the order. Ignored if \code{contrast="none"}. } \item{group_2}{ -Also a single character string specifying one of the levels of \code{variable}. This corresponds to the right side of the difference/ratio equation. See argument \code{group_2}. Ignored if \code{difference=FALSE} and \code{ratio=FALSE}. +Also a single character string specifying one of the levels of \code{variable}. This corresponds to the right side of the difference/ratio equation. See argument \code{group_2}. Ignored if \code{contrast="none"}. } } \details{ @@ -61,7 +58,7 @@ If the survival probability never drops below \code{p}, the survival time quanti When estimating the adjusted survival time quantiles directly, the confidence intervals are simply read off the survival curves similarly to the point estimates. For example, this means that the lower limit of the confidence interval for some survival time quantile is simply the first point in time at which the curve defined by the lower confidence limit goes below \eqn{p}. -If \code{difference} or \code{ratio} are set to \code{TRUE}, a different approach is used. Note that this functionality works only with bootstrapping (regardless of whether \code{use_boot} is \code{TRUE} or \code{FALSE}). In this case, the survival time quantiles are calculated for each bootstrap sample separately first. Afterwards, the standard deviation of the resulting estimates is calculated for each group. For differences, the pooled standard error is then estimated as \eqn{SE_{group_1 - group_2} = \sqrt{SE_{group_1}^2 + SE_{group_2}^2}} and used along with the normal approximation to calculate confidence intervals. This is similar to the strategy of Wu (2011). The p-value is also estimated using the pooled standard error and a one-sample two-sided t-test with the null-hypothesis that the difference is equal to 0. The confidence interval and p-value of the ratio is estimated using the method of Fieller (1954). +If \code{contrast="none"}, a different approach is used. Note that this functionality works only with bootstrapping (regardless of whether \code{use_boot} is \code{TRUE} or \code{FALSE}). In this case, the survival time quantiles are calculated for each bootstrap sample separately first. Afterwards, the standard deviation of the resulting estimates is calculated for each group. For differences, the pooled standard error is then estimated as \eqn{SE_{group_1 - group_2} = \sqrt{SE_{group_1}^2 + SE_{group_2}^2}} and used along with the normal approximation to calculate confidence intervals. This is similar to the strategy of Wu (2011). The p-value is also estimated using the pooled standard error and a one-sample two-sided t-test with the null-hypothesis that the difference is equal to 0. The confidence interval and p-value of the ratio is estimated using the method of Fieller (1954). If one or both of the survival time quantiles used for the difference / ratio calculation could not be estimated from the respective bootstrap sample (due to the curves not extending that far) it is simply discarded. @@ -71,7 +68,7 @@ If more than two groups are present in \code{variable}, all other comparisons ex \strong{\emph{Multiple Imputation}} -If multiple imputation was used in the original function call, the survival time quantiles are read off the final pooled survival curves directly. This also works when using \code{difference=TRUE} or \code{ratio=TRUE}. When using either \code{difference=TRUE} or \code{ratio=TRUE} in conunction with \code{conf_int=TRUE} (plus bootstrapping), the standard errors of the difference or ratio are estimated for each imputed dataset separately and pooled using Rubins Rule afterwards. The pooled standard errors are then used to perform the same calculations as described above. +If multiple imputation was used in the original function call, the survival time quantiles are read off the final pooled survival curves directly. This also works when using \code{contrast="diff"} or \code{contrast="ratio"}. When using either \code{contrast="diff"} or \code{contrast="ratio"} in conunction with \code{conf_int=TRUE} (plus bootstrapping), the standard errors of the difference or ratio are estimated for each imputed dataset separately and pooled using Rubins Rule afterwards. The pooled standard errors are then used to perform the same calculations as described above. } \value{ @@ -80,7 +77,7 @@ Returns a \code{data.frame} containing the columns \code{p} (the quantiles from If \code{conf_int=TRUE} was used it also includes the confidence limits in the \code{ci_lower} and \code{ci_upper} columns. -If \code{difference} was used, the resulting \code{data.frame} instead contains the columns \code{p} (the quantiles from the original function all) and \code{diff} (the difference between the two survival time quantiles). If \code{conf_int=TRUE} was used it additionally contains the columns \code{se} (the standard deviation of the bootstrap estimates), \code{ci_lower} and \code{ci_upper} (the confidence interval) and \code{n_boot} (the number of bootstrap samples that could be used). The output is similar when using \code{ratio=TRUE}, except that the \code{diff} column is called \code{ratio}. +If \code{contrast="diff"} was used, the resulting \code{data.frame} instead contains the columns \code{p} (the quantiles from the original function all) and \code{diff} (the difference between the two survival time quantiles). If \code{conf_int=TRUE} was used it additionally contains the columns \code{se} (the standard deviation of the bootstrap estimates), \code{ci_lower} and \code{ci_upper} (the confidence interval) and \code{n_boot} (the number of bootstrap samples that could be used). The output is similar when using \code{contrast="ratio"}, except that the \code{diff} column is called \code{ratio}. } \references{ Omer Ben-Aharon, Racheli Magnezi, Moshe Leshno, and Daniel A. Goldstein (2019). "Median Survival or Mean Survival: Which Measure is the Most Appropriate for Patients, Physicians, and Policymakers?" In: The Oncologist 24, pp. 1469-1478 @@ -125,7 +122,7 @@ adjsurv <- adjustedsurv(data=sim_dat, adjusted_surv_quantile(adjsurv) # calculate difference of adjusted median survival times between groups -adjusted_surv_quantile(adjsurv, difference=TRUE) +adjusted_surv_quantile(adjsurv, contrast="diff") # calculate other quantiles + confidence intervals adjusted_surv_quantile(adjsurv, conf_int=TRUE, p=c(0.2, 0.4)) diff --git a/tests/testthat/test_MI_adjustedsurv.r b/tests/testthat/test_MI_adjustedsurv.r index 419231a..9dc78e6 100644 --- a/tests/testthat/test_MI_adjustedsurv.r +++ b/tests/testthat/test_MI_adjustedsurv.r @@ -439,17 +439,17 @@ test_that("adjusted_surv_quantile, 2 treatments, no boot", { }) test_that("adjusted_surv_quantile, 2 treatments, no boot, difference", { - adj_med <- adjusted_surv_quantile(adjsurv, difference=TRUE) + adj_med <- adjusted_surv_quantile(adjsurv, contrast="diff") expect_equal(round(adj_med$diff, 4), -0.1468) }) test_that("adjusted_surv_quantile, 2 treatments, no boot, difference, p", { - adj_med <- adjusted_surv_quantile(adjsurv, difference=TRUE, p=c(0.4, 0.5)) + adj_med <- adjusted_surv_quantile(adjsurv, contrast="diff", p=c(0.4, 0.5)) expect_equal(round(adj_med$diff, 4), c(-0.1350, -0.1468)) }) test_that("adjusted_surv_quantile, 2 treatments, conf_int, difference", { - adj_med <- adjusted_surv_quantile(adjsurv, difference=TRUE, conf_int=TRUE) + adj_med <- adjusted_surv_quantile(adjsurv, contrast="diff", conf_int=TRUE) expect_equal(round(adj_med$diff, 4), -0.1468) expect_equal(round(adj_med$se, 4), 0.1271) expect_equal(round(adj_med$ci_lower, 4), -0.3958) @@ -457,7 +457,7 @@ test_that("adjusted_surv_quantile, 2 treatments, conf_int, difference", { }) test_that("adjusted_surv_quantile, 2 treatments, no boot, ratio", { - adj_med <- adjusted_surv_quantile(adjsurv, ratio=TRUE) + adj_med <- adjusted_surv_quantile(adjsurv, contrast="ratio") expect_equal(round(adj_med$ratio, 4), 0.7653) }) diff --git a/tests/testthat/test_adjusted_surv_quantile.r b/tests/testthat/test_adjusted_surv_quantile.r index e556961..c41b3c7 100644 --- a/tests/testthat/test_adjusted_surv_quantile.r +++ b/tests/testthat/test_adjusted_surv_quantile.r @@ -22,18 +22,18 @@ test_that("q surv defaults", { }) test_that("q surv defaults, difference", { - adj_q <- adjusted_surv_quantile(adj, difference=TRUE) + adj_q <- adjusted_surv_quantile(adj, contrast="diff") expect_equal(round(adj_q$diff, 4), -0.1468) }) test_that("q surv defaults, difference, groups", { - adj_q <- adjusted_surv_quantile(adj, difference=TRUE, group_1="1", + adj_q <- adjusted_surv_quantile(adj, contrast="diff", group_1="1", group_2="0") expect_equal(round(adj_q$diff, 4), 0.1468) }) test_that("q surv defaults, ratio", { - adj_q <- adjusted_surv_quantile(adj, ratio=TRUE) + adj_q <- adjusted_surv_quantile(adj, contrast="ratio") expect_equal(round(adj_q$ratio, 4), 0.7653) }) @@ -52,7 +52,7 @@ test_that("q surv, conf_int, with boot", { }) test_that("q surv, conf_int, difference", { - adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, difference=TRUE) + adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, contrast="diff") expect_equal(round(adj_q$diff, 4), -0.1468) expect_equal(round(adj_q$se, 4), 0.1165) expect_equal(round(adj_q$ci_lower, 4), -0.3751) @@ -61,7 +61,7 @@ test_that("q surv, conf_int, difference", { }) test_that("q surv, conf_int, difference, group", { - adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, difference=TRUE, + adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, contrast="diff", group_1="1", group_2="0") expect_equal(round(adj_q$diff, 4), 0.1468) expect_equal(round(adj_q$ci_lower, 4), -0.0816) @@ -69,7 +69,7 @@ test_that("q surv, conf_int, difference, group", { }) test_that("q surv, conf_int, ratio", { - adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, ratio=TRUE) + adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, contrast="ratio") expect_equal(round(adj_q$ratio, 4), 0.7653) expect_equal(round(adj_q$ci_lower, 4), 0.4006) expect_equal(round(adj_q$ci_upper, 4), 1.1306) @@ -92,7 +92,7 @@ test_that("q surv, multiple p", { test_that("q surv, multiple p, difference", { adj_q <- adjusted_surv_quantile(adj, p=c(0.1, 0.2), conf_int=TRUE, - difference=TRUE) + contrast="diff") expect_equal(round(adj_q$diff, 4), c(-0.1801, -0.1714)) expect_equal(round(adj_q$ci_lower, 4), c(-0.1801, -0.6733)) expect_equal(round(adj_q$ci_upper, 4), c(-0.1801, 0.3306)) @@ -100,7 +100,7 @@ test_that("q surv, multiple p, difference", { test_that("q surv, multiple p, ratio", { adj_q <- adjusted_surv_quantile(adj, p=c(0.1, 0.2), conf_int=TRUE, - ratio=TRUE) + contrast="ratio") expect_equal(round(adj_q$ratio, 4), c(0.8536, 0.7929)) expect_equal(round(adj_q$ci_lower, 4), c(0.8536, 0.2761)) expect_equal(round(adj_q$ci_upper, 4), c(0.8536, 1.5041)) @@ -154,8 +154,8 @@ test_that("q surv, conf_int, with boot, conf_level", { test_that("q surv, conf_int, difference, conf_level", { adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, conf_level=0.9, - difference=TRUE) - adj_q0.9 <- adjusted_surv_quantile(adj0.9, conf_int=TRUE, difference=TRUE) + contrast="diff") + adj_q0.9 <- adjusted_surv_quantile(adj0.9, conf_int=TRUE, contrast="diff") expect_equal(adj_q$diff, adj_q0.9$diff) expect_equal(adj_q$ci_lower, adj_q0.9$ci_lower) expect_equal(adj_q$ci_upper, adj_q0.9$ci_upper) @@ -164,8 +164,8 @@ test_that("q surv, conf_int, difference, conf_level", { test_that("q surv, conf_int, ratio, conf_level", { adj_q <- adjusted_surv_quantile(adj, conf_int=TRUE, conf_level=0.9, - ratio=TRUE) - adj_q0.9 <- adjusted_surv_quantile(adj0.9, conf_int=TRUE, ratio=TRUE) + contrast="ratio") + adj_q0.9 <- adjusted_surv_quantile(adj0.9, conf_int=TRUE, contrast="ratio") expect_equal(adj_q$ratio, adj_q0.9$ratio) expect_equal(adj_q$ci_lower, adj_q0.9$ci_lower) expect_equal(adj_q$ci_upper, adj_q0.9$ci_upper) diff --git a/vignettes/comparing_groups.rmd b/vignettes/comparing_groups.rmd index e7aed7d..efe6656 100644 --- a/vignettes/comparing_groups.rmd +++ b/vignettes/comparing_groups.rmd @@ -153,13 +153,13 @@ plot(adjsurv, max_t=1) + This type of difference may also be estimated directly using the `adjusted_surv_quantile()` function. Please note that this is only possible when bootstrapping was performed in the original `adjustedsurv()` call, which is the case here. The syntax when using the difference is as follows: ```{r} -adjusted_surv_quantile(adjsurv, p=0.5, conf_int=TRUE, difference=TRUE) +adjusted_surv_quantile(adjsurv, p=0.5, conf_int=TRUE, contrast="diff") ``` The difference is rather small, with a confidence interval that contains 0 and a relatively large p-value, indicating that there is no difference between the curves, as expected (we simulated the data with no actual group effect). It can be interpreted as the horizontal difference between the two survival curves at $q$. Similar results can be obtained using the ratio of the two median survival times: ```{r} -adjusted_surv_quantile(adjsurv, p=0.5, conf_int=TRUE, ratio=TRUE) +adjusted_surv_quantile(adjsurv, p=0.5, conf_int=TRUE, contrast="ratio") ``` Again, since we are testing essentially the same hypothesis here, the p-values will always be identical. Choosing whether to use the ratio or the difference is mostly a matter of personal taste.