From ab52dc80241db672589c7841145c6b6b679ec4ca Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Thu, 25 Apr 2024 10:45:35 -0400 Subject: [PATCH 01/13] adding diff function --- NAMESPACE | 1 + R/ard_survival_survfit_diff.R | 115 +++++++++++++++++++++++++++++ R/construction_helpers.R | 5 ++ _pkgdown.yml | 1 + man/ard_emmeans_mean_difference.Rd | 7 +- man/ard_stats_anova.Rd | 7 +- man/ard_survival_survfit_diff.Rd | 28 +++++++ man/construction_helpers.Rd | 7 +- 8 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 R/ard_survival_survfit_diff.R create mode 100644 man/ard_survival_survfit_diff.Rd diff --git a/NAMESPACE b/NAMESPACE index d1e1abdba..c9b4e7ed9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -40,6 +40,7 @@ export(ard_survey_svyranktest) export(ard_survey_svyttest) export(ard_survival_survdiff) export(ard_survival_survfit) +export(ard_survival_survfit_diff) export(bt) export(bt_strip) export(construct_model) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R new file mode 100644 index 000000000..f18390a45 --- /dev/null +++ b/R/ard_survival_survfit_diff.R @@ -0,0 +1,115 @@ +#' ARD Survival Differences +#' +#' Calculate differences in the Kaplan-Meier estimator of survival using the +#' results from [`survival::survfit()`]. +#' +#' @param x (`survift`)\cr +#' object of class `'survfit'` typically created with [`survival::survfit()`] +#' @inheritParams ard_survival_survfit +#' +#' @return an ARD data frame of class 'card' +#' @export +#' +#' @examples +#' library(survival) +#' +#' survfit(Surv(time, status) ~ ph.ecog, data = lung) |> +#' ard_survival_survfit_diff(times = c(100, 200)) +ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { + set_cli_abort_call() + + # check installed packages --------------------------------------------------- + check_pkg_installed(c("survival", "broom"), reference_pkg = "cardx") + check_not_missing(x) + check_not_missing(times) + check_class(x, "survfit") + if (inherits(x, "survfitms")) { + cli::cli_abort( + "Argument {.arg x} cannot be class {.cls survfitms}.", + call = get_cli_abort_call() + ) + } + check_range(conf.level, range = c(0, 1)) + check_length( + as.list(sf$call)[["formula"]] |> as.formula() |> stats::terms() |> attr("term.labels"), + length = 1L, + message = "The {.cls survift} object passed in argument {.arg x} must be stratified by a single variable." + ) + if (length(x$strata) < 2) { + cli::cli_abort( + "The {.cls survift} object passed in argument {.arg x} must have more than 1 stratifying level.", + call = get_cli_abort_call() + ) + } + + # calculate survival at the specified times + summary(x, times = times) |> + tidy_summary.survfit() |> + dplyr::select(any_of(c("strata", "time", "estimate", "std.error"))) %>% + {dplyr::left_join( + dplyr::filter(., .data$strata != .data$strata[1]) |> dplyr::mutate(reference = .$strata[1]), + dplyr::filter(., .data$strata == .data$strata[1]) |> + dplyr::select(-"strata") |> + dplyr::rename_with(.fn = ~paste0(., "0"), .cols = -"time"), + by = "time" + )} |> + dplyr::mutate( + difference = .data$estimate0 - .data$estimate, + difference.std.error = sqrt(.data$std.error0^2 + .data$std.error^2), + statistic = difference / difference.std.error, + conf.low = difference - difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + conf.high = difference + difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + p.value = 2 * (1 - stats::pnorm(abs(statistic))) + ) |> + dplyr::select( + "strata", "reference", "time", + estimate = "difference", + std.error = "difference.std.error", + "statistic", "conf.low", "conf.high", "p.value" + ) |> + tidyr::separate_wider_delim("strata", "=", names = c("group1", "group1_level")) |> + dplyr::mutate( + across(-cards::all_ard_groups("names"), as.list) + ) |> + tidyr::pivot_longer( + cols = -c(cards::all_ard_groups(), "time"), + names_to = "stat_name", + values_to = "stat" + ) |> + dplyr::rename(variable_level = "time") |> + dplyr::mutate( + variable = "time", + error = list(NULL), + warning = list(NULL), + fmt_fn = list(1L), + stat_label = + dplyr::case_when( + .data$stat_name %in% "reference" ~ "Reference Group (ref - est)", + .data$stat_name %in% "estimate" ~ "Survival Difference", + .data$stat_name %in% "std.error" ~ "Survival Difference Standard Error", + .data$stat_name %in% "conf.low" ~ "CI Lower Bound", + .data$stat_name %in% "conf.high" ~ "CI Upper Bound", + .data$stat_name %in% "p.value" ~ "p-value", + .default = .data$stat_name + ), + context = "survival_survfit_diff", + ) |> + cards::tidy_ard_column_order() %>% + structure(., class = c("card", class(.))) +} + + +tidy_summary.survfit <- function(x) { + dplyr::tibble( + strata = x$strata, + time = x$time, + n.risk = x$n.risk, + n.event = x$n.event, + n.censor = x$n.censor, + estimate = x$surv, + std.error = x$std.err, + conf.low = x$lower, + conf.high = x$upper + ) +} + diff --git a/R/construction_helpers.R b/R/construction_helpers.R index ae5873532..74e56c136 100644 --- a/R/construction_helpers.R +++ b/R/construction_helpers.R @@ -25,6 +25,11 @@ #' must be specified in the `package` argument. #' @param method.args (named `list`)\cr #' named list of arguments that will be passed to `fn`. +#' +#' Note that this list may contain non-standard evaluation components. +#' If you are wrapping this function in other functions, the argument +#' must be passed in a way that does not evaluate the list, e.g. +#' using rlang's embrace opoerator `{{ . }}`. #' @param package (`string`)\cr #' string of package name that will be temporarily loaded when function #' specified in `method` is executed. diff --git a/_pkgdown.yml b/_pkgdown.yml index e22a3e83d..9f9a16e28 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -41,6 +41,7 @@ reference: - ard_survey_svyranktest - ard_survey_svyttest - ard_survival_survdiff + - ard_survival_survfit_diff - subtitle: "Estimation" - contents: diff --git a/man/ard_emmeans_mean_difference.Rd b/man/ard_emmeans_mean_difference.Rd index 97466d7d6..a29b62da2 100644 --- a/man/ard_emmeans_mean_difference.Rd +++ b/man/ard_emmeans_mean_difference.Rd @@ -28,7 +28,12 @@ If function belongs to a library that is not attached, the package name must be specified in the \code{package} argument.} \item{method.args}{(named \code{list})\cr -named list of arguments that will be passed to \code{fn}.} +named list of arguments that will be passed to \code{fn}. + +Note that this list may contain non-standard evaluation components. +If you are wrapping this function in other functions, the argument +must be passed in a way that does not evaluate the list, e.g. +using rlang's embrace opoerator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function diff --git a/man/ard_stats_anova.Rd b/man/ard_stats_anova.Rd index c2ec37b56..380e38dec 100644 --- a/man/ard_stats_anova.Rd +++ b/man/ard_stats_anova.Rd @@ -42,7 +42,12 @@ If function belongs to a library that is not attached, the package name must be specified in the \code{package} argument.} \item{method.args}{(named \code{list})\cr -named list of arguments that will be passed to \code{fn}.} +named list of arguments that will be passed to \code{fn}. + +Note that this list may contain non-standard evaluation components. +If you are wrapping this function in other functions, the argument +must be passed in a way that does not evaluate the list, e.g. +using rlang's embrace opoerator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function diff --git a/man/ard_survival_survfit_diff.Rd b/man/ard_survival_survfit_diff.Rd new file mode 100644 index 000000000..dcc6b1b58 --- /dev/null +++ b/man/ard_survival_survfit_diff.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ard_survival_survfit_diff.R +\name{ard_survival_survfit_diff} +\alias{ard_survival_survfit_diff} +\title{ARD Survival Differences} +\usage{ +ard_survival_survfit_diff(x, times, conf.level = 0.95) +} +\arguments{ +\item{x}{(\code{survift})\cr +object of class \code{'survfit'} typically created with \code{\link[survival:survfit]{survival::survfit()}}} + +\item{times}{(\code{numeric})\cr +a vector of times for which to return survival probabilities.} +} +\value{ +an ARD data frame of class 'card' +} +\description{ +Calculate differences in the Kaplan-Meier estimator of survival using the +results from \code{\link[survival:survfit]{survival::survfit()}}. +} +\examples{ +library(survival) + +survfit(Surv(time, status) ~ ph.ecog, data = lung) |> + ard_survival_survfit_diff(times = c(100, 200)) +} diff --git a/man/construction_helpers.Rd b/man/construction_helpers.Rd index 82d5439a3..b48e22176 100644 --- a/man/construction_helpers.Rd +++ b/man/construction_helpers.Rd @@ -63,7 +63,12 @@ If function belongs to a library that is not attached, the package name must be specified in the \code{package} argument.} \item{method.args}{(named \code{list})\cr -named list of arguments that will be passed to \code{fn}.} +named list of arguments that will be passed to \code{fn}. + +Note that this list may contain non-standard evaluation components. +If you are wrapping this function in other functions, the argument +must be passed in a way that does not evaluate the list, e.g. +using rlang's embrace opoerator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function From 9a3ce4b31fe6faa34e98cec8d0e724b051dc196b Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Thu, 25 Apr 2024 10:55:37 -0400 Subject: [PATCH 02/13] updates --- R/ard_survival_survfit_diff.R | 29 ++++++++++++++++------------- R/construction_helpers.R | 2 +- inst/WORDLIST | 1 + man/ard_emmeans_mean_difference.Rd | 2 +- man/ard_stats_anova.Rd | 2 +- man/ard_survival_survfit_diff.Rd | 3 +++ man/construction_helpers.Rd | 2 +- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index f18390a45..843b9f344 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -5,6 +5,8 @@ #' #' @param x (`survift`)\cr #' object of class `'survfit'` typically created with [`survival::survfit()`] +#' @param conf.level (scalar `numeric`)\cr +#' confidence level for confidence interval. Default is `0.95`. #' @inheritParams ard_survival_survfit #' #' @return an ARD data frame of class 'card' @@ -31,7 +33,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { } check_range(conf.level, range = c(0, 1)) check_length( - as.list(sf$call)[["formula"]] |> as.formula() |> stats::terms() |> attr("term.labels"), + as.list(x$call)[["formula"]] |> stats::as.formula() |> stats::terms() |> attr("term.labels"), length = 1L, message = "The {.cls survift} object passed in argument {.arg x} must be stratified by a single variable." ) @@ -46,20 +48,22 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { summary(x, times = times) |> tidy_summary.survfit() |> dplyr::select(any_of(c("strata", "time", "estimate", "std.error"))) %>% - {dplyr::left_join( - dplyr::filter(., .data$strata != .data$strata[1]) |> dplyr::mutate(reference = .$strata[1]), - dplyr::filter(., .data$strata == .data$strata[1]) |> - dplyr::select(-"strata") |> - dplyr::rename_with(.fn = ~paste0(., "0"), .cols = -"time"), - by = "time" - )} |> + { + dplyr::left_join( + dplyr::filter(., .data$strata != .data$strata[1]) |> dplyr::mutate(reference = .$strata[1]), + dplyr::filter(., .data$strata == .data$strata[1]) |> + dplyr::select(-"strata") |> + dplyr::rename_with(.fn = ~ paste0(., "0"), .cols = -"time"), + by = "time" + ) + } |> dplyr::mutate( difference = .data$estimate0 - .data$estimate, difference.std.error = sqrt(.data$std.error0^2 + .data$std.error^2), - statistic = difference / difference.std.error, - conf.low = difference - difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), - conf.high = difference + difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), - p.value = 2 * (1 - stats::pnorm(abs(statistic))) + statistic = .data$difference / .data$difference.std.error, + conf.low = .data$difference - .data$difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + conf.high = .data$difference + .data$difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + p.value = 2 * (1 - stats::pnorm(abs(.data$statistic))) ) |> dplyr::select( "strata", "reference", "time", @@ -112,4 +116,3 @@ tidy_summary.survfit <- function(x) { conf.high = x$upper ) } - diff --git a/R/construction_helpers.R b/R/construction_helpers.R index 74e56c136..662dceab2 100644 --- a/R/construction_helpers.R +++ b/R/construction_helpers.R @@ -29,7 +29,7 @@ #' Note that this list may contain non-standard evaluation components. #' If you are wrapping this function in other functions, the argument #' must be passed in a way that does not evaluate the list, e.g. -#' using rlang's embrace opoerator `{{ . }}`. +#' using rlang's embrace operator `{{ . }}`. #' @param package (`string`)\cr #' string of package name that will be temporarily loaded when function #' specified in `method` is executed. diff --git a/inst/WORDLIST b/inst/WORDLIST index 073fb6ff4..c4c2c8363 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -30,6 +30,7 @@ jeffreys pearson pre quosures +rlang's sd strat vif diff --git a/man/ard_emmeans_mean_difference.Rd b/man/ard_emmeans_mean_difference.Rd index a29b62da2..f34b88338 100644 --- a/man/ard_emmeans_mean_difference.Rd +++ b/man/ard_emmeans_mean_difference.Rd @@ -33,7 +33,7 @@ named list of arguments that will be passed to \code{fn}. Note that this list may contain non-standard evaluation components. If you are wrapping this function in other functions, the argument must be passed in a way that does not evaluate the list, e.g. -using rlang's embrace opoerator \code{{{ . }}}.} +using rlang's embrace operator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function diff --git a/man/ard_stats_anova.Rd b/man/ard_stats_anova.Rd index 380e38dec..4c029a93e 100644 --- a/man/ard_stats_anova.Rd +++ b/man/ard_stats_anova.Rd @@ -47,7 +47,7 @@ named list of arguments that will be passed to \code{fn}. Note that this list may contain non-standard evaluation components. If you are wrapping this function in other functions, the argument must be passed in a way that does not evaluate the list, e.g. -using rlang's embrace opoerator \code{{{ . }}}.} +using rlang's embrace operator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function diff --git a/man/ard_survival_survfit_diff.Rd b/man/ard_survival_survfit_diff.Rd index dcc6b1b58..15e1856f5 100644 --- a/man/ard_survival_survfit_diff.Rd +++ b/man/ard_survival_survfit_diff.Rd @@ -12,6 +12,9 @@ object of class \code{'survfit'} typically created with \code{\link[survival:sur \item{times}{(\code{numeric})\cr a vector of times for which to return survival probabilities.} + +\item{conf.level}{(scalar \code{numeric})\cr +confidence level for confidence interval. Default is \code{0.95}.} } \value{ an ARD data frame of class 'card' diff --git a/man/construction_helpers.Rd b/man/construction_helpers.Rd index b48e22176..e10d9603a 100644 --- a/man/construction_helpers.Rd +++ b/man/construction_helpers.Rd @@ -68,7 +68,7 @@ named list of arguments that will be passed to \code{fn}. Note that this list may contain non-standard evaluation components. If you are wrapping this function in other functions, the argument must be passed in a way that does not evaluate the list, e.g. -using rlang's embrace opoerator \code{{{ . }}}.} +using rlang's embrace operator \code{{{ . }}}.} \item{package}{(\code{string})\cr string of package name that will be temporarily loaded when function From f16c3d5c7ece85c297a5b4f9df6f7181c7f89ea6 Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 26 Apr 2024 15:02:32 -0400 Subject: [PATCH 03/13] updates --- R/ard_survival_survfit.R | 5 ++++- R/ard_survival_survfit_diff.R | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/R/ard_survival_survfit.R b/R/ard_survival_survfit.R index 9a30adf5f..571b14f43 100644 --- a/R/ard_survival_survfit.R +++ b/R/ard_survival_survfit.R @@ -142,7 +142,10 @@ ard_survival_survfit <- function(x, times = NULL, probs = NULL, type = NULL) { dplyr::vars("conf.high", "conf.low"), ~ ifelse(is.na(.) & .data$std.error == 0, .data$estimate, .) ) %>% - dplyr::select(dplyr::any_of(c("time", "estimate", "conf.high", "conf.low", "strata"))) %>% + dplyr::select(dplyr::any_of(c( + "time", "estimate", "std.error", "conf.high", "conf.low", + "strata", "n.risk" + ))) %>% # add data for time 0 dplyr::bind_rows( dplyr::group_by_at(., dplyr::vars(dplyr::any_of("strata"))) %>% diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 843b9f344..3f45256c0 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -48,15 +48,16 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { summary(x, times = times) |> tidy_summary.survfit() |> dplyr::select(any_of(c("strata", "time", "estimate", "std.error"))) %>% - { - dplyr::left_join( - dplyr::filter(., .data$strata != .data$strata[1]) |> dplyr::mutate(reference = .$strata[1]), - dplyr::filter(., .data$strata == .data$strata[1]) |> - dplyr::select(-"strata") |> - dplyr::rename_with(.fn = ~ paste0(., "0"), .cols = -"time"), - by = "time" - ) - } |> + # styler: off + {dplyr::left_join( + dplyr::filter(., .data$strata != .data$strata[1]) |> + dplyr::mutate(contrast = paste(.$strata[1], "-", .data$strata)), + dplyr::filter(., .data$strata == .data$strata[1]) |> + dplyr::select(-"strata") |> + dplyr::rename_with(.fn = ~ paste0(., "0"), .cols = -"time"), + by = "time" + )} |> + # styler: on dplyr::mutate( difference = .data$estimate0 - .data$estimate, difference.std.error = sqrt(.data$std.error0^2 + .data$std.error^2), @@ -66,12 +67,13 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { p.value = 2 * (1 - stats::pnorm(abs(.data$statistic))) ) |> dplyr::select( - "strata", "reference", "time", + "strata", "contrast", "time", estimate = "difference", std.error = "difference.std.error", "statistic", "conf.low", "conf.high", "p.value" ) |> - tidyr::separate_wider_delim("strata", "=", names = c("group1", "group1_level")) |> + extract_multi_strata(x = x, df_stat = _) + tidyr::separate_wider_delim("strata", "=", names = c("group1", "group1_level")) |> dplyr::mutate( across(-cards::all_ard_groups("names"), as.list) ) |> @@ -88,7 +90,6 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { fmt_fn = list(1L), stat_label = dplyr::case_when( - .data$stat_name %in% "reference" ~ "Reference Group (ref - est)", .data$stat_name %in% "estimate" ~ "Survival Difference", .data$stat_name %in% "std.error" ~ "Survival Difference Standard Error", .data$stat_name %in% "conf.low" ~ "CI Lower Bound", From 38cc3d924244b1411de82b0e077c15d644b6daba Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Tue, 30 Apr 2024 15:18:02 -0700 Subject: [PATCH 04/13] Update _pkgdown.yml --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index a1acfa498..336176a8a 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -68,6 +68,7 @@ reference: - subtitle: "{survival} package" - contents: - ard_survival_survfit + - ard_survival_survfit_diff - ard_survival_survdiff - subtitle: "Other ARD functions" From 2d3a972558d336354e7b76defdb34dc536c69d11 Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Thu, 23 May 2024 21:31:08 -0700 Subject: [PATCH 05/13] progress --- R/ard_stats_t_test_onesample.R | 2 +- R/ard_stats_wilcox_test_onesample.R | 2 +- R/ard_survival_survfit_diff.R | 98 ++++++++++++++--------------- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/R/ard_stats_t_test_onesample.R b/R/ard_stats_t_test_onesample.R index 9eb8a79c0..1a419b21a 100644 --- a/R/ard_stats_t_test_onesample.R +++ b/R/ard_stats_t_test_onesample.R @@ -64,7 +64,7 @@ ard_stats_t_test_onesample <- function(data, variables, by = dplyr::group_vars(d ) |> dplyr::mutate( stat_label = dplyr::coalesce(.data$stat_label, .data$stat_name), - context = "ard_stats_t_test_onesample", + context = "stats_t_test_onesample", ) |> cards::tidy_ard_row_order() |> cards::tidy_ard_column_order() diff --git a/R/ard_stats_wilcox_test_onesample.R b/R/ard_stats_wilcox_test_onesample.R index 7741c2cab..88885fd0e 100644 --- a/R/ard_stats_wilcox_test_onesample.R +++ b/R/ard_stats_wilcox_test_onesample.R @@ -65,7 +65,7 @@ ard_stats_wilcox_test_onesample <- function(data, variables, by = dplyr::group_v ) |> dplyr::mutate( stat_label = dplyr::coalesce(.data$stat_label, .data$stat_name), - context = "ard_stats_wilcox_test_onesample", + context = "stats_wilcox_test_onesample", ) |> cards::tidy_ard_row_order() |> cards::tidy_ard_column_order() diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 3f45256c0..4d6b2e93c 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -31,7 +31,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { call = get_cli_abort_call() ) } - check_range(conf.level, range = c(0, 1)) + check_scalar_range(conf.level, range = c(0, 1)) check_length( as.list(x$call)[["formula"]] |> stats::as.formula() |> stats::terms() |> attr("term.labels"), length = 1L, @@ -44,49 +44,61 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { ) } - # calculate survival at the specified times - summary(x, times = times) |> - tidy_summary.survfit() |> - dplyr::select(any_of(c("strata", "time", "estimate", "std.error"))) %>% - # styler: off + # calculate the survival at the specified times + ard_survival_survfit <- + ard_survival_survfit(x = x, times = times) |> + dplyr::filter(.data$stat_name %in% c("estimate", "std.error")) |> + dplyr::select(-c("stat_label", "context", "fmt_fn")) + + # transform the survival ARD into a cards object with the survival difference + card <- + ard_survival_survfit %>% {dplyr::left_join( - dplyr::filter(., .data$strata != .data$strata[1]) |> - dplyr::mutate(contrast = paste(.$strata[1], "-", .data$strata)), - dplyr::filter(., .data$strata == .data$strata[1]) |> - dplyr::select(-"strata") |> - dplyr::rename_with(.fn = ~ paste0(., "0"), .cols = -"time"), - by = "time" + # remove the first group from the data frame (this is our reference group) + dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() > 1L) |> + dplyr::rename(stat1 = "stat"), + # merge the reference group data + dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() == 1L) |> + dplyr::select(stat0 = "stat", everything(), -c("group1_level", "error", "warning")), + by = c("group1", "variable", "variable_level", "stat_name") )} |> - # styler: on - dplyr::mutate( - difference = .data$estimate0 - .data$estimate, - difference.std.error = sqrt(.data$std.error0^2 + .data$std.error^2), - statistic = .data$difference / .data$difference.std.error, - conf.low = .data$difference - .data$difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), - conf.high = .data$difference + .data$difference.std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), - p.value = 2 * (1 - stats::pnorm(abs(.data$statistic))) - ) |> - dplyr::select( - "strata", "contrast", "time", - estimate = "difference", - std.error = "difference.std.error", - "statistic", "conf.low", "conf.high", "p.value" + # reshape to put the stats that need to be combined on the same row + tidyr::pivot_wider( + id_cols = c("group1", "group1_level", "variable", "variable_level"), + names_from = "stat_name", + values_from = c("stat0", "stat1"), + values_fn = unlist ) |> - extract_multi_strata(x = x, df_stat = _) - tidyr::separate_wider_delim("strata", "=", names = c("group1", "group1_level")) |> + # calcualte the primary statistics to return dplyr::mutate( - across(-cards::all_ard_groups("names"), as.list) + # reference level + reference_level = ard_survival_survfit[["group1_level"]][1], + # survival difference + estimate = .data$stat0_estimate - .data$stat1_estimate, + # survival difference standard error + std.error = sqrt(.data$stat0_std.error^2 + .data$stat1_std.error^2), + # Z test statistic + statistic = .data$estimate / .data$std.error, + # confidence limits of the survival difference + conf.low = .data$estimate - .data$std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + conf.high = .data$estimate + .data$std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), + # p-value for test where H0: no difference + p.value = 2 * (1 - stats::pnorm(abs(.data$statistic))), + across(c("reference_level", "estimate", "std.error", "statistic", "conf.low", "conf.high", "p.value"), as.list) ) |> + # reshape into the cards structure + dplyr::select(-starts_with("stat0_"), -starts_with("stat1_")) |> tidyr::pivot_longer( - cols = -c(cards::all_ard_groups(), "time"), + cols = -c(cards::all_ard_groups(), cards::all_ard_variables()), names_to = "stat_name", values_to = "stat" - ) |> - dplyr::rename(variable_level = "time") |> + ) + + # final prepping of the cards object ----------------------------------------- + card |> dplyr::mutate( - variable = "time", - error = list(NULL), - warning = list(NULL), + warning = ard_survival_survfit[["warning"]][1], + error = ard_survival_survfit[["error"]][1], fmt_fn = list(1L), stat_label = dplyr::case_when( @@ -94,6 +106,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { .data$stat_name %in% "std.error" ~ "Survival Difference Standard Error", .data$stat_name %in% "conf.low" ~ "CI Lower Bound", .data$stat_name %in% "conf.high" ~ "CI Upper Bound", + .data$stat_name %in% "statistic" ~ "z statistic", .data$stat_name %in% "p.value" ~ "p-value", .default = .data$stat_name ), @@ -102,18 +115,3 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { cards::tidy_ard_column_order() %>% structure(., class = c("card", class(.))) } - - -tidy_summary.survfit <- function(x) { - dplyr::tibble( - strata = x$strata, - time = x$time, - n.risk = x$n.risk, - n.event = x$n.event, - n.censor = x$n.censor, - estimate = x$surv, - std.error = x$std.err, - conf.low = x$lower, - conf.high = x$upper - ) -} From 01bb016ebe54e3a1cf5c676267379ae75b01d563 Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Thu, 23 May 2024 21:32:36 -0700 Subject: [PATCH 06/13] Update ard_survival_survfit_diff.R --- R/ard_survival_survfit_diff.R | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 4d6b2e93c..d52ca7aa9 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -53,15 +53,15 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { # transform the survival ARD into a cards object with the survival difference card <- ard_survival_survfit %>% - {dplyr::left_join( - # remove the first group from the data frame (this is our reference group) - dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() > 1L) |> - dplyr::rename(stat1 = "stat"), - # merge the reference group data - dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() == 1L) |> - dplyr::select(stat0 = "stat", everything(), -c("group1_level", "error", "warning")), - by = c("group1", "variable", "variable_level", "stat_name") - )} |> + {dplyr::left_join( # styler: off + # remove the first group from the data frame (this is our reference group) + dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() > 1L) |> + dplyr::rename(stat1 = "stat"), + # merge the reference group data + dplyr::filter(., .by = cards::all_ard_groups(), dplyr::cur_group_id() == 1L) |> + dplyr::select(stat0 = "stat", everything(), -c("group1_level", "error", "warning")), + by = c("group1", "variable", "variable_level", "stat_name") + )} |> # styler: off # reshape to put the stats that need to be combined on the same row tidyr::pivot_wider( id_cols = c("group1", "group1_level", "variable", "variable_level"), From a64f53ab030f506cc387569305c95cdaaa8cf2fd Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 06:54:09 -0700 Subject: [PATCH 07/13] progress --- R/ard_survival_survfit_diff.R | 7 ++- .../_snaps/ard_survival_survfit_diff.md | 27 +++++++++ .../testthat/test-ard_survival_survfit_diff.R | 56 +++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/testthat/_snaps/ard_survival_survfit_diff.md create mode 100644 tests/testthat/test-ard_survival_survfit_diff.R diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index d52ca7aa9..7617c1e52 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -25,9 +25,10 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { check_not_missing(x) check_not_missing(times) check_class(x, "survfit") - if (inherits(x, "survfitms")) { + + if (inherits(x, c("survfitms", "survfitcox"))) { cli::cli_abort( - "Argument {.arg x} cannot be class {.cls survfitms}.", + "Argument {.arg x} cannot be class {.cls {c('survfitms', 'survfitcox')}}.", call = get_cli_abort_call() ) } @@ -39,7 +40,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { ) if (length(x$strata) < 2) { cli::cli_abort( - "The {.cls survift} object passed in argument {.arg x} must have more than 1 stratifying level.", + "The {.cls survift} object's stratifying variable must have 2 or more levels.", call = get_cli_abort_call() ) } diff --git a/tests/testthat/_snaps/ard_survival_survfit_diff.md b/tests/testthat/_snaps/ard_survival_survfit_diff.md new file mode 100644 index 000000000..4ac19b2f4 --- /dev/null +++ b/tests/testthat/_snaps/ard_survival_survfit_diff.md @@ -0,0 +1,27 @@ +# ard_survival_survfit_diff() messaging + + Code + ard_survival_survfit_diff(survfit(Surv(AVAL, 1 - CNSR) ~ SEX + TRTA, cards::ADTTE), + times = c(25, 50)) + Condition + Error in `ard_survival_survfit_diff()`: + ! The object passed in argument `x` must be stratified by a single variable. + +--- + + Code + ard_survival_survfit_diff(survfit(Surv(AVAL, 1 - CNSR) ~ constant, dplyr::mutate( + cards::ADTTE, constant = 1L)), times = c(25, 50)) + Condition + Error in `ard_survival_survfit_diff()`: + ! The object's stratifying variable must have 2 or more levels. + +--- + + Code + ard_survival_survfit_diff(survfit(coxph(Surv(AVAL, CNSR) ~ SEX + strata(TRTA), + cards::ADTTE)), times = c(25, 50)) + Condition + Error in `ard_survival_survfit_diff()`: + ! Argument `x` cannot be class . + diff --git a/tests/testthat/test-ard_survival_survfit_diff.R b/tests/testthat/test-ard_survival_survfit_diff.R new file mode 100644 index 000000000..b4977a7f1 --- /dev/null +++ b/tests/testthat/test-ard_survival_survfit_diff.R @@ -0,0 +1,56 @@ +test_that("ard_survival_survfit_diff() works", { + withr::local_package("survival") + sf <- survfit(Surv(AVAL, 1- CNSR) ~ SEX, cards::ADTTE) + expect_silent( + ard1 <- ard_survival_survfit_diff(sf, times = c(25, 50)) + ) + + # check the survival differences are accurate + expect_equal( + ard1 |> + dplyr::filter(variable_level == 25, stat_name == "estimate") |> + dplyr::pull(stat) |> + unlist(), + summary(sf, times = 25) |> + getElement("surv") |> + reduce(`-`) + ) + expect_equal( + ard1 |> + dplyr::filter(variable_level == 50, stat_name == "estimate") |> + dplyr::pull(stat) |> + unlist(), + summary(sf, times = 50) |> + getElement("surv") |> + reduce(`-`) + ) +}) + +test_that("ard_survival_survfit_diff() messaging", { + withr::local_package("survival") + + # we can only do one stratifying variable at a time + expect_snapshot( + error = TRUE, + survfit(Surv(AVAL, 1- CNSR) ~ SEX + TRTA, cards::ADTTE) |> + ard_survival_survfit_diff(times = c(25, 50)) + ) + + # the stratifying variable must have 2 or more levels + expect_snapshot( + error = TRUE, + survfit( + Surv(AVAL, 1- CNSR) ~ constant, + cards::ADTTE |> dplyr::mutate(constant = 1L) + ) |> + ard_survival_survfit_diff(times = c(25, 50)) + ) + + # cannot pass a multi-state model or stratified Cox + expect_snapshot( + error = TRUE, + coxph(Surv(AVAL, CNSR) ~ SEX + strata(TRTA), cards::ADTTE) |> + survfit() |> + ard_survival_survfit_diff(times = c(25, 50)) + ) +}) From 9afaea80f063c39459dafb7f8a31da511374698a Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 06:59:04 -0700 Subject: [PATCH 08/13] progress --- R/ard_survival_survfit_diff.R | 4 +++- tests/testthat/test-ard_survival_survfit_diff.R | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 7617c1e52..d63342a07 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -74,6 +74,8 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { dplyr::mutate( # reference level reference_level = ard_survival_survfit[["group1_level"]][1], + # short description of method + method = "Survival Difference (Z-test)", # survival difference estimate = .data$stat0_estimate - .data$stat1_estimate, # survival difference standard error @@ -85,7 +87,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { conf.high = .data$estimate + .data$std.error * stats::qnorm(1 - (1 - .env$conf.level) / 2), # p-value for test where H0: no difference p.value = 2 * (1 - stats::pnorm(abs(.data$statistic))), - across(c("reference_level", "estimate", "std.error", "statistic", "conf.low", "conf.high", "p.value"), as.list) + across(c("reference_level", "estimate", "std.error", "statistic", "conf.low", "conf.high", "p.value", "method"), as.list) ) |> # reshape into the cards structure dplyr::select(-starts_with("stat0_"), -starts_with("stat1_")) |> diff --git a/tests/testthat/test-ard_survival_survfit_diff.R b/tests/testthat/test-ard_survival_survfit_diff.R index b4977a7f1..990621dd0 100644 --- a/tests/testthat/test-ard_survival_survfit_diff.R +++ b/tests/testthat/test-ard_survival_survfit_diff.R @@ -24,6 +24,11 @@ test_that("ard_survival_survfit_diff() works", { getElement("surv") |> reduce(`-`) ) + + # check the structure of the ARD object + expect_silent( + cards::check_ard_structure(ard1) + ) }) test_that("ard_survival_survfit_diff() messaging", { From 181123ea3a3e297fea1ea0d41045ca37117c114a Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 07:19:50 -0700 Subject: [PATCH 09/13] Update test-ard_survival_survfit_diff.R --- tests/testthat/test-ard_survival_survfit_diff.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-ard_survival_survfit_diff.R b/tests/testthat/test-ard_survival_survfit_diff.R index 990621dd0..231150ac9 100644 --- a/tests/testthat/test-ard_survival_survfit_diff.R +++ b/tests/testthat/test-ard_survival_survfit_diff.R @@ -1,6 +1,6 @@ test_that("ard_survival_survfit_diff() works", { withr::local_package("survival") - sf <- survfit(Surv(AVAL, 1- CNSR) ~ SEX, cards::ADTTE) + sf <- survfit(Surv(AVAL, 1 - CNSR) ~ SEX, cards::ADTTE) expect_silent( ard1 <- ard_survival_survfit_diff(sf, times = c(25, 50)) ) @@ -37,7 +37,7 @@ test_that("ard_survival_survfit_diff() messaging", { # we can only do one stratifying variable at a time expect_snapshot( error = TRUE, - survfit(Surv(AVAL, 1- CNSR) ~ SEX + TRTA, cards::ADTTE) |> + survfit(Surv(AVAL, 1 - CNSR) ~ SEX + TRTA, cards::ADTTE) |> ard_survival_survfit_diff(times = c(25, 50)) ) @@ -45,7 +45,7 @@ test_that("ard_survival_survfit_diff() messaging", { expect_snapshot( error = TRUE, survfit( - Surv(AVAL, 1- CNSR) ~ constant, + Surv(AVAL, 1 - CNSR) ~ constant, cards::ADTTE |> dplyr::mutate(constant = 1L) ) |> ard_survival_survfit_diff(times = c(25, 50)) From bfbde9aaf89fa3c998b92989baa554c49110e15e Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 07:41:17 -0700 Subject: [PATCH 10/13] progress --- R/ard_survival_survfit_diff.R | 7 ++++--- man/ard_survival_survfit_diff.Rd | 7 +++++-- tests/testthat/test-ard_survival_survfit_diff.R | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index d63342a07..656d1c150 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -12,11 +12,12 @@ #' @return an ARD data frame of class 'card' #' @export #' -#' @examples +#' @examplesIf do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = c("survival", "ggsurvfit"), reference_pkg = "cardx")) +#' library(ggsurvfit) #' library(survival) #' -#' survfit(Surv(time, status) ~ ph.ecog, data = lung) |> -#' ard_survival_survfit_diff(times = c(100, 200)) +#' survfit(Surv_CNSR() ~ TRTA, data = cards::ADTTE) |> +#' ard_survival_survfit_diff(times = c(25, 50)) ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { set_cli_abort_call() diff --git a/man/ard_survival_survfit_diff.Rd b/man/ard_survival_survfit_diff.Rd index 15e1856f5..3a7a7bd7d 100644 --- a/man/ard_survival_survfit_diff.Rd +++ b/man/ard_survival_survfit_diff.Rd @@ -24,8 +24,11 @@ Calculate differences in the Kaplan-Meier estimator of survival using the results from \code{\link[survival:survfit]{survival::survfit()}}. } \examples{ +\dontshow{if (do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = c("survival", "ggsurvfit"), reference_pkg = "cardx"))) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +library(ggsurvfit) library(survival) -survfit(Surv(time, status) ~ ph.ecog, data = lung) |> - ard_survival_survfit_diff(times = c(100, 200)) +survfit(Surv_CNSR() ~ TRTA, data = cards::ADTTE) |> + ard_survival_survfit_diff(times = c(25, 50)) +\dontshow{\}) # examplesIf} } diff --git a/tests/testthat/test-ard_survival_survfit_diff.R b/tests/testthat/test-ard_survival_survfit_diff.R index 231150ac9..e1f00dd9a 100644 --- a/tests/testthat/test-ard_survival_survfit_diff.R +++ b/tests/testthat/test-ard_survival_survfit_diff.R @@ -1,3 +1,5 @@ +skip_if_not(is_pkg_installed(c("survival", "broom"), reference_pkg = "cardx")) + test_that("ard_survival_survfit_diff() works", { withr::local_package("survival") sf <- survfit(Surv(AVAL, 1 - CNSR) ~ SEX, cards::ADTTE) From cebbdf9fcf391c47f6e29d64a8954c63f123bea2 Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 15:45:41 -0700 Subject: [PATCH 11/13] Update R/ard_survival_survfit_diff.R Co-authored-by: Emily de la Rua <59304861+edelarua@users.noreply.github.com> Signed-off-by: Daniel Sjoberg --- R/ard_survival_survfit_diff.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 656d1c150..039d4731d 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -37,7 +37,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { check_length( as.list(x$call)[["formula"]] |> stats::as.formula() |> stats::terms() |> attr("term.labels"), length = 1L, - message = "The {.cls survift} object passed in argument {.arg x} must be stratified by a single variable." + message = "The {.cls survfit} object passed in argument {.arg x} must be stratified by a single variable." ) if (length(x$strata) < 2) { cli::cli_abort( From f5ce75a1a2e0d52804d65231b1b90f586c671b6e Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 15:45:49 -0700 Subject: [PATCH 12/13] Update R/ard_survival_survfit_diff.R Co-authored-by: Emily de la Rua <59304861+edelarua@users.noreply.github.com> Signed-off-by: Daniel Sjoberg --- R/ard_survival_survfit_diff.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ard_survival_survfit_diff.R b/R/ard_survival_survfit_diff.R index 039d4731d..5700c2e5c 100644 --- a/R/ard_survival_survfit_diff.R +++ b/R/ard_survival_survfit_diff.R @@ -41,7 +41,7 @@ ard_survival_survfit_diff <- function(x, times, conf.level = 0.95) { ) if (length(x$strata) < 2) { cli::cli_abort( - "The {.cls survift} object's stratifying variable must have 2 or more levels.", + "The {.cls survfit} object's stratifying variable must have 2 or more levels.", call = get_cli_abort_call() ) } From fae6d19f558db51d4286bdcbfb33afc4004225cb Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Fri, 24 May 2024 15:53:57 -0700 Subject: [PATCH 13/13] snap updates --- .../testthat/_snaps/ard_attributes.survey.design.md | 13 +++++++++++++ tests/testthat/_snaps/ard_survival_survfit_diff.md | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/_snaps/ard_attributes.survey.design.md diff --git a/tests/testthat/_snaps/ard_attributes.survey.design.md b/tests/testthat/_snaps/ard_attributes.survey.design.md new file mode 100644 index 000000000..6c373f28f --- /dev/null +++ b/tests/testthat/_snaps/ard_attributes.survey.design.md @@ -0,0 +1,13 @@ +# ard_attributes.survey.design() works + + Code + attr(dclus1$variables$sname, "label") <- "School Name" + as.data.frame(ard_attributes(dclus1, variables = c(sname, dname), label = list( + dname = "District Name"))) + Output + variable context stat_name stat_label stat + 1 sname attributes label Variable Label School Name + 2 sname attributes class Variable Class character + 3 dname attributes label Variable Label District Name + 4 dname attributes class Variable Class character + diff --git a/tests/testthat/_snaps/ard_survival_survfit_diff.md b/tests/testthat/_snaps/ard_survival_survfit_diff.md index 4ac19b2f4..c5ffc0c7c 100644 --- a/tests/testthat/_snaps/ard_survival_survfit_diff.md +++ b/tests/testthat/_snaps/ard_survival_survfit_diff.md @@ -5,7 +5,7 @@ times = c(25, 50)) Condition Error in `ard_survival_survfit_diff()`: - ! The object passed in argument `x` must be stratified by a single variable. + ! The object passed in argument `x` must be stratified by a single variable. --- @@ -14,7 +14,7 @@ cards::ADTTE, constant = 1L)), times = c(25, 50)) Condition Error in `ard_survival_survfit_diff()`: - ! The object's stratifying variable must have 2 or more levels. + ! The object's stratifying variable must have 2 or more levels. ---