diff --git a/DESCRIPTION b/DESCRIPTION index 02712e0d6..f50542d30 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,7 +19,7 @@ BugReports: https://github.com/insightsengineering/cardx/issues Depends: R (>= 4.1) Imports: - cards (>= 0.2.0), + cards (>= 0.2.1.9003), cli (>= 3.6.1), dplyr (>= 1.1.2), glue (>= 1.6.2), @@ -30,7 +30,7 @@ Suggests: broom (>= 1.0.5), broom.helpers (>= 1.15.0), broom.mixed (>= 0.2.9), - car (>= 3.0-11), + car (>= 3.1-2), effectsize (>= 0.8.8), emmeans (>= 1.7.3), geepack (>= 1.3.2), @@ -43,6 +43,7 @@ Suggests: survival (>= 3.6-4), testthat (>= 3.2.0), withr (>= 2.5.0) +Remotes: insightsengineering/cards Config/Needs/website: insightsengineering/nesttemplate Config/testthat/edition: 3 Config/testthat/parallel: true diff --git a/NAMESPACE b/NAMESPACE index dee097e98..31301dd35 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,6 +12,7 @@ S3method(ard_missing,survey.design) S3method(ard_regression,default) S3method(ard_stats_anova,anova) S3method(ard_stats_anova,data.frame) +S3method(ard_total_n,survey.design) S3method(construct_model,data.frame) S3method(construct_model,survey.design) export("%>%") @@ -57,6 +58,7 @@ export(ard_survey_svyttest) export(ard_survival_survdiff) export(ard_survival_survfit) export(ard_survival_survfit_diff) +export(ard_total_n) export(bt) export(bt_strip) export(construct_model) @@ -83,6 +85,7 @@ importFrom(cards,ard_categorical) importFrom(cards,ard_continuous) importFrom(cards,ard_dichotomous) importFrom(cards,ard_missing) +importFrom(cards,ard_total_n) importFrom(dplyr,"%>%") importFrom(dplyr,across) importFrom(dplyr,all_of) diff --git a/NEWS.md b/NEWS.md index 6fb8a0df5..36042fb92 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # cardx 0.2.0.9005 +* Bug fix in `ard_categorical.survey.design()` where all unweighted statistics were returned, even in the case where they were explicitly not requested. + +* Added S3 method `ard_total_n.survey.design()` which returns an ARD with both the survey-weighted and unweighted total sample size. + # cardx 0.2.0 ### Breaking Changes diff --git a/R/add_total_n.survey.design.R b/R/add_total_n.survey.design.R new file mode 100644 index 000000000..80d515799 --- /dev/null +++ b/R/add_total_n.survey.design.R @@ -0,0 +1,33 @@ +#' ARD Total N +#' +#' Returns the total N for a survey object. +#' The placeholder variable name returned in the object is `"..ard_total_n.."` +#' +#' @inheritParams ard_dichotomous.survey.design +#' @inheritParams rlang::args_dots_empty +#' +#' @return an ARD data frame of class 'card' +#' @export +#' +#' @examplesIf cardx:::is_pkg_installed("survey", reference_pkg = "cardx") +#' svy_titanic <- survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~Freq) +#' +#' ard_total_n(svy_titanic) +ard_total_n.survey.design <- function(data, ...) { + # process inputs ------------------------------------------------------------- + set_cli_abort_call() + check_dots_empty() + + # calculate total N ---------------------------------------------------------- + data$variables <- + data$variables |> + dplyr::mutate(..ard_total_n.. = TRUE) + + data |> + ard_dichotomous( + variables = "..ard_total_n..", + statistic = list(..ard_total_n.. = c("N", "N_unweighted")) + ) |> + dplyr::mutate(context = "total_n") |> + dplyr::select(-cards::all_ard_variables("levels")) +} diff --git a/R/ard_categorical.survey.design.R b/R/ard_categorical.survey.design.R index 831c80e86..7cd33deab 100644 --- a/R/ard_categorical.survey.design.R +++ b/R/ard_categorical.survey.design.R @@ -163,26 +163,33 @@ ard_categorical.survey.design <- function(data, ) # add unweighted statistics -------------------------------------------------- - cards_unweighted <- - ard_categorical( - data = data[["variables"]], - variables = all_of(variables), - by = any_of(by), - denominator = denominator - ) |> - # all the survey levels are reported as character, so we do the same here. - dplyr::mutate( - across( - c(cards::all_ard_groups("levels"), cards::all_ard_variables("levels")), - ~ map(.x, as.character) + statistic_unweighted <- statistic |> + lapply(\(x) keep(x, ~ endsWith(.x, "_unweighted")) |> str_remove("_unweighted$")) |> + compact() + + if (!is_empty(statistic_unweighted)) { + cards_unweighted <- + ard_categorical( + data = data[["variables"]], + variables = all_of(names(statistic_unweighted)), + by = any_of(by), + statistic = statistic_unweighted, + denominator = denominator + ) |> + # all the survey levels are reported as character, so we do the same here. + dplyr::mutate( + across( + c(cards::all_ard_groups("levels"), cards::all_ard_variables("levels")), + ~ map(.x, as.character) + ) + ) |> + dplyr::select(-c("stat_label", "fmt_fn", "warning", "error")) |> + dplyr::mutate( + stat_name = + dplyr::case_match(.data$stat_name, "n" ~ "n_unweighted", "N" ~ "N_unweighted", "p" ~ "p_unweighted") ) - ) |> - dplyr::select(-c("stat_label", "fmt_fn", "warning", "error")) |> - dplyr::mutate( - stat_name = - dplyr::case_match(.data$stat_name, "n" ~ "n_unweighted", "N" ~ "N_unweighted", "p" ~ "p_unweighted") - ) - cards <- cards |> dplyr::bind_rows(cards_unweighted) # styler: off + cards <- cards |> dplyr::bind_rows(cards_unweighted) # styler: off + } # final processing of fmt_fn ------------------------------------------------- cards <- cards |> diff --git a/R/reexports.R b/R/reexports.R index adc647104..c9d91bff8 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -19,6 +19,10 @@ cards::ard_missing #' @export cards::ard_attributes +#' @importFrom cards ard_total_n +#' @export +cards::ard_total_n + # dplyr ------------------------------------------------------------------------ #' @export #' @importFrom dplyr %>% diff --git a/_pkgdown.yml b/_pkgdown.yml index c86be4d47..ea59918fa 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -69,6 +69,7 @@ reference: - ard_attributes.survey.design - ard_continuous_ci.survey.design - ard_categorical_ci.survey.design + - ard_total_n.survey.design - ard_survey_svychisq - ard_survey_svyranktest - ard_survey_svyttest diff --git a/man/ard_total_n.survey.design.Rd b/man/ard_total_n.survey.design.Rd new file mode 100644 index 000000000..6f9e409dd --- /dev/null +++ b/man/ard_total_n.survey.design.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_total_n.survey.design.R +\name{ard_total_n.survey.design} +\alias{ard_total_n.survey.design} +\title{ARD Total N} +\usage{ +\method{ard_total_n}{survey.design}(data, ...) +} +\arguments{ +\item{data}{(\code{survey.design})\cr +a design object often created with \code{\link[survey:svydesign]{survey::svydesign()}}.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\value{ +an ARD data frame of class 'card' +} +\description{ +Returns the total N for a survey object. +The placeholder variable name returned in the object is \code{"..ard_total_n.."} +} +\examples{ +\dontshow{if (cardx:::is_pkg_installed("survey", reference_pkg = "cardx")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +svy_titanic <- survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~Freq) + +ard_total_n(svy_titanic) +\dontshow{\}) # examplesIf} +} diff --git a/man/reexports.Rd b/man/reexports.Rd index 927068d41..cdd10463e 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -8,6 +8,7 @@ \alias{ard_dichotomous} \alias{ard_missing} \alias{ard_attributes} +\alias{ard_total_n} \alias{\%>\%} \alias{starts_with} \alias{ends_with} @@ -27,7 +28,7 @@ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ - \item{cards}{\code{\link[cards]{ard_attributes}}, \code{\link[cards]{ard_categorical}}, \code{\link[cards]{ard_continuous}}, \code{\link[cards]{ard_dichotomous}}, \code{\link[cards]{ard_missing}}} + \item{cards}{\code{\link[cards]{ard_attributes}}, \code{\link[cards]{ard_categorical}}, \code{\link[cards]{ard_continuous}}, \code{\link[cards]{ard_dichotomous}}, \code{\link[cards]{ard_missing}}, \code{\link[cards]{ard_total_n}}} \item{dplyr}{\code{\link[dplyr:reexports]{\%>\%}}, \code{\link[dplyr:reexports]{all_of}}, \code{\link[dplyr:reexports]{any_of}}, \code{\link[dplyr:reexports]{contains}}, \code{\link[dplyr:reexports]{ends_with}}, \code{\link[dplyr:reexports]{everything}}, \code{\link[dplyr:reexports]{last_col}}, \code{\link[dplyr:reexports]{matches}}, \code{\link[dplyr:reexports]{num_range}}, \code{\link[dplyr:reexports]{one_of}}, \code{\link[dplyr:reexports]{starts_with}}, \code{\link[dplyr:reexports]{where}}} }} diff --git a/tests/testthat/_snaps/ard_total_n.survey.design.md b/tests/testthat/_snaps/ard_total_n.survey.design.md new file mode 100644 index 000000000..3fe737742 --- /dev/null +++ b/tests/testthat/_snaps/ard_total_n.survey.design.md @@ -0,0 +1,14 @@ +# ard_total_n.survey.design() works + + Code + ard_total_n(survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~ + Freq)) + Message + {cards} data frame: 2 x 8 + Output + variable context stat_name stat_label stat fmt_fn + 1 ..ard_total_n.. total_n N N 2201 + 2 ..ard_total_n.. total_n N_unweighted Unweight… 32 + Message + i 2 more variables: warning, error + diff --git a/tests/testthat/test-ard_categorical.survey.design.R b/tests/testthat/test-ard_categorical.survey.design.R index 015efc1ea..dbc680d61 100644 --- a/tests/testthat/test-ard_categorical.survey.design.R +++ b/tests/testthat/test-ard_categorical.survey.design.R @@ -1290,3 +1290,22 @@ test_that("ard_categorical.survey.design() works when using generic names ", { ard_categorical(svy_titanic2, variables = c(row, column), by = cell, denominator = "row") |> dplyr::select(stat) ) }) + +test_that("ard_categorical.survey.design(statistic) properly excluded unweighted stats not selected", { + svy_titanic <- survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~Freq) + + expect_equal( + ard_categorical( + svy_titanic, + variables = Sex, + statistic = ~ c("N", "N_unweighted") + ) |> + dplyr::select(variable, variable_level, stat_name, stat_label, stat), + ard_categorical( + svy_titanic, + variables = Sex + ) |> + dplyr::filter(stat_name %in% c("N", "N_unweighted")) |> + dplyr::select(variable, variable_level, stat_name, stat_label, stat) + ) +}) diff --git a/tests/testthat/test-ard_total_n.survey.design.R b/tests/testthat/test-ard_total_n.survey.design.R new file mode 100644 index 000000000..ed9e0b28b --- /dev/null +++ b/tests/testthat/test-ard_total_n.survey.design.R @@ -0,0 +1,16 @@ +skip_if_not(is_pkg_installed("survey", reference_pkg = "cardx")) + +test_that("ard_total_n.survey.design() works", { + expect_snapshot( + survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~Freq) |> + ard_total_n() + ) +}) + +test_that("ard_total_n.survey.design() follows ard structure", { + expect_silent( + survey::svydesign(~1, data = as.data.frame(Titanic), weights = ~Freq) |> + ard_total_n() |> + cards::check_ard_structure(method = FALSE) + ) +})