diff --git a/NAMESPACE b/NAMESPACE index 31301dd35..928e14497 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -47,6 +47,7 @@ export(ard_stats_mood_test) export(ard_stats_oneway_test) export(ard_stats_paired_t_test) export(ard_stats_paired_wilcox_test) +export(ard_stats_poisson_test) export(ard_stats_prop_test) export(ard_stats_t_test) export(ard_stats_t_test_onesample) diff --git a/R/ard_stats_poisson_test.R b/R/ard_stats_poisson_test.R new file mode 100644 index 000000000..dd833d1da --- /dev/null +++ b/R/ard_stats_poisson_test.R @@ -0,0 +1,166 @@ +#' ARD Poisson Test +#' +#' @description +#' Analysis results data for exact tests of a simple null hypothesis about the rate parameter +#' in Poisson distribution, or the comparison of two rate parameters. +#' +#' @param data (`data.frame`)\cr +#' a data frame. See below for details. +#' @param variables ([`tidy-select`][dplyr::dplyr_tidy_select])\cr +#' names of the event and time variables (in that order) to be used in computations. Must be of length 2. +#' @param by ([`tidy-select`][dplyr::dplyr_tidy_select])\cr +#' optional column name to compare by. +#' @param conf.level (scalar `numeric`)\cr +#' confidence level for confidence interval. Default is `0.95`. +#' @param na.rm (scalar `logical`)\cr +#' whether missing values should be removed before computations. Default is `TRUE`. +#' @param ... arguments passed to [poisson.test()]. +#' @return an ARD data frame of class 'card' +#' @name ard_stats_poisson_test +#' +#' @details +#' * For the `ard_stats_poisson_test()` function, the data is expected to be one row per subject. +#' * If `by` is not specified, an exact Poisson test of the rate parameter will be performed. Otherwise, a +#' Poisson comparison of two rate parameters will be performed on the levels of `by`. If `by` has more than 2 +#' levels, an error will occur. +#' +#' @examplesIf do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = "broom", reference_pkg = "cardx")) +#' # Exact test of rate parameter against null hypothesis +#' cards::ADTTE |> +#' ard_stats_poisson_test(variables = c(CNSR, AVAL)) +#' +#' # Comparison test of ratio of 2 rate parameters against null hypothesis +#' cards::ADTTE |> +#' dplyr::filter(TRTA %in% c("Placebo", "Xanomeline High Dose")) |> +#' ard_stats_poisson_test(by = TRTA, variables = c(CNSR, AVAL)) +NULL + +#' @rdname ard_stats_poisson_test +#' @export +ard_stats_poisson_test <- function(data, variables, na.rm = TRUE, by = NULL, conf.level = 0.95, ...) { + set_cli_abort_call() + + # check installed packages --------------------------------------------------- + check_pkg_installed("broom", reference_pkg = "cardx") + + # check/process inputs ------------------------------------------------------- + check_not_missing(data) + check_not_missing(variables) + check_data_frame(data) + data <- dplyr::ungroup(data) + cards::process_selectors(data, by = {{ by }}, variables = {{ variables }}) + check_length(variables, 2) + check_logical(na.rm) + check_scalar(by, allow_empty = TRUE) + check_range(conf.level, range = c(0, 1)) + + # return empty ARD if no variables selected ---------------------- + if (is_empty(variables)) { + return(dplyr::tibble() |> cards::as_card()) + } + + # check number of levels in `by` + if (!is_empty(by) && dplyr::n_distinct(data[[by]], na.rm = TRUE) != 2L) { + cli::cli_abort( + "The {.arg by} argument must have a maximum of two levels.", + call = get_cli_abort_call() + ) + } + + # calculate numerator and denominator values + if (!is_empty(by)) { + num <- data |> + dplyr::group_by(.data[[by]]) |> + dplyr::summarise(sum = sum(.data[[variables[1]]], na.rm = na.rm)) |> + dplyr::pull(sum) + denom <- data |> + dplyr::group_by(.data[[by]]) |> + dplyr::summarise(sum = sum(.data[[variables[2]]], na.rm = na.rm)) |> + dplyr::pull(sum) + } else { + num <- sum(data[[variables[1]]], na.rm = na.rm) + denom <- sum(data[[variables[2]]], na.rm = na.rm) + } + + # build ARD ------------------------------------------------------------------ + .format_poissontest_results( + by = by, + variables = variables, + lst_tidy = + cards::eval_capture_conditions( + stats::poisson.test(x = num, T = denom, conf.level = conf.level, ...) |> broom::tidy() + ), + ... + ) +} + +#' Convert Poisson test to ARD +#' +#' @inheritParams cards::tidy_as_ard +#' @inheritParams stats::poisson.test +#' @param by (`string`)\cr by column name +#' @param variables (`character`)\cr names of the event and time variables +#' @param ... passed to [poisson.test()] +#' +#' @return ARD data frame +#' @keywords internal +#' @examplesIf do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = "broom", reference_pkg = "cardx")) +#' cardx:::.format_poissontest_results( +#' by = "ARM", +#' variables = c("CNSR", "AVAL"), +#' lst_tidy = +#' cards::eval_capture_conditions( +#' stats::poisson.test(sum(cards::ADTTE[["CNSR"]]), sum(cards::ADTTE[["AVAL"]])) |> +#' broom::tidy() +#' ) +#' ) +.format_poissontest_results <- function(by = NULL, variables, lst_tidy, ...) { + # build ARD ------------------------------------------------------------------ + ret <- + cards::tidy_as_ard( + lst_tidy = lst_tidy, + tidy_result_names = + c( + "estimate", "statistic", + "p.value", "parameter", "conf.low", "conf.high", + "method", "alternative" + ), + fun_args_to_record = c("conf.level", "r"), + formals = formals(asNamespace("stats")[["poisson.test"]]), + passed_args = dots_list(...), + lst_ard_columns = list(context = "stats_poisson_test", variable = variables[2]) + ) |> + dplyr::distinct() + + # rename "r" statistic to "mu" + ret$stat_name[ret$stat_name == "r"] <- "mu" + + if (!is_empty(by)) { + ret <- ret |> + dplyr::mutate(group1 = by) + } + + # add the stat label --------------------------------------------------------- + ret |> + dplyr::left_join( + .df_poissontest_stat_labels(by = by), + by = "stat_name" + ) |> + dplyr::mutate(stat_label = dplyr::coalesce(.data$stat_label, .data$stat_name)) |> + cards::as_card() |> + cards::tidy_ard_column_order() +} + +.df_poissontest_stat_labels <- function(by = NULL) { + dplyr::tribble( + ~stat_name, ~stat_label, + "estimate", ifelse(is_empty(by), "Estimated Rate", "Estimated Rate Ratio"), + "statistic", ifelse(is_empty(by), "Number of Events", "Number of Events in First Sample"), + "p.value", "p-value", + "parameter", "Expected Count", + "conf.low", "CI Lower Bound", + "conf.high", "CI Upper Bound", + "mu", "H0 Mean", + "conf.level", "CI Confidence Level" + ) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index ea59918fa..f48f87f6b 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -32,6 +32,7 @@ reference: - ard_stats_mood_test - ard_stats_mcnemar_test - ard_stats_oneway_test + - ard_stats_poisson_test - ard_stats_prop_test - ard_stats_t_test - ard_stats_t_test_onesample diff --git a/man/ard_stats_poisson_test.Rd b/man/ard_stats_poisson_test.Rd new file mode 100644 index 000000000..6bdcea7a0 --- /dev/null +++ b/man/ard_stats_poisson_test.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ard_stats_poisson_test.R +\name{ard_stats_poisson_test} +\alias{ard_stats_poisson_test} +\title{ARD Poisson Test} +\usage{ +ard_stats_poisson_test( + data, + variables, + na.rm = TRUE, + by = NULL, + conf.level = 0.95, + ... +) +} +\arguments{ +\item{data}{(\code{data.frame})\cr +a data frame. See below for details.} + +\item{variables}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr +names of the event and time variables (in that order) to be used in computations. Must be of length 2.} + +\item{na.rm}{(scalar \code{logical})\cr +whether missing values should be removed before computations. Default is \code{TRUE}.} + +\item{by}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr +optional column name to compare by.} + +\item{conf.level}{(scalar \code{numeric})\cr +confidence level for confidence interval. Default is \code{0.95}.} + +\item{...}{arguments passed to \code{\link[=poisson.test]{poisson.test()}}.} +} +\value{ +an ARD data frame of class 'card' +} +\description{ +Analysis results data for exact tests of a simple null hypothesis about the rate parameter +in Poisson distribution, or the comparison of two rate parameters. +} +\details{ +\itemize{ +\item For the \code{ard_stats_poisson_test()} function, the data is expected to be one row per subject. +\item If \code{by} is not specified, an exact Poisson test of the rate parameter will be performed. Otherwise, a +Poisson comparison of two rate parameters will be performed on the levels of \code{by}. If \code{by} has more than 2 +levels, an error will occur. +} +} +\examples{ +\dontshow{if (do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = "broom", reference_pkg = "cardx"))) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +# Exact test of rate parameter against null hypothesis +cards::ADTTE |> + ard_stats_poisson_test(variables = c(CNSR, AVAL)) + +# Comparison test of ratio of 2 rate parameters against null hypothesis +cards::ADTTE |> + dplyr::filter(TRTA \%in\% c("Placebo", "Xanomeline High Dose")) |> + ard_stats_poisson_test(by = TRTA, variables = c(CNSR, AVAL)) +\dontshow{\}) # examplesIf} +} diff --git a/man/dot-format_poissontest_results.Rd b/man/dot-format_poissontest_results.Rd new file mode 100644 index 000000000..19a451d83 --- /dev/null +++ b/man/dot-format_poissontest_results.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ard_stats_poisson_test.R +\name{.format_poissontest_results} +\alias{.format_poissontest_results} +\title{Convert Poisson test to ARD} +\usage{ +.format_poissontest_results(by = NULL, variables, lst_tidy, ...) +} +\arguments{ +\item{by}{(\code{string})\cr by column name} + +\item{variables}{(\code{character})\cr names of the event and time variables} + +\item{lst_tidy}{(named \code{list})\cr +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{\link[=poisson.test]{poisson.test()}}} +} +\value{ +ARD data frame +} +\description{ +Convert Poisson test to ARD +} +\examples{ +\dontshow{if (do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = "broom", reference_pkg = "cardx"))) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +cardx:::.format_poissontest_results( + by = "ARM", + variables = c("CNSR", "AVAL"), + lst_tidy = + cards::eval_capture_conditions( + stats::poisson.test(sum(cards::ADTTE[["CNSR"]]), sum(cards::ADTTE[["AVAL"]])) |> + broom::tidy() + ) +) +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/tests/testthat/_snaps/ard_stats_poisson_test.md b/tests/testthat/_snaps/ard_stats_poisson_test.md new file mode 100644 index 000000000..0c60b4489 --- /dev/null +++ b/tests/testthat/_snaps/ard_stats_poisson_test.md @@ -0,0 +1,8 @@ +# ard_stats_poisson_test() errors are handled correctly + + Code + ard_stats_poisson_test(cards::ADTTE, by = TRTA, variables = c(CNSR, AVAL)) + Condition + Error in `ard_stats_poisson_test()`: + ! The `by` argument must have a maximum of two levels. + diff --git a/tests/testthat/test-ard_stats_poisson_test.R b/tests/testthat/test-ard_stats_poisson_test.R new file mode 100644 index 000000000..0a083dd99 --- /dev/null +++ b/tests/testthat/test-ard_stats_poisson_test.R @@ -0,0 +1,86 @@ +skip_if_not(is_pkg_installed("broom", reference_pkg = "cardx")) + +test_that("ard_stats_poisson_test() works for one sample tests", { + # Default values work + expect_silent( + ard_stats_poisson_test(cards::ADTTE, variables = c(CNSR, AVAL)) + ) + + # Custom values work + expect_silent( + ard_single <- ard_stats_poisson_test( + cards::ADTTE, + variables = c("CNSR", "AVAL"), + conf.level = 0.90, + r = 0.8, + alternative = "greater" + ) + ) + + # Statistics calculated correctly + expect_equal( + ard_single |> + cards::get_ard_statistics( + stat_name %in% + c("estimate", "statistic", "p.value", "parameter", "conf.low", "conf.high", "method", "alternative") + ), + poisson.test( + x = sum(cards::ADTTE$CNSR), + T = sum(cards::ADTTE$AVAL), + r = 0.8, + conf.level = 0.9, + alternative = "greater" + ) |> + broom::tidy() |> + unclass(), + ignore_attr = TRUE + ) +}) + +test_that("ard_stats_poisson_test() works for two sample tests", { + expect_silent( + ard_compare <- + cards::ADTTE |> + dplyr::filter(TRTA %in% c("Placebo", "Xanomeline High Dose")) |> + ard_stats_poisson_test(by = TRTA, variables = c(CNSR, AVAL)) + ) + + # Statistics calculated correctly + expect_equal( + ard_compare |> + cards::get_ard_statistics( + stat_name %in% + c("estimate", "statistic", "p.value", "parameter", "conf.low", "conf.high", "method", "alternative") + ), + poisson.test( + x = cards::ADTTE |> + dplyr::filter(TRTA %in% c("Placebo", "Xanomeline High Dose")) |> + dplyr::group_by(TRTA) |> + dplyr::summarise(sum = sum(CNSR)) |> + dplyr::pull(sum), + T = cards::ADTTE |> + dplyr::filter(TRTA %in% c("Placebo", "Xanomeline High Dose")) |> + dplyr::group_by(TRTA) |> + dplyr::summarise(sum = sum(AVAL)) |> + dplyr::pull(sum) + ) |> + broom::tidy() |> + unclass(), + ignore_attr = TRUE + ) +}) + +test_that("ard_stats_poisson_test() errors are handled correctly", { + expect_snapshot( + cards::ADTTE |> + ard_stats_poisson_test(by = TRTA, variables = c(CNSR, AVAL)), + error = TRUE + ) +}) + +test_that("ard_stats_poisson_test() follows ard structure", { + expect_silent( + ard_stats_poisson_test(cards::ADTTE, variables = c(CNSR, AVAL)) |> + cards::check_ard_structure(method = T) + ) +})