From 15f9e4b31834468005c1b918cd06869c192428b3 Mon Sep 17 00:00:00 2001 From: Melkiades Date: Wed, 14 Feb 2024 15:19:46 +0100 Subject: [PATCH 1/8] feat: addition of mcnemartest --- NAMESPACE | 1 + R/ard_mcnemartest.R | 112 ++++++++++++++++++++++++++ R/ard_wilcoxtest.R | 13 ++- man/ard_mcnemartest.Rd | 40 +++++++++ man/dot-format_mcnemartest_results.Rd | 39 +++++++++ man/dot-format_wilcoxtest_results.Rd | 12 ++- tests/testthat/test-ard_mcnemartest.R | 37 +++++++++ 7 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 R/ard_mcnemartest.R create mode 100644 man/ard_mcnemartest.Rd create mode 100644 man/dot-format_mcnemartest_results.Rd create mode 100644 tests/testthat/test-ard_mcnemartest.R diff --git a/NAMESPACE b/NAMESPACE index 6ac965856..b61822cf1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ export("%>%") export(all_of) export(any_of) +export(ard_mcnemartest) export(ard_paired_ttest) export(ard_paired_wilcoxtest) export(ard_ttest) diff --git a/R/ard_mcnemartest.R b/R/ard_mcnemartest.R new file mode 100644 index 000000000..71acafd22 --- /dev/null +++ b/R/ard_mcnemartest.R @@ -0,0 +1,112 @@ +#' ARD McNemar's Test +#' +#' @description +#' Analysis results data for McNemar's statistical test. +#' +#' @param data (`data.frame`)\cr +#' a data frame. See below for details. +#' @param by ([`tidy-select`][dplyr::dplyr_tidy_select])\cr +#' column name to compare by. +#' @param variable ([`tidy-select`][dplyr::dplyr_tidy_select])\cr +#' column name to be compared. +#' @param id ([`tidy-select`][dplyr::dplyr_tidy_select])\cr +#' column name of the subject or participant ID. +#' @param ... arguments passed to `stats::mcnemar.test(...)` +#' +#' @return ARD data frame +#' @name ard_mcnemartest +#' +#' @details +#' For the `ard_mcnemartest()` function, the data is expected to be one row per subject. +#' The data is passed as `stats::mcnemar.test(x = data[[variable]], y = data[[by]], ...)`. +#' `variable` and `by` are expected to be dichotomous variables. Please +#' use `table(x = data[[variable]], y = data[[by]])` to check the contingency table. +#' +#' @examples +#' cards::ADSL |> +#' ard_mcnemartest(by = "SEX", variable = "EFFFL") +#' +NULL + +#' @rdname ard_mcnemartest +#' @export +ard_mcnemartest <- function(data, by, variable, ...) { + # check installed packages --------------------------------------------------- + cards::check_pkg_installed("broom", reference_pkg = "cardx") + + # check/process inputs ------------------------------------------------------- + check_not_missing(data) + check_not_missing(variable) + check_not_missing(by) + check_class_data_frame(x = data) + data <- dplyr::ungroup(data) + cards::process_selectors(data, by = {{ by }}, variable = {{ variable }}) + check_scalar(by) + check_scalar(variable) + + # build ARD ------------------------------------------------------------------ + .format_mcnemartest_results( + by = by, + variable = variable, + lst_tidy = + cards::eval_capture_conditions( + stats::mcnemar.test(x = data[[variable]], y = data[[by]], ...) |> + broom::tidy() + ), + ... + ) +} + +#' Convert McNemar's test to ARD +#' +#' @inheritParams cards::tidy_as_ard +#' @inheritParams stats::mcnemar.test +#' @param by (`string`)\cr by column name +#' @param variable (`string`)\cr variable column name +#' @param ... passed to `stats::mcnemar.test(...)` +#' +#' @return ARD data frame +#' +#' @examples +#' cardx:::.format_mcnemartest_results( +#' by = "ARM", +#' variable = "AGE", +#' paired = FALSE, +#' lst_tidy = +#' cards::eval_capture_conditions( +#' stats::mcnemar.test(cards::ADSL[["SEX"]], cards::ADSL[["EFFFL"]]) |> +#' broom::tidy() +#' ) +#' ) +#' +#' @keywords internal +.format_mcnemartest_results <- function(by, variable, lst_tidy, paired, ...) { + # build ARD ------------------------------------------------------------------ + ret <- + cards::tidy_as_ard( + lst_tidy = lst_tidy, + tidy_result_names = c("statistic", "p.value", "method"), + fun_args_to_record = c("correct"), + formals = formals(asNamespace("stats")[["mcnemar.test"]]), + passed_args = dots_list(...), + lst_ard_columns = list(group1 = by, variable = variable, context = "ttest") + ) + + # add the stat label --------------------------------------------------------- + ret |> + dplyr::left_join( + .df_mcnemar_stat_labels(), + by = "stat_name" + ) |> + dplyr::mutate(stat_label = dplyr::coalesce(.data$stat_label, .data$stat_name)) |> + cards::tidy_ard_column_order() +} + +.df_mcnemar_stat_labels <- function() { + dplyr::tribble( + ~stat_name, ~stat_label, + "statistic", "X-squared Statistic", + "parameter", "Degrees of Freedom", + "p.value", "p-value", + ) +} diff --git a/R/ard_wilcoxtest.R b/R/ard_wilcoxtest.R index 2ecc87ec7..b3c7829a4 100644 --- a/R/ard_wilcoxtest.R +++ b/R/ard_wilcoxtest.R @@ -105,17 +105,22 @@ ard_paired_wilcoxtest <- function(data, by, variable, id, ...) { ) } -#' Convert t-test to ARD +#' Convert Wilcoxon Rank-Sum test to ARD #' #' @inheritParams cards::tidy_as_ard #' @inheritParams stats::wilcox.test #' @param by (`string`)\cr by column name #' @param variable (`string`)\cr variable column name -#' @param ... passed to `wilcox.test(...)` +#' @param ... passed to `stats::wilcox.test(...)` #' #' @return ARD data frame -#' @keywords internal +#' #' @examples +#' # Pre-processing ADSL to have grouping factor (ARM here) with 2 levels +#' ADSL <- cards::ADSL |> +#' dplyr::filter(ARM %in% c("Placebo", "Xanomeline High Dose")) |> +#' ard_wilcoxtest(by = "ARM", variable = "AGE") +#' #' cardx:::.format_wilcoxtest_results( #' by = "ARM", #' variable = "AGE", @@ -126,6 +131,8 @@ ard_paired_wilcoxtest <- function(data, by, variable, id, ...) { #' broom::tidy() #' ) #' ) +#' +#' @keywords internal .format_wilcoxtest_results <- function(by, variable, lst_tidy, paired, ...) { # build ARD ------------------------------------------------------------------ ret <- diff --git a/man/ard_mcnemartest.Rd b/man/ard_mcnemartest.Rd new file mode 100644 index 000000000..b87c3461e --- /dev/null +++ b/man/ard_mcnemartest.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ard_mcnemartest.R +\name{ard_mcnemartest} +\alias{ard_mcnemartest} +\title{ARD McNemar's Test} +\usage{ +ard_mcnemartest(data, by, variable, ...) +} +\arguments{ +\item{data}{(\code{data.frame})\cr +a data frame. See below for details.} + +\item{by}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr +column name to compare by.} + +\item{variable}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr +column name to be compared.} + +\item{...}{arguments passed to \code{stats::mcnemar.test(...)}} + +\item{id}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr +column name of the subject or participant ID.} +} +\value{ +ARD data frame +} +\description{ +Analysis results data for McNemar's statistical test. +} +\details{ +For the \code{ard_mcnemartest()} function, the data is expected to be one row per subject. +The data is passed as \code{stats::mcnemar.test(x = data[[variable]], y = data[[by]], ...)}. +\code{variable} and \code{by} are expected to be dichotomous variables. Please +use \code{table(x = data[[variable]], y = data[[by]])} to check the contingency table. +} +\examples{ +cards::ADSL |> + ard_mcnemartest(by = "SEX", variable = "EFFFL") + +} diff --git a/man/dot-format_mcnemartest_results.Rd b/man/dot-format_mcnemartest_results.Rd new file mode 100644 index 000000000..80f338b1c --- /dev/null +++ b/man/dot-format_mcnemartest_results.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ard_mcnemartest.R +\name{.format_mcnemartest_results} +\alias{.format_mcnemartest_results} +\title{Convert McNemar's test to ARD} +\usage{ +.format_mcnemartest_results(by, variable, lst_tidy, paired, ...) +} +\arguments{ +\item{by}{(\code{string})\cr by column name} + +\item{variable}{(\code{string})\cr variable column name} + +\item{lst_tidy}{(named \code{list})\cr +list of tidied results constructed with \code{eval_capture_conditions()}, +e.g. \code{eval_capture_conditions(t.test(mtcars$mpg ~ mtcars$am) |> broom::tidy())}} + +\item{...}{passed to \code{stats::mcnemar.test(...)}} +} +\value{ +ARD data frame +} +\description{ +Convert McNemar's test to ARD +} +\examples{ +cardx:::.format_mcnemartest_results( + by = "ARM", + variable = "AGE", + paired = FALSE, + lst_tidy = + cards::eval_capture_conditions( + stats::mcnemar.test(cards::ADSL[["SEX"]], cards::ADSL[["EFFFL"]]) |> + broom::tidy() + ) +) + +} +\keyword{internal} diff --git a/man/dot-format_wilcoxtest_results.Rd b/man/dot-format_wilcoxtest_results.Rd index b4b8925aa..50ee29a99 100644 --- a/man/dot-format_wilcoxtest_results.Rd +++ b/man/dot-format_wilcoxtest_results.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/ard_wilcoxtest.R \name{.format_wilcoxtest_results} \alias{.format_wilcoxtest_results} -\title{Convert t-test to ARD} +\title{Convert Wilcoxon Rank-Sum test to ARD} \usage{ .format_wilcoxtest_results(by, variable, lst_tidy, paired, ...) } @@ -17,15 +17,20 @@ e.g. \code{eval_capture_conditions(t.test(mtcars$mpg ~ mtcars$am) |> broom::tidy \item{paired}{a logical indicating whether you want a paired test.} -\item{...}{passed to \code{wilcox.test(...)}} +\item{...}{passed to \code{stats::wilcox.test(...)}} } \value{ ARD data frame } \description{ -Convert t-test to ARD +Convert Wilcoxon Rank-Sum test to ARD } \examples{ +# Pre-processing ADSL to have grouping factor (ARM here) with 2 levels +ADSL <- cards::ADSL |> + dplyr::filter(ARM \%in\% c("Placebo", "Xanomeline High Dose")) |> + ard_wilcoxtest(by = "ARM", variable = "AGE") + cardx:::.format_wilcoxtest_results( by = "ARM", variable = "AGE", @@ -36,5 +41,6 @@ cardx:::.format_wilcoxtest_results( broom::tidy() ) ) + } \keyword{internal} diff --git a/tests/testthat/test-ard_mcnemartest.R b/tests/testthat/test-ard_mcnemartest.R new file mode 100644 index 000000000..24ec1a654 --- /dev/null +++ b/tests/testthat/test-ard_mcnemartest.R @@ -0,0 +1,37 @@ +test_that("ard_mcnemartest() works", { + expect_error( + ard_mcnemartest <- + cards::ADSL |> + ard_mcnemartest(by = SEX, variable = EFFFL), + NA + ) + + expect_equal( + ard_mcnemartest |> + cards::get_ard_statistics(stat_name %in% c("statistic", "p.value", "parameter", "method")), + stats::mcnemar.test(cards::ADSL[["SEX"]], cards::ADSL[["EFFFL"]], correct = TRUE) |> + broom::tidy() |> + unclass(), + ignore_attr = TRUE + ) + + # errors are properly handled + expect_equal( + cards::ADSL |> + ard_mcnemartest(by = ARM, variable = AGE, correct = FALSE) |> + dplyr::pull(error) |> + getElement(1L), + "'x' and 'y' must have the same number of levels (minimum 2)" + ) + + # non-syntactic column names work too + ADSL_tmp <- cards::ADSL |> + dplyr::rename("if" = AGE, "_c d" = EFFFL) + + expect_error( + ard_mcnemartest <- + ADSL_tmp |> + ard_mcnemartest(by = `if`, variable = `_c d`), + NA + ) +}) From 4dece7ec7535a17a60cbe1332b654380bf349608 Mon Sep 17 00:00:00 2001 From: Melkiades Date: Thu, 15 Feb 2024 09:40:37 +0100 Subject: [PATCH 2/8] spell check --- inst/WORDLIST | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 14073fb50..13082aef1 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -3,6 +3,7 @@ Biopharmaceutical Clopper Hoffmann Jeffreys +McNemar's Newcombe Su XG From 72e736827d7327adeb72cf046670f7dea0d3dd87 Mon Sep 17 00:00:00 2001 From: Melkiades Date: Thu, 15 Feb 2024 14:15:10 +0100 Subject: [PATCH 3/8] fix: roxygenize after updates upstream --- NAMESPACE | 2 +- man/dot-format_mcnemartest_results.Rd | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 7f0a25d38..90fa2ede3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,9 +4,9 @@ S3method(ard_regression,default) export("%>%") export(all_of) export(any_of) -export(ard_mcnemartest) export(ard_chisqtest) export(ard_fishertest) +export(ard_mcnemartest) export(ard_paired_ttest) export(ard_paired_wilcoxtest) export(ard_proportion_ci) diff --git a/man/dot-format_mcnemartest_results.Rd b/man/dot-format_mcnemartest_results.Rd index 80f338b1c..778b62080 100644 --- a/man/dot-format_mcnemartest_results.Rd +++ b/man/dot-format_mcnemartest_results.Rd @@ -12,8 +12,8 @@ \item{variable}{(\code{string})\cr variable column name} \item{lst_tidy}{(named \code{list})\cr -list of tidied results constructed with \code{eval_capture_conditions()}, -e.g. \code{eval_capture_conditions(t.test(mtcars$mpg ~ mtcars$am) |> broom::tidy())}} +list of tidied results constructed with \code{\link[cards:eval_capture_conditions]{eval_capture_conditions()}}, +e.g. \code{eval_capture_conditions(t.test(mtcars$mpg ~ mtcars$am) |> broom::tidy())}.} \item{...}{passed to \code{stats::mcnemar.test(...)}} } From 545c592a0ea3a1fe240cbf0fd92f507145417bb1 Mon Sep 17 00:00:00 2001 From: Melkiades Date: Thu, 15 Feb 2024 15:34:35 +0100 Subject: [PATCH 4/8] fix: rm unused `id` param --- R/ard_mcnemartest.R | 2 -- man/ard_mcnemartest.Rd | 3 --- 2 files changed, 5 deletions(-) diff --git a/R/ard_mcnemartest.R b/R/ard_mcnemartest.R index 71acafd22..0f997de89 100644 --- a/R/ard_mcnemartest.R +++ b/R/ard_mcnemartest.R @@ -9,8 +9,6 @@ #' column name to compare by. #' @param variable ([`tidy-select`][dplyr::dplyr_tidy_select])\cr #' column name to be compared. -#' @param id ([`tidy-select`][dplyr::dplyr_tidy_select])\cr -#' column name of the subject or participant ID. #' @param ... arguments passed to `stats::mcnemar.test(...)` #' #' @return ARD data frame diff --git a/man/ard_mcnemartest.Rd b/man/ard_mcnemartest.Rd index b87c3461e..a2deb6659 100644 --- a/man/ard_mcnemartest.Rd +++ b/man/ard_mcnemartest.Rd @@ -17,9 +17,6 @@ column name to compare by.} column name to be compared.} \item{...}{arguments passed to \code{stats::mcnemar.test(...)}} - -\item{id}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr -column name of the subject or participant ID.} } \value{ ARD data frame From 27f580fc227ef07e17621b20dfeeb442593085f4 Mon Sep 17 00:00:00 2001 From: Melkiades Date: Thu, 15 Feb 2024 15:42:07 +0100 Subject: [PATCH 5/8] finally: pkgdown --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 35ce4f4a5..9d7b4e53f 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -26,6 +26,7 @@ reference: - contents: - ard_chisqtest - ard_fishertest + - ard_mcnemartest - ard_ttest - ard_wilcoxtest From 2680b1d82db35a831fe377a70f238ed6125aaa2e Mon Sep 17 00:00:00 2001 From: Davide Garolini Date: Fri, 16 Feb 2024 15:36:18 +0100 Subject: [PATCH 6/8] Update R/ard_mcnemartest.R Co-authored-by: Abinaya Yogasekaram <73252787+ayogasekaram@users.noreply.github.com> Signed-off-by: Davide Garolini --- R/ard_mcnemartest.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ard_mcnemartest.R b/R/ard_mcnemartest.R index 0f997de89..2d5a3de9b 100644 --- a/R/ard_mcnemartest.R +++ b/R/ard_mcnemartest.R @@ -87,7 +87,7 @@ ard_mcnemartest <- function(data, by, variable, ...) { fun_args_to_record = c("correct"), formals = formals(asNamespace("stats")[["mcnemar.test"]]), passed_args = dots_list(...), - lst_ard_columns = list(group1 = by, variable = variable, context = "ttest") + lst_ard_columns = list(group1 = by, variable = variable, context = "mcnemartest") ) # add the stat label --------------------------------------------------------- From 6c35766b793b589e611b076e27b9a92c128f0fae Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Sat, 17 Feb 2024 13:33:27 -0800 Subject: [PATCH 7/8] Update DESCRIPTION --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index bdaa8473c..c2930261e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,7 +12,7 @@ BugReports: https://github.com/insightsengineering/cardx/issues Depends: R (>= 4.0) Imports: - cards (>= 0.0.0.9035), + cards (>= 0.0.0.9037), cli (>= 3.6.1), dplyr (>= 1.1.2), glue (>= 1.6.2), From 03264a2f9390e0479d5dfbf9a30811f1d50b2dcc Mon Sep 17 00:00:00 2001 From: Melkiades Date: Wed, 21 Feb 2024 17:49:38 +0100 Subject: [PATCH 8/8] changes after review --- R/ard_mcnemartest.R | 6 ++---- man/ard_mcnemartest.Rd | 3 +-- man/dot-format_mcnemartest_results.Rd | 3 +-- tests/testthat/test-ard_mcnemartest.R | 13 ++++++++----- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/R/ard_mcnemartest.R b/R/ard_mcnemartest.R index 2d5a3de9b..168a8f51c 100644 --- a/R/ard_mcnemartest.R +++ b/R/ard_mcnemartest.R @@ -17,8 +17,7 @@ #' @details #' For the `ard_mcnemartest()` function, the data is expected to be one row per subject. #' The data is passed as `stats::mcnemar.test(x = data[[variable]], y = data[[by]], ...)`. -#' `variable` and `by` are expected to be dichotomous variables. Please -#' use `table(x = data[[variable]], y = data[[by]])` to check the contingency table. +#' Please use `table(x = data[[variable]], y = data[[by]])` to check the contingency table. #' #' @examples #' cards::ADSL |> @@ -69,7 +68,6 @@ ard_mcnemartest <- function(data, by, variable, ...) { #' cardx:::.format_mcnemartest_results( #' by = "ARM", #' variable = "AGE", -#' paired = FALSE, #' lst_tidy = #' cards::eval_capture_conditions( #' stats::mcnemar.test(cards::ADSL[["SEX"]], cards::ADSL[["EFFFL"]]) |> @@ -78,7 +76,7 @@ ard_mcnemartest <- function(data, by, variable, ...) { #' ) #' #' @keywords internal -.format_mcnemartest_results <- function(by, variable, lst_tidy, paired, ...) { +.format_mcnemartest_results <- function(by, variable, lst_tidy, ...) { # build ARD ------------------------------------------------------------------ ret <- cards::tidy_as_ard( diff --git a/man/ard_mcnemartest.Rd b/man/ard_mcnemartest.Rd index a2deb6659..7fdfafceb 100644 --- a/man/ard_mcnemartest.Rd +++ b/man/ard_mcnemartest.Rd @@ -27,8 +27,7 @@ Analysis results data for McNemar's statistical test. \details{ For the \code{ard_mcnemartest()} function, the data is expected to be one row per subject. The data is passed as \code{stats::mcnemar.test(x = data[[variable]], y = data[[by]], ...)}. -\code{variable} and \code{by} are expected to be dichotomous variables. Please -use \code{table(x = data[[variable]], y = data[[by]])} to check the contingency table. +Please use \code{table(x = data[[variable]], y = data[[by]])} to check the contingency table. } \examples{ cards::ADSL |> diff --git a/man/dot-format_mcnemartest_results.Rd b/man/dot-format_mcnemartest_results.Rd index 778b62080..a809a791c 100644 --- a/man/dot-format_mcnemartest_results.Rd +++ b/man/dot-format_mcnemartest_results.Rd @@ -4,7 +4,7 @@ \alias{.format_mcnemartest_results} \title{Convert McNemar's test to ARD} \usage{ -.format_mcnemartest_results(by, variable, lst_tidy, paired, ...) +.format_mcnemartest_results(by, variable, lst_tidy, ...) } \arguments{ \item{by}{(\code{string})\cr by column name} @@ -27,7 +27,6 @@ Convert McNemar's test to ARD cardx:::.format_mcnemartest_results( by = "ARM", variable = "AGE", - paired = FALSE, lst_tidy = cards::eval_capture_conditions( stats::mcnemar.test(cards::ADSL[["SEX"]], cards::ADSL[["EFFFL"]]) |> diff --git a/tests/testthat/test-ard_mcnemartest.R b/tests/testthat/test-ard_mcnemartest.R index 24ec1a654..de4f4e582 100644 --- a/tests/testthat/test-ard_mcnemartest.R +++ b/tests/testthat/test-ard_mcnemartest.R @@ -28,10 +28,13 @@ test_that("ard_mcnemartest() works", { ADSL_tmp <- cards::ADSL |> dplyr::rename("if" = AGE, "_c d" = EFFFL) - expect_error( - ard_mcnemartest <- - ADSL_tmp |> - ard_mcnemartest(by = `if`, variable = `_c d`), - NA + expect_equal( + cards::ADSL |> + dplyr::rename(`Planned Tx` = TRT01P, `Age Group` = AGEGR1) |> + ard_mcnemartest(by = `Planned Tx`, variable = `Age Group`) |> + cards::get_ard_statistics(), + cards::ADSL |> + ard_mcnemartest(by = TRT01P, variable = AGEGR1) |> + cards::get_ard_statistics() ) })