Skip to content

Commit

Permalink
Add ard_*() function for stats::poisson.test() (#194)
Browse files Browse the repository at this point in the history
**What changes are proposed in this pull request?**
* Added function `ard_stats_poisson_test()` for calculating Poisson test
results and returning the results as an ARD. (#187)

I'm not quite sure the best way to take in the two required variables
(`x` and `T`), or if there are other types of variables that should be
accepted and then converted. Currently I have them as `variables`. Let
me know what you think!

Closes #187 


--------------------------------------------------------------------------------

Pre-review Checklist (if item does not apply, mark is as complete)
- [x] **All** GitHub Action workflows pass with a ✅
- [x] PR branch has pulled the most recent updates from master branch:
`usethis::pr_merge_main()`
- [x] If a bug was fixed, a unit test was added.
- [x] If a new `ard_*()` function was added, it passes the ARD
structural checks from `cards::check_ard_structure()`.
- [x] If a new `ard_*()` function was added, `set_cli_abort_call()` has
been set.
- [x] If a new `ard_*()` function was added and it depends on another
package (such as, `broom`), `is_pkg_installed("broom", reference_pkg =
"cardx")` has been set in the function call and the following added to
the roxygen comments: `@examplesIf
do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = "broom"",
reference_pkg = "cardx"))`
- [x] Code coverage is suitable for any new functions/features
(generally, 100% coverage for new code): `devtools::test_coverage()`

Reviewer Checklist (if item does not apply, mark is as complete)

- [ ] If a bug was fixed, a unit test was added.
- [ ] Code coverage is suitable for any new functions/features:
`devtools::test_coverage()`

When the branch is ready to be merged:
- [ ] Update `NEWS.md` with the changes from this pull request under the
heading "`# cardx (development version)`". If there is an issue
associated with the pull request, reference it in parentheses at the end
update (see `NEWS.md` for examples).
- [ ] **All** GitHub Action workflows pass with a ✅
- [ ] Approve Pull Request
- [ ] Merge the PR. Please use "Squash and merge" or "Rebase and merge".

---------

Co-authored-by: Daniel Sjoberg <[email protected]>
Co-authored-by: ayogasekaram <[email protected]>
  • Loading branch information
3 people authored Aug 26, 2024
1 parent f9377fd commit 65d05c7
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 0 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
166 changes: 166 additions & 0 deletions R/ard_stats_poisson_test.R
Original file line number Diff line number Diff line change
@@ -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"
)
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions man/ard_stats_poisson_test.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions man/dot-format_poissontest_results.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions tests/testthat/_snaps/ard_stats_poisson_test.md
Original file line number Diff line number Diff line change
@@ -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.

86 changes: 86 additions & 0 deletions tests/testthat/test-ard_stats_poisson_test.R
Original file line number Diff line number Diff line change
@@ -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)
)
})

0 comments on commit 65d05c7

Please sign in to comment.