├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── FEATURE.yml │ └── bug_report.yml ├── workflows │ ├── cla.yaml │ ├── post-release.yaml │ ├── recheck.yml │ ├── docs.yaml │ ├── R-CMD-check.yaml │ ├── test-coverage.yaml │ ├── release.yaml │ └── scheduled.yaml └── PULL_REQUEST_TEMPLATE.md ├── vignettes └── .gitignore ├── .revdeprefs.yaml ├── data ├── ADAE.rda ├── ADLB.rda ├── ADSL.rda └── ADTTE.rda ├── man ├── figures │ ├── logo.png │ ├── lifecycle-defunct.svg │ ├── lifecycle-archived.svg │ ├── lifecycle-maturing.svg │ ├── lifecycle-deprecated.svg │ ├── lifecycle-superseded.svg │ ├── lifecycle-questioning.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-soft-deprecated.svg │ └── lifecycle-stable.svg ├── default_stat_labels.Rd ├── dot-is_named_list.Rd ├── as_card.Rd ├── dot-default_fmt_fun.Rd ├── dot-purrr_list_flatten.Rd ├── ard_total_n.Rd ├── as_nested_list.Rd ├── maximum_variable_value.Rd ├── dot-check_dichotomous_value.Rd ├── dot-one_row_ard_to_nested_list.Rd ├── dot-check_var_nms.Rd ├── dot-trim_ard.Rd ├── apply_fmt_fun.Rd ├── dot-create_list_for_attributes.Rd ├── dot-rename_last_group_as_variable.Rd ├── dot-check_no_ard_columns.Rd ├── dot-unique_and_sorted.Rd ├── dot-cli_condition_messaging.Rd ├── dot-fill_grps_from_variables.Rd ├── label_round.Rd ├── dot-eval_ard_calls.Rd ├── adam.Rd ├── round5.Rd ├── dot-detect_msgs.Rd ├── check_ard_structure.Rd ├── dot-check_fmt_string.Rd ├── dot-process_nested_list_as_df.Rd ├── dot-cli_groups_and_variable.Rd ├── rename_ard_groups.Rd ├── print_ard_conditions.Rd ├── dot-lst_results_as_df.Rd ├── ard_identity.Rd ├── dot-derive_overall_labels.Rd ├── reexports.Rd ├── dot-check_for_missing_combos_in_denom.Rd ├── dot-calculate_stats_as_ard.Rd ├── get_ard_statistics.Rd ├── cards.options.Rd ├── alias_as_fmt_fun.Rd ├── dot-fill_overall_grp_values.Rd ├── dot-table_as_df.Rd ├── ard_formals.Rd ├── ard_pairwise.Rd ├── tidy_ard_order.Rd ├── replace_null_statistic.Rd ├── unlist_ard_columns.Rd ├── print.card.Rd ├── summary_functions.Rd ├── dot-nesting_rename_ard_columns.Rd ├── ard_attributes.Rd ├── ard_tabulate_rows.Rd ├── selectors.Rd ├── cards-package.Rd ├── dot-process_denominator.Rd ├── add_calculated_row.Rd ├── as_cards_fn.Rd ├── rename_ard_columns.Rd ├── bind_ard.Rd ├── dot-calculate_tabulation_statistics.Rd ├── ard_strata.Rd ├── nest_for_ard.Rd ├── update_ard.Rd ├── ard_stack.Rd └── mock.Rd ├── CRAN-SUBMISSION ├── pkgdown ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ └── apple-touch-icon-180x180.png └── index.Rmd ├── data-raw ├── cards-hex-sticker.png └── DATASET.R ├── .gitignore ├── .gitlab-ci.yml ├── tests ├── testthat │ ├── test-ard_tabulate_rows.R │ ├── test-as_nested_list.R │ ├── _snaps │ │ ├── ard_identity.md │ │ ├── options.md │ │ ├── ard_total_n.md │ │ ├── check_ard_structure.md │ │ ├── as_card.md │ │ ├── ard_tabulate_rows.md │ │ ├── process_selectors.md │ │ ├── rename_ard_columns.md │ │ ├── ard_formals.md │ │ ├── get_ard_statistics.md │ │ ├── ard_attributes.md │ │ ├── ard_mvsummary.md │ │ ├── rename_ard_groups.md │ │ ├── round5.md │ │ ├── tidy_ard_row_order.md │ │ ├── ard_pairwise.md │ │ └── add_calculated_row.md │ ├── test-as_cards_fn.R │ ├── test-check_ard_structure.R │ ├── test-ard_formals.R │ ├── test-ard_total_n.R │ ├── test-label_round.R │ ├── test-get_ard_statistics.R │ ├── test-replace_null_statistic.R │ ├── test-round5.R │ ├── test-as_card.R │ ├── test-nest_for_ard.R │ ├── test-tidy_ard_row_order.R │ ├── test-ard_attributes.R │ ├── test-print.R │ ├── test-add_calculated_row.R │ ├── test-ard_identity.R │ ├── test-rename_ard_groups.R │ ├── test-options.R │ ├── test-rename_ard_columns.R │ ├── test-tidy_ard_column_order.R │ ├── test-bind_ard.R │ ├── test-unlist_ard_columns.R │ ├── test-ard_strata.R │ ├── test-selectors.R │ ├── test-ard_missing.R │ └── test-eval_capture_conditions.R └── testthat.R ├── codecov.yml ├── R ├── data.R ├── cards-package.R ├── default_stat_labels.R ├── as_card.R ├── maximum_variable_value.R ├── round5.R ├── reexports.R ├── options.R ├── ard_total_n.R ├── ard_tabulate_rows.R ├── import-standalone-tibble.R ├── ard_identity.R ├── replace_null_statistic.R ├── import-standalone-cli_call_env.R ├── ard_formals.R ├── syntax.R ├── as_card_fn.R ├── get_ard_statistics.R ├── unlist_ard_columns.R ├── utils.R ├── summary_functions.R ├── import-standalone-forcats.R └── tidy_ard_order.R ├── cran-comments.md ├── .Rbuildignore ├── cards.Rproj ├── inst └── WORDLIST └── DESCRIPTION /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /.revdeprefs.yaml: -------------------------------------------------------------------------------- 1 | - insightsengineering/cardx 2 | - insightsengineering/crane 3 | -------------------------------------------------------------------------------- /data/ADAE.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/data/ADAE.rda -------------------------------------------------------------------------------- /data/ADLB.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/data/ADLB.rda -------------------------------------------------------------------------------- /data/ADSL.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/data/ADSL.rda -------------------------------------------------------------------------------- /data/ADTTE.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/data/ADTTE.rda -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /CRAN-SUBMISSION: -------------------------------------------------------------------------------- 1 | Version: 0.7.1 2 | Date: 2025-12-02 05:31:23 UTC 3 | SHA: cf2c2e7e78ed097b08b42d48ee0b66c670c305f9 4 | -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /data-raw/cards-hex-sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/data-raw/cards-hex-sticker.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | docs 3 | .DS_Store 4 | tests/testthat/_snaps/**/*.new.md 5 | tests/testthat/_snaps/**/*.new.svg 6 | revdep 7 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/cards/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | include: 4 | - project: 'nest/automation/gitlab-shared-library' 5 | ref: main 6 | file: R/R_NEST_min.gitlab-ci.yml 7 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_tabulate_rows.R: -------------------------------------------------------------------------------- 1 | test_that("ard_tabulate_rows() works", { 2 | expect_snapshot( 3 | ard_tabulate_rows(ADSL, by = TRTA) 4 | ) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/testthat/test-as_nested_list.R: -------------------------------------------------------------------------------- 1 | test_that("as_nested_list() works", { 2 | expect_snapshot( 3 | ard_summary(mtcars, by = "cyl", variables = "hp") |> 4 | as_nested_list() 5 | ) 6 | }) 7 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_identity.md: -------------------------------------------------------------------------------- 1 | # ard_identity() messaging 2 | 3 | Code 4 | ard_identity(x = as.list(letters), variable = "AGE") 5 | Condition 6 | Error in `ard_identity()`: 7 | ! The `x` argument must be named. 8 | 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Ask a Question 4 | url: https://stackoverflow.com/questions/ask?tags=r-cards,r 5 | about: Question must include a minimal reproducible example, aka a reprex. 6 | -------------------------------------------------------------------------------- /tests/testthat/test-as_cards_fn.R: -------------------------------------------------------------------------------- 1 | test_that("as_cards_fn() works", { 2 | expect_silent( 3 | fn <- as_cards_fn(\(x) list(one = 1, two = 2), stat_names = c("one", "two")) 4 | ) 5 | expect_s3_class(fn, "cards_fn") 6 | expect_equal(get_cards_fn_stat_names(fn), c("one", "two")) 7 | }) 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' Example ADaM Data 2 | #' 3 | #' Data frame imported from the [CDISC SDTM/ADaM Pilot Project](https://github.com/cdisc-org/sdtm-adam-pilot-project) 4 | #' @name adam 5 | #' @keywords datasets 6 | "ADSL" 7 | 8 | #' @rdname adam 9 | "ADAE" 10 | 11 | #' @rdname adam 12 | "ADTTE" 13 | 14 | #' @rdname adam 15 | "ADLB" 16 | -------------------------------------------------------------------------------- /tests/testthat/test-check_ard_structure.R: -------------------------------------------------------------------------------- 1 | test_that("check_ard_structure() works", { 2 | expect_snapshot( 3 | ard_summary(ADSL, variables = "AGE") |> 4 | dplyr::mutate(stat = unlist(stat)) |> 5 | dplyr::select(-error) |> 6 | structure(class = "data.frame") |> 7 | check_ard_structure() 8 | ) 9 | }) 10 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 7 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 0 new problems 6 | * We failed to check 0 packages 7 | 8 | ## R CMD check results 9 | 10 | 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ 11 | 12 | ## Additional Comments 13 | 14 | * Thank you for your time! 15 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^cards\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | ^\.git$ 9 | ^\.github$ 10 | ^codecov\.yml$ 11 | ^data-raw$ 12 | ^vignettes/articles$ 13 | ^\.DS_Store$ 14 | ^cran-comments\.md$ 15 | ^CRAN-SUBMISSION$ 16 | ^.revdeprefs\.yaml$ 17 | ^revdep$ 18 | ^\.covrignore$ 19 | ^\.gitlab-ci\.yml$ 20 | ^CODE_OF_CONDUCT\.md$ 21 | -------------------------------------------------------------------------------- /R/cards-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @import rlang 3 | #' @importFrom dplyr across 4 | #' @importFrom lifecycle deprecated 5 | "_PACKAGE" 6 | 7 | ## usethis namespace: start 8 | ## usethis namespace: end 9 | NULL 10 | 11 | utils::globalVariables(c(".", "!<-", "parse_expr<-")) 12 | 13 | release_bullets <- function() { 14 | c("Install package and re-build `pkgdown/index.Rmd`") 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | name: CLA 🔏 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | # For PRs that originate from forks 8 | pull_request_target: 9 | types: 10 | - opened 11 | - closed 12 | - synchronize 13 | 14 | jobs: 15 | CLA: 16 | name: CLA 📝 17 | uses: insightsengineering/.github/.github/workflows/cla.yaml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/post-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Post release ✨ 3 | 4 | on: 5 | release: 6 | types: ["released"] 7 | 8 | jobs: 9 | vbump: 10 | name: Version Bump 🤜🤛 11 | uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main 12 | secrets: 13 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 14 | with: 15 | vbump-after-release: true 16 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_formals.R: -------------------------------------------------------------------------------- 1 | test_that("ard_formals() works", { 2 | expect_snapshot( 3 | ard_formals(fun = mcnemar.test, arg_names = "correct") 4 | ) 5 | 6 | expect_snapshot( 7 | ard_formals( 8 | fun = asNamespace("stats")[["t.test.default"]], 9 | arg_names = c("mu", "paired", "var.equal", "conf.level"), 10 | passed_args = list(conf.level = 0.90) 11 | ) 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_total_n.R: -------------------------------------------------------------------------------- 1 | test_that("ard_total_n() works", { 2 | expect_snapshot( 3 | ard_total_n(ADSL) |> 4 | as.data.frame() 5 | ) 6 | 7 | expect_snapshot( 8 | error = TRUE, 9 | ard_total_n(letters) 10 | ) 11 | }) 12 | 13 | test_that("ard_total_n() follows ard structure", { 14 | expect_silent( 15 | ard_total_n(ADSL) |> 16 | check_ard_structure(method = FALSE) 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(cards) 11 | 12 | test_check("cards") 13 | -------------------------------------------------------------------------------- /man/default_stat_labels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/default_stat_labels.R 3 | \name{default_stat_labels} 4 | \alias{default_stat_labels} 5 | \title{Defaults for Statistical Arguments} 6 | \usage{ 7 | default_stat_labels() 8 | } 9 | \value{ 10 | named list 11 | } 12 | \description{ 13 | Returns a named list of statistics labels 14 | } 15 | \examples{ 16 | # stat labels 17 | default_stat_labels() 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/test-label_round.R: -------------------------------------------------------------------------------- 1 | test_that("label_round() works", { 2 | expect_equal( 3 | label_round(scale = 100, digits = 2)(9:10), 4 | c("900.00", "1000.00") 5 | ) 6 | 7 | expect_equal( 8 | label_round(digits = 2, width = 5)(9:10), 9 | c(" 9.00", "10.00") 10 | ) 11 | 12 | expect_equal( 13 | label_round()(NA), 14 | NA_character_ 15 | ) 16 | 17 | expect_equal( 18 | label_round(width = 5)(c(NA, 1)), 19 | c(NA_character_, " 1.0") 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/options.md: -------------------------------------------------------------------------------- 1 | # options(cards.round_type) messaging 2 | 3 | Code 4 | withr::with_options(list(cards.round_type = "NOT-CORRECT"), ard_tabulate( 5 | data.frame(x = c(T, F)), variables = everything(), statistic = ~"p")) 6 | Condition 7 | Error in `dplyr::mutate()`: 8 | i In argument: `fmt_fun = pmap(...)`. 9 | Caused by error in `ard_tabulate()`: 10 | ! The `cards.round_type` option must be one of "round-half-up" and "round-to-even". 11 | 12 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_total_n.md: -------------------------------------------------------------------------------- 1 | # ard_total_n() works 2 | 3 | Code 4 | as.data.frame(ard_total_n(ADSL)) 5 | Output 6 | variable context stat_name stat_label stat fmt_fun warning error 7 | 1 ..ard_total_n.. total_n N N 254 0 NULL NULL 8 | 9 | --- 10 | 11 | Code 12 | ard_total_n(letters) 13 | Condition 14 | Error in `UseMethod()`: 15 | ! no applicable method for 'ard_total_n' applied to an object of class "character" 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/check_ard_structure.md: -------------------------------------------------------------------------------- 1 | # check_ard_structure() works 2 | 3 | Code 4 | check_ard_structure(structure(dplyr::select(dplyr::mutate(ard_summary(ADSL, 5 | variables = "AGE"), stat = unlist(stat)), -error), class = "data.frame")) 6 | Message 7 | Object is not of class . 8 | The following columns are not present: "error". 9 | Expecting a row with `stat_name = 'method'`, but it is not present. 10 | The following columns are expected to be list columns: "stat". 11 | 12 | -------------------------------------------------------------------------------- /tests/testthat/test-get_ard_statistics.R: -------------------------------------------------------------------------------- 1 | test_that("get_ard_statistics() works", { 2 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 3 | 4 | expect_snapshot( 5 | get_ard_statistics( 6 | ard, 7 | group1_level %in% "Placebo", 8 | variable_level %in% "65-80" 9 | ) 10 | ) 11 | 12 | expect_snapshot( 13 | get_ard_statistics( 14 | ard, 15 | group1_level %in% "Placebo", 16 | variable_level %in% "65-80", 17 | .attributes = c("warning", "error") 18 | ) 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /.github/workflows/recheck.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | which: 5 | type: choice 6 | description: Which dependents to check 7 | options: 8 | - strong 9 | - most 10 | 11 | name: Reverse dependency check 12 | 13 | jobs: 14 | revdep_check: 15 | name: Reverse check ${{ inputs.which }} dependents 16 | uses: r-devel/recheck/.github/workflows/recheck.yml@v1 17 | with: 18 | which: ${{ inputs.which }} 19 | subdirectory: '' #if your package is in a git subdir 20 | -------------------------------------------------------------------------------- /cards.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 11aa741b-8d1b-4431-8528-e24313ebcf83 3 | 4 | RestoreWorkspace: No 5 | SaveWorkspace: No 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | LineEndingConversion: Posix 19 | 20 | BuildType: Package 21 | PackageUseDevtools: Yes 22 | PackageInstallArgs: --no-multiarch --with-keep.source 23 | PackageRoxygenize: rd,collate,namespace 24 | -------------------------------------------------------------------------------- /man/dot-is_named_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.is_named_list} 4 | \alias{.is_named_list} 5 | \title{Named List Predicate} 6 | \usage{ 7 | .is_named_list(x, allow_df = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{any})\cr 11 | object to check} 12 | } 13 | \value{ 14 | a logical 15 | } 16 | \description{ 17 | A predicate function to check whether input is a named list and \emph{not} a data frame. 18 | } 19 | \examples{ 20 | cards:::.is_named_list(list(a = 1:3)) 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/as_card.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_card.R 3 | \name{as_card} 4 | \alias{as_card} 5 | \title{Data Frame as ARD} 6 | \usage{ 7 | as_card(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | } 13 | \value{ 14 | an ARD data frame of class 'card' 15 | } 16 | \description{ 17 | Convert data frames to ARDs of class 'card'. 18 | } 19 | \examples{ 20 | data.frame( 21 | stat_name = c("N", "mean"), 22 | stat_label = c("N", "Mean"), 23 | stat = c(10, 0.5) 24 | ) |> 25 | as_card() 26 | } 27 | -------------------------------------------------------------------------------- /tests/testthat/test-replace_null_statistic.R: -------------------------------------------------------------------------------- 1 | test_that("replace_null_statistic() works", { 2 | expect_error( 3 | ard_with_missing_stats <- 4 | data.frame(x = rep_len(NA_character_, 10)) |> 5 | ard_summary( 6 | variables = x, 7 | statistic = ~ continuous_summary_fns(c("median", "p25", "p75")) 8 | ) |> 9 | replace_null_statistic(rows = !is.null(error)), 10 | NA 11 | ) 12 | 13 | # all results should now be NA_character 14 | expect_equal( 15 | ard_with_missing_stats$stat |> unlist() |> unique(), 16 | NA_character_ 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/as_card.md: -------------------------------------------------------------------------------- 1 | # as_card() works 2 | 3 | Code 4 | as_card(data.frame(stat_name = c("N", "mean"), stat_label = c("N", "Mean"), 5 | stat = c(10, 0.5))) 6 | Message 7 | {cards} data frame: 2 x 3 8 | Output 9 | stat_name stat_label stat 10 | 1 N N 10 11 | 2 mean Mean 0.5 12 | 13 | # as_card() error catching works correctly 14 | 15 | Code 16 | as_card("notadataframe") 17 | Condition 18 | Error in `as_card()`: 19 | ! The `x` argument must be class , not a string. 20 | 21 | -------------------------------------------------------------------------------- /tests/testthat/test-round5.R: -------------------------------------------------------------------------------- 1 | test_that("round5() works", { 2 | expect_snapshot({ 3 | x <- seq.int(-10L, 10L, by = 1L) / 2 4 | x <- x[x %% 1 != 0] # remove integers 5 | round5(x) |> setNames(nm = x) 6 | }) 7 | 8 | expect_snapshot({ 9 | x <- seq.int(-100000L, 100000L, by = 10000L) - 1L / 2L 10 | x <- x[x %% 1 != 0] # remove integers 11 | round5(x) |> setNames(nm = x) 12 | }) 13 | 14 | expect_snapshot({ 15 | x <- seq.int(-100000L, 100000L, by = 10000L) + 1L / 2L 16 | x <- x[x %% 1 != 0] # remove integers 17 | round5(x) |> setNames(nm = x) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | ADAE 2 | ADCM 3 | ADLB 4 | ADaM 5 | AE 6 | AEs 7 | ARD 8 | ARD's 9 | ARDs 10 | CDISC 11 | CMD 12 | Codecov 13 | DIARRHOEA 14 | GlaxoSmithKline 15 | Hoffmann 16 | IEC 17 | Lifecycle 18 | ORCID 19 | Pre 20 | Rua 21 | SAS's 22 | SDTM 23 | Unlist 24 | Xanomeline 25 | ata 26 | cardx 27 | cli 28 | de 29 | env 30 | esult 31 | ets 32 | funder 33 | hms 34 | httr 35 | jsonlite 36 | mis 37 | nalysis 38 | namespaced 39 | pre 40 | quosures 41 | reusability 42 | sd 43 | tfrmt 44 | tibble 45 | tibbles 46 | tidyselect 47 | tidyselector 48 | tidyselectors 49 | univariable 50 | unlist 51 | unlists 52 | unnested 53 | unnests 54 | wilcox 55 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_tabulate_rows.md: -------------------------------------------------------------------------------- 1 | # ard_tabulate_rows() works 2 | 3 | Code 4 | ard_tabulate_rows(ADSL, by = TRTA) 5 | Message 6 | {cards} data frame: 3 x 11 7 | Output 8 | group1 group1_level variable variable_level stat_name stat_label stat 9 | 1 TRTA Placebo ..row_count.. TRUE n n 86 10 | 2 TRTA Xanomeli… ..row_count.. TRUE n n 84 11 | 3 TRTA Xanomeli… ..row_count.. TRUE n n 84 12 | Message 13 | i 4 more variables: context, fmt_fun, warning, error 14 | 15 | -------------------------------------------------------------------------------- /man/dot-default_fmt_fun.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_summary.R 3 | \name{.default_fmt_fun} 4 | \alias{.default_fmt_fun} 5 | \title{Add Default Formatting Functions} 6 | \usage{ 7 | .default_fmt_fun(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | } 13 | \value{ 14 | a data frame 15 | } 16 | \description{ 17 | Add Default Formatting Functions 18 | } 19 | \examples{ 20 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") |> 21 | dplyr::mutate(fmt_fun = NA) 22 | 23 | cards:::.default_fmt_fun(ard) 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /tests/testthat/test-as_card.R: -------------------------------------------------------------------------------- 1 | test_that("as_card() works", { 2 | expect_snapshot( 3 | data.frame( 4 | stat_name = c("N", "mean"), 5 | stat_label = c("N", "Mean"), 6 | stat = c(10, 0.5) 7 | ) |> 8 | as_card() 9 | ) 10 | }) 11 | 12 | test_that("as_card() does not affect 'card' objects", { 13 | my_ard <- ard_summary(ADSL, by = "ARM", variables = "AGE") 14 | 15 | expect_identical( 16 | my_ard |> as_card(), 17 | my_ard 18 | ) 19 | }) 20 | 21 | test_that("as_card() error catching works correctly", { 22 | expect_snapshot( 23 | "notadataframe" |> 24 | as_card(), 25 | error = TRUE 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /man/dot-purrr_list_flatten.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.purrr_list_flatten} 4 | \alias{.purrr_list_flatten} 5 | \title{A list_flatten()-like Function} 6 | \usage{ 7 | .purrr_list_flatten(x) 8 | } 9 | \arguments{ 10 | \item{x}{(named \code{list})\cr 11 | a named list} 12 | } 13 | \value{ 14 | a named list 15 | } 16 | \description{ 17 | Function operates similarly to \code{purrr::list_flatten(x, name_spec = "{inner}")}. 18 | } 19 | \examples{ 20 | x <- list(a = 1, b = list(b1 = 2, b2 = 3), c = list(c1 = 4, c2 = list(c2a = 5))) 21 | 22 | cards:::.purrr_list_flatten(x) 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/ard_total_n.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_total_n.R 3 | \name{ard_total_n} 4 | \alias{ard_total_n} 5 | \alias{ard_total_n.data.frame} 6 | \title{ARD Total N} 7 | \usage{ 8 | ard_total_n(data, ...) 9 | 10 | \method{ard_total_n}{data.frame}(data, ...) 11 | } 12 | \arguments{ 13 | \item{data}{(\code{data.frame})\cr 14 | a data frame} 15 | 16 | \item{...}{Arguments passed to methods.} 17 | } 18 | \value{ 19 | an ARD data frame of class 'card' 20 | } 21 | \description{ 22 | Returns the total N for the data frame. 23 | The placeholder variable name returned in the object is \code{"..ard_total_n.."} 24 | } 25 | \examples{ 26 | ard_total_n(ADSL) 27 | } 28 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/process_selectors.md: -------------------------------------------------------------------------------- 1 | # process_formula_selectors() error messaging 2 | 3 | Code 4 | process_formula_selectors(mtcars, variables = list(letters)) 5 | Condition 6 | Error: 7 | ! The `variables` argument must be a named list, list of formulas, a single formula, or empty. 8 | i Review ?syntax (`?cards::syntax()`) for examples and details. 9 | 10 | # compute_formula_selector() selects the last assignment when multiple appear 11 | 12 | Code 13 | lapply(lst_compute_test, function(x) structure(x, .Environment = NULL)) 14 | Output 15 | $hp 16 | [1] "THE DEFAULT" 17 | 18 | $mpg 19 | [1] "Special for MPG" 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature Request 2 | description: Request or propose a new feature 3 | title: 'Feature Request: ' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Do not use this form to ask a question, or ask for assistance. Instead, ask on using the `r-cards` tag. 9 | - type: textarea 10 | id: feature 11 | attributes: 12 | label: What's the feature? 13 | description: Describe the feature and, if applicable, create a mock-up of what you'd like to see. 14 | placeholder: Tell us what you see! 15 | value: "Brief description of the feature request." 16 | validations: 17 | required: true 18 | -------------------------------------------------------------------------------- /man/as_nested_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_nested_list.R 3 | \name{as_nested_list} 4 | \alias{as_nested_list} 5 | \title{ARD as Nested List} 6 | \usage{ 7 | as_nested_list(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | } 13 | \value{ 14 | a nested list 15 | } 16 | \description{ 17 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}\cr 18 | Convert ARDs to nested lists. 19 | } 20 | \examples{ 21 | ard_summary(mtcars, by = "cyl", variables = c("mpg", "hp")) |> 22 | as_nested_list() 23 | } 24 | -------------------------------------------------------------------------------- /man/maximum_variable_value.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/maximum_variable_value.R 3 | \name{maximum_variable_value} 4 | \alias{maximum_variable_value} 5 | \title{Maximum Value} 6 | \usage{ 7 | maximum_variable_value(data) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | } 13 | \value{ 14 | a named list 15 | } 16 | \description{ 17 | For each column in the passed data frame, the function returns a named list 18 | with the value being the largest/last element after a sort. 19 | For factors, the last level is returned, and for logical vectors \code{TRUE} is returned. 20 | } 21 | \examples{ 22 | ADSL[c("AGEGR1", "BMIBLGR1")] |> maximum_variable_value() 23 | } 24 | -------------------------------------------------------------------------------- /man/dot-check_dichotomous_value.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate_value.R 3 | \name{.check_dichotomous_value} 4 | \alias{.check_dichotomous_value} 5 | \title{Perform Value Checks} 6 | \usage{ 7 | .check_dichotomous_value(data, value) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{value}{(named \code{list})\cr 14 | a named list} 15 | } 16 | \value{ 17 | returns invisible if check is successful, throws an error message if not. 18 | } 19 | \description{ 20 | Check the validity of the values passed in \code{ard_tabulate_value(value)}. 21 | } 22 | \examples{ 23 | cards:::.check_dichotomous_value(mtcars, list(cyl = 4)) 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/rename_ard_columns.md: -------------------------------------------------------------------------------- 1 | # rename_ard_columns(columns) messsaging 2 | 3 | Code 4 | rename_ard_columns(ard_tabulate(ADSL, by = ARM, variables = AGEGR1), columns = all_ard_groups()) 5 | Condition 6 | Error in `rename_ard_columns()`: 7 | ! The `column` argument may only select columns using `all_ard_groups("names")` and `all_ard_variables("names")` 8 | i Column "group1_level" is not a valid selection. 9 | 10 | --- 11 | 12 | Code 13 | rename_ard_columns(ard_tabulate(dplyr::rename(ADSL, stat = AGEGR1), by = ARM, 14 | variables = stat)) 15 | Condition 16 | Error in `rename_ard_columns()`: 17 | ! New column name(s) "stat" cannot be added, because they are already present. 18 | 19 | -------------------------------------------------------------------------------- /tests/testthat/test-nest_for_ard.R: -------------------------------------------------------------------------------- 1 | test_that("nest_for_ard() works", { 2 | expect_equal( 3 | nest_for_ard(mtcars, strata = c("cyl", "gear"), rename = TRUE) |> 4 | nrow(), 5 | 8L 6 | ) 7 | 8 | expect_equal( 9 | nest_for_ard(mtcars, rename = TRUE) |> 10 | nrow(), 11 | 1L 12 | ) 13 | 14 | expect_equal( 15 | nest_for_ard(mtcars, by = "am", strata = c("cyl", "gear"), rename = TRUE) |> 16 | nrow(), 17 | 16L 18 | ) 19 | 20 | # check order of lgl variables (see Issue #411) 21 | expect_equal( 22 | mtcars |> 23 | dplyr::mutate(am = as.logical(am)) |> 24 | nest_for_ard(by = "am", include_data = FALSE) |> 25 | dplyr::pull(group1_level) |> 26 | unlist(), 27 | c(FALSE, TRUE) 28 | ) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/testthat/test-tidy_ard_row_order.R: -------------------------------------------------------------------------------- 1 | test_that("tidy_ard_row_order() works", { 2 | skip_if_pkg_not_installed("withr") 3 | withr::local_options(list(width = 120)) 4 | withr::local_seed(1) 5 | 6 | # ensure rows are ordered within descending groups but not variables 7 | expect_snapshot( 8 | data.frame( 9 | x1 = sample(LETTERS[1:5], 30, replace = TRUE), 10 | x2 = sample(LETTERS[6:10], 30, replace = TRUE), 11 | x3 = sample(LETTERS[11:15], 30, replace = TRUE), 12 | zz = 1L, 13 | aa = 1L 14 | ) |> 15 | ard_tabulate( 16 | by = x1:x3, 17 | variables = c(zz, aa), 18 | statistic = everything() ~ "n" 19 | ) |> 20 | dplyr::select(all_ard_groups(), all_ard_variables()) 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_formals.md: -------------------------------------------------------------------------------- 1 | # ard_formals() works 2 | 3 | Code 4 | ard_formals(fun = mcnemar.test, arg_names = "correct") 5 | Message 6 | {cards} data frame: 1 x 3 7 | Output 8 | stat_name stat_label stat 9 | 1 correct correct TRUE 10 | 11 | --- 12 | 13 | Code 14 | ard_formals(fun = asNamespace("stats")[["t.test.default"]], arg_names = c("mu", 15 | "paired", "var.equal", "conf.level"), passed_args = list(conf.level = 0.9)) 16 | Message 17 | {cards} data frame: 4 x 3 18 | Output 19 | stat_name stat_label stat 20 | 1 mu mu 0 21 | 2 paired paired FALSE 22 | 3 var.equal var.equal FALSE 23 | 4 conf.level conf.lev… 0.9 24 | 25 | -------------------------------------------------------------------------------- /R/default_stat_labels.R: -------------------------------------------------------------------------------- 1 | #' Defaults for Statistical Arguments 2 | #' 3 | #' Returns a named list of statistics labels 4 | #' 5 | #' @return named list 6 | #' @export 7 | #' 8 | #' @examples 9 | #' # stat labels 10 | #' default_stat_labels() 11 | default_stat_labels <- function() { 12 | list( 13 | mean = "Mean", 14 | sd = "SD", 15 | var = "Variance", 16 | median = "Median", 17 | p25 = "Q1", 18 | p75 = "Q3", 19 | min = "Min", 20 | max = "Max", 21 | n = "n", 22 | N = "N", 23 | p = "%", 24 | n_cum = "Cumulative n", 25 | p_cum = "Cumulative %", 26 | N_obs = "Vector Length", 27 | N_miss = "N Missing", 28 | N_nonmiss = "N Non-missing", 29 | p_miss = "% Missing", 30 | p_nonmiss = "% Non-missing" 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /man/dot-one_row_ard_to_nested_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_nested_list.R 3 | \name{.one_row_ard_to_nested_list} 4 | \alias{.one_row_ard_to_nested_list} 5 | \title{Convert One Row to Nested List} 6 | \usage{ 7 | .one_row_ard_to_nested_list(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card' with one row} 12 | } 13 | \value{ 14 | an expression that represents an element of a nested list 15 | } 16 | \description{ 17 | Convert One Row to Nested List 18 | } 19 | \examples{ 20 | ard_summary(mtcars, variables = mpg) |> 21 | dplyr::filter(dplyr::row_number() \%in\% 1L) |> 22 | apply_fmt_fun() |> 23 | cards:::.one_row_ard_to_nested_list() 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/dot-check_var_nms.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.check_var_nms} 4 | \alias{.check_var_nms} 5 | \title{Check Variable Names} 6 | \usage{ 7 | .check_var_nms(x, vars_protected) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{vars_protected}{(\code{character})\cr 14 | a character vector of protected names} 15 | } 16 | \value{ 17 | a data frame 18 | } 19 | \description{ 20 | Checks variable names in a data frame against protected names and modifies 21 | them if needed 22 | } 23 | \examples{ 24 | data <- data.frame(a = "x", b = "y", c = "z", ..cards_idx.. = 1) 25 | 26 | cards:::.check_var_nms(data, vars_protected = c("x", "z")) 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /R/as_card.R: -------------------------------------------------------------------------------- 1 | #' Data Frame as ARD 2 | #' 3 | #' Convert data frames to ARDs of class 'card'. 4 | #' 5 | #' @param x (`data.frame`)\cr 6 | #' a data frame 7 | #' 8 | #' @return an ARD data frame of class 'card' 9 | #' @export 10 | #' 11 | #' @examples 12 | #' data.frame( 13 | #' stat_name = c("N", "mean"), 14 | #' stat_label = c("N", "Mean"), 15 | #' stat = c(10, 0.5) 16 | #' ) |> 17 | #' as_card() 18 | as_card <- function(x) { 19 | set_cli_abort_call() 20 | 21 | # check in inputs ------------------------------------------------------------ 22 | check_class(x, cls = "data.frame") 23 | 24 | # convert to class "card" ---------------------------------------------------- 25 | if (inherits(x, "card")) { 26 | x 27 | } else { 28 | structure(x, class = c("card", class(x))) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /man/dot-trim_ard.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.trim_ard} 4 | \alias{.trim_ard} 5 | \title{Trim ARD} 6 | \usage{ 7 | .trim_ard(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | } 13 | \value{ 14 | a tibble 15 | } 16 | \description{ 17 | This function ingests an ARD object and trims columns and rows for downstream use in 18 | displays. The resulting data frame contains only numeric results, no supplemental 19 | information about errors/warnings, and unnested list columns. 20 | } 21 | \examples{ 22 | ard <- bind_ard( 23 | ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1"), 24 | ard_tabulate(ADSL, variables = "ARM") 25 | ) |> 26 | shuffle_ard(trim = FALSE) 27 | 28 | ard |> cards:::.trim_ard() 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. 3 | title: 'Bug Report: ' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Do not use this form to ask a question, or ask for assistance. Instead, ask on using the `r-cards` tag. 9 | - type: textarea 10 | id: what-happened 11 | attributes: 12 | label: What happened? 13 | description: INCLUDE a *minimal* reproducible example (AKA a reprex). If you've never heard of a [reprex](https://reprex.tidyverse.org/) before, start by reading . It'll take minutes to master creating a reprex. 14 | placeholder: Tell us what you see! 15 | value: "Brief description of the problem." 16 | validations: 17 | required: true 18 | 19 | -------------------------------------------------------------------------------- /man/apply_fmt_fun.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/apply_fmt_fun.R 3 | \name{apply_fmt_fun} 4 | \alias{apply_fmt_fun} 5 | \title{Apply Formatting Functions} 6 | \usage{ 7 | apply_fmt_fun(x, replace = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{replace}{(scalar \code{logical})\cr 14 | logical indicating whether to replace values in the \code{'stat_fmt'} column (if present). 15 | Default is \code{FALSE}.} 16 | } 17 | \value{ 18 | an ARD data frame of class 'card' 19 | } 20 | \description{ 21 | Apply the formatting functions to each of the raw statistics. 22 | Function aliases are converted to functions using \code{\link[=alias_as_fmt_fun]{alias_as_fmt_fun()}}. 23 | } 24 | \examples{ 25 | ard_summary(ADSL, variables = "AGE") |> 26 | apply_fmt_fun() 27 | } 28 | -------------------------------------------------------------------------------- /man/dot-create_list_for_attributes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_ard_statistics.R 3 | \name{.create_list_for_attributes} 4 | \alias{.create_list_for_attributes} 5 | \title{Create List for Attributes} 6 | \usage{ 7 | .create_list_for_attributes(ard_subset, attributes, i) 8 | } 9 | \arguments{ 10 | \item{ard_subset}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{attributes}{(\code{character})\cr 14 | a character vector of attribute names} 15 | 16 | \item{i}{(\code{integer})\cr 17 | a row index number} 18 | } 19 | \value{ 20 | a named list 21 | } 22 | \description{ 23 | Create List for Attributes 24 | } 25 | \examples{ 26 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 27 | 28 | cards:::.create_list_for_attributes(ard, c("group1", "group1_level"), 1) 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /man/dot-rename_last_group_as_variable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_hierarchical.R 3 | \name{.rename_last_group_as_variable} 4 | \alias{.rename_last_group_as_variable} 5 | \title{Rename Last Group to Variable} 6 | \usage{ 7 | .rename_last_group_as_variable(df_result, by, variables) 8 | } 9 | \arguments{ 10 | \item{df_result}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | } 13 | \value{ 14 | an ARD data frame of class 'card' 15 | } 16 | \description{ 17 | In the \verb{ard_hierarchical*()} functions, the last grouping variable is 18 | renamed to \code{variable} and \code{variable_level} before being returned. 19 | } 20 | \examples{ 21 | data <- data.frame(x = 1, y = 2, group1 = 3, group2 = 4) 22 | 23 | cards:::.rename_last_group_as_variable(data, by = "ARM", variables = "AESOC") 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/dot-check_no_ard_columns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_summary.R 3 | \name{.check_no_ard_columns} 4 | \alias{.check_no_ard_columns} 5 | \title{Check Protected Column Names} 6 | \usage{ 7 | .check_no_ard_columns(x, exceptions = "...ard_dummy_for_counting...") 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{exceptions}{(\code{string})\cr 14 | character string of column names to exclude from checks} 15 | } 16 | \value{ 17 | returns invisible if check is successful, throws an error message if not. 18 | } 19 | \description{ 20 | Checks that column names in a passed data frame are not protected, that is, 21 | they do not begin with \code{"...ard_"} and end with \code{"..."}. 22 | } 23 | \examples{ 24 | data <- data.frame("ard_x" = 1) 25 | 26 | cards:::.check_no_ard_columns(data) 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /man/dot-unique_and_sorted.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.unique_and_sorted} 4 | \alias{.unique_and_sorted} 5 | \title{ARD-flavor of unique()} 6 | \usage{ 7 | .unique_and_sorted(x, useNA = c("no", "always")) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{any})\cr 11 | a vector} 12 | } 13 | \value{ 14 | a vector 15 | } 16 | \description{ 17 | Essentially a wrapper for \code{unique(x) |> sort()} with \code{NA} levels removed. 18 | For factors, all levels are returned even if they are unobserved. 19 | Similarly, logical vectors always return \code{c(TRUE, FALSE)}, even if 20 | both levels are not observed. 21 | } 22 | \examples{ 23 | cards:::.unique_and_sorted(factor(letters[c(5, 5:1)], levels = letters)) 24 | 25 | cards:::.unique_and_sorted(c(FALSE, TRUE, TRUE, FALSE)) 26 | 27 | cards:::.unique_and_sorted(c(5, 5:1)) 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/dot-cli_condition_messaging.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print_ard_conditions.R 3 | \name{.cli_condition_messaging} 4 | \alias{.cli_condition_messaging} 5 | \title{Print Condition Messages Saved in an ARD} 6 | \usage{ 7 | .cli_condition_messaging(x, msg_type, condition_type) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{msg_type}{(\code{string})\cr 14 | message type. Options are \code{"warning"} and \code{"error"}.} 15 | } 16 | \value{ 17 | returns invisible if check is successful, throws warning/error messages if not. 18 | } 19 | \description{ 20 | Print Condition Messages Saved in an ARD 21 | } 22 | \examples{ 23 | ard <- ard_summary( 24 | ADSL, 25 | by = ARM, 26 | variables = AGE 27 | ) 28 | 29 | cards:::.cli_condition_messaging(ard, msg_type = "error") 30 | } 31 | \keyword{internal} 32 | -------------------------------------------------------------------------------- /R/maximum_variable_value.R: -------------------------------------------------------------------------------- 1 | #' Maximum Value 2 | #' 3 | #' For each column in the passed data frame, the function returns a named list 4 | #' with the value being the largest/last element after a sort. 5 | #' For factors, the last level is returned, and for logical vectors `TRUE` is returned. 6 | #' 7 | #' @param data (`data.frame`)\cr 8 | #' a data frame 9 | #' 10 | #' @return a named list 11 | #' @export 12 | #' 13 | #' @examples 14 | #' ADSL[c("AGEGR1", "BMIBLGR1")] |> maximum_variable_value() 15 | maximum_variable_value <- function(data) { 16 | data |> 17 | lapply( 18 | function(x) { 19 | if (inherits(x, "factor")) { 20 | return(levels(x) |> dplyr::last()) 21 | } 22 | if (inherits(x, "logical")) { 23 | return(TRUE) 24 | } 25 | stats::na.omit(x) |> 26 | unique() |> 27 | sort() |> 28 | dplyr::last() 29 | } 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /man/dot-fill_grps_from_variables.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.fill_grps_from_variables} 4 | \alias{.fill_grps_from_variables} 5 | \title{Back Fill Group Variables} 6 | \usage{ 7 | .fill_grps_from_variables(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | } 13 | \value{ 14 | data frame 15 | } 16 | \description{ 17 | This function back fills the values of group variables using 18 | variable/variable_levels. The back filling will occur if the value of the 19 | \code{variable} column matches the name of a grouping variable, and the grouping 20 | variable's value is \code{NA}. 21 | } 22 | \examples{ 23 | data <- data.frame( 24 | variable = c(rep("A", 3), rep("B", 2)), 25 | variable_level = 1:5, 26 | A = rep(NA, 5), 27 | B = rep(NA, 5) 28 | ) 29 | 30 | cards:::.fill_grps_from_variables(data) 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /R/round5.R: -------------------------------------------------------------------------------- 1 | #' Rounding of Numbers 2 | #' 3 | #' Rounds the values in its first argument to the specified number of 4 | #' decimal places (default 0). Importantly, `round5()` **does not** use Base R's 5 | #' "round to even" default. Standard rounding methods are implemented, for example, 6 | #' `cards::round5(0.5) = 1`, whereas `base::round(0.5) = 0`. 7 | #' 8 | #' @details 9 | #' Function inspired by `janitor::round_half_up()`. 10 | #' 11 | #' @param x (`numeric`)\cr 12 | #' a numeric vector 13 | #' @param digits (`integer`)\cr 14 | #' integer indicating the number of decimal places 15 | #' 16 | #' @return a numeric vector 17 | #' @export 18 | #' 19 | #' @examples 20 | #' x <- 0:4 / 2 21 | #' round5(x) |> setNames(x) 22 | #' 23 | #' # compare results to Base R 24 | #' round(x) |> setNames(x) 25 | round5 <- function(x, digits = 0) { 26 | trunc(abs(x) * 10^digits + 0.5 + sqrt(.Machine$double.eps)) / 10^digits * sign(as.numeric(x)) 27 | } 28 | -------------------------------------------------------------------------------- /man/label_round.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/apply_fmt_fun.R 3 | \name{label_round} 4 | \alias{label_round} 5 | \title{Generate Formatting Function} 6 | \usage{ 7 | label_round(digits = 1, scale = 1, width = NULL) 8 | } 9 | \arguments{ 10 | \item{digits}{(\code{integer})\cr 11 | a non-negative integer specifying the number of decimal places 12 | round statistics to} 13 | 14 | \item{scale}{(\code{numeric})\cr 15 | a scalar real number. Before rounding, the input will be scaled by 16 | this quantity} 17 | 18 | \item{width}{(\code{integer})\cr 19 | a non-negative integer specifying the minimum width of the 20 | returned formatted values} 21 | } 22 | \value{ 23 | a function 24 | } 25 | \description{ 26 | Returns a function with the requested rounding and scaling schema. 27 | } 28 | \examples{ 29 | label_round(2)(pi) 30 | label_round(1, scale = 100)(pi) 31 | label_round(2, width = 5)(pi) 32 | } 33 | -------------------------------------------------------------------------------- /man/dot-eval_ard_calls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_stack.R 3 | \name{.eval_ard_calls} 4 | \alias{.eval_ard_calls} 5 | \title{Evaluate the \verb{ard_*()} function calls} 6 | \usage{ 7 | .eval_ard_calls(data, .by, ...) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{.by}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 14 | columns to tabulate by in the series of ARD function calls} 15 | 16 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 17 | Series of ARD function calls to be run and stacked} 18 | } 19 | \value{ 20 | list of ARD data frames of class 'card' 21 | } 22 | \description{ 23 | Evaluate the \verb{ard_*()} function calls 24 | } 25 | \examples{ 26 | cards:::.eval_ard_calls( 27 | data = ADSL, 28 | .by = "ARM", 29 | ard_tabulate(variables = "AGEGR1"), 30 | ard_summary(variables = "AGE") 31 | ) 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get_ard_statistics.md: -------------------------------------------------------------------------------- 1 | # get_ard_statistics() works 2 | 3 | Code 4 | get_ard_statistics(ard, group1_level %in% "Placebo", variable_level %in% 5 | "65-80") 6 | Output 7 | $n 8 | [1] 42 9 | 10 | $N 11 | [1] 86 12 | 13 | $p 14 | [1] 0.4883721 15 | 16 | 17 | --- 18 | 19 | Code 20 | get_ard_statistics(ard, group1_level %in% "Placebo", variable_level %in% 21 | "65-80", .attributes = c("warning", "error")) 22 | Output 23 | $n 24 | [1] 42 25 | attr(,"warning") 26 | [1] "ARM" 27 | attr(,"error") 28 | [1] "Placebo" 29 | 30 | $N 31 | [1] 86 32 | attr(,"warning") 33 | [1] "ARM" 34 | attr(,"error") 35 | [1] "Placebo" 36 | 37 | $p 38 | [1] 0.4883721 39 | attr(,"warning") 40 | [1] "ARM" 41 | attr(,"error") 42 | [1] "Placebo" 43 | 44 | 45 | -------------------------------------------------------------------------------- /man/adam.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{adam} 5 | \alias{adam} 6 | \alias{ADSL} 7 | \alias{ADAE} 8 | \alias{ADTTE} 9 | \alias{ADLB} 10 | \title{Example ADaM Data} 11 | \format{ 12 | An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 254 rows and 49 columns. 13 | 14 | An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 1191 rows and 56 columns. 15 | 16 | An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 254 rows and 26 columns. 17 | 18 | An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 5784 rows and 46 columns. 19 | } 20 | \usage{ 21 | ADSL 22 | 23 | ADAE 24 | 25 | ADTTE 26 | 27 | ADLB 28 | } 29 | \description{ 30 | Data frame imported from the \href{https://github.com/cdisc-org/sdtm-adam-pilot-project}{CDISC SDTM/ADaM Pilot Project} 31 | } 32 | \keyword{datasets} 33 | -------------------------------------------------------------------------------- /man/round5.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/round5.R 3 | \name{round5} 4 | \alias{round5} 5 | \title{Rounding of Numbers} 6 | \usage{ 7 | round5(x, digits = 0) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{numeric})\cr 11 | a numeric vector} 12 | 13 | \item{digits}{(\code{integer})\cr 14 | integer indicating the number of decimal places} 15 | } 16 | \value{ 17 | a numeric vector 18 | } 19 | \description{ 20 | Rounds the values in its first argument to the specified number of 21 | decimal places (default 0). Importantly, \code{round5()} \strong{does not} use Base R's 22 | "round to even" default. Standard rounding methods are implemented, for example, 23 | \code{cards::round5(0.5) = 1}, whereas \code{base::round(0.5) = 0}. 24 | } 25 | \details{ 26 | Function inspired by \code{janitor::round_half_up()}. 27 | } 28 | \examples{ 29 | x <- 0:4 / 2 30 | round5(x) |> setNames(x) 31 | 32 | # compare results to Base R 33 | round(x) |> setNames(x) 34 | } 35 | -------------------------------------------------------------------------------- /man/dot-detect_msgs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.detect_msgs} 4 | \alias{.detect_msgs} 5 | \title{Detect Columns with Non-Null Contents} 6 | \usage{ 7 | .detect_msgs(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 14 | columns to search within} 15 | } 16 | \description{ 17 | Function looks for non-null contents in requested columns and notifies user 18 | before removal. Specifically used for detecting messages. 19 | } 20 | \examples{ 21 | ard <- ard_summary( 22 | ADSL, 23 | by = ARM, 24 | variables = AGE, 25 | statistic = ~ list( 26 | mean = \(x) mean(x), 27 | mean_warning = \(x) { 28 | warning("warn1") 29 | warning("warn2") 30 | mean(x) 31 | }, 32 | err_fn = \(x) stop("'tis an error") 33 | ) 34 | ) 35 | 36 | cards:::.detect_msgs(ard, "warning", "error") 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /man/check_ard_structure.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check_ard_structure.R 3 | \name{check_ard_structure} 4 | \alias{check_ard_structure} 5 | \title{Check ARD Structure} 6 | \usage{ 7 | check_ard_structure(x, column_order = TRUE, method = TRUE) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{column_order}{(scalar \code{logical})\cr 14 | check whether ordering of columns adheres to to \code{cards::tidy_ard_column_order()}.} 15 | 16 | \item{method}{(scalar \code{logical})\cr 17 | check whether a \code{"stat_name"} equal to \code{"method"} appears in results.} 18 | } 19 | \value{ 20 | an ARD data frame of class 'card' (invisible) 21 | } 22 | \description{ 23 | Function tests the structure and returns notes when object does not 24 | conform to expected structure. 25 | } 26 | \examples{ 27 | ard_summary(ADSL, variables = "AGE") |> 28 | dplyr::select(-warning, -error) |> 29 | check_ard_structure() 30 | } 31 | -------------------------------------------------------------------------------- /man/dot-check_fmt_string.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/apply_fmt_fun.R 3 | \name{.check_fmt_string} 4 | \alias{.check_fmt_string} 5 | \title{Check 'xx' Format Structure} 6 | \usage{ 7 | .check_fmt_string(x, variable, stat_name) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{string})\cr 11 | string to check} 12 | 13 | \item{variable}{(\code{character})\cr the variable whose statistic is to be formatted} 14 | 15 | \item{stat_name}{(\code{character})\cr the name of the statistic that is to be formatted} 16 | } 17 | \value{ 18 | a logical 19 | } 20 | \description{ 21 | A function that checks a \strong{single} string for consistency. 22 | String must begin with 'x' and only consist of x's, a single period or none, 23 | and may end with a percent symbol. 24 | 25 | If string is consistent, \code{TRUE} is returned. Otherwise an error. 26 | } 27 | \examples{ 28 | cards:::.check_fmt_string("xx.x") # TRUE 29 | cards:::.check_fmt_string("xx.x\%") # TRUE 30 | } 31 | \keyword{internal} 32 | -------------------------------------------------------------------------------- /man/dot-process_nested_list_as_df.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_summary.R 3 | \name{.process_nested_list_as_df} 4 | \alias{.process_nested_list_as_df} 5 | \title{Convert Nested Lists to Column} 6 | \usage{ 7 | .process_nested_list_as_df(x, arg, new_column, unlist = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | result data frame} 12 | 13 | \item{arg}{(\code{list})\cr 14 | the nested list} 15 | 16 | \item{new_column}{(\code{string})\cr 17 | new column name} 18 | 19 | \item{unlist}{(\code{logical})\cr 20 | whether to fully unlist final results} 21 | } 22 | \value{ 23 | a data frame 24 | } 25 | \description{ 26 | Some arguments, such as \code{stat_label}, are passed as nested lists. This 27 | function properly unnests these lists and adds them to the results data frame. 28 | } 29 | \examples{ 30 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 31 | 32 | cards:::.process_nested_list_as_df(ard, NULL, "new_col") 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/dot-cli_groups_and_variable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print_ard_conditions.R 3 | \name{.cli_groups_and_variable} 4 | \alias{.cli_groups_and_variable} 5 | \title{Locate Condition Messages in an ARD} 6 | \usage{ 7 | .cli_groups_and_variable(x) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | } 13 | \value{ 14 | a string 15 | } 16 | \description{ 17 | Prints a string of all \code{group##}/\code{group##_level} column values and 18 | \code{variable} column values where condition messages occur, formatted 19 | using glue syntax. 20 | } 21 | \examples{ 22 | ard <- ard_summary( 23 | ADSL, 24 | by = ARM, 25 | variables = AGE, 26 | statistic = ~ list( 27 | mean = \(x) mean(x), 28 | mean_warning = \(x) { 29 | warning("warn1") 30 | warning("warn2") 31 | mean(x) 32 | }, 33 | err_fn = \(x) stop("'tis an error") 34 | ) 35 | ) 36 | 37 | cards:::.cli_groups_and_variable(ard) 38 | } 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /R/reexports.R: -------------------------------------------------------------------------------- 1 | # dplyr ------------------------------------------------------------------------ 2 | #' @export 3 | #' @importFrom dplyr %>% 4 | dplyr::`%>%` 5 | 6 | #' @importFrom dplyr starts_with 7 | #' @export 8 | dplyr::starts_with 9 | 10 | #' @importFrom dplyr ends_with 11 | #' @export 12 | dplyr::ends_with 13 | 14 | #' @importFrom dplyr contains 15 | #' @export 16 | dplyr::contains 17 | 18 | #' @importFrom dplyr matches 19 | #' @export 20 | dplyr::matches 21 | 22 | #' @importFrom dplyr num_range 23 | #' @export 24 | dplyr::num_range 25 | 26 | #' @importFrom dplyr all_of 27 | #' @export 28 | dplyr::all_of 29 | 30 | #' @importFrom dplyr any_of 31 | #' @export 32 | dplyr::any_of 33 | 34 | #' @importFrom dplyr everything 35 | #' @export 36 | dplyr::everything 37 | 38 | #' @importFrom dplyr where 39 | #' @export 40 | dplyr::where 41 | 42 | #' @importFrom dplyr last_col 43 | #' @export 44 | dplyr::last_col 45 | 46 | #' @importFrom dplyr one_of 47 | #' @export 48 | dplyr::one_of 49 | 50 | #' @importFrom dplyr vars 51 | #' @export 52 | dplyr::vars 53 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_attributes.R: -------------------------------------------------------------------------------- 1 | skip_if_pkg_not_installed("withr") 2 | 3 | test_that("ard_attributes() works", { 4 | withr::local_options(list(width = 120)) 5 | expect_snapshot({ 6 | df <- dplyr::tibble(var1 = letters, var2 = LETTERS) 7 | attr(df$var1, "label") <- "Lowercase Letters" 8 | 9 | ard_attributes(df, variables = everything(), label = list(var2 = "UPPERCASE LETTERS")) |> 10 | as.data.frame() 11 | }) 12 | }) 13 | 14 | test_that("ard_attributes() errors when there is no dataframe", { 15 | expect_error( 16 | ard_attributes("test"), 17 | "There is no method for objects of class ." 18 | ) 19 | }) 20 | 21 | test_that("ard_attributes() follows ard structure", { 22 | expect_silent( 23 | ard_attributes(ADSL[c("AGE", "AGEGR1")]) |> 24 | check_ard_structure(method = FALSE) 25 | ) 26 | }) 27 | 28 | test_that("ard_attributes() requires label as a named list", { 29 | expect_snapshot( 30 | error = TRUE, 31 | ard_attributes(ADSL[c("AGE", "AGEGR1")], 32 | label = list("test") 33 | ) 34 | ) 35 | }) 36 | -------------------------------------------------------------------------------- /man/rename_ard_groups.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rename_ard_groups.R 3 | \name{rename_ard_groups} 4 | \alias{rename_ard_groups} 5 | \alias{rename_ard_groups_shift} 6 | \alias{rename_ard_groups_reverse} 7 | \title{Rename ARD Group Columns} 8 | \usage{ 9 | rename_ard_groups_shift(x, shift = -1) 10 | 11 | rename_ard_groups_reverse(x) 12 | } 13 | \arguments{ 14 | \item{x}{(\code{data.frame})\cr 15 | an ARD data frame of class 'card'.} 16 | 17 | \item{shift}{(\code{integer})\cr 18 | an integer specifying how many values to shift the group IDs, 19 | e.g. \code{shift=-1} renames \code{group2} to \code{group1}.} 20 | } 21 | \value{ 22 | an ARD data frame of class 'card' 23 | } 24 | \description{ 25 | Functions for renaming group columns names in ARDs. 26 | } 27 | \examples{ 28 | ard <- ard_summary(ADSL, by = c(SEX, ARM), variables = AGE) 29 | 30 | # Example 1 ---------------------------------- 31 | rename_ard_groups_shift(ard, shift = -1) 32 | 33 | # Example 2 ---------------------------------- 34 | rename_ard_groups_reverse(ard) 35 | } 36 | -------------------------------------------------------------------------------- /man/print_ard_conditions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print_ard_conditions.R 3 | \name{print_ard_conditions} 4 | \alias{print_ard_conditions} 5 | \title{Print ARD Condition Messages} 6 | \usage{ 7 | print_ard_conditions(x, condition_type = c("inform", "identity")) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{condition_type}{(\code{string})\cr 14 | indicates how warnings and errors are returned. 15 | Default is \code{"inform"} where all are returned as messages. 16 | When \code{"identity"}, errors are returned as errors and warnings as warnings.} 17 | } 18 | \value{ 19 | returns invisible if check is successful, throws all condition messages if not. 20 | } 21 | \description{ 22 | Function parses the errors and warnings observed while calculating the 23 | statistics requested in the ARD and prints them to the console as messages. 24 | } 25 | \examples{ 26 | # passing a character variable for numeric summary 27 | ard_summary(ADSL, variables = AGEGR1) |> 28 | print_ard_conditions() 29 | } 30 | -------------------------------------------------------------------------------- /man/dot-lst_results_as_df.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_summary.R 3 | \name{.lst_results_as_df} 4 | \alias{.lst_results_as_df} 5 | \title{Prepare Results as Data Frame} 6 | \usage{ 7 | .lst_results_as_df(x, variable, fun_name, fun) 8 | } 9 | \arguments{ 10 | \item{x}{(named \code{list})\cr 11 | the result from \code{\link[=eval_capture_conditions]{eval_capture_conditions()}}} 12 | 13 | \item{variable}{(\code{string})\cr 14 | variable name of the results} 15 | 16 | \item{fun_name}{(\code{string})\cr 17 | name of function called to get results in \code{x}} 18 | } 19 | \value{ 20 | a data frame 21 | } 22 | \description{ 23 | Function takes the results from \code{\link[=eval_capture_conditions]{eval_capture_conditions()}}, which is a 24 | named list, e.g. \code{list(result=, warning=, error=)}, and converts it to a data 25 | frame. 26 | } 27 | \examples{ 28 | msgs <- eval_capture_conditions({ 29 | warning("Warning 1") 30 | warning("Warning 2") 31 | letters[1:2] 32 | }) 33 | 34 | cards:::.lst_results_as_df(msgs, "result", "mean") 35 | } 36 | \keyword{internal} 37 | -------------------------------------------------------------------------------- /R/options.R: -------------------------------------------------------------------------------- 1 | #' Options in \{cards\} 2 | #' 3 | #' @name cards.options 4 | #' @description 5 | #' See below for options available in the \{cards\} package 6 | #' 7 | #' @section cards.round_type: 8 | #' There are two types of rounding types in the \{cards\} package that are implemented 9 | #' in `label_round()`, `alias_as_fmt_fun()`, and `apply_fmt_fun()` functions. 10 | #' 11 | #' - `'round-half-up'` (_default_): rounding method where values exactly halfway 12 | #' between two numbers are rounded to the larger in magnitude number. 13 | #' Rounding is implemented via [`round5()`]. 14 | #' - `'round-to-even'`: base R's default IEC 60559 rounding standard. 15 | #' See [`round()`] for details. 16 | #' 17 | #' To change the default rounding to use IEC 60559, this option must be set **both** 18 | #' when the ARDs are created and when `apply_fmt_fun()` is run. This ensures that 19 | #' any _default_ formatting functions created with `label_round()` utilize the 20 | #' specified rounding method and the method is used what aliases are converted 21 | #' into functions (which occurs in `apply_fmt_fun()` when it calls `alias_as_fmt_fun()`). 22 | NULL 23 | -------------------------------------------------------------------------------- /man/ard_identity.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_identity.R 3 | \name{ard_identity} 4 | \alias{ard_identity} 5 | \title{ARD Identity} 6 | \usage{ 7 | ard_identity(x, variable, context = "identity") 8 | } 9 | \arguments{ 10 | \item{x}{(named \code{list}/\code{data.frame})\cr 11 | named list of results or a data frame. 12 | Names are the statistic names, and the values 13 | are the statistic values. These comprise the \code{"stat_name"} and \code{"stat"} 14 | columns in the returned ARD.} 15 | 16 | \item{variable}{(\code{string})\cr 17 | string of a variable name that is assigned to the \code{"variable"} column in the 18 | ARD.} 19 | 20 | \item{context}{(\code{string})\cr 21 | string to be added to the \code{"context"} column. Default is \code{"identity"}.} 22 | } 23 | \value{ 24 | a ARD 25 | } 26 | \description{ 27 | Function ingests pre-calculated statistics and returns the identical results, 28 | but in an ARD format. 29 | } 30 | \examples{ 31 | t.test(formula = AGE ~ 1, data = ADSL)[c("statistic", "parameter", "p.value")] |> 32 | ard_identity(variable = "AGE", context = "onesample_t_test") 33 | } 34 | -------------------------------------------------------------------------------- /tests/testthat/test-print.R: -------------------------------------------------------------------------------- 1 | test_that("print.card() works", { 2 | expect_snapshot( 3 | ard_summary(ADSL, by = "ARM", variables = "AGE") 4 | ) 5 | 6 | expect_snapshot( 7 | ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 8 | ) 9 | 10 | expect_snapshot( 11 | ard_summary(ADSL, variables = "AGE", fmt_fun = AGE ~ list(~ \(x) round(x, 3))) 12 | ) 13 | 14 | # checking the print of Dates 15 | expect_snapshot( 16 | ard_summary( 17 | data = data.frame(x = seq(as.Date("2000-01-01"), length.out = 10L, by = "day")), 18 | variables = x, 19 | statistic = ~ continuous_summary_fns(c("min", "max", "sd")) 20 | ) |> 21 | dplyr::select(-fmt_fun) 22 | ) 23 | 24 | # checking the print of a complex matrix statistic result 25 | expect_snapshot( 26 | bind_ard( 27 | ard_attributes(mtcars, variables = mpg), 28 | ard_summary( 29 | mtcars, 30 | variables = mpg, 31 | statistic = 32 | ~ continuous_summary_fns( 33 | "mean", 34 | other_stats = list(vcov = \(x) lm(mpg ~ am, mtcars) |> vcov()) 35 | ) 36 | ) 37 | ) 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs 📚 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "inst/templates/**" 10 | - "_pkgdown.*" 11 | - DESCRIPTION 12 | - "**.md" 13 | - "**.Rmd" 14 | - "man/**" 15 | - "LICENSE.*" 16 | - NAMESPACE 17 | pull_request: 18 | types: 19 | - opened 20 | - synchronize 21 | - reopened 22 | - ready_for_review 23 | branches: 24 | - main 25 | paths: 26 | - "inst/templates/**" 27 | - "_pkgdown.*" 28 | - DESCRIPTION 29 | - "**.md" 30 | - "**.Rmd" 31 | - "man/**" 32 | - "LICENSE.*" 33 | - NAMESPACE 34 | workflow_dispatch: 35 | 36 | jobs: 37 | docs: 38 | name: Pkgdown Docs 📚 39 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 40 | secrets: 41 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 42 | with: 43 | default-landing-page: latest-tag 44 | additional-unit-test-report-directories: unit-test-report-non-cran 45 | deps-installation-method: setup-r-dependencies 46 | update-r-packages: true 47 | -------------------------------------------------------------------------------- /tests/testthat/test-add_calculated_row.R: -------------------------------------------------------------------------------- 1 | test_that("add_calculated_row(x)", { 2 | expect_snapshot( 3 | ard_summary(mtcars, variables = mpg) |> 4 | add_calculated_row(expr = max - min, stat_name = "range") |> 5 | apply_fmt_fun() 6 | ) 7 | 8 | expect_snapshot( 9 | ard_summary(mtcars, variables = mpg) |> 10 | add_calculated_row( 11 | expr = 12 | dplyr::case_when( 13 | mean > median ~ "Right Skew", 14 | mean < median ~ "Left Skew", 15 | .default = "Symmetric" 16 | ), 17 | stat_name = "skew" 18 | ) |> 19 | apply_fmt_fun() 20 | ) 21 | }) 22 | 23 | 24 | test_that("add_calculated_row(expr) messaging", { 25 | expect_snapshot( 26 | ard_summary(mtcars, variables = mpg) |> 27 | add_calculated_row(expr = not_a_stat * 2, stat_name = "this_doesnt_work"), 28 | error = TRUE 29 | ) 30 | }) 31 | 32 | test_that("add_calculated_row(by) messaging", { 33 | expect_snapshot( 34 | ard_summary(mtcars, variables = mpg, by = cyl) |> 35 | add_calculated_row(expr = max - min, stat_name = "range", by = "context"), 36 | error = TRUE 37 | ) 38 | }) 39 | -------------------------------------------------------------------------------- /man/dot-derive_overall_labels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.derive_overall_labels} 4 | \alias{.derive_overall_labels} 5 | \title{Derive overall labels} 6 | \usage{ 7 | .derive_overall_labels(x, cur_col = dplyr::cur_column()) 8 | } 9 | \arguments{ 10 | \item{x}{(character) content of target (current) column} 11 | 12 | \item{cur_col}{(character) name of current column} 13 | } 14 | \value{ 15 | a character vector 16 | } 17 | \description{ 18 | Transform the \code{"..cards_overall.."} and \code{"..hierarchical_overall.."} labels 19 | into \code{"Overall "} and \code{"Any "} respectively. 20 | Also it ensures the labels are unique (in case they already exist) with 21 | \code{make.unique()} which appends a sequence number. 22 | } 23 | \examples{ 24 | data <- dplyr::tibble( 25 | ARM = c("..cards_overall..", "Overall ARM", NA, "BB", NA), 26 | TRTA = c(NA, NA, "..hierarchical_overall..", "C", "C") 27 | ) 28 | 29 | data |> 30 | dplyr::mutate( 31 | dplyr::across( 32 | ARM:TRTA, 33 | cards:::.derive_overall_labels 34 | ) 35 | ) 36 | } 37 | \keyword{internal} 38 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_attributes.md: -------------------------------------------------------------------------------- 1 | # ard_attributes() works 2 | 3 | Code 4 | df <- dplyr::tibble(var1 = letters, var2 = LETTERS) 5 | attr(df$var1, "label") <- "Lowercase Letters" 6 | as.data.frame(ard_attributes(df, variables = everything(), label = list(var2 = "UPPERCASE LETTERS"))) 7 | Output 8 | variable context stat_name stat_label stat fmt_fun warning error 9 | 1 var1 attributes label Variable Label Lowercase Letters .Primitive("as.character") NULL NULL 10 | 2 var1 attributes class Variable Class character NULL NULL NULL 11 | 3 var2 attributes label Variable Label UPPERCASE LETTERS .Primitive("as.character") NULL NULL 12 | 4 var2 attributes class Variable Class character NULL NULL NULL 13 | 14 | # ard_attributes() requires label as a named list 15 | 16 | Code 17 | ard_attributes(ADSL[c("AGE", "AGEGR1")], label = list("test")) 18 | Condition 19 | Error in `ard_attributes()`: 20 | ! The `label` argument must be a named list with each element a string. 21 | 22 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reexports.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{\%>\%} 7 | \alias{starts_with} 8 | \alias{ends_with} 9 | \alias{contains} 10 | \alias{matches} 11 | \alias{num_range} 12 | \alias{all_of} 13 | \alias{any_of} 14 | \alias{everything} 15 | \alias{where} 16 | \alias{last_col} 17 | \alias{one_of} 18 | \alias{vars} 19 | \title{Objects exported from other packages} 20 | \keyword{internal} 21 | \description{ 22 | These objects are imported from other packages. Follow the links 23 | below to see their documentation. 24 | 25 | \describe{ 26 | \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]{vars}}, \code{\link[dplyr:reexports]{where}}} 27 | }} 28 | 29 | -------------------------------------------------------------------------------- /man/dot-check_for_missing_combos_in_denom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate.R 3 | \name{.check_for_missing_combos_in_denom} 4 | \alias{.check_for_missing_combos_in_denom} 5 | \title{Check for Missing Levels in \code{denominator}} 6 | \usage{ 7 | .check_for_missing_combos_in_denom(data, denominator, by, strata) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{denominator}{(\code{data.frame})\cr 14 | denominator data frame} 15 | 16 | \item{by}{(\code{character})\cr 17 | character vector of by column names} 18 | 19 | \item{strata}{(\code{character})\cr 20 | character vector of strata column names} 21 | } 22 | \value{ 23 | returns invisible if check is successful, throws an error message if not. 24 | } 25 | \description{ 26 | When a user passes a data frame in the \code{denominator} argument, this function 27 | checks that the data frame contains all the same levels of the \code{by} 28 | and \code{strata} variables that appear in \code{data}. 29 | } 30 | \examples{ 31 | cards:::.check_for_missing_combos_in_denom(ADSL, denominator = "col", by = "ARM", strata = "AGEGR1") 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_mvsummary.md: -------------------------------------------------------------------------------- 1 | # ard_mvsummary() messaging 2 | 3 | Code 4 | ard_mvsummary(ADSL, by = "ARM", variables = c("AGE", "BMIBL"), statistic = list( 5 | AGE = list(mean = function(x, ...) mean(x)))) 6 | Condition 7 | Error in `ard_mvsummary()`: 8 | ! The following columns do not have `statistic` defined: "BMIBL". 9 | 10 | # ard_mvsummary() errors with incorrect factor columns 11 | 12 | Code 13 | ard_mvsummary(dplyr::mutate(mtcars, am = factor(am, levels = character(0))), 14 | by = "am", variables = "mpg", statistic = list(mpg = list(mean = function(x, 15 | ...) mean(x)))) 16 | Condition 17 | Error in `ard_mvsummary()`: 18 | ! Factors with empty "levels" attribute are not allowed, which was identified in column "am". 19 | 20 | --- 21 | 22 | Code 23 | ard_mvsummary(dplyr::mutate(mtcars, am = factor(am, levels = c(0, 1, NA), 24 | exclude = NULL)), by = "am", variables = "mpg", statistic = list(mpg = list( 25 | mean = function(x, ...) mean(x)))) 26 | Condition 27 | Error in `ard_mvsummary()`: 28 | ! Factors with NA levels are not allowed, which are present in column "am". 29 | 30 | -------------------------------------------------------------------------------- /man/dot-calculate_stats_as_ard.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_summary.R 3 | \name{.calculate_stats_as_ard} 4 | \alias{.calculate_stats_as_ard} 5 | \title{Calculate Continuous Statistics} 6 | \usage{ 7 | .calculate_stats_as_ard( 8 | df_nested, 9 | variables, 10 | statistic, 11 | by, 12 | strata, 13 | data, 14 | new_col_name = "...ard_all_stats..." 15 | ) 16 | } 17 | \arguments{ 18 | \item{df_nested}{(\code{data.frame})\cr 19 | a nested data frame} 20 | 21 | \item{variables}{(\code{character})\cr 22 | character vector of variables} 23 | 24 | \item{statistic}{(named \code{list})\cr 25 | named list of statistical functions} 26 | } 27 | \value{ 28 | an ARD data frame of class 'card' 29 | } 30 | \description{ 31 | Calculate statistics and return in an ARD format 32 | } 33 | \examples{ 34 | data_nested <- ADSL |> 35 | nest_for_ard( 36 | by = "ARM", 37 | strata = NULL, 38 | key = "...ard_nested_data..." 39 | ) 40 | 41 | cards:::.calculate_stats_as_ard( 42 | df_nested = data_nested, 43 | variables = "AGE", 44 | statistic = list(mean = "mean"), 45 | by = "ARM", 46 | strata = NULL, 47 | data = ADSL 48 | ) 49 | } 50 | \keyword{internal} 51 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_identity.R: -------------------------------------------------------------------------------- 1 | test_that("ard_identity() works", { 2 | ttest_result <- t.test(formula = AGE ~ 1, data = ADSL) 3 | lst_result <- ttest_result[c("statistic", "parameter", "p.value")] 4 | 5 | # here we convert a named list to an ARD, then back to a list to ensure accurate conversion 6 | expect_equal( 7 | ard_identity(lst_result, variable = "AGE", context = "ard_onesample_t_test") |> 8 | get_ard_statistics(), 9 | lst_result[c("statistic", "parameter", "p.value")] 10 | ) 11 | expect_equal( 12 | as.data.frame(lst_result) |> 13 | ard_identity(variable = "AGE", context = "ard_onesample_t_test") |> 14 | get_ard_statistics(), 15 | lst_result[c("statistic", "parameter", "p.value")], 16 | ignore_attr = TRUE 17 | ) 18 | 19 | expect_silent( 20 | as.data.frame(lst_result) %>% 21 | {dplyr::bind_rows(., ., .)} |> #styler: off 22 | ard_identity(variable = "AGE", context = "ard_onesample_t_test") |> 23 | get_ard_statistics() 24 | ) 25 | }) 26 | 27 | 28 | test_that("ard_identity() messaging", { 29 | # passing results that are not a named list 30 | expect_snapshot( 31 | error = TRUE, 32 | ard_identity(x = as.list(letters), variable = "AGE") 33 | ) 34 | }) 35 | -------------------------------------------------------------------------------- /R/ard_total_n.R: -------------------------------------------------------------------------------- 1 | #' ARD Total N 2 | #' 3 | #' Returns the total N for the data frame. 4 | #' The placeholder variable name returned in the object is `"..ard_total_n.."` 5 | #' 6 | #' @inheritParams ard_tabulate 7 | #' @inheritParams rlang::args_dots_empty 8 | #' 9 | #' @return an ARD data frame of class 'card' 10 | #' @name ard_total_n 11 | #' 12 | #' @examples 13 | #' ard_total_n(ADSL) 14 | NULL 15 | 16 | #' @rdname ard_total_n 17 | #' @export 18 | ard_total_n <- function(data, ...) { 19 | check_not_missing(data) 20 | UseMethod("ard_total_n") 21 | } 22 | 23 | #' @rdname ard_total_n 24 | #' @export 25 | ard_total_n.data.frame <- function(data, ...) { 26 | # process inputs ------------------------------------------------------------- 27 | set_cli_abort_call() 28 | check_dots_empty() 29 | check_data_frame(data) 30 | 31 | # calculate total N ---------------------------------------------------------- 32 | data |> 33 | dplyr::mutate(..ard_total_n.. = TRUE) |> 34 | ard_tabulate_value( 35 | variables = "..ard_total_n..", 36 | statistic = list(..ard_total_n.. = "N"), 37 | value = list(..ard_total_n.. = TRUE) 38 | ) |> 39 | dplyr::mutate(context = "total_n") |> 40 | dplyr::select(-all_ard_variables("levels")) 41 | } 42 | -------------------------------------------------------------------------------- /R/ard_tabulate_rows.R: -------------------------------------------------------------------------------- 1 | #' Row Tabulate ARD 2 | #' 3 | #' Tabulate the number of rows in a data frame. 4 | #' 5 | #' @inheritParams ard_tabulate_value 6 | #' @param colname (`string`)\cr 7 | #' name of the column that will be returned along with the row tabulation. 8 | #' 9 | #' @return an ARD data frame of class 'card' 10 | #' @export 11 | #' 12 | #' @examples 13 | #' ard_tabulate_rows(ADSL, by = TRTA) 14 | ard_tabulate_rows <- function(data, 15 | colname = "..row_count..", 16 | by = dplyr::group_vars(data), 17 | strata = NULL, 18 | fmt_fun = NULL) { 19 | set_cli_abort_call() 20 | 21 | # check inputs --------------------------------------------------------------- 22 | check_data_frame(data) 23 | check_string(colname) 24 | 25 | # tabulate number of rows ---------------------------------------------------- 26 | ard_tabulate_value( 27 | data = dplyr::mutate(data, "{colname}" := TRUE), 28 | variables = all_of(colname), 29 | by = {{ by }}, 30 | strata = {{ strata }}, 31 | statistic = list("n") |> stats::setNames(colname), 32 | fmt_fun = fmt_fun, 33 | value = list(TRUE) |> stats::setNames(colname) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/rename_ard_groups.md: -------------------------------------------------------------------------------- 1 | # rename_ard_groups_shift() 2 | 3 | Code 4 | dplyr::select(rename_ard_groups_shift(ard_summary(ADSL, variables = AGE, by = c( 5 | SEX, ARM)), shift = 1L), all_ard_groups()) %>% 1L[] 6 | Message 7 | {cards} data frame: 1 x 4 8 | Output 9 | group2 group2_level group3 group3_level 10 | 1 SEX F ARM Placebo 11 | 12 | # rename_ard_groups_shift() messaging 13 | 14 | Code 15 | dplyr::select(rename_ard_groups_shift(ard_summary(ADSL, variables = AGE, by = c( 16 | SEX, ARM)), shift = -1L), all_ard_groups()) %>% 1L[] 17 | Message 18 | There are now non-standard group column names: "group0" and "group0_level". 19 | i Is this the shift you had planned? 20 | {cards} data frame: 1 x 4 21 | Output 22 | group0 group0_level group1 group1_level 23 | 1 SEX F ARM Placebo 24 | 25 | # rename_ard_groups_reverse() 26 | 27 | Code 28 | dplyr::select(rename_ard_groups_reverse(ard_summary(ADSL, variables = AGE, by = c( 29 | SEX, ARM))), all_ard_groups()) %>% 1L[] 30 | Message 31 | {cards} data frame: 1 x 4 32 | Output 33 | group1 group1_level group2 group2_level 34 | 1 ARM Placebo SEX F 35 | 36 | -------------------------------------------------------------------------------- /man/get_ard_statistics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_ard_statistics.R 3 | \name{get_ard_statistics} 4 | \alias{get_ard_statistics} 5 | \title{ARD Statistics as List} 6 | \usage{ 7 | get_ard_statistics(x, ..., .column = "stat", .attributes = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 14 | optional arguments indicating rows to subset of the ARD. 15 | For example, to return only rows where the column \code{"AGEGR1"} is \code{"65-80"}, 16 | pass \code{AGEGR1 \%in\% "65-80"}.} 17 | 18 | \item{.column}{(\code{string})\cr 19 | string indicating the column that will be returned in the list. 20 | Default is \code{"statistic"}} 21 | 22 | \item{.attributes}{(\code{character})\cr 23 | character vector of column names that will be returned 24 | in the list as attributes. 25 | Default is \code{NULL}} 26 | } 27 | \value{ 28 | named list 29 | } 30 | \description{ 31 | Returns the statistics from an ARD as a named list. 32 | } 33 | \examples{ 34 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 35 | 36 | get_ard_statistics( 37 | ard, 38 | group1_level \%in\% "Placebo", 39 | variable_level \%in\% "65-80", 40 | .attributes = "stat_label" 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /tests/testthat/test-rename_ard_groups.R: -------------------------------------------------------------------------------- 1 | test_that("rename_ard_groups_shift()", { 2 | # no errors when no grouping variables 3 | expect_equal( 4 | ard_summary(ADSL, variables = AGE) |> 5 | rename_ard_groups_shift(), 6 | ard_summary(ADSL, variables = AGE) 7 | ) 8 | 9 | # works under normal circumstances 10 | expect_snapshot( 11 | ard_summary(ADSL, variables = AGE, by = c(SEX, ARM)) |> 12 | rename_ard_groups_shift(shift = 1L) |> 13 | dplyr::select(all_ard_groups()) %>% 14 | `[`(1L, ) 15 | ) 16 | }) 17 | 18 | test_that("rename_ard_groups_shift() messaging", { 19 | expect_snapshot( 20 | ard_summary(ADSL, variables = AGE, by = c(SEX, ARM)) |> 21 | rename_ard_groups_shift(shift = -1L) |> 22 | dplyr::select(all_ard_groups()) %>% 23 | `[`(1L, ) 24 | ) 25 | }) 26 | 27 | test_that("rename_ard_groups_reverse()", { 28 | # no errors when no grouping variables 29 | expect_equal( 30 | ard_summary(ADSL, variables = AGE) |> 31 | rename_ard_groups_reverse(), 32 | ard_summary(ADSL, variables = AGE) 33 | ) 34 | 35 | # works under normal circumstances 36 | expect_snapshot( 37 | ard_summary(ADSL, variables = AGE, by = c(SEX, ARM)) |> 38 | rename_ard_groups_reverse() |> 39 | dplyr::select(all_ard_groups()) %>% 40 | `[`(1L, ) 41 | ) 42 | }) 43 | -------------------------------------------------------------------------------- /man/cards.options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options.R 3 | \name{cards.options} 4 | \alias{cards.options} 5 | \title{Options in \{cards\}} 6 | \description{ 7 | See below for options available in the \{cards\} package 8 | } 9 | \section{cards.round_type}{ 10 | 11 | There are two types of rounding types in the \{cards\} package that are implemented 12 | in \code{label_round()}, \code{alias_as_fmt_fun()}, and \code{apply_fmt_fun()} functions. 13 | \itemize{ 14 | \item \code{'round-half-up'} (\emph{default}): rounding method where values exactly halfway 15 | between two numbers are rounded to the larger in magnitude number. 16 | Rounding is implemented via \code{\link[=round5]{round5()}}. 17 | \item \code{'round-to-even'}: base R's default IEC 60559 rounding standard. 18 | See \code{\link[=round]{round()}} for details. 19 | } 20 | 21 | To change the default rounding to use IEC 60559, this option must be set \strong{both} 22 | when the ARDs are created and when \code{apply_fmt_fun()} is run. This ensures that 23 | any \emph{default} formatting functions created with \code{label_round()} utilize the 24 | specified rounding method and the method is used what aliases are converted 25 | into functions (which occurs in \code{apply_fmt_fun()} when it calls \code{alias_as_fmt_fun()}). 26 | } 27 | 28 | -------------------------------------------------------------------------------- /man/alias_as_fmt_fun.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/apply_fmt_fun.R 3 | \name{alias_as_fmt_fun} 4 | \alias{alias_as_fmt_fun} 5 | \title{Convert Alias to Function} 6 | \usage{ 7 | alias_as_fmt_fun(x, variable, stat_name) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{integer}, \code{string}, or \code{function})\cr 11 | a non-negative integer, string alias, or function} 12 | 13 | \item{variable}{(\code{character})\cr the variable whose statistic is to be formatted} 14 | 15 | \item{stat_name}{(\code{character})\cr the name of the statistic that is to be formatted} 16 | } 17 | \value{ 18 | a function 19 | } 20 | \description{ 21 | Accepted aliases are non-negative integers and strings. 22 | 23 | The integers are converted to functions that round the statistics 24 | to the number of decimal places to match the integer. 25 | 26 | The formatting strings come in the form \code{"xx"}, \code{"xx.x"}, \code{"xx.x\%"}, etc. 27 | The number of \code{x}s that appear after the decimal place indicate the number of 28 | decimal places the statistics will be rounded to. 29 | The number of \code{x}s that appear before the decimal place indicate the leading 30 | spaces that are added to the result. 31 | If the string ends in \code{"\%"}, results are scaled by 100 before rounding. 32 | } 33 | \examples{ 34 | alias_as_fmt_fun(1) 35 | alias_as_fmt_fun("xx.x") 36 | } 37 | -------------------------------------------------------------------------------- /tests/testthat/test-options.R: -------------------------------------------------------------------------------- 1 | skip_if_pkg_not_installed("withr") 2 | 3 | test_that("options(cards.round_type)", { 4 | # test that the p is rounded to zero (ie rounded to even) for aliases called by `apply_fmt_fun()` 5 | withr::local_options(list(cards.round_type = "round-to-even")) 6 | expect_equal( 7 | data.frame(x = c(T, F)) |> 8 | ard_tabulate(variables = everything(), statistic = ~"p") |> 9 | update_ard_fmt_fun(stat_names = "p", fmt_fun = 0) |> 10 | apply_fmt_fun() |> 11 | dplyr::pull("stat_fmt") |> 12 | unique() |> 13 | unlist(), 14 | "0" 15 | ) 16 | 17 | # test that the p is rounded to zero (ie rounded to even) for default fmt functions 18 | expect_equal( 19 | data.frame(x = rep_len(TRUE, 1999) |> c(FALSE)) |> 20 | ard_tabulate(variables = everything(), statistic = ~"p") |> 21 | apply_fmt_fun() |> 22 | dplyr::filter(variable_level %in% FALSE) |> 23 | dplyr::pull("stat_fmt") |> 24 | unlist(), 25 | "0.0" 26 | ) 27 | }) 28 | 29 | test_that("options(cards.round_type) messaging", { 30 | # test message when the option is the wrong value 31 | expect_snapshot( 32 | error = TRUE, 33 | withr::with_options( 34 | list(cards.round_type = "NOT-CORRECT"), 35 | data.frame(x = c(T, F)) |> 36 | ard_tabulate(variables = everything(), statistic = ~"p") 37 | ) 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /man/figures/lifecycle-defunct.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: defunct 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | defunct 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-archived.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: archived 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | archived 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-maturing.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: maturing 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | maturing 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: superseded 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | superseded 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-questioning.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: questioning 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | questioning 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: experimental 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | experimental 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-soft-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: soft-deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | soft-deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /R/import-standalone-tibble.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: 3 | # ---------------------------------------------------------------------- 4 | # 5 | # --- 6 | # repo: insightsengineering/standalone 7 | # file: standalone-tibble.R 8 | # last-updated: 2024-05-07 9 | # license: https://unlicense.org 10 | # imports: [dplyr] 11 | # --- 12 | # 13 | # This file provides a minimal shim to provide a tibble-like API on top of 14 | # base R functions. They are not drop-in replacements but allow a similar style 15 | # of programming. 16 | # 17 | # ## Changelog 18 | # 19 | # nocov start 20 | # styler: off 21 | 22 | deframe <- function(x) { 23 | if (ncol(x) == 1L) return(x[[1]]) 24 | x[[2]] |> stats::setNames(x[[1]]) 25 | } 26 | 27 | enframe <- function(x, name = "name", value = "value") { 28 | if (!is.null(names(x))) { 29 | lst <- list(names(x), unname(x)) |> stats::setNames(c(name, value)) 30 | } 31 | else { 32 | lst <- list(seq_along(x), unname(x)) |> stats::setNames(c(name, value)) 33 | } 34 | dplyr::tibble(!!!lst) 35 | } 36 | 37 | remove_rownames <- function(.data) { 38 | rownames(.data) <- NULL 39 | .data 40 | } 41 | 42 | rownames_to_column <- function(.data, var = "rowname") { 43 | .data[[var]] <- rownames(.data) 44 | 45 | dplyr::relocate(.data, dplyr::all_of(var), .before = 1L) 46 | } 47 | 48 | # nocov end 49 | # styler: on 50 | -------------------------------------------------------------------------------- /man/dot-fill_overall_grp_values.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffle_ard.R 3 | \name{.fill_overall_grp_values} 4 | \alias{.fill_overall_grp_values} 5 | \title{Fill Overall Group Variables} 6 | \usage{ 7 | .fill_overall_grp_values(x, vars_protected) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | } 13 | \value{ 14 | data frame 15 | } 16 | \description{ 17 | This function fills the missing values of grouping variables with 18 | \code{"Overall "} or \code{"Any "}where relevant. 19 | Specifically, it will modify grouping values from rows with likely overall 20 | calculations present (e.g. non-missing variable/variable_level, missing group 21 | variables, and evidence that the \code{variable} has been computed by group in 22 | other rows). \code{"Overall"} values will be populated only for grouping variables 23 | that have been used in other calculations of the same variable and statistics. 24 | \code{"Any"} will be used if it is likely to be a hierarchical calculation. 25 | } 26 | \examples{ 27 | data <- dplyr::tibble( 28 | grp = c("AA", "AA", NA, "BB", NA), 29 | variable = c("A", "B", "A", "C", "C"), 30 | variable_level = c(1, 2, 1, 3, 3), 31 | A = rep(NA, 5), 32 | B = rep(NA, 5), 33 | ..cards_idx.. = c(1:5) 34 | ) 35 | 36 | cards:::.fill_overall_grp_values(data, vars_protected = "..cards_idx..") 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /man/dot-table_as_df.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate.R 3 | \name{.table_as_df} 4 | \alias{.table_as_df} 5 | \title{Results from \code{table()} as Data Frame} 6 | \usage{ 7 | .table_as_df( 8 | data, 9 | variable = NULL, 10 | by = NULL, 11 | strata = NULL, 12 | useNA = c("no", "always"), 13 | count_column = "...ard_n..." 14 | ) 15 | } 16 | \arguments{ 17 | \item{data}{(\code{data.frame})\cr 18 | a data frame} 19 | 20 | \item{variable}{(\code{string})\cr 21 | a string indicating a column in data} 22 | 23 | \item{by}{(\code{character})\cr 24 | a character vector indicating columns in data} 25 | 26 | \item{strata}{(\code{character})\cr 27 | a character vector indicating columns in data} 28 | 29 | \item{useNA}{(\code{string})\cr 30 | one of \code{"no"} and \code{"always"}. Will be passed to \code{table(useNA)}.} 31 | } 32 | \value{ 33 | data frame 34 | } 35 | \description{ 36 | Takes the results from \code{\link[=table]{table()}} and returns them as a data frame. 37 | After the \code{\link[=table]{table()}} results are made into a data frame, all the variables 38 | are made into character columns, and the function also restores the 39 | column types to their original classes. For \code{strata} columns, 40 | only observed combinations are returned. 41 | } 42 | \examples{ 43 | cards:::.table_as_df(ADSL, variable = "ARM", by = "AGEGR1", strata = NULL) 44 | } 45 | \keyword{internal} 46 | -------------------------------------------------------------------------------- /man/ard_formals.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_formals.R 3 | \name{ard_formals} 4 | \alias{ard_formals} 5 | \title{Argument Values ARD} 6 | \usage{ 7 | ard_formals(fun, arg_names, passed_args = list(), envir = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{fun}{(\code{function})\cr 11 | a \link{function} passed to \code{formals(fun)}} 12 | 13 | \item{arg_names}{(\code{character})\cr 14 | character vector of argument names to return} 15 | 16 | \item{passed_args}{(named \code{list})\cr 17 | a named list of user-passed arguments. Default is \code{list()}, which returns 18 | all default values from a function} 19 | 20 | \item{envir}{(\code{environment})\cr 21 | an environment passed to \code{formals(envir)}} 22 | } 23 | \value{ 24 | an partial ARD data frame of class 'card' 25 | } 26 | \description{ 27 | Place default and passed argument values to a function into an ARD structure. 28 | } 29 | \examples{ 30 | # Example 1 ---------------------------------- 31 | # add the `mcnemar.test(correct)` argument to an ARD structure 32 | ard_formals(fun = mcnemar.test, arg_names = "correct") 33 | 34 | # Example 2 ---------------------------------- 35 | # S3 Methods need special handling to access the underlying method 36 | ard_formals( 37 | fun = asNamespace("stats")[["t.test.default"]], 38 | arg_names = c("mu", "paired", "var.equal", "conf.level"), 39 | passed_args = list(conf.level = 0.90) 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /man/ard_pairwise.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_pairwise.R 3 | \name{ard_pairwise} 4 | \alias{ard_pairwise} 5 | \title{Pairwise ARD} 6 | \usage{ 7 | ard_pairwise(data, variable, .f, include = NULL) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{variable}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 14 | Column to perform pairwise analyses for.} 15 | 16 | \item{.f}{(\code{function})\cr 17 | a function that creates ARDs. The function accepts a single argument and 18 | a subset of \code{data} will be passed including the two levels of \code{variable} 19 | for the pairwise analysis.} 20 | 21 | \item{include}{(\code{vector})\cr 22 | a vector of levels of the \code{variable} column to include in comparisons. 23 | Pairwise comparisons will only be performed for pairs that have a level 24 | specified here. Default is \code{NULL} and all pairwise computations are included.} 25 | } 26 | \value{ 27 | list of ARDs 28 | } 29 | \description{ 30 | Utility to perform pairwise comparisons. 31 | } 32 | \examples{ 33 | ard_pairwise( 34 | ADSL, 35 | variable = ARM, 36 | .f = \(df) { 37 | ard_mvsummary( 38 | df, 39 | variables = AGE, 40 | statistic = ~ list(ttest = \(x, data, ...) t.test(x ~ data$ARM)[c("statistic", "p.value")]) 41 | ) 42 | }, 43 | include = "Placebo" # only include comparisons to the "Placebo" group 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: stable 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | lifecycle 21 | 22 | 25 | 26 | stable 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/testthat/test-rename_ard_columns.R: -------------------------------------------------------------------------------- 1 | test_that("rename_ard_columns(columns)", { 2 | expect_equal( 3 | ADSL |> 4 | ard_tabulate(by = ARM, variables = AGEGR1) |> 5 | rename_ard_columns() %>% 6 | `[`(1:2) |> 7 | names(), 8 | c("ARM", "AGEGR1") 9 | ) 10 | 11 | # testing stack output 12 | expect_silent( 13 | ard_stack <- 14 | ard_stack( 15 | ADSL, 16 | ard_tabulate(variables = AGEGR1), 17 | .by = ARM 18 | ) |> 19 | rename_ard_columns() 20 | ) 21 | 22 | # check the overall ARM tabulations 23 | expect_equal( 24 | ard_stack |> 25 | dplyr::filter(is.na(AGEGR1)) |> 26 | dplyr::select(-AGEGR1), 27 | ard_tabulate(ADSL, variables = ARM) |> 28 | rename_ard_columns() 29 | ) 30 | }) 31 | 32 | test_that("rename_ard_columns(columns) messsaging", { 33 | expect_snapshot( 34 | error = TRUE, 35 | ADSL |> 36 | ard_tabulate(by = ARM, variables = AGEGR1) |> 37 | rename_ard_columns(columns = all_ard_groups()) 38 | ) 39 | 40 | expect_snapshot( 41 | error = TRUE, 42 | ADSL |> 43 | dplyr::rename(stat = AGEGR1) |> 44 | ard_tabulate(by = ARM, variables = stat) |> 45 | rename_ard_columns() 46 | ) 47 | }) 48 | 49 | test_that("rename_ard_columns(unlist) lifecycle", { 50 | lifecycle::expect_deprecated( 51 | ADSL |> 52 | ard_tabulate(by = ARM, variables = AGEGR1) |> 53 | rename_ard_columns(unlist = "stat") 54 | ) 55 | }) 56 | -------------------------------------------------------------------------------- /man/tidy_ard_order.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidy_ard_order.R 3 | \name{tidy_ard_order} 4 | \alias{tidy_ard_order} 5 | \alias{tidy_ard_column_order} 6 | \alias{tidy_ard_row_order} 7 | \title{Standard Order of ARD} 8 | \usage{ 9 | tidy_ard_column_order(x, group_order = c("ascending", "descending")) 10 | 11 | tidy_ard_row_order(x) 12 | } 13 | \arguments{ 14 | \item{x}{(\code{data.frame})\cr 15 | an ARD data frame of class 'card'} 16 | 17 | \item{group_order}{(\code{string})\cr 18 | specifies the ordering of the grouping variables. 19 | Must be one of \code{c("ascending", "descending")}. 20 | Default is \code{"ascending"}, where grouping variables begin with \code{"group1"} variables, 21 | followed by \code{"group2"} variables, etc.} 22 | } 23 | \value{ 24 | an ARD data frame of class 'card' 25 | } 26 | \description{ 27 | ARD functions for relocating columns and rows to the standard order. 28 | \itemize{ 29 | \item \code{tidy_ard_column_order()} relocates columns of the ARD to the standard order. 30 | \item \code{tidy_ard_row_order()} orders rows of ARD according to groups and 31 | strata (group 1, then group2, etc), while retaining the column order of the input ARD. 32 | } 33 | } 34 | \examples{ 35 | # order columns 36 | ard <- 37 | dplyr::bind_rows( 38 | ard_summary(mtcars, variables = "mpg"), 39 | ard_summary(mtcars, variables = "mpg", by = "cyl") 40 | ) 41 | 42 | tidy_ard_column_order(ard) |> 43 | tidy_ard_row_order() 44 | } 45 | -------------------------------------------------------------------------------- /man/replace_null_statistic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/replace_null_statistic.R 3 | \name{replace_null_statistic} 4 | \alias{replace_null_statistic} 5 | \title{Replace NULL Statistics with Specified Value} 6 | \usage{ 7 | replace_null_statistic(x, value = NA, rows = TRUE) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | an ARD data frame of class 'card'} 12 | 13 | \item{value}{(usually a \code{scalar})\cr 14 | The value to replace \code{NULL} values with. Default is \code{NA}.} 15 | 16 | \item{rows}{(\code{\link[rlang:args_data_masking]{data-masking}})\cr 17 | Expression that return a logical value, and are defined in terms of the variables in \code{.data}. 18 | Only rows for which the condition evaluates to \code{TRUE} are replaced. 19 | Default is \code{TRUE}, which applies to all rows.} 20 | } 21 | \value{ 22 | an ARD data frame of class 'card' 23 | } 24 | \description{ 25 | When a statistical summary function errors, the \code{"stat"} column will be 26 | \code{NULL}. It is, however, sometimes useful to replace these values with a 27 | non-\code{NULL} value, e.g. \code{NA}. 28 | } 29 | \examples{ 30 | # the quantile functions error because the input is character, while the median function returns NA 31 | data.frame(x = rep_len(NA_character_, 10)) |> 32 | ard_summary( 33 | variables = x, 34 | statistic = ~ continuous_summary_fns(c("median", "p25", "p75")) 35 | ) |> 36 | replace_null_statistic(rows = !is.null(error)) 37 | } 38 | -------------------------------------------------------------------------------- /man/unlist_ard_columns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/unlist_ard_columns.R 3 | \name{unlist_ard_columns} 4 | \alias{unlist_ard_columns} 5 | \title{Unlist ARD Columns} 6 | \usage{ 7 | unlist_ard_columns( 8 | x, 9 | columns = c(where(is.list), -any_of(c("warning", "error", "fmt_fun"))), 10 | fill = NA, 11 | fct_as_chr = TRUE 12 | ) 13 | } 14 | \arguments{ 15 | \item{x}{(\code{data.frame})\cr 16 | an ARD data frame of class 'card' or any data frame} 17 | 18 | \item{columns}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 19 | columns to unlist. Default is 20 | \code{c(where(is.list), -any_of(c("warning", "error", "fmt_fun")))}.} 21 | 22 | \item{fill}{(scalar)\cr 23 | scalar to fill NULL values with before unlisting (if they are present). 24 | Default is \code{NA}.} 25 | 26 | \item{fct_as_chr}{(scalar \code{logical})\cr 27 | When \code{TRUE}, factor elements will be converted to character before unlisting. 28 | When the column being unlisted contains mixed types of classes, the 29 | factor elements are often converted to the underlying integer value instead 30 | of retaining the label. Default is \code{TRUE}.} 31 | } 32 | \value{ 33 | a data frame 34 | } 35 | \description{ 36 | Unlist ARD Columns 37 | } 38 | \examples{ 39 | ADSL |> 40 | ard_tabulate(by = ARM, variables = AGEGR1) |> 41 | apply_fmt_fun() |> 42 | unlist_ard_columns() 43 | 44 | ADSL |> 45 | ard_summary(by = ARM, variables = AGE) |> 46 | apply_fmt_fun() |> 47 | unlist_ard_columns() 48 | } 49 | -------------------------------------------------------------------------------- /tests/testthat/test-tidy_ard_column_order.R: -------------------------------------------------------------------------------- 1 | test_that("tidy_ard_column_order() works", { 2 | skip_if_pkg_not_installed("withr") 3 | withr::local_seed(1) 4 | 5 | # ensure 10+ groups are ordered correctly 6 | expect_equal( 7 | data.frame( 8 | x1 = sample(LETTERS[1:2], 30, replace = TRUE), 9 | x2 = sample(LETTERS[3:4], 30, replace = TRUE), 10 | x3 = sample(LETTERS[5:6], 30, replace = TRUE), 11 | x4 = sample(LETTERS[7:8], 30, replace = TRUE), 12 | x5 = sample(LETTERS[9:10], 30, replace = TRUE), 13 | x6 = sample(LETTERS[11:12], 30, replace = TRUE), 14 | x7 = sample(LETTERS[13:14], 30, replace = TRUE), 15 | x8 = sample(LETTERS[15:16], 30, replace = TRUE), 16 | x9 = sample(LETTERS[17:18], 30, replace = TRUE), 17 | x10 = sample(LETTERS[19:20], 30, replace = TRUE), 18 | dummy = 1L 19 | ) |> 20 | ard_tabulate( 21 | variables = "dummy", 22 | strata = x1:x10, 23 | statistic = everything() ~ "n" 24 | ) |> 25 | dplyr::select(all_ard_groups(), all_ard_variables()) |> 26 | names(), 27 | c( 28 | "group1", "group1_level", 29 | "group2", "group2_level", 30 | "group3", "group3_level", 31 | "group4", "group4_level", 32 | "group5", "group5_level", 33 | "group6", "group6_level", 34 | "group7", "group7_level", 35 | "group8", "group8_level", 36 | "group9", "group9_level", 37 | "group10", "group10_level", 38 | "variable", "variable_level" 39 | ) 40 | ) 41 | }) 42 | -------------------------------------------------------------------------------- /man/print.card.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print.R 3 | \name{print.card} 4 | \alias{print.card} 5 | \title{Print} 6 | \usage{ 7 | \method{print}{card}(x, n = NULL, columns = c("auto", "all"), n_col = 6L, ...) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | object of class 'card'} 12 | 13 | \item{n}{(\code{integer})\cr 14 | integer specifying the number of rows to print} 15 | 16 | \item{columns}{(\code{string})\cr 17 | string indicating whether to print a selected number of columns or all.} 18 | 19 | \item{n_col}{(\code{integer})\cr 20 | some columns are removed when there are more than a threshold of 21 | columns present. This argument sets that threshold. This is only used 22 | when \code{columns='auto'} and default is \code{6L}. 23 | Columns \code{'error'}, \code{'warning'}, \code{'context'}, and \code{'fmt_fun'} \emph{may} be removed 24 | from the print. All other columns will be printed, even if more than \code{n_col} 25 | columns are present.} 26 | 27 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 28 | not used} 29 | } 30 | \value{ 31 | an ARD data frame of class 'card' (invisibly) 32 | } 33 | \description{ 34 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}\cr 35 | Print method for objects of class 'card' 36 | } 37 | \examples{ 38 | ard_tabulate(ADSL, variables = AGEGR1) |> 39 | print() 40 | } 41 | \keyword{internal} 42 | -------------------------------------------------------------------------------- /R/ard_identity.R: -------------------------------------------------------------------------------- 1 | #' ARD Identity 2 | #' 3 | #' Function ingests pre-calculated statistics and returns the identical results, 4 | #' but in an ARD format. 5 | #' 6 | #' @param x (named `list`/`data.frame`)\cr 7 | #' named list of results or a data frame. 8 | #' Names are the statistic names, and the values 9 | #' are the statistic values. These comprise the `"stat_name"` and `"stat"` 10 | #' columns in the returned ARD. 11 | #' @param variable (`string`)\cr 12 | #' string of a variable name that is assigned to the `"variable"` column in the 13 | #' ARD. 14 | #' @param context (`string`)\cr 15 | #' string to be added to the `"context"` column. Default is `"identity"`. 16 | #' 17 | #' @returns a ARD 18 | #' @export 19 | #' 20 | #' @examples 21 | #' t.test(formula = AGE ~ 1, data = ADSL)[c("statistic", "parameter", "p.value")] |> 22 | #' ard_identity(variable = "AGE", context = "onesample_t_test") 23 | ard_identity <- function(x, variable, context = "identity") { 24 | # check inputs --------------------------------------------------------------- 25 | set_cli_abort_call() 26 | check_class(x, c("list", "data.frame")) 27 | check_named(x) 28 | check_string(variable) 29 | check_string(context) 30 | 31 | # build data frame for calculation ------------------------------------------- 32 | dplyr::tibble("{variable}" := TRUE) |> 33 | ard_summary( 34 | variables = all_of(variable), 35 | statistic = everything() ~ list(identity = \(xxx) x) 36 | ) |> 37 | dplyr::mutate( 38 | context = .env$context 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /man/summary_functions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/summary_functions.R 3 | \name{summary_functions} 4 | \alias{summary_functions} 5 | \alias{continuous_summary_fns} 6 | \title{Summary Functions} 7 | \usage{ 8 | continuous_summary_fns( 9 | summaries = c("N", "mean", "sd", "median", "p25", "p75", "min", "max"), 10 | other_stats = NULL 11 | ) 12 | } 13 | \arguments{ 14 | \item{summaries}{(\code{character})\cr 15 | a character vector of results to include in output. Select one or more from 16 | 'N', 'mean', 'sd', 'median', 'p25', 'p75', 'min', 'max'.} 17 | 18 | \item{other_stats}{(named \code{list})\cr 19 | named list of other statistic functions to supplement the pre-programmed functions.} 20 | } 21 | \value{ 22 | named list of summary statistics 23 | } 24 | \description{ 25 | \itemize{ 26 | \item \code{continuous_summary_fns()} returns a named list of summary functions 27 | for continuous variables. Some functions include slight modifications to 28 | their base equivalents. For example, the \code{min()} and \code{max()} functions 29 | return \code{NA} instead of \code{Inf} when an empty vector is passed. 30 | Statistics \code{"p25"} and \code{"p75"} are calculated with \code{quantile(type = 2)}, 31 | which matches 32 | \href{https://psiaims.github.io/CAMIS/Comp/r-sas-summary-stats.html}{SAS's default value}. 33 | } 34 | } 35 | \examples{ 36 | # continuous variable summaries 37 | ard_summary( 38 | ADSL, 39 | variables = "AGE", 40 | statistic = ~ continuous_summary_fns(c("N", "median")) 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /man/dot-nesting_rename_ard_columns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nest_for_ard.R 3 | \name{.nesting_rename_ard_columns} 4 | \alias{.nesting_rename_ard_columns} 5 | \title{Rename ARD Columns} 6 | \usage{ 7 | .nesting_rename_ard_columns(x, variable = NULL, by = NULL, strata = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{variable}{(\code{character})\cr 14 | name of \code{variable} column in \code{x}. Default is \code{NULL}.} 15 | 16 | \item{by}{(\code{character})\cr 17 | character vector of names of \code{by} columns in \code{x}. Default is \code{NULL}.} 18 | 19 | \item{strata}{(\code{character})\cr 20 | character vector of names of \code{strata} columns in \code{x}. Default is \code{NULL}.} 21 | } 22 | \value{ 23 | a tibble 24 | } 25 | \description{ 26 | If \code{variable} is provided, adds the standard \code{variable} column to \code{x}. If \code{by}/\code{strata} are 27 | provided, adds the standard \code{group##} column(s) to \code{x} and renames the provided columns to 28 | \code{group##_level} in \code{x}, where \verb{##} is determined by the column's position in \code{c(by, strata)}. 29 | } 30 | \examples{ 31 | ard <- nest_for_ard( 32 | data = 33 | ADAE |> 34 | dplyr::left_join(ADSL[c("USUBJID", "ARM")], by = "USUBJID") |> 35 | dplyr::filter(AOCCSFL \%in\% "Y"), 36 | by = "ARM", 37 | strata = "AESOC", 38 | rename_columns = FALSE 39 | ) 40 | 41 | cards:::.nesting_rename_ard_columns(ard, by = "ARM", strata = "AESOC") 42 | } 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/round5.md: -------------------------------------------------------------------------------- 1 | # round5() works 2 | 3 | Code 4 | x <- seq.int(-10L, 10L, by = 1L) / 2 5 | x <- x[x %% 1 != 0] 6 | setNames(round5(x), nm = x) 7 | Output 8 | -4.5 -3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5 4.5 9 | -5 -4 -3 -2 -1 1 2 3 4 5 10 | 11 | --- 12 | 13 | Code 14 | x <- seq.int(-100000L, 100000L, by = 10000L) - 1L / 2L 15 | x <- x[x %% 1 != 0] 16 | setNames(round5(x), nm = x) 17 | Output 18 | -100000.5 -90000.5 -80000.5 -70000.5 -60000.5 -50000.5 -40000.5 -30000.5 19 | -100001 -90001 -80001 -70001 -60001 -50001 -40001 -30001 20 | -20000.5 -10000.5 -0.5 9999.5 19999.5 29999.5 39999.5 49999.5 21 | -20001 -10001 -1 10000 20000 30000 40000 50000 22 | 59999.5 69999.5 79999.5 89999.5 99999.5 23 | 60000 70000 80000 90000 100000 24 | 25 | --- 26 | 27 | Code 28 | x <- seq.int(-100000L, 100000L, by = 10000L) + 1L / 2L 29 | x <- x[x %% 1 != 0] 30 | setNames(round5(x), nm = x) 31 | Output 32 | -99999.5 -89999.5 -79999.5 -69999.5 -59999.5 -49999.5 -39999.5 -29999.5 33 | -100000 -90000 -80000 -70000 -60000 -50000 -40000 -30000 34 | -19999.5 -9999.5 0.5 10000.5 20000.5 30000.5 40000.5 50000.5 35 | -20000 -10000 1 10001 20001 30001 40001 50001 36 | 60000.5 70000.5 80000.5 90000.5 100000.5 37 | 60001 70001 80001 90001 100001 38 | 39 | -------------------------------------------------------------------------------- /man/ard_attributes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_attributes.R 3 | \name{ard_attributes} 4 | \alias{ard_attributes} 5 | \alias{ard_attributes.data.frame} 6 | \alias{ard_attributes.default} 7 | \title{ARD Attributes} 8 | \usage{ 9 | ard_attributes(data, ...) 10 | 11 | \method{ard_attributes}{data.frame}(data, variables = everything(), label = NULL, ...) 12 | 13 | \method{ard_attributes}{default}(data, ...) 14 | } 15 | \arguments{ 16 | \item{data}{(\code{data.frame})\cr 17 | a data frame} 18 | 19 | \item{...}{These dots are for future extensions and must be empty.} 20 | 21 | \item{variables}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 22 | variables to include} 23 | 24 | \item{label}{(named \code{list})\cr 25 | named list of variable labels, e.g. \code{list(cyl = "No. Cylinders")}. 26 | Default is \code{NULL}} 27 | } 28 | \value{ 29 | an ARD data frame of class 'card' 30 | } 31 | \description{ 32 | Add variable attributes to an ARD data frame. 33 | \itemize{ 34 | \item The \code{label} attribute will be added for all columns, and when no label 35 | is specified and no label has been set for a column using the \verb{label=} argument, 36 | the column name will be placed in the label statistic. 37 | \item The \code{class} attribute will also be returned for all columns. 38 | \item Any other attribute returned by \code{attributes()} will also be added, e.g. factor levels. 39 | } 40 | } 41 | \examples{ 42 | df <- dplyr::tibble(var1 = letters, var2 = LETTERS) 43 | attr(df$var1, "label") <- "Lowercase Letters" 44 | 45 | ard_attributes(df, variables = everything()) 46 | } 47 | -------------------------------------------------------------------------------- /man/ard_tabulate_rows.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate_rows.R 3 | \name{ard_tabulate_rows} 4 | \alias{ard_tabulate_rows} 5 | \title{Row Tabulate ARD} 6 | \usage{ 7 | ard_tabulate_rows( 8 | data, 9 | colname = "..row_count..", 10 | by = dplyr::group_vars(data), 11 | strata = NULL, 12 | fmt_fun = NULL 13 | ) 14 | } 15 | \arguments{ 16 | \item{data}{(\code{data.frame})\cr 17 | a data frame} 18 | 19 | \item{colname}{(\code{string})\cr 20 | name of the column that will be returned along with the row tabulation.} 21 | 22 | \item{by, strata}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 23 | columns to use for grouping or stratifying the table output. 24 | Arguments are similar, but with an important distinction: 25 | 26 | \code{by}: results are tabulated by \strong{all combinations} of the columns specified, 27 | including unobserved combinations and unobserved factor levels. 28 | 29 | \code{strata}: results are tabulated by \strong{all \emph{observed} combinations} of the 30 | columns specified. 31 | 32 | Arguments may be used in conjunction with one another.} 33 | 34 | \item{fmt_fun}{(\code{\link[=syntax]{formula-list-selector}})\cr 35 | a named list, a list of formulas, 36 | or a single formula where the list element is a named list of functions 37 | (or the RHS of a formula), 38 | e.g. \verb{list(mpg = list(mean = \\(x) round(x, digits = 2) |> as.character()))}.} 39 | } 40 | \value{ 41 | an ARD data frame of class 'card' 42 | } 43 | \description{ 44 | Tabulate the number of rows in a data frame. 45 | } 46 | \examples{ 47 | ard_tabulate_rows(ADSL, by = TRTA) 48 | } 49 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tidy_ard_row_order.md: -------------------------------------------------------------------------------- 1 | # tidy_ard_row_order() works 2 | 3 | Code 4 | dplyr::select(ard_tabulate(data.frame(x1 = sample(LETTERS[1:5], 30, replace = TRUE), x2 = sample(LETTERS[6:10], 30, 5 | replace = TRUE), x3 = sample(LETTERS[11:15], 30, replace = TRUE), zz = 1L, aa = 1L), by = x1:x3, variables = c(zz, aa), 6 | statistic = everything() ~ "n"), all_ard_groups(), all_ard_variables()) 7 | Message 8 | {cards} data frame: 250 x 8 9 | Output 10 | group1 group1_level group2 group2_level group3 group3_level variable variable_level 11 | 1 x1 A x2 F x3 K zz 1 12 | 2 x1 A x2 F x3 K aa 1 13 | 3 x1 A x2 F x3 L zz 1 14 | 4 x1 A x2 F x3 L aa 1 15 | 5 x1 A x2 F x3 M zz 1 16 | 6 x1 A x2 F x3 M aa 1 17 | 7 x1 A x2 F x3 N zz 1 18 | 8 x1 A x2 F x3 N aa 1 19 | 9 x1 A x2 F x3 O zz 1 20 | 10 x1 A x2 F x3 O aa 1 21 | Message 22 | i 240 more rows 23 | i Use `print(n = ...)` to see more rows 24 | 25 | -------------------------------------------------------------------------------- /man/selectors.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/selectors.R 3 | \name{selectors} 4 | \alias{selectors} 5 | \alias{all_ard_groups} 6 | \alias{all_ard_variables} 7 | \alias{all_ard_group_n} 8 | \alias{all_missing_columns} 9 | \title{ARD Selectors} 10 | \usage{ 11 | all_ard_groups(types = c("names", "levels")) 12 | 13 | all_ard_variables(types = c("names", "levels")) 14 | 15 | all_ard_group_n(n, types = c("names", "levels")) 16 | 17 | all_missing_columns() 18 | } 19 | \arguments{ 20 | \item{types}{(\code{character})\cr 21 | type(s) of columns to select. \code{"names"} selects the columns variable name columns, 22 | and \code{"levels"} selects the level columns. Default is \code{c("names", "levels")}.} 23 | 24 | \item{n}{(\code{integer})\cr 25 | integer(s) indicating which grouping columns to select.} 26 | } 27 | \value{ 28 | tidyselect output 29 | } 30 | \description{ 31 | These selection helpers match variables according to a given pattern. 32 | \itemize{ 33 | \item \code{all_ard_groups()}: Function selects grouping columns, e.g. columns 34 | named \code{"group##"} or \code{"group##_level"}. 35 | \item \code{all_ard_variables()}: Function selects variables columns, e.g. columns 36 | named \code{"variable"} or \code{"variable_level"}. 37 | \item \code{all_ard_group_n()}: Function selects \code{n} grouping columns. 38 | \item \code{all_missing_columns()}: Function selects columns that are all \code{NA} or empty. 39 | } 40 | } 41 | \examples{ 42 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 43 | 44 | ard |> dplyr::select(all_ard_groups()) 45 | ard |> dplyr::select(all_ard_variables()) 46 | } 47 | -------------------------------------------------------------------------------- /man/cards-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cards-package.R 3 | \docType{package} 4 | \name{cards-package} 5 | \alias{cards} 6 | \alias{cards-package} 7 | \title{cards: Analysis Results Data} 8 | \description{ 9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 10 | 11 | Construct CDISC (Clinical Data Interchange Standards Consortium) compliant Analysis Results Data objects. These objects are used and re-used to construct summary tables, visualizations, and written reports. The package also exports utilities for working with these objects and creating new Analysis Results Data objects. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://github.com/insightsengineering/cards} 17 | \item \url{https://insightsengineering.github.io/cards/} 18 | \item Report bugs at \url{https://github.com/insightsengineering/cards/issues} 19 | } 20 | 21 | } 22 | \author{ 23 | \strong{Maintainer}: Daniel D. Sjoberg \email{danield.sjoberg@gmail.com} (\href{https://orcid.org/0000-0003-0862-2018}{ORCID}) 24 | 25 | Authors: 26 | \itemize{ 27 | \item Becca Krouse \email{becca.z.krouse@gsk.com} 28 | \item Emily de la Rua \email{emily.de_la_rua@contractors.roche.com} (\href{https://orcid.org/0009-0000-8738-5561}{ORCID}) 29 | \item Malan Bosman \email{malanbos@gmail.com} (\href{https://orcid.org/0000-0002-3020-195X}{ORCID}) 30 | } 31 | 32 | Other contributors: 33 | \itemize{ 34 | \item F. Hoffmann-La Roche AG [copyright holder, funder] 35 | \item GlaxoSmithKline Research & Development Limited [copyright holder] 36 | } 37 | 38 | } 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/dot-process_denominator.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate.R 3 | \name{.process_denominator} 4 | \alias{.process_denominator} 5 | \title{Process \code{denominator} Argument} 6 | \usage{ 7 | .process_denominator(data, variables, denominator, by, strata) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{variables}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 14 | columns to include in summaries. Default is \code{everything()}.} 15 | 16 | \item{denominator}{(\code{string}, \code{data.frame}, \code{integer})\cr 17 | Specify this argument to change the denominator, 18 | e.g. the \code{"N"} statistic. Default is \code{'column'}. See below for details.} 19 | 20 | \item{by, strata}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 21 | columns to use for grouping or stratifying the table output. 22 | Arguments are similar, but with an important distinction: 23 | 24 | \code{by}: results are tabulated by \strong{all combinations} of the columns specified, 25 | including unobserved combinations and unobserved factor levels. 26 | 27 | \code{strata}: results are tabulated by \strong{all \emph{observed} combinations} of the 28 | columns specified. 29 | 30 | Arguments may be used in conjunction with one another.} 31 | } 32 | \value{ 33 | a data frame 34 | } 35 | \description{ 36 | Function takes the \code{ard_tabulate(denominator)} argument and returns a 37 | structured data frame that is merged with the count data and used as the 38 | denominator in percentage calculations. 39 | } 40 | \examples{ 41 | cards:::.process_denominator(mtcars, denominator = 1000, variables = "cyl", by = "gear") 42 | } 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /data-raw/DATASET.R: -------------------------------------------------------------------------------- 1 | ## code to prepare `DATASET` dataset goes here 2 | 3 | ADSL <- haven::read_xpt( 4 | "https://github.com/cdisc-org/sdtm-adam-pilot-project/raw/master/updated-pilot-submission-package/900172/m5/datasets/cdiscpilot01/analysis/adam/datasets/adsl.xpt" 5 | ) 6 | ADAE <- haven::read_xpt( 7 | "https://github.com/cdisc-org/sdtm-adam-pilot-project/raw/master/updated-pilot-submission-package/900172/m5/datasets/cdiscpilot01/analysis/adam/datasets/adae.xpt" 8 | ) 9 | ADTTE <- haven::read_xpt( 10 | "https://github.com/cdisc-org/sdtm-adam-pilot-project/raw/master/updated-pilot-submission-package/900172/m5/datasets/cdiscpilot01/analysis/adam/datasets/adtte.xpt" 11 | ) 12 | 13 | ADLB <- haven::read_xpt( 14 | "https://github.com/cdisc-org/sdtm-adam-pilot-project/raw/master/updated-pilot-submission-package/900172/m5/datasets/cdiscpilot01/analysis/adam/datasets/adlbc.xpt" 15 | ) 16 | 17 | ADSL$TRTA <- ADSL$TRT01A 18 | labelled::var_label(ADSL$TRTA) <- labelled::var_label(ADAE$TRTA) 19 | 20 | ADAE <- 21 | withr::with_seed( 22 | seed = 1L, 23 | ADAE |> 24 | dplyr::rowwise() |> 25 | dplyr::mutate( 26 | .after = "AESEV", 27 | AETOXGR = dplyr::case_when( 28 | AESDTH == "Y" ~ 5, 29 | AESEV == "SEVERE" ~ 4, 30 | AESEV == "MODERATE" ~ sample(2:3, 1), 31 | AESEV == "MILD" ~ 1, 32 | ) |> factor(levels = 1:5) 33 | ) |> 34 | dplyr::ungroup() 35 | ) |> 36 | labelled::set_variable_labels(AETOXGR = "Toxicity Grade") 37 | 38 | # Reduce ADLB Dataset (744264 rows to 5784) 39 | ADLB <- ADLB |> 40 | dplyr::filter(SUBJID %in% head(unique(SUBJID), 20)) |> 41 | dplyr::mutate(dplyr::across(where(is.character), trimws)) 42 | 43 | usethis::use_data(ADSL, ADAE, ADTTE, ADLB, overwrite = TRUE) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-bind_ard.R: -------------------------------------------------------------------------------- 1 | test_that("bind_ard() works", { 2 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 3 | 4 | expect_error( 5 | bind_ard(ard, ard, .update = TRUE), 6 | NA 7 | ) 8 | }) 9 | 10 | 11 | test_that("ARD helpers messaging", { 12 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 13 | 14 | expect_snapshot( 15 | bind_ard(ard, ard, .update = letters), 16 | error = TRUE 17 | ) 18 | 19 | expect_snapshot( 20 | bind_ard(ard, ard, .distinct = FALSE, .update = FALSE), 21 | error = TRUE 22 | ) 23 | }) 24 | 25 | test_that("bind_ard() .order argument works", { 26 | withr::local_options(list(width = 120)) 27 | withr::local_seed(1123) 28 | expect_snapshot( 29 | bind_ard( 30 | ard_tabulate(ADSL, by = "ARM", variables = "SEX") %>% 31 | # randomly sort data 32 | {dplyr::slice(., sample.int(nrow(.)))}, # styler: off 33 | .order = TRUE 34 | ) |> 35 | as.data.frame() |> 36 | dplyr::select(-c(context, fmt_fun, warning, error)) 37 | ) 38 | 39 | expect_snapshot( 40 | bind_ard( 41 | ard_tabulate(ADSL, by = "ARM", variables = "SEX") %>% 42 | # randomly sort data 43 | {dplyr::slice(., sample.int(nrow(.)))}, # styler: off 44 | .order = FALSE 45 | ) |> 46 | as.data.frame() |> 47 | dplyr::select(-c(context, fmt_fun, warning, error)) 48 | ) 49 | }) 50 | 51 | test_that("bind_ard(.quiet)", { 52 | expect_silent( 53 | ard_summary(ADSL, variables = AGE) %>% 54 | {bind_ard(., ., .update = TRUE, .quiet = TRUE)} # styler: off 55 | ) 56 | }) 57 | 58 | test_that("bind_ard(.distinct)", { 59 | expect_snapshot( 60 | ard_summary(ADSL, variables = AGE) %>% 61 | {bind_ard(., ., .update = FALSE)} # styler: off 62 | ) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/testthat/test-unlist_ard_columns.R: -------------------------------------------------------------------------------- 1 | test_that("unlist_ard_columns()", { 2 | expect_equal( 3 | ard_tabulate(ADSL, variables = AGEGR1) |> 4 | unlist_ard_columns() |> 5 | dplyr::pull("stat") |> 6 | class(), 7 | "numeric" 8 | ) 9 | 10 | expect_equal( 11 | ard_tabulate(ADSL, variables = AGEGR1) |> 12 | unlist_ard_columns() |> 13 | dplyr::pull("variable_level") |> 14 | class(), 15 | "character" 16 | ) 17 | 18 | expect_equal( 19 | ard_tabulate(ADSL, variables = AGEGR1) |> 20 | unlist_ard_columns(columns = "error") |> 21 | dplyr::pull("error") |> 22 | unique(), 23 | NA 24 | ) 25 | }) 26 | 27 | test_that("unlist_ard_columns() messaging", { 28 | expect_message( 29 | ard_tabulate(ADSL, variables = AGEGR1) |> 30 | dplyr::mutate( 31 | stat = ifelse(dplyr::row_number() == 1L, list(matrix(1:4)), stat) 32 | ) |> 33 | unlist_ard_columns(columns = "stat"), 34 | "Cannot unlist column" 35 | ) 36 | }) 37 | 38 | test_that("unlist_ard_columns(fct_as_chr)", { 39 | # check that a mixed-type column has factors converted to character by default. 40 | expect_true( 41 | cards::ADSL |> 42 | dplyr::mutate(ARM = factor(ARM)) |> 43 | ard_stack( 44 | ard_summary(variables = AGE), 45 | .by = ARM 46 | ) |> 47 | unlist_ard_columns() |> 48 | dplyr::pull("group1_level") |> 49 | is.character() 50 | ) 51 | 52 | # check fct_to_chr = FALSE 53 | expect_true( 54 | cards::ADSL |> 55 | dplyr::mutate(ARM = factor(ARM)) |> 56 | ard_stack( 57 | ard_summary(variables = AGE), 58 | .by = ARM 59 | ) |> 60 | unlist_ard_columns(fct_as_chr = FALSE) |> 61 | dplyr::pull("group1_level") |> 62 | is.integer() 63 | ) 64 | }) 65 | -------------------------------------------------------------------------------- /man/add_calculated_row.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_calculated_row.R 3 | \name{add_calculated_row} 4 | \alias{add_calculated_row} 5 | \title{Add Calculated Row} 6 | \usage{ 7 | add_calculated_row( 8 | x, 9 | expr, 10 | stat_name, 11 | by = c(all_ard_groups(), all_ard_variables(), any_of("context")), 12 | stat_label = stat_name, 13 | fmt_fun = NULL, 14 | fmt_fn = deprecated() 15 | ) 16 | } 17 | \arguments{ 18 | \item{x}{(\code{card})\cr 19 | data frame of class \code{'card'}} 20 | 21 | \item{expr}{(\code{expression})\cr 22 | an expression} 23 | 24 | \item{stat_name}{(\code{string})\cr 25 | string naming the new statistic} 26 | 27 | \item{by}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 28 | Grouping variables to calculate statistics within} 29 | 30 | \item{stat_label}{(\code{string})\cr 31 | string of the statistic label. Default is the \code{stat_name}.} 32 | 33 | \item{fmt_fun}{(\code{integer}, \code{function}, \code{string})\cr 34 | a function of an integer or string that can be converted to a function with 35 | \code{alias_as_fmt_fun()}.} 36 | 37 | \item{fmt_fn}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}}} 38 | } 39 | \value{ 40 | an ARD data frame of class 'card' 41 | } 42 | \description{ 43 | Use this function to add a new statistic row that is a function of the 44 | other statistics in an ARD. 45 | } 46 | \examples{ 47 | ard_summary(mtcars, variables = mpg) |> 48 | add_calculated_row(expr = max - min, stat_name = "range") 49 | 50 | ard_summary(mtcars, variables = mpg) |> 51 | add_calculated_row( 52 | expr = 53 | dplyr::case_when( 54 | mean > median ~ "Right Skew", 55 | mean < median ~ "Left Skew", 56 | .default = "Symmetric" 57 | ), 58 | stat_name = "skew" 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /R/replace_null_statistic.R: -------------------------------------------------------------------------------- 1 | #' Replace NULL Statistics with Specified Value 2 | #' 3 | #' When a statistical summary function errors, the `"stat"` column will be 4 | #' `NULL`. It is, however, sometimes useful to replace these values with a 5 | #' non-`NULL` value, e.g. `NA`. 6 | #' 7 | #' 8 | #' @param x (`data.frame`)\cr 9 | #' an ARD data frame of class 'card' 10 | #' @param value (usually a `scalar`)\cr 11 | #' The value to replace `NULL` values with. Default is `NA`. 12 | #' @param rows ([`data-masking`][rlang::args_data_masking])\cr 13 | #' Expression that return a logical value, and are defined in terms of the variables in `.data`. 14 | #' Only rows for which the condition evaluates to `TRUE` are replaced. 15 | #' Default is `TRUE`, which applies to all rows. 16 | #' 17 | #' @return an ARD data frame of class 'card' 18 | #' @export 19 | #' 20 | #' @examples 21 | #' # the quantile functions error because the input is character, while the median function returns NA 22 | #' data.frame(x = rep_len(NA_character_, 10)) |> 23 | #' ard_summary( 24 | #' variables = x, 25 | #' statistic = ~ continuous_summary_fns(c("median", "p25", "p75")) 26 | #' ) |> 27 | #' replace_null_statistic(rows = !is.null(error)) 28 | replace_null_statistic <- function(x, value = NA, rows = TRUE) { 29 | set_cli_abort_call() 30 | 31 | # check inputs --------------------------------------------------------------- 32 | check_class(x, "card") 33 | 34 | # replace NULL values -------------------------------------------------------- 35 | x |> 36 | dplyr::rowwise() |> 37 | dplyr::mutate( 38 | # styler: off 39 | stat = 40 | if (is.null(.data$stat) && {{ rows }}) list(.env$value) 41 | else list(.data$stat) 42 | # styler: on 43 | ) |> 44 | # restore previous grouping structure and original class of x 45 | dplyr::group_by(dplyr::pick(dplyr::group_vars(x))) |> 46 | structure(class = class(x)) 47 | } 48 | -------------------------------------------------------------------------------- /man/as_cards_fn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_card_fn.R 3 | \name{as_cards_fn} 4 | \alias{as_cards_fn} 5 | \alias{is_cards_fn} 6 | \alias{get_cards_fn_stat_names} 7 | \title{As card function} 8 | \usage{ 9 | as_cards_fn(f, stat_names) 10 | 11 | is_cards_fn(f) 12 | 13 | get_cards_fn_stat_names(f) 14 | } 15 | \arguments{ 16 | \item{f}{(\code{function})\cr 17 | a function} 18 | 19 | \item{stat_names}{(\code{character})\cr 20 | a character vector of the expected statistic names returned by function \code{f}} 21 | } 22 | \value{ 23 | an ARD data frame of class 'card' 24 | } 25 | \description{ 26 | Add attributes to a function that specify the expected results. 27 | It is used when \code{ard_summary()} or \code{ard_mvsummary()} errors and constructs 28 | an ARD with the correct structure when the results cannot be calculated. 29 | } 30 | \examples{ 31 | # When there is no error, everything works as if we hadn't used `as_card_fn()` 32 | ttest_works <- 33 | as_cards_fn( 34 | \(x) t.test(x)[c("statistic", "p.value")], 35 | stat_names = c("statistic", "p.value") 36 | ) 37 | ard_summary( 38 | mtcars, 39 | variables = mpg, 40 | statistic = ~ list(ttest = ttest_works) 41 | ) 42 | 43 | # When there is an error and we use `as_card_fn()`, 44 | # we will see the same structure as when there is no error 45 | ttest_error <- 46 | as_cards_fn( 47 | \(x) { 48 | t.test(x)[c("statistic", "p.value")] 49 | stop("Intentional Error") 50 | }, 51 | stat_names = c("statistic", "p.value") 52 | ) 53 | ard_summary( 54 | mtcars, 55 | variables = mpg, 56 | statistic = ~ list(ttest = ttest_error) 57 | ) 58 | 59 | # if we don't use `as_card_fn()` and there is an error, 60 | # the returned result is only one row 61 | ard_summary( 62 | mtcars, 63 | variables = mpg, 64 | statistic = ~ list(ttest = \(x) { 65 | t.test(x)[c("statistic", "p.value")] 66 | stop("Intentional Error") 67 | }) 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /R/import-standalone-cli_call_env.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: https://github.com/insightsengineering/standalone/blob/HEAD/R/standalone-cli_call_env.R 3 | # Generated by: usethis::use_standalone("insightsengineering/standalone", "cli_call_env") 4 | # ---------------------------------------------------------------------- 5 | # 6 | # --- 7 | # repo: insightsengineering/standalone 8 | # file: standalone-cli_call_env.R 9 | # last-updated: 2024-04-10 10 | # license: https://unlicense.org 11 | # imports: [rlang, cli] 12 | # --- 13 | # 14 | # This file provides functions to set and access the call environment to use in cli::cli_abort() in check functions. 15 | # 16 | # ## Changelog 17 | # nocov start 18 | # styler: off 19 | 20 | #' Set Call Environment for [cli::cli_abort()] 21 | #' 22 | #' Set a call environment to be used as the `call` parameter in [cli::cli_abort()] for package checks. This function 23 | #' is used to ensure that the correct user-facing function is reported for errors generated by internal checks that 24 | #' use [cli::cli_abort()]. 25 | #' 26 | #' @param env (`enviroment`)\cr 27 | #' call environment used as the `call` parameter in [cli::cli_abort()] for package checks 28 | #' 29 | #' @seealso `get_cli_abort_call()` 30 | #' 31 | #' @keywords internal 32 | #' @noRd 33 | set_cli_abort_call <- function(env = rlang::caller_env()) { 34 | if (getOption("cli_abort_call") |> is.null()) { 35 | options(cli_abort_call = env) 36 | set_call <- as.call(list(function() options(cli_abort_call = NULL))) 37 | do.call(on.exit, list(expr = set_call, add = TRUE, after = FALSE), envir = env) 38 | } 39 | invisible() 40 | } 41 | 42 | #' Get Call Environment for [cli::cli_abort()] 43 | #' 44 | #' @inheritParams set_cli_abort_call 45 | #' @seealso `set_cli_abort_call()` 46 | #' 47 | #' @keywords internal 48 | #' @noRd 49 | get_cli_abort_call <- function() { 50 | getOption("cli_abort_call", default = parent.frame()) 51 | } 52 | 53 | # nocov end 54 | # styler: on 55 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | 12 | name: R-CMD-check 13 | 14 | jobs: 15 | R-CMD-check: 16 | runs-on: ${{ matrix.config.os }} 17 | 18 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | config: 24 | - {os: macos-latest, r: 'release'} 25 | 26 | - {os: windows-latest, r: 'release'} 27 | # use 4.1 to check with rtools40's older compiler 28 | - {os: windows-latest, r: '4.1'} 29 | 30 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 31 | - {os: ubuntu-latest, r: 'release'} 32 | - {os: ubuntu-latest, r: 'oldrel-1'} 33 | - {os: ubuntu-latest, r: 'oldrel-2'} 34 | # - {os: ubuntu-latest, r: 'oldrel-3'} 35 | # - {os: ubuntu-latest, r: 'oldrel-4'} 36 | 37 | env: 38 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 39 | R_KEEP_PKG_SOURCE: yes 40 | 41 | steps: 42 | - uses: actions/checkout@v3 43 | 44 | - uses: r-lib/actions/setup-pandoc@v2 45 | 46 | - uses: r-lib/actions/setup-r@v2 47 | with: 48 | r-version: ${{ matrix.config.r }} 49 | http-user-agent: ${{ matrix.config.http-user-agent }} 50 | use-public-rspm: true 51 | 52 | - uses: r-lib/actions/setup-r-dependencies@v2 53 | with: 54 | extra-packages: any::rcmdcheck 55 | needs: check 56 | 57 | - uses: r-lib/actions/check-r-package@v2 58 | with: 59 | upload-snapshots: true 60 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cards 2 | Title: Analysis Results Data 3 | Version: 0.7.1.9002 4 | Authors@R: c( 5 | person("Daniel D.", "Sjoberg", , "danield.sjoberg@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-0862-2018")), 7 | person("Becca", "Krouse", , "becca.z.krouse@gsk.com", role = "aut"), 8 | person("Emily", "de la Rua", , "emily.de_la_rua@contractors.roche.com", role = "aut", 9 | comment = c(ORCID = "0009-0000-8738-5561")), 10 | person("Malan", "Bosman", , "malanbos@gmail.com", role = "aut", 11 | comment = c(ORCID = "0000-0002-3020-195X")), 12 | person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")), 13 | person("GlaxoSmithKline Research & Development Limited", role = "cph") 14 | ) 15 | Description: Construct CDISC (Clinical Data Interchange Standards 16 | Consortium) compliant Analysis Results Data objects. These objects are 17 | used and re-used to construct summary tables, visualizations, and 18 | written reports. The package also exports utilities for working with 19 | these objects and creating new Analysis Results Data objects. 20 | License: Apache License 2.0 21 | URL: https://github.com/insightsengineering/cards, 22 | https://insightsengineering.github.io/cards/ 23 | BugReports: https://github.com/insightsengineering/cards/issues 24 | Depends: 25 | R (>= 4.1) 26 | Imports: 27 | cli (>= 3.6.5), 28 | dplyr (>= 1.1.4), 29 | glue (>= 1.8.0), 30 | lifecycle (>= 1.0.4), 31 | rlang (>= 1.1.6), 32 | tidyr (>= 1.3.1), 33 | tidyselect (>= 1.2.1) 34 | Suggests: 35 | testthat (>= 3.2.3), 36 | withr (>= 3.0.0) 37 | Config/Needs/coverage: hms 38 | Config/Needs/website: rmarkdown, jsonlite, yaml, gtsummary, tfrmt, cardx, 39 | gt, fontawesome, insightsengineering/crane, 40 | insightsengineering/nesttemplate 41 | Config/testthat/edition: 3 42 | Config/testthat/parallel: true 43 | Encoding: UTF-8 44 | Language: en-US 45 | LazyData: true 46 | Roxygen: list(markdown = TRUE) 47 | RoxygenNote: 7.3.3 48 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ard_pairwise.md: -------------------------------------------------------------------------------- 1 | # ard_pairwise(variable) messaging 2 | 3 | Code 4 | ard_pairwise(ADSL, variable = c(ARM, AGEGR1), .f = function(df) ard_mvsummary( 5 | df, variables = AGE, statistic = ~ list(ttest = ttest_fn))) 6 | Condition 7 | Error in `ard_pairwise()`: 8 | ! The `variable` argument must be length 1. 9 | 10 | --- 11 | 12 | Code 13 | ard_pairwise(ADSL, variable = NOT_A_COLUMN, .f = function(df) ard_mvsummary(df, 14 | variables = AGE, statistic = ~ list(ttest = ttest_fn))) 15 | Condition 16 | Error in `ard_pairwise()`: 17 | ! Error processing `variable` argument. 18 | ! Can't select columns that don't exist. x Column `NOT_A_COLUMN` doesn't exist. 19 | i Select among columns "STUDYID", "USUBJID", "SUBJID", "SITEID", "SITEGR1", "ARM", "TRT01P", "TRT01PN", "TRT01A", "TRT01AN", "TRTSDT", "TRTEDT", "TRTDUR", "AVGDD", "CUMDOSE", "AGE", "AGEGR1", "AGEGR1N", ..., "MMSETOT", and "TRTA" 20 | 21 | # ard_pairwise(.f) messaging 22 | 23 | Code 24 | ard_pairwise(ADSL, variable = ARM, .f = function(df) stop("I MADE THIS ERROR")) 25 | Condition 26 | Error in `ard_pairwise()`: 27 | ! The following error occurred for 'Placebo' vs. 'Xanomeline High Dose'. See message below. 28 | x I MADE THIS ERROR 29 | 30 | # ard_pairwise(include) messaging 31 | 32 | Code 33 | ard_pairwise(ADSL, variable = ARM, .f = function(df) ard_mvsummary(df, 34 | variables = AGE, statistic = ~ list(ttest = ttest_fn)), include = "NOT_A_LEVEL") 35 | Condition 36 | Error in `ard_pairwise()`: 37 | ! The `include` argument must be NULL or one or more of "Placebo", "Xanomeline High Dose", and "Xanomeline Low Dose". 38 | 39 | --- 40 | 41 | Code 42 | ard_pairwise(ADSL, variable = ARM, .f = function(df) ard_mvsummary(df, 43 | variables = AGE, statistic = ~ list(ttest = ttest_fn)), include = mtcars) 44 | Condition 45 | Error in `ard_pairwise()`: 46 | ! The `include` argument must be a simple vector, not a data frame. 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | covr::to_cobertura(cov) 38 | shell: Rscript {0} 39 | 40 | - uses: codecov/codecov-action@v4 41 | with: 42 | # Fail if error if not on PR, or if on PR and token is given 43 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 44 | file: ./cobertura.xml 45 | plugin: noop 46 | disable_search: true 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | - name: Show testthat output 50 | if: always() 51 | run: | 52 | ## -------------------------------------------------------------------- 53 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 54 | shell: bash 55 | 56 | - name: Upload test results 57 | if: failure() 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: coverage-test-failures 61 | path: ${{ runner.temp }}/package 62 | -------------------------------------------------------------------------------- /man/rename_ard_columns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rename_ard_columns.R 3 | \name{rename_ard_columns} 4 | \alias{rename_ard_columns} 5 | \title{Rename ARD Variables} 6 | \usage{ 7 | rename_ard_columns( 8 | x, 9 | columns = c(all_ard_groups("names"), all_ard_variables("names")), 10 | fill = "{colname}", 11 | unlist = NULL 12 | ) 13 | } 14 | \arguments{ 15 | \item{x}{(\code{data.frame})\cr 16 | an ARD data frame of class 'card'} 17 | 18 | \item{columns}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 19 | columns to rename, e.g. selecting columns \code{c('group1', 'group2', 'variable')} 20 | will rename \code{'group1_level'} to the name of the variable found in \code{'group1'}. 21 | When, for example, the \code{'group1_level'} does not exist, the values of the 22 | new column are filled with the values in the \code{fill} argument. 23 | Default is \code{c(all_ard_groups("names"), all_ard_variables("names"))}.} 24 | 25 | \item{fill}{(scalar/glue)\cr 26 | a scalar to fill column values when the variable does not have levels. 27 | If a character is passed, then it is processed with \code{glue::glue()} 28 | where the \code{colname} element is available to inject into the string, 29 | e.g. \code{'Overall {colname}'} may resolve to \code{'Overall AGE'} for an AGE column. 30 | Default is \code{'{colname}'}.} 31 | 32 | \item{unlist}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}}} 33 | } 34 | \value{ 35 | data frame 36 | } 37 | \description{ 38 | Rename the grouping and variable columns to their original column names. 39 | } 40 | \examples{ 41 | # Example 1 ---------------------------------- 42 | ADSL |> 43 | ard_tabulate(by = ARM, variables = AGEGR1) |> 44 | apply_fmt_fun() |> 45 | rename_ard_columns() |> 46 | unlist_ard_columns() 47 | 48 | # Example 2 ---------------------------------- 49 | ADSL |> 50 | ard_summary(by = ARM, variables = AGE) |> 51 | apply_fmt_fun() |> 52 | rename_ard_columns(fill = "Overall {colname}") |> 53 | unlist_ard_columns() 54 | } 55 | -------------------------------------------------------------------------------- /R/ard_formals.R: -------------------------------------------------------------------------------- 1 | #' Argument Values ARD 2 | #' 3 | #' Place default and passed argument values to a function into an ARD structure. 4 | #' 5 | #' @param fun (`function`)\cr 6 | #' a [function] passed to `formals(fun)` 7 | #' @param arg_names (`character`)\cr 8 | #' character vector of argument names to return 9 | #' @param passed_args (named `list`)\cr 10 | #' a named list of user-passed arguments. Default is `list()`, which returns 11 | #' all default values from a function 12 | #' @param envir (`environment`)\cr 13 | #' an environment passed to `formals(envir)` 14 | #' 15 | #' @return an partial ARD data frame of class 'card' 16 | #' @export 17 | #' 18 | #' @examples 19 | #' # Example 1 ---------------------------------- 20 | #' # add the `mcnemar.test(correct)` argument to an ARD structure 21 | #' ard_formals(fun = mcnemar.test, arg_names = "correct") 22 | #' 23 | #' # Example 2 ---------------------------------- 24 | #' # S3 Methods need special handling to access the underlying method 25 | #' ard_formals( 26 | #' fun = asNamespace("stats")[["t.test.default"]], 27 | #' arg_names = c("mu", "paired", "var.equal", "conf.level"), 28 | #' passed_args = list(conf.level = 0.90) 29 | #' ) 30 | ard_formals <- function(fun, arg_names, passed_args = list(), 31 | envir = parent.frame()) { 32 | # check inputs --------------------------------------------------------------- 33 | set_cli_abort_call() 34 | check_not_missing(fun) 35 | check_not_missing(arg_names) 36 | check_class(passed_args, "list") 37 | check_class(fun, "function") 38 | check_class(arg_names, "character") 39 | check_class(envir, "environment") 40 | 41 | # prepare named list of arguments -------------------------------------------- 42 | lst_args <- 43 | formals(fun = fun, envir = envir)[arg_names] |> 44 | utils::modifyList(val = passed_args[intersect(arg_names, names(passed_args))], keep.null = TRUE) 45 | 46 | # put formals list in ARD structure ------------------------------------------ 47 | enframe(lst_args[arg_names], "stat_name", "stat") |> 48 | dplyr::mutate(stat_label = .data$stat_name, .after = "stat_name") |> 49 | as_card() 50 | } 51 | -------------------------------------------------------------------------------- /man/bind_ard.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/bind_ard.R 3 | \name{bind_ard} 4 | \alias{bind_ard} 5 | \title{Bind ARDs} 6 | \usage{ 7 | bind_ard( 8 | ..., 9 | .distinct = TRUE, 10 | .update = FALSE, 11 | .order = FALSE, 12 | .quiet = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 17 | ARDs to combine. Each argument can either be an ARD, 18 | or a list of ARDs. Columns are matched by name, and any missing 19 | columns will be filled with \code{NA}.} 20 | 21 | \item{.distinct}{(\code{logical})\cr 22 | logical indicating whether to remove non-distinct values from the ARD. 23 | Duplicates are checked across grouping variables, primary variables, 24 | context (if present), the \strong{statistic name and the statistic value}. 25 | Default is \code{TRUE}. If a statistic name and value is repeated and \code{.distinct=TRUE}, 26 | the more recently added statistics will be retained, and the other(s) omitted.} 27 | 28 | \item{.update}{(\code{logical})\cr 29 | logical indicating whether to update ARD and remove duplicated named statistics. 30 | Duplicates are checked across grouping variables, primary variables, and the 31 | \strong{statistic name}. 32 | Default is \code{FALSE}. If a statistic name is repeated and \code{.update=TRUE}, 33 | the more recently added statistics will be retained, and the other(s) omitted.} 34 | 35 | \item{.order}{(\code{logical})\cr 36 | logical indicating whether to order the rows of the stacked ARDs, allowing 37 | statistics that share common group and variable values to appear in 38 | consecutive rows. Default is \code{FALSE}. Ordering will be based on the order 39 | of the group/variable values prior to stacking.} 40 | 41 | \item{.quiet}{(\code{logical})\cr 42 | logical indicating whether to suppress any messaging. Default is \code{FALSE}} 43 | } 44 | \value{ 45 | an ARD data frame of class 'card' 46 | } 47 | \description{ 48 | Wrapper for \code{dplyr::bind_rows()} with additional checks 49 | for duplicated statistics. 50 | } 51 | \examples{ 52 | ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 53 | 54 | bind_ard(ard, ard, .update = TRUE) 55 | } 56 | -------------------------------------------------------------------------------- /man/dot-calculate_tabulation_statistics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_tabulate.R 3 | \name{.calculate_tabulation_statistics} 4 | \alias{.calculate_tabulation_statistics} 5 | \title{Calculate Tabulation Statistics} 6 | \usage{ 7 | .calculate_tabulation_statistics( 8 | data, 9 | variables, 10 | by, 11 | strata, 12 | denominator, 13 | statistic 14 | ) 15 | } 16 | \arguments{ 17 | \item{data}{(\code{data.frame})\cr 18 | a data frame} 19 | 20 | \item{variables}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 21 | columns to include in summaries. Default is \code{everything()}.} 22 | 23 | \item{by, strata}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 24 | columns to use for grouping or stratifying the table output. 25 | Arguments are similar, but with an important distinction: 26 | 27 | \code{by}: results are tabulated by \strong{all combinations} of the columns specified, 28 | including unobserved combinations and unobserved factor levels. 29 | 30 | \code{strata}: results are tabulated by \strong{all \emph{observed} combinations} of the 31 | columns specified. 32 | 33 | Arguments may be used in conjunction with one another.} 34 | 35 | \item{denominator}{(\code{string}, \code{data.frame}, \code{integer})\cr 36 | Specify this argument to change the denominator, 37 | e.g. the \code{"N"} statistic. Default is \code{'column'}. See below for details.} 38 | 39 | \item{statistic}{(\code{\link[=syntax]{formula-list-selector}})\cr 40 | a named list, a list of formulas, 41 | or a single formula where the list element one or more of \code{c("n", "N", "p", "n_cum", "p_cum")} 42 | (on the RHS of a formula).} 43 | } 44 | \value{ 45 | an ARD data frame of class 'card' 46 | } 47 | \description{ 48 | Function takes the summary instructions from the 49 | \code{statistic = list(variable_name = list(tabulation=c("n", "N", "p")))} 50 | argument, and returns the tabulations in an ARD structure. 51 | } 52 | \examples{ 53 | cards:::.calculate_tabulation_statistics( 54 | ADSL, 55 | variables = "ARM", 56 | by = NULL, 57 | strata = NULL, 58 | denominator = "cell", 59 | statistic = list(ARM = list(tabulation = c("N"))) 60 | ) 61 | } 62 | \keyword{internal} 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 🎈 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docs: 12 | name: Pkgdown Docs 📚 13 | needs: release 14 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 15 | secrets: 16 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 17 | with: 18 | default-landing-page: latest-tag 19 | update-r-packages: true 20 | deps-installation-method: setup-r-dependencies 21 | validation: 22 | name: R Package Validation report 📃 23 | needs: release 24 | uses: insightsengineering/r.pkg.template/.github/workflows/validation.yaml@main 25 | secrets: 26 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 27 | release: 28 | name: Create release 🎉 29 | uses: insightsengineering/r.pkg.template/.github/workflows/release.yaml@main 30 | secrets: 31 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 32 | build: 33 | name: Build package and reports 🎁 34 | needs: [release, docs] 35 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 36 | secrets: 37 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 38 | with: 39 | additional-env-vars: | 40 | _R_CHECK_CRAN_INCOMING_REMOTE_=false 41 | additional-r-cmd-check-params: --as-cran 42 | enforce-note-blocklist: true 43 | note-blocklist: | 44 | checking dependencies in R code .* NOTE 45 | checking R code for possible problems .* NOTE 46 | checking examples .* NOTE 47 | checking Rd line widths .* NOTE 48 | checking top-level files .* NOTE 49 | skip-r-cmd-install: true 50 | update-r-packages: true 51 | coverage: 52 | name: Coverage 📔 53 | needs: [release, docs] 54 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 55 | secrets: 56 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 57 | with: 58 | additional-env-vars: | 59 | NOT_CRAN=true 60 | wasm: 61 | name: Build WASM packages 🧑‍🏭 62 | needs: release 63 | uses: insightsengineering/r.pkg.template/.github/workflows/wasm.yaml@main 64 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_strata.R: -------------------------------------------------------------------------------- 1 | test_that("ard_strata() works", { 2 | expect_snapshot( 3 | ard_strata( 4 | ADSL, 5 | .by = ARM, 6 | .f = ~ ard_summary(.x, variables = AGE) 7 | ) 8 | ) 9 | 10 | expect_snapshot( 11 | ard_strata( 12 | ADSL, 13 | .strata = ARM, 14 | .f = ~ ard_summary(.x, variables = AGE, by = AGEGR1) 15 | ) 16 | ) 17 | 18 | expect_equal( 19 | ard_strata(ADSL, .by = ARM, .f = ~ ard_summary(.x, by = c(SEX, AGEGR1), variables = AGE)) |> 20 | tidy_ard_column_order() |> 21 | tidy_ard_row_order(), 22 | ard_summary(ADSL, by = c(SEX, AGEGR1, ARM), variables = AGE) |> 23 | tidy_ard_row_order() 24 | ) 25 | }) 26 | 27 | test_that("ard_strata(by,strata) when both empty", { 28 | expect_equal( 29 | ard_strata(ADSL, .f = ~ ard_summary(.x, variables = AGE)), 30 | ard_summary(ADSL, variables = AGE) 31 | ) 32 | 33 | expect_equal( 34 | ard_strata(ADSL, .f = ~ ard_summary(.x, by = ARM, variables = AGE)), 35 | ard_summary(ADSL, by = ARM, variables = AGE) 36 | ) 37 | }) 38 | 39 | test_that("ard_strata computes stats for parameter specific strata", { 40 | withr::local_options(list(width = 180)) 41 | 42 | df <- data.frame( 43 | USUBJID = 1:12, 44 | PARAMCD = rep(c("PARAM1", "PARAM2"), each = 6), 45 | AVALC = c( 46 | "Yes", "No", "Yes", # PARAM1 47 | "Yes", "Yes", "No", # PARAM1 48 | "Low", "Medium", "High", # PARAM2 49 | "Low", "Low", "Medium" # PARAM2 50 | ) 51 | ) 52 | param_levels <- 53 | list( 54 | PARAM1 = c("Yes", "No"), 55 | PARAM2 = c("Zero", "Low", "Medium", "High") 56 | ) 57 | 58 | tbl <- ard_strata( 59 | df, 60 | .strata = PARAMCD, 61 | .f = \(.x) { 62 | param <- .x[["PARAMCD"]][1] 63 | .x |> 64 | dplyr::mutate( 65 | AVALC = factor(AVALC, levels = param_levels[[param]]) 66 | ) |> 67 | ard_tabulate(variables = AVALC) 68 | } 69 | ) 70 | 71 | ## line added to fix failing snapshot test on ubuntu-latest (devel) 72 | ## TODO: resolve after release of R-devel 73 | skip_if_not(package_version(paste(R.version$major, R.version$minor, sep = ".")) <= package_version("4.5.0")) 74 | 75 | expect_snapshot(as.data.frame(tbl)) 76 | }) 77 | -------------------------------------------------------------------------------- /pkgdown/index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | ```{r child='../README.Rmd'} 6 | ``` 7 | 8 | ## Other Resources 9 | 10 | ```{r, echo=FALSE} 11 | dplyr::tribble( 12 | ~venue, ~title, ~url_slides, ~url_video, 13 | "`posit::conf(2025)`", "{pharmaverse} workshop", "https://posit-conf-2025.github.io/pharmaverse/", NA_character_, 14 | "2025 CDISC COSA Spotlight", "ARD-based Reporting in R with {cards} and {gtsummary} packages", "https://www.danieldsjoberg.com/CDISC-COSA-Spotlight-ARD-gtsummary-2025/", "https://www.cdisc.org/events/webinar/cosa-spotlight-q2-2025", 15 | "2025 R/Medicine", "Supercharging Statistical Analysis with ARDs and the {cards} R Package", "https://melkiades.github.io/ARD_rmedicine25/", "https://youtu.be/C_Y1pzpCS5o?feature=shared", 16 | "2025 PHUSE US Connect", "Analysis Results Datasets Using Open-Source Tools from the {pharmaverse}", "https://www.danieldsjoberg.com/ARD-PHUSE-workshop-2025/", NA_character_, 17 | "2025 PHUSE US Connect", "Harnessing Analysis Results Datasets (ARD) for Clinical Reporting in R 18 | Our First ARD-Based Filing Experience with {cards}+{gtsummary}", "https://www.danieldsjoberg.com/ARD-PHUSE-talk-2025/", NA_character_, 19 | "2024 R/Pharma", "Harnessing CDISC's Emerging Analysis Results Datasets Standard", "https://www.danieldsjoberg.com/ARD-RinPharma-talk-2024/", "https://www.youtube.com/watch?v=tDb6O6a5lbc", 20 | "2024 R/Pharma", "Workshop: Unlocking Analysis Results Datasets ", "https://www.danieldsjoberg.com/ARD-RinPharma-workshop-2024/", NA_character_, 21 | "`posit::conf(2024)`", "{pharmaverse} workshop", "https://posit-conf-2024.github.io/pharmaverse/#schedule", NA_character_, 22 | "2024 China Pharma R User Conference", "Keynote Address", "https://www.danieldsjoberg.com/china-pharma-keynote-2024/material.html", NA_character_ 23 | ) |> 24 | gt::gt() |> 25 | gt::fmt_markdown(columns = c(venue, title)) |> 26 | gt::fmt_url( 27 | columns = url_slides, 28 | rows = !is.na(url_slides), 29 | label = fontawesome::fa("display", fill = "#808080") 30 | ) |> 31 | gt::fmt_url( 32 | columns = url_video, 33 | rows = !is.na(url_video), 34 | label = fontawesome::fa("youtube", fill = "#808080") 35 | ) |> 36 | gt::sub_missing(missing_text = "") |> 37 | gt::cols_align(align = "center", columns = dplyr::contains("url")) |> 38 | gt::tab_options(column_labels.hidden = TRUE) 39 | ``` 40 | -------------------------------------------------------------------------------- /tests/testthat/test-selectors.R: -------------------------------------------------------------------------------- 1 | test_that("selectors work", { 2 | ard_testing <- ard_tabulate(ADSL, by = ARM, variables = AGE) 3 | 4 | expect_equal( 5 | ard_testing |> 6 | dplyr::select(all_ard_groups()) |> 7 | names(), 8 | c("group1", "group1_level") 9 | ) 10 | expect_equal( 11 | ard_testing |> 12 | dplyr::select(all_ard_groups("names")) |> 13 | names(), 14 | "group1" 15 | ) 16 | expect_equal( 17 | ard_testing |> 18 | dplyr::select(all_ard_groups("levels")) |> 19 | names(), 20 | "group1_level" 21 | ) 22 | 23 | expect_equal( 24 | ard_testing |> 25 | dplyr::select(all_ard_variables()) |> 26 | names(), 27 | c("variable", "variable_level") 28 | ) 29 | expect_equal( 30 | ard_testing |> 31 | dplyr::select(all_ard_variables("names")) |> 32 | names(), 33 | "variable" 34 | ) 35 | expect_equal( 36 | ard_testing |> 37 | dplyr::select(all_ard_variables("levels")) |> 38 | names(), 39 | "variable_level" 40 | ) 41 | 42 | # test group selector works for 10+ groups 43 | expect_equal( 44 | suppressMessages( 45 | rep_len(list(mtcars[c("am", "vs")]), length.out = 11) |> dplyr::bind_cols() 46 | ) |> 47 | ard_tabulate( 48 | variables = "vs...2", 49 | by = starts_with("am"), 50 | statistic = ~"n" 51 | ) |> 52 | dplyr::select(all_ard_groups()) |> 53 | names() |> 54 | length(), 55 | 22L 56 | ) 57 | 58 | # all_ard_group_n() works 59 | expect_equal( 60 | ard_tabulate( 61 | mtcars, 62 | by = c(am, vs), 63 | variables = cyl 64 | ) |> 65 | dplyr::select(all_ard_group_n(1L)) |> 66 | names(), 67 | c("group1", "group1_level") 68 | ) 69 | 70 | expect_equal( 71 | ard_tabulate( 72 | mtcars, 73 | by = c(am, vs), 74 | variables = cyl 75 | ) |> 76 | dplyr::select(all_ard_group_n(1:2)) |> 77 | names(), 78 | c("group1", "group1_level", "group2", "group2_level") 79 | ) 80 | 81 | # all_missing_columns() works 82 | expect_equal( 83 | bind_ard( 84 | ard_tabulate(mtcars, by = am, variables = cyl), 85 | ard_tabulate(mtcars, variables = vs) 86 | ) |> 87 | dplyr::filter(variable == "vs") |> 88 | dplyr::select(all_missing_columns()) |> 89 | names(), 90 | c("group1", "group1_level", "warning", "error") 91 | ) 92 | }) 93 | -------------------------------------------------------------------------------- /man/ard_strata.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_strata.R 3 | \name{ard_strata} 4 | \alias{ard_strata} 5 | \title{Stratified ARD} 6 | \usage{ 7 | ard_strata(.data, .by = NULL, .strata = NULL, .f, ...) 8 | } 9 | \arguments{ 10 | \item{.data}{(\code{data.frame})\cr 11 | a data frame} 12 | 13 | \item{.by, .strata}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 14 | columns to tabulate by/stratify by for calculation. 15 | Arguments are similar, but with an important distinction: 16 | 17 | \code{.by}: results are tabulated by \strong{all combinations} of the columns specified, 18 | including unobserved combinations and unobserved factor levels. 19 | 20 | \code{.strata}: results are tabulated by \strong{all \emph{observed} combinations} of the 21 | columns specified. 22 | 23 | These argument \emph{should not} include any columns that appear in the \code{.f} argument.} 24 | 25 | \item{.f}{(\code{function}, \code{formula})\cr 26 | a function or a formula that can be coerced to a function with 27 | \code{rlang::as_function()} (similar to \code{purrr::map(.f)})} 28 | 29 | \item{...}{Additional arguments passed on to the \code{.f} function.} 30 | } 31 | \value{ 32 | an ARD data frame of class 'card' 33 | } 34 | \description{ 35 | General function for calculating ARD results within subgroups. 36 | 37 | While the examples below show use with other functions from the cards package, 38 | this function would primarily be used with the statistical functions in the 39 | cardx functions. 40 | } 41 | \examples{ 42 | # Example 1 ---------------------------------- 43 | ard_strata( 44 | ADSL, 45 | .by = ARM, 46 | .f = ~ ard_summary(.x, variables = AGE) 47 | ) 48 | 49 | # Example 2 ---------------------------------- 50 | df <- data.frame( 51 | USUBJID = 1:12, 52 | PARAMCD = rep(c("PARAM1", "PARAM2"), each = 6), 53 | AVALC = c( 54 | "Yes", "No", "Yes", # PARAM1 55 | "Yes", "Yes", "No", # PARAM1 56 | "Low", "Medium", "High", # PARAM2 57 | "Low", "Low", "Medium" # PARAM2 58 | ) 59 | ) 60 | 61 | ard_strata( 62 | df, 63 | .strata = PARAMCD, 64 | .f = \(.x) { 65 | lvls <- 66 | switch(.x[["PARAMCD"]][1], 67 | "PARAM1" = c("Yes", "No"), 68 | "PARAM2" = c("Zero", "Low", "Medium", "High") 69 | ) 70 | 71 | .x |> 72 | dplyr::mutate(AVALC = factor(AVALC, levels = lvls)) |> 73 | ard_tabulate(variables = AVALC) 74 | } 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /man/nest_for_ard.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nest_for_ard.R 3 | \name{nest_for_ard} 4 | \alias{nest_for_ard} 5 | \title{ARD Nesting} 6 | \usage{ 7 | nest_for_ard( 8 | data, 9 | by = NULL, 10 | strata = NULL, 11 | key = "data", 12 | rename_columns = TRUE, 13 | list_columns = TRUE, 14 | include_data = TRUE, 15 | include_by_and_strata = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{data}{(\code{data.frame})\cr 20 | a data frame} 21 | 22 | \item{by, strata}{(\code{character})\cr 23 | columns to nest by/stratify by. Arguments are similar, 24 | but with an important distinction: 25 | 26 | \code{by}: data frame is nested by \strong{all combinations} of the columns specified, 27 | including unobserved combinations and unobserved factor levels. 28 | 29 | \code{strata}: data frame is nested by \strong{all \emph{observed} combinations} of the 30 | columns specified. 31 | 32 | Arguments may be used in conjunction with one another.} 33 | 34 | \item{key}{(\code{string})\cr 35 | the name of the new column with the nested data frame. Default is \code{"data"}.} 36 | 37 | \item{rename_columns}{(\code{logical})\cr 38 | logical indicating whether to rename the \code{by} and \code{strata} variables. 39 | Default is \code{TRUE}.} 40 | 41 | \item{list_columns}{(\code{logical})\cr 42 | logical indicating whether to put levels of \code{by} and 43 | \code{strata} columns in a list. Default is \code{TRUE}.} 44 | 45 | \item{include_data}{(scalar \code{logical})\cr 46 | logical indicating whether to include the data subsets as a list-column. 47 | Default is \code{TRUE}.} 48 | 49 | \item{include_by_and_strata}{(\code{logical})\cr 50 | When \code{TRUE}, the \code{by} and \code{strata} variables are included in the nested 51 | data frames.} 52 | } 53 | \value{ 54 | a nested tibble 55 | } 56 | \description{ 57 | This function is similar to \code{\link[tidyr:nest]{tidyr::nest()}}, except that it retains 58 | rows for unobserved combinations (and unobserved factor levels) of by 59 | variables, and unobserved combinations of stratifying variables. 60 | 61 | The levels are wrapped in lists so they can be stacked with other types 62 | of different classes. 63 | } 64 | \examples{ 65 | nest_for_ard( 66 | data = 67 | ADAE |> 68 | dplyr::left_join(ADSL[c("USUBJID", "ARM")], by = "USUBJID") |> 69 | dplyr::filter(AOCCSFL \%in\% "Y"), 70 | by = "ARM", 71 | strata = "AESOC" 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /R/syntax.R: -------------------------------------------------------------------------------- 1 | #' Selecting Syntax 2 | #' 3 | #' @name syntax 4 | #' @keywords internal 5 | #' 6 | #' @description 7 | #' # Selectors 8 | #' 9 | #' The cards package also utilizes selectors: selectors from the tidyselect 10 | #' package and custom selectors. Review their help files for details. 11 | #' 12 | #' - **tidy selectors** 13 | #' 14 | #' [everything()], [all_of()], [any_of()], [starts_with()], [ends_with()], 15 | #' [contains()], [matches()], [num_range()], [last_col()] 16 | #' 17 | #' - **cards selectors** 18 | #' 19 | #' [all_ard_groups()], [all_ard_variables()] 20 | #' 21 | #' # Formula and List Selectors 22 | #' 23 | #' Some arguments in the cards package accept list and 24 | #' formula notation, e.g. `ard_summary(statistic=)`. 25 | #' Below enumerates a few tips and shortcuts for using the list and formulas. 26 | #' 27 | #' 1. **List of Formulas** 28 | #' 29 | #' Typical usage includes a list of formulas, where the LHS is a variable 30 | #' name or a selector. 31 | #' 32 | #' ```r 33 | #' ard_summary(statistic = list(age ~ list(N = \(x) length(x)), starts_with("a") ~ list(mean = mean))) 34 | #' ``` 35 | #' 36 | #' 2. **Named List** 37 | #' 38 | #' You may also pass a named list; however, the tidyselect selectors 39 | #' are not supported with this syntax. 40 | #' 41 | #' ```r 42 | #' ard_summary(statistic = list(age = list(N = \(x) length(x)))) 43 | #' ``` 44 | #' 45 | #' 3. **Hybrid Named List/List of Formulas** 46 | #' 47 | #' You can pass a combination of formulas and named elements. 48 | #' 49 | #' ```r 50 | #' ard_summary(statistic = list(age = list(N = \(x) length(x)), starts_with("a") ~ list(mean = mean))) 51 | #' ``` 52 | #' 53 | #' 4. **Shortcuts** 54 | #' 55 | #' You can pass a single formula, which is equivalent to passing the formula 56 | #' in a list. 57 | #' 58 | #' ```r 59 | #' ard_summary(statistic = starts_with("a") ~ list(mean = mean) 60 | #' ``` 61 | #' 62 | #' As a shortcut to select all variables, you can omit the LHS of the formula. 63 | #' The two calls below are equivalent. 64 | #' 65 | #' ```r 66 | #' ard_summary(statistic = ~list(N = \(x) length(x))) 67 | #' ard_summary(statistic = everything() ~ list(N = \(x) length(x))) 68 | #' ``` 69 | #' 70 | #' 5. **Combination Selectors** 71 | #' 72 | #' Selectors can be combined using the `c()` function. 73 | #' 74 | #' ```r 75 | #' ard_summary(statistic = c(everything(), -age) ~ list(N = \(x) length(x))) 76 | #' ``` 77 | NULL 78 | -------------------------------------------------------------------------------- /man/update_ard.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/update_ard.R 3 | \name{update_ard} 4 | \alias{update_ard} 5 | \alias{update_ard_fmt_fun} 6 | \alias{update_ard_stat_label} 7 | \title{Update ARDs} 8 | \usage{ 9 | update_ard_fmt_fun( 10 | x, 11 | variables = everything(), 12 | stat_names, 13 | fmt_fun, 14 | filter = TRUE, 15 | fmt_fn = deprecated() 16 | ) 17 | 18 | update_ard_stat_label( 19 | x, 20 | variables = everything(), 21 | stat_names, 22 | stat_label, 23 | filter = TRUE 24 | ) 25 | } 26 | \arguments{ 27 | \item{x}{(\code{data.frame})\cr 28 | an ARD data frame of class 'card'} 29 | 30 | \item{variables}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 31 | variables in \code{x$variable} to apply update. Default is \code{everything()}.} 32 | 33 | \item{stat_names}{(\code{character})\cr 34 | character vector of the statistic names (i.e. values from \code{x$stat_name}) to 35 | apply the update.} 36 | 37 | \item{fmt_fun}{(\code{function})\cr 38 | a function or alias recognized by \code{alias_as_fmt_fun()}.} 39 | 40 | \item{filter}{(\code{expression})\cr 41 | an expression that evaluates to a logical vector identifying rows in \code{x} 42 | to apply the update to. Default is \code{TRUE}, and update is applied to 43 | all rows.} 44 | 45 | \item{fmt_fn}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}}} 46 | 47 | \item{stat_label}{(\code{function})\cr 48 | a string of the updated statistic label.} 49 | } 50 | \value{ 51 | an ARD data frame of class 'card' 52 | } 53 | \description{ 54 | Functions used to update ARD formatting functions and statistic labels. 55 | 56 | This is a helper function to streamline the update process. If it does not 57 | exactly meet your needs, recall that an ARD is just a data frame and it 58 | can be modified directly. 59 | } 60 | \examples{ 61 | ard_summary(ADSL, variables = AGE) |> 62 | update_ard_fmt_fun(stat_names = c("mean", "sd"), fmt_fun = 8L) |> 63 | update_ard_stat_label(stat_names = c("mean", "sd"), stat_label = "Mean (SD)") |> 64 | apply_fmt_fun() 65 | 66 | # same as above, but only apply update to the Placebo level 67 | ard_summary( 68 | ADSL, 69 | by = ARM, 70 | variables = AGE, 71 | statistic = ~ continuous_summary_fns(c("N", "mean")) 72 | ) |> 73 | update_ard_fmt_fun(stat_names = "mean", fmt_fun = 8L, filter = group1_level == "Placebo") |> 74 | apply_fmt_fun() 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Scheduled 🕰️ 3 | 4 | on: 5 | schedule: 6 | - cron: '45 3 * * 0' 7 | workflow_dispatch: 8 | inputs: 9 | chosen-workflow: 10 | description: | 11 | Select which workflow you'd like to run 12 | required: true 13 | type: choice 14 | default: rhub 15 | options: 16 | - rhub 17 | - dependency-test 18 | - branch-cleanup 19 | - revdepcheck 20 | 21 | jobs: 22 | dependency-test: 23 | if: > 24 | github.event_name == 'schedule' || ( 25 | github.event_name == 'workflow_dispatch' && 26 | inputs.chosen-workflow == 'dependency-test' 27 | ) 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | test-strategy: ["min_cohort", "min_isolated", "release", "max"] 32 | uses: insightsengineering/r.pkg.template/.github/workflows/verdepcheck.yaml@main 33 | name: Dependency Test - ${{ matrix.test-strategy }} 🔢 34 | secrets: 35 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 36 | GCHAT_WEBHOOK: ${{ secrets.GCHAT_WEBHOOK }} 37 | with: 38 | strategy: ${{ matrix.test-strategy }} 39 | additional-env-vars: | 40 | PKG_SYSREQS_DRY_RUN=true 41 | branch-cleanup: 42 | if: > 43 | github.event_name == 'schedule' || ( 44 | github.event_name == 'workflow_dispatch' && 45 | inputs.chosen-workflow == 'branch-cleanup' 46 | ) 47 | name: Branch Cleanup 🧹 48 | uses: insightsengineering/r.pkg.template/.github/workflows/branch-cleanup.yaml@main 49 | secrets: 50 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 51 | with: 52 | last-commit-age-days: 365 53 | cran-status: 54 | name: CRAN Status Monitor 📺 55 | uses: insightsengineering/r.pkg.template/.github/workflows/cran-status.yaml@main 56 | with: 57 | issue-assignees: "ddsjoberg" 58 | revdepcheck: 59 | if: > 60 | github.event_name == 'schedule' || ( 61 | github.event_name == 'workflow_dispatch' && 62 | inputs.chosen-workflow == 'revdepcheck' 63 | ) 64 | name: revdepcheck ↩️ 65 | uses: insightsengineering/r.pkg.template/.github/workflows/revdepcheck.yaml@main 66 | rhub: 67 | if: > 68 | github.event_name == 'schedule' || ( 69 | github.event_name == 'workflow_dispatch' && 70 | inputs.chosen-workflow == 'rhub' 71 | ) 72 | name: R-hub 🌐 73 | uses: insightsengineering/r.pkg.template/.github/workflows/rhub.yaml@main 74 | -------------------------------------------------------------------------------- /tests/testthat/test-ard_missing.R: -------------------------------------------------------------------------------- 1 | test_that("ard_missing() works", { 2 | expect_error( 3 | ard <- ard_missing(ADSL, by = "ARM", variables = "BMIBL"), 4 | NA 5 | ) 6 | 7 | expect_snapshot( 8 | ard |> 9 | dplyr::select(-"fmt_fun") |> 10 | as.data.frame() 11 | ) 12 | 13 | # confirm missing rate is correct 14 | expect_equal( 15 | ard |> 16 | dplyr::filter(stat_name %in% "p_miss") |> 17 | dplyr::pull(stat) |> 18 | unlist(), 19 | ADSL |> 20 | dplyr::mutate(BMIBL = is.na(BMIBL)) |> 21 | dplyr::summarise( 22 | .by = ARM, 23 | stat = mean(BMIBL) 24 | ) |> 25 | dplyr::pull(stat) 26 | ) 27 | }) 28 | 29 | test_that("ard_missing(stat_label) argument works", { 30 | # formula 31 | expect_snapshot( 32 | ard_missing( 33 | data = ADSL, 34 | by = "ARM", 35 | variables = c("AGE", "BMIBL"), 36 | stat_label = everything() ~ list(c("N_obs", "N_miss") ~ "N, miss") 37 | ) |> 38 | as.data.frame() |> 39 | dplyr::select(stat_name, stat_label) |> 40 | dplyr::filter(stat_name %in% c("N_obs", "N_miss")) |> 41 | unique() 42 | ) 43 | 44 | # list 45 | expect_snapshot( 46 | ard_missing( 47 | data = ADSL, 48 | by = "ARM", 49 | variables = c("AGEGR1", "SEX"), 50 | stat_label = everything() ~ list(p_miss = "% miss", p_nonmiss = "% non miss") 51 | ) |> 52 | as.data.frame() |> 53 | dplyr::select(stat_name, stat_label) |> 54 | dplyr::filter(stat_name %in% c("p_miss", "p_nonmiss")) |> 55 | unique() 56 | ) 57 | 58 | # variable-specific 59 | expect_snapshot( 60 | ard_missing( 61 | data = ADSL, 62 | by = "ARM", 63 | variables = c("AGE", "BMIBL"), 64 | stat_label = AGE ~ list(N_obs = "Number of Obs") 65 | ) |> 66 | as.data.frame() |> 67 | dplyr::select(variable, stat_name, stat_label) |> 68 | dplyr::filter(stat_name == "N_obs") |> 69 | unique() 70 | ) 71 | }) 72 | 73 | test_that("ard_missing() with grouped data works", { 74 | expect_equal( 75 | ADSL |> 76 | dplyr::group_by(ARM) |> 77 | ard_missing(variables = "BMIBL"), 78 | ard_missing( 79 | data = ADSL, 80 | by = "ARM", 81 | variables = "BMIBL" 82 | ) 83 | ) 84 | }) 85 | 86 | test_that("ard_missing() follows ard structure", { 87 | expect_silent( 88 | ADSL |> 89 | dplyr::group_by(ARM) |> 90 | ard_missing(variables = "BMIBL") |> 91 | check_ard_structure(method = FALSE) 92 | ) 93 | }) 94 | -------------------------------------------------------------------------------- /tests/testthat/test-eval_capture_conditions.R: -------------------------------------------------------------------------------- 1 | test_that("eval_capture_conditions() works", { 2 | # no errors 3 | expect_snapshot( 4 | eval_capture_conditions( 5 | expr(TRUE) 6 | ) 7 | ) 8 | 9 | # capture the error 10 | expect_snapshot( 11 | eval_capture_conditions( 12 | expr(cli::cli_abort("BIG ERROR")) 13 | ) 14 | ) 15 | 16 | # capture warning 17 | expect_snapshot({ 18 | one_warn_foo <- function() { 19 | cli::cli_warn("BIG WARNING") 20 | TRUE 21 | } 22 | eval_capture_conditions(expr(one_warn_foo())) 23 | }) 24 | 25 | # capture multiple warning 26 | expect_snapshot({ 27 | two_warn_foo <- function() { 28 | cli::cli_warn("{.emph BIG} WARNING1") 29 | cli::cli_warn("{.emph BIG} WARNING2") 30 | TRUE 31 | } 32 | eval_capture_conditions(expr(two_warn_foo())) 33 | }) 34 | }) 35 | 36 | # captured_condition_as_message() ---------------------------------------------- 37 | test_that("captured_condition_as_message() works", { 38 | # we get the result back when there is no error or warning 39 | expect_equal( 40 | eval_capture_conditions(letters) |> 41 | captured_condition_as_message(), 42 | letters 43 | ) 44 | 45 | # print error as message with curly brackets in it 46 | expect_snapshot( 47 | eval_capture_conditions(stop("This is an {error}!")) |> 48 | captured_condition_as_message() 49 | ) 50 | 51 | # print multiple warnings 52 | expect_snapshot( 53 | eval_capture_conditions({ 54 | warning("This is a {warning} 1") 55 | warning("This is a {warning} 2") 56 | NULL 57 | }) |> 58 | captured_condition_as_message(type = "warning") 59 | ) 60 | }) 61 | 62 | # captured_condition_as_error() ---------------------------------------------- 63 | test_that("captured_condition_as_error() works", { 64 | # we get the result back when there is no error or warning 65 | expect_equal( 66 | eval_capture_conditions(letters) |> 67 | captured_condition_as_error(), 68 | letters 69 | ) 70 | 71 | # print error as message with curly brackets in it 72 | expect_snapshot( 73 | error = TRUE, 74 | eval_capture_conditions(stop("This is an {error}!")) |> 75 | captured_condition_as_error() 76 | ) 77 | 78 | # print multiple warnings 79 | expect_snapshot( 80 | error = TRUE, 81 | eval_capture_conditions({ 82 | warning("This is a {warning} 1") 83 | warning("This is a {warning} 2") 84 | NULL 85 | }) |> 86 | captured_condition_as_error(type = "warning") 87 | ) 88 | }) 89 | -------------------------------------------------------------------------------- /R/as_card_fn.R: -------------------------------------------------------------------------------- 1 | #' As card function 2 | #' 3 | #' Add attributes to a function that specify the expected results. 4 | #' It is used when `ard_summary()` or `ard_mvsummary()` errors and constructs 5 | #' an ARD with the correct structure when the results cannot be calculated. 6 | #' 7 | #' @param f (`function`)\cr 8 | #' a function 9 | #' @param stat_names (`character`)\cr 10 | #' a character vector of the expected statistic names returned by function `f` 11 | #' 12 | #' @return an ARD data frame of class 'card' 13 | #' @name as_cards_fn 14 | #' 15 | #' @examples 16 | #' # When there is no error, everything works as if we hadn't used `as_card_fn()` 17 | #' ttest_works <- 18 | #' as_cards_fn( 19 | #' \(x) t.test(x)[c("statistic", "p.value")], 20 | #' stat_names = c("statistic", "p.value") 21 | #' ) 22 | #' ard_summary( 23 | #' mtcars, 24 | #' variables = mpg, 25 | #' statistic = ~ list(ttest = ttest_works) 26 | #' ) 27 | #' 28 | #' # When there is an error and we use `as_card_fn()`, 29 | #' # we will see the same structure as when there is no error 30 | #' ttest_error <- 31 | #' as_cards_fn( 32 | #' \(x) { 33 | #' t.test(x)[c("statistic", "p.value")] 34 | #' stop("Intentional Error") 35 | #' }, 36 | #' stat_names = c("statistic", "p.value") 37 | #' ) 38 | #' ard_summary( 39 | #' mtcars, 40 | #' variables = mpg, 41 | #' statistic = ~ list(ttest = ttest_error) 42 | #' ) 43 | #' 44 | #' # if we don't use `as_card_fn()` and there is an error, 45 | #' # the returned result is only one row 46 | #' ard_summary( 47 | #' mtcars, 48 | #' variables = mpg, 49 | #' statistic = ~ list(ttest = \(x) { 50 | #' t.test(x)[c("statistic", "p.value")] 51 | #' stop("Intentional Error") 52 | #' }) 53 | #' ) 54 | NULL 55 | 56 | #' @rdname as_cards_fn 57 | #' @export 58 | as_cards_fn <- function(f, stat_names) { 59 | set_cli_abort_call() 60 | 61 | # check inputs --------------------------------------------------------------- 62 | check_class(f, "function") 63 | check_class(stat_names, "character") 64 | 65 | # add attribute -------------------------------------------------------------- 66 | attr(f, "stat_names") <- stat_names 67 | 68 | # return function and add a class -------------------------------------------- 69 | structure(f, class = c("cards_fn", class(f))) 70 | } 71 | 72 | #' @rdname as_cards_fn 73 | #' @export 74 | is_cards_fn <- function(f) { 75 | inherits(f, "cards_fn") 76 | } 77 | 78 | #' @rdname as_cards_fn 79 | #' @export 80 | get_cards_fn_stat_names <- function(f) { 81 | check_class(f, "cards_fn") 82 | attr(f, "stat_names") 83 | } 84 | -------------------------------------------------------------------------------- /R/get_ard_statistics.R: -------------------------------------------------------------------------------- 1 | #' ARD Statistics as List 2 | #' 3 | #' Returns the statistics from an ARD as a named list. 4 | #' 5 | #' @param x (`data.frame`)\cr 6 | #' an ARD data frame of class 'card' 7 | #' @param ... ([`dynamic-dots`][rlang::dyn-dots])\cr 8 | #' optional arguments indicating rows to subset of the ARD. 9 | #' For example, to return only rows where the column `"AGEGR1"` is `"65-80"`, 10 | #' pass `AGEGR1 %in% "65-80"`. 11 | #' @param .column (`string`)\cr 12 | #' string indicating the column that will be returned in the list. 13 | #' Default is `"statistic"` 14 | #' @param .attributes (`character`)\cr 15 | #' character vector of column names that will be returned 16 | #' in the list as attributes. 17 | #' Default is `NULL` 18 | #' 19 | #' @return named list 20 | #' @export 21 | #' 22 | #' @examples 23 | #' ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 24 | #' 25 | #' get_ard_statistics( 26 | #' ard, 27 | #' group1_level %in% "Placebo", 28 | #' variable_level %in% "65-80", 29 | #' .attributes = "stat_label" 30 | #' ) 31 | get_ard_statistics <- function(x, 32 | ..., 33 | .column = "stat", 34 | .attributes = NULL) { 35 | set_cli_abort_call() 36 | 37 | # subset the ARD 38 | ard_subset <- dplyr::filter(x, ...) 39 | 40 | # return a named list of the selected stats 41 | # add attributes for the label, formatting function, warnings, and errors 42 | seq_len(nrow(ard_subset)) |> 43 | lapply( 44 | FUN = function(i) { 45 | # styler: off 46 | ard_subset[[.column]][[i]] %>% 47 | {inject(structure( 48 | ., !!!.create_list_for_attributes(ard_subset, .attributes, i) 49 | ))} 50 | } 51 | # styler: on 52 | ) |> 53 | stats::setNames(ard_subset[["stat_name"]]) 54 | } 55 | 56 | #' Create List for Attributes 57 | #' 58 | #' @param ard_subset (`data.frame`)\cr 59 | #' an ARD data frame of class 'card' 60 | #' @param attributes (`character`)\cr 61 | #' a character vector of attribute names 62 | #' @param i (`integer`)\cr 63 | #' a row index number 64 | #' 65 | #' @return a named list 66 | #' @keywords internal 67 | #' 68 | #' @examples 69 | #' ard <- ard_tabulate(ADSL, by = "ARM", variables = "AGEGR1") 70 | #' 71 | #' cards:::.create_list_for_attributes(ard, c("group1", "group1_level"), 1) 72 | .create_list_for_attributes <- function(ard_subset, attributes, i) { 73 | ret <- list() 74 | for (attr in seq_along(attributes)) { 75 | ret <- c(ret, list(ard_subset[[attr]][[i]])) 76 | } 77 | stats::setNames(ret, nm = attributes) 78 | } 79 | -------------------------------------------------------------------------------- /R/unlist_ard_columns.R: -------------------------------------------------------------------------------- 1 | #' Unlist ARD Columns 2 | #' 3 | #' @param x (`data.frame`)\cr 4 | #' an ARD data frame of class 'card' or any data frame 5 | #' @param columns ([`tidy-select`][dplyr::dplyr_tidy_select])\cr 6 | #' columns to unlist. Default is 7 | #' `c(where(is.list), -any_of(c("warning", "error", "fmt_fun")))`. 8 | #' @param fill (scalar)\cr 9 | #' scalar to fill NULL values with before unlisting (if they are present). 10 | #' Default is `NA`. 11 | #' @param fct_as_chr (scalar `logical`)\cr 12 | #' When `TRUE`, factor elements will be converted to character before unlisting. 13 | #' When the column being unlisted contains mixed types of classes, the 14 | #' factor elements are often converted to the underlying integer value instead 15 | #' of retaining the label. Default is `TRUE`. 16 | #' 17 | #' 18 | #' @returns a data frame 19 | #' @export 20 | #' 21 | #' @examples 22 | #' ADSL |> 23 | #' ard_tabulate(by = ARM, variables = AGEGR1) |> 24 | #' apply_fmt_fun() |> 25 | #' unlist_ard_columns() 26 | #' 27 | #' ADSL |> 28 | #' ard_summary(by = ARM, variables = AGE) |> 29 | #' apply_fmt_fun() |> 30 | #' unlist_ard_columns() 31 | unlist_ard_columns <- function(x, 32 | columns = c(where(is.list), -any_of(c("warning", "error", "fmt_fun"))), 33 | fill = NA, 34 | fct_as_chr = TRUE) { 35 | # check inputs --------------------------------------------------------------- 36 | set_cli_abort_call() 37 | check_data_frame(x) 38 | process_selectors(x, columns = {{ columns }}) 39 | check_scalar(fill) 40 | check_scalar_logical(fct_as_chr) 41 | 42 | # first replace any NULL values with the fill value -------------------------- 43 | if (isTRUE(fct_as_chr)) { 44 | x <- x |> 45 | dplyr::mutate( 46 | across( 47 | all_of(columns), 48 | ~ map(., \(value) { 49 | if (inherits(value, "factor")) value <- as.character(value) # styler: off 50 | value %||% .env$fill 51 | }) 52 | ) 53 | ) 54 | } else { 55 | x <- x |> 56 | dplyr::mutate( 57 | across(all_of(columns), ~ map(., \(value) value %||% .env$fill)) 58 | ) 59 | } 60 | 61 | 62 | # unlist the columns --------------------------------------------------------- 63 | for (var in columns) { 64 | var_unlisted <- unlist(x[[var]]) 65 | if (length(var_unlisted) != length(x[[var]])) { 66 | cli::cli_inform("Cannot unlist column {.val {var}}.") 67 | next 68 | } 69 | x[[var]] <- var_unlisted 70 | } 71 | 72 | # return unlisted object ----------------------------------------------------- 73 | x 74 | } 75 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' ARD-flavor of unique() 2 | #' 3 | #' Essentially a wrapper for `unique(x) |> sort()` with `NA` levels removed. 4 | #' For factors, all levels are returned even if they are unobserved. 5 | #' Similarly, logical vectors always return `c(TRUE, FALSE)`, even if 6 | #' both levels are not observed. 7 | #' 8 | #' @param x (`any`)\cr 9 | #' a vector 10 | #' 11 | #' @return a vector 12 | #' @keywords internal 13 | #' 14 | #' @examples 15 | #' cards:::.unique_and_sorted(factor(letters[c(5, 5:1)], levels = letters)) 16 | #' 17 | #' cards:::.unique_and_sorted(c(FALSE, TRUE, TRUE, FALSE)) 18 | #' 19 | #' cards:::.unique_and_sorted(c(5, 5:1)) 20 | .unique_and_sorted <- function(x, useNA = c("no", "always")) { 21 | # styler: off 22 | useNA <- match.arg(useNA) 23 | # if a factor return a factor that includes the same levels (including unobserved levels) 24 | if (inherits(x, "factor")) { 25 | return( 26 | factor( 27 | if (useNA == "no") levels(x) 28 | else c(levels(x), NA_character_), 29 | levels = levels(x) 30 | ) 31 | ) 32 | } 33 | if (inherits(x, "logical")) { 34 | if (useNA == "no") return(c(FALSE, TRUE)) 35 | else return(c(FALSE, TRUE, NA)) 36 | } 37 | 38 | # otherwise, return a simple unique and sort of the vector 39 | if (useNA == "no") return(unique(x) |> sort()) 40 | else return(unique(x) |> sort() |> c(NA)) 41 | # styler: on 42 | } 43 | 44 | 45 | #' Named List Predicate 46 | #' 47 | #' A predicate function to check whether input is a named list and _not_ a data frame. 48 | #' 49 | #' @param x (`any`)\cr 50 | #' object to check 51 | #' 52 | #' @return a logical 53 | #' @keywords internal 54 | #' 55 | #' @examples 56 | #' cards:::.is_named_list(list(a = 1:3)) 57 | .is_named_list <- function(x, allow_df = FALSE) { 58 | if (isFALSE(allow_df)) { 59 | return(is.list(x) && is_named(x) && !is.data.frame(x)) 60 | } 61 | if (isTRUE(allow_df)) { 62 | return(is.list(x) && is_named(x)) 63 | } 64 | } 65 | 66 | #' A list_flatten()-like Function 67 | #' 68 | #' Function operates similarly to `purrr::list_flatten(x, name_spec = "{inner}")`. 69 | #' 70 | #' @param x (named `list`)\cr 71 | #' a named list 72 | #' 73 | #' @return a named list 74 | #' @keywords internal 75 | #' 76 | #' @examples 77 | #' x <- list(a = 1, b = list(b1 = 2, b2 = 3), c = list(c1 = 4, c2 = list(c2a = 5))) 78 | #' 79 | #' cards:::.purrr_list_flatten(x) 80 | .purrr_list_flatten <- function(x) { 81 | ret <- list() 82 | 83 | for (i in seq_along(x)) { 84 | if (.is_named_list(x[[i]])) { 85 | ret <- append(ret, values = x[[i]]) 86 | } else { 87 | ret <- append(ret, values = x[i]) 88 | } 89 | } 90 | 91 | ret 92 | } 93 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What changes are proposed in this pull request?** 2 | * Style this entry in a way that can be copied directly into `NEWS.md`. (#, @) 3 | 4 | Provide more detail here as needed. 5 | 6 | **Reference GitHub issue associated with pull request.** _e.g., 'closes #'_ 7 | 8 | 9 | -------------------------------------------------------------------------------- 10 | 11 | Pre-review Checklist (if item does not apply, mark is as complete) 12 | - [ ] **All** GitHub Action workflows pass with a :white_check_mark: 13 | - [ ] PR branch has pulled the most recent updates from master branch: `usethis::pr_merge_main()` 14 | - [ ] If a bug was fixed, a unit test was added. 15 | - [ ] Code coverage is suitable for any new functions/features (generally, 100% coverage for new code): `devtools::test_coverage()` 16 | - [ ] Request a reviewer 17 | 18 | Reviewer Checklist (if item does not apply, mark is as complete) 19 | 20 | - [ ] If a bug was fixed, a unit test was added. 21 | - [ ] Run `pkgdown::build_site()`. Check the R console for errors, and review the rendered website. 22 | - [ ] Code coverage is suitable for any new functions/features: `devtools::test_coverage()` 23 | 24 | When the branch is ready to be merged: 25 | - [ ] Update `NEWS.md` with the changes from this pull request under the heading "`# cards (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). 26 | - [ ] **All** GitHub Action workflows pass with a :white_check_mark: 27 | - [ ] Approve Pull Request 28 | - [ ] Merge the PR. Please use "Squash and merge" or "Rebase and merge". 29 | 30 | _Optional Reverse Dependency Checks_: 31 | 32 | Install `checked` with `pak::pak("Genentech/checked")` or `pak::pak("checked")` 33 | 34 | ```shell 35 | # Check dev versions of `cardx`, `gtsummary`, and `tfrmt` which are in the `ddsjoberg` R Universe 36 | Rscript -e "options(checked.check_envvars = c(NOT_CRAN = TRUE)); checked::check_rev_deps(path = '.', n = parallel::detectCores() - 2L, repos = c('https://ddsjoberg.r-universe.dev', 'https://cloud.r-project.org'))" 37 | 38 | # Check CRAN reverse dependencies but run tests skipped on CRAN 39 | Rscript -e "options(checked.check_envvars = c(NOT_CRAN = TRUE)); checked::check_rev_deps(path = '.', n = parallel::detectCores() - 2, repos = 'https://cloud.r-project.org')" 40 | 41 | # Check CRAN reverse dependencies in a CRAN-like environment 42 | Rscript -e "options(checked.check_envvars = c(NOT_CRAN = FALSE), checked.check_build_args = '--as-cran'); checked::check_rev_deps(path = '.', n = parallel::detectCores() - 2, repos = 'https://cloud.r-project.org')" 43 | ``` 44 | -------------------------------------------------------------------------------- /R/summary_functions.R: -------------------------------------------------------------------------------- 1 | #' Summary Functions 2 | #' 3 | #' @description 4 | #' - `continuous_summary_fns()` returns a named list of summary functions 5 | #' for continuous variables. Some functions include slight modifications to 6 | #' their base equivalents. For example, the `min()` and `max()` functions 7 | #' return `NA` instead of `Inf` when an empty vector is passed. 8 | #' Statistics `"p25"` and `"p75"` are calculated with `quantile(type = 2)`, 9 | #' which matches 10 | #' [SAS's default value](https://psiaims.github.io/CAMIS/Comp/r-sas-summary-stats.html). 11 | #' 12 | #' @param summaries (`character`)\cr 13 | #' a character vector of results to include in output. Select one or more from 14 | #' `r eval(formals(continuous_summary_fns)$summaries) %>% {paste(shQuote(., "sh"), collapse = ", ")}`. 15 | #' @param other_stats (named `list`)\cr 16 | #' named list of other statistic functions to supplement the pre-programmed functions. 17 | #' 18 | #' @return named list of summary statistics 19 | #' @name summary_functions 20 | #' 21 | #' @examples 22 | #' # continuous variable summaries 23 | #' ard_summary( 24 | #' ADSL, 25 | #' variables = "AGE", 26 | #' statistic = ~ continuous_summary_fns(c("N", "median")) 27 | #' ) 28 | NULL 29 | 30 | #' @rdname summary_functions 31 | #' @export 32 | continuous_summary_fns <- function(summaries = c( 33 | "N", "mean", "sd", "median", 34 | "p25", "p75", "min", "max" 35 | ), 36 | other_stats = NULL) { 37 | set_cli_abort_call() 38 | 39 | # process the selection of the summary stats to include ---------------------- 40 | summaries <- arg_match(summaries, multiple = TRUE) 41 | 42 | # list all functions available by default ------------------------------------ 43 | list_fns <- 44 | list( 45 | N = function(x) length(x), 46 | mean = function(x) mean(x, na.rm = TRUE), 47 | sd = function(x) stats::sd(x, na.rm = TRUE), 48 | median = function(x) stats::median(x, na.rm = TRUE), 49 | p25 = function(x) stats::quantile(x, probs = 0.25, na.rm = TRUE, type = 2) |> unname(), 50 | p75 = function(x) stats::quantile(x, probs = 0.75, na.rm = TRUE, type = 2) |> unname(), 51 | min = function(x) { 52 | if (length(x) == 0L) { 53 | return(structure(NA, class = class(x))) 54 | } 55 | min(x, na.rm = TRUE) 56 | }, 57 | max = function(x) { 58 | if (length(x) == 0L) { 59 | return(structure(NA, class = class(x))) 60 | } 61 | max(x, na.rm = TRUE) 62 | } 63 | ) 64 | 65 | # return list of functions --------------------------------------------------- 66 | list_fns[summaries] |> 67 | c(other_stats) 68 | } 69 | -------------------------------------------------------------------------------- /man/ard_stack.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ard_stack.R 3 | \name{ard_stack} 4 | \alias{ard_stack} 5 | \title{Stack ARDs} 6 | \usage{ 7 | ard_stack( 8 | data, 9 | ..., 10 | .by = NULL, 11 | .overall = FALSE, 12 | .missing = FALSE, 13 | .attributes = FALSE, 14 | .total_n = FALSE, 15 | .shuffle = FALSE, 16 | .by_stats = TRUE 17 | ) 18 | } 19 | \arguments{ 20 | \item{data}{(\code{data.frame})\cr 21 | a data frame} 22 | 23 | \item{...}{(\code{\link[rlang:dyn-dots]{dynamic-dots}})\cr 24 | Series of ARD function calls to be run and stacked} 25 | 26 | \item{.by}{(\code{\link[dplyr:dplyr_tidy_select]{tidy-select}})\cr 27 | columns to tabulate by in the series of ARD function calls. 28 | Any rows with \code{NA} or \code{NaN} values are removed from all calculations.} 29 | 30 | \item{.overall}{(\code{logical})\cr logical indicating whether overall statistics 31 | should be calculated (i.e. re-run all \verb{ard_*()} calls with \code{by=NULL}). 32 | Default is \code{FALSE}.} 33 | 34 | \item{.missing}{(\code{logical})\cr 35 | logical indicating whether to include the results of \code{ard_missing()} for all 36 | variables represented in the ARD. Default is \code{FALSE}.} 37 | 38 | \item{.attributes}{(\code{logical})\cr 39 | logical indicating whether to include the results of \code{ard_attributes()} for all 40 | variables represented in the ARD. Default is \code{FALSE}.} 41 | 42 | \item{.total_n}{(\code{logical})\cr 43 | logical indicating whether to include of \code{ard_total_n()} in the returned ARD.} 44 | 45 | \item{.shuffle}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} support for \code{.shuffle = TRUE} 46 | has been removed.} 47 | 48 | \item{.by_stats}{(\code{logical})\cr 49 | logical indicating whether to include overall stats of the \code{by} variables in the returned ARD.} 50 | } 51 | \value{ 52 | an ARD data frame of class 'card' 53 | } 54 | \description{ 55 | Stack multiple ARD calls sharing common input \code{data} and \code{by} variables. 56 | Optionally incorporate additional information on represented variables, e.g. 57 | overall calculations, rates of missingness, attributes, or transform results 58 | with \code{shuffle_ard()}. 59 | 60 | If the \code{ard_stack(by)} argument is specified, a univariate tabulation of the 61 | by variable will also be returned. 62 | } 63 | \examples{ 64 | ard_stack( 65 | data = ADSL, 66 | ard_tabulate(variables = "AGEGR1"), 67 | ard_summary(variables = "AGE"), 68 | .by = "ARM", 69 | .overall = TRUE, 70 | .attributes = TRUE 71 | ) 72 | 73 | ard_stack( 74 | data = ADSL, 75 | ard_tabulate(variables = "AGEGR1"), 76 | ard_summary(variables = "AGE"), 77 | .by = "ARM" 78 | ) 79 | 80 | } 81 | -------------------------------------------------------------------------------- /R/import-standalone-forcats.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: https://github.com/insightsengineering/standalone/blob/HEAD/R/standalone-forcats.R 3 | # Generated by: usethis::use_standalone("insightsengineering/standalone", "forcats") 4 | # ---------------------------------------------------------------------- 5 | # 6 | # --- 7 | # repo: insightsengineering/standalone 8 | # file: standalone-forcats.R 9 | # last-updated: 2025-05-03 10 | # license: https://unlicense.org 11 | # imports: 12 | # --- 13 | # 14 | # This file provides a minimal shim to provide a forcats-like API on top of 15 | # base R functions. They are not drop-in replacements but allow a similar style 16 | # of programming. 17 | # 18 | # ## Changelog 19 | # 2025-05-03 20 | # - `add fct_relevel()` fix for non-factor inputs 21 | # 2025-02-24 22 | # - `add fct_relevel()` function. 23 | # 24 | # nocov start 25 | # styler: off 26 | 27 | fct_infreq <- function(f, ordered = NA) { 28 | # reorder by frequency 29 | factor( 30 | f, 31 | levels = table(f) |> sort(decreasing = TRUE) |> names(), 32 | ordered = ifelse(is.na(ordered), is.ordered(f), ordered) 33 | ) 34 | } 35 | 36 | fct_inorder <- function(f, ordered = NA) { 37 | factor( 38 | f, 39 | levels = stats::na.omit(unique(f)) |> union(levels(f)), 40 | ordered = ifelse(is.na(ordered), is.ordered(f), ordered) 41 | ) 42 | } 43 | 44 | fct_rev <- function(f) { 45 | if (!inherits(f, "factor")) f <- factor(f) 46 | 47 | factor( 48 | f, 49 | levels = rev(levels(f)), 50 | ordered = is.ordered(f) 51 | ) 52 | } 53 | 54 | fct_expand <- function(f, ..., after = Inf) { 55 | if (!inherits(f, "factor")) f <- factor(f) 56 | 57 | old_levels <- levels(f) 58 | new_levels <- 59 | old_levels |> 60 | append(values = setdiff(c(...), old_levels), after = after) 61 | factor(f, levels = new_levels) 62 | } 63 | 64 | fct_na_value_to_level <- function(f, level = NA) { 65 | if (!inherits(f, "factor")) f <- factor(f) 66 | 67 | # make NA an explicit level 68 | f <- addNA(f, ifany = FALSE) 69 | 70 | # replace NA level with the string passed in `level` argument 71 | if (!is.na(level)) levels(f)[is.na(levels(f))] <- level 72 | 73 | f 74 | } 75 | 76 | 77 | fct_relevel <- function(f, ..., after = 0L) { 78 | if (!inherits(f, "factor")) f <- as.factor(f) 79 | old_levels <- levels(f) 80 | # Handle re-leveling function or specified levels 81 | first_levels <- if (rlang::dots_n(...) == 1L && (is.function(..1) || rlang::is_formula(..1))) { 82 | fun <- rlang::as_function(..1) 83 | fun(old_levels) 84 | } else { 85 | rlang::chr(...) 86 | } 87 | 88 | # Reorder levels 89 | new_levels <- append(setdiff(old_levels, first_levels), first_levels, after = after) 90 | new_factor <- factor(f, levels = new_levels) 91 | return(new_factor) 92 | } 93 | 94 | # nocov end 95 | # styler: on 96 | -------------------------------------------------------------------------------- /R/tidy_ard_order.R: -------------------------------------------------------------------------------- 1 | #' Standard Order of ARD 2 | #' 3 | #' @description 4 | #' ARD functions for relocating columns and rows to the standard order. 5 | #' 6 | #' - `tidy_ard_column_order()` relocates columns of the ARD to the standard order. 7 | #' 8 | #' - `tidy_ard_row_order()` orders rows of ARD according to groups and 9 | #' strata (group 1, then group2, etc), while retaining the column order of the input ARD. 10 | #' 11 | #' @param x (`data.frame`)\cr 12 | #' an ARD data frame of class 'card' 13 | #' @param group_order (`string`)\cr 14 | #' specifies the ordering of the grouping variables. 15 | #' Must be one of `c("ascending", "descending")`. 16 | #' Default is `"ascending"`, where grouping variables begin with `"group1"` variables, 17 | #' followed by `"group2"` variables, etc. 18 | #' 19 | #' @return an ARD data frame of class 'card' 20 | #' @name tidy_ard_order 21 | #' 22 | #' @examples 23 | #' # order columns 24 | #' ard <- 25 | #' dplyr::bind_rows( 26 | #' ard_summary(mtcars, variables = "mpg"), 27 | #' ard_summary(mtcars, variables = "mpg", by = "cyl") 28 | #' ) 29 | #' 30 | #' tidy_ard_column_order(ard) |> 31 | #' tidy_ard_row_order() 32 | NULL 33 | 34 | #' @rdname tidy_ard_order 35 | #' @export 36 | tidy_ard_column_order <- function(x, group_order = c("ascending", "descending")) { 37 | set_cli_abort_call() 38 | group_order <- arg_match(group_order) 39 | 40 | # specify the ordering the grouping variables 41 | group_cols <- 42 | data.frame(colname = dplyr::select(x, all_ard_groups()) |> names()) |> 43 | dplyr::arrange( 44 | case_switch( 45 | group_order == "ascending" ~ as.integer(unlist(str_extract_all(.data$colname, "\\d+"))), 46 | group_order == "descending" ~ dplyr::desc(as.integer(unlist(str_extract_all(.data$colname, "\\d+")))) 47 | ), 48 | .data$colname 49 | ) |> 50 | dplyr::pull("colname") 51 | 52 | # selecting the columns in the tidy order 53 | dplyr::select( 54 | x, 55 | all_of(group_cols), 56 | all_ard_variables(), 57 | any_of(c( 58 | "context", 59 | "stat_name", "stat_label", "stat", "stat_fmt", "fmt_fun", 60 | "warning", "error" 61 | )), 62 | dplyr::everything() 63 | ) 64 | } 65 | 66 | 67 | #' @rdname tidy_ard_order 68 | #' @export 69 | tidy_ard_row_order <- function(x) { 70 | set_cli_abort_call() 71 | 72 | # get columns that dictate ordering 73 | cols <- x |> 74 | dplyr::select(all_ard_groups(c("names", "levels"))) |> 75 | names() 76 | if (!is_empty(cols)) { 77 | max_group_n <- as.integer(unlist(str_extract_all(cols, "\\d+"))) |> max() 78 | cols <- 79 | map(seq_len(max_group_n), ~ c(paste0("group", .x), paste0("group", .x, "_level"))) |> 80 | unlist() |> 81 | intersect(cols) 82 | } 83 | 84 | # perform the ordering 85 | x |> dplyr::arrange(across(all_of(cols), .fns = function(x) match(x, unique(x)))) 86 | } 87 | -------------------------------------------------------------------------------- /man/mock.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mock.R 3 | \name{mock} 4 | \alias{mock} 5 | \alias{mock_categorical} 6 | \alias{mock_continuous} 7 | \alias{mock_dichotomous} 8 | \alias{mock_missing} 9 | \alias{mock_attributes} 10 | \alias{mock_total_n} 11 | \title{Mock ARDs} 12 | \usage{ 13 | mock_categorical( 14 | variables, 15 | statistic = everything() ~ c("n", "p", "N"), 16 | by = NULL 17 | ) 18 | 19 | mock_continuous( 20 | variables, 21 | statistic = everything() ~ c("N", "mean", "sd", "median", "p25", "p75", "min", "max"), 22 | by = NULL 23 | ) 24 | 25 | mock_dichotomous( 26 | variables, 27 | statistic = everything() ~ c("n", "p", "N"), 28 | by = NULL 29 | ) 30 | 31 | mock_missing( 32 | variables, 33 | statistic = everything() ~ c("N_obs", "N_miss", "N_nonmiss", "p_miss", "p_nonmiss"), 34 | by = NULL 35 | ) 36 | 37 | mock_attributes(label) 38 | 39 | mock_total_n() 40 | } 41 | \arguments{ 42 | \item{variables}{(\code{character} or named \code{list})\cr 43 | a character vector of variable names for functions \code{mock_continuous()}, 44 | \code{mock_missing()}, and \code{mock_attributes()}. 45 | 46 | a named list for functions \code{mock_categorical()} and \code{mock_dichotomous()}, 47 | where the list element is a vector of variable values. For 48 | \code{mock_dichotomous()}, only a single value is allowed for each variable.} 49 | 50 | \item{statistic}{(\code{\link[=syntax]{formula-list-selector}})\cr 51 | a named list, a list of formulas, or a single formula where the list elements 52 | are character vectors of statistic names to appear in the ARD.} 53 | 54 | \item{by}{(named \code{list})\cr 55 | a named list where the list element is a vector of variable values.} 56 | 57 | \item{label}{(named \code{list})\cr 58 | named list of variable labels, e.g. \code{list(cyl = "No. Cylinders")}.} 59 | } 60 | \value{ 61 | an ARD data frame of class 'card' 62 | } 63 | \description{ 64 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}\cr 65 | Create empty ARDs used to create mock tables or table shells. 66 | Where applicable, the formatting functions are set to return \code{'xx'} or \code{'xx.x'}. 67 | } 68 | \examples{ 69 | mock_categorical( 70 | variables = 71 | list( 72 | AGEGR1 = factor(c("<65", "65-80", ">80"), levels = c("<65", "65-80", ">80")) 73 | ), 74 | by = list(TRTA = c("Placebo", "Xanomeline High Dose", "Xanomeline Low Dose")) 75 | ) |> 76 | apply_fmt_fun() 77 | 78 | mock_continuous( 79 | variables = c("AGE", "BMIBL"), 80 | by = list(TRTA = c("Placebo", "Xanomeline High Dose", "Xanomeline Low Dose")) 81 | ) |> 82 | # update the mock to report 'xx.xx' for standard deviations 83 | update_ard_fmt_fun(variables = c("AGE", "BMIBL"), stat_names = "sd", fmt_fun = \(x) "xx.xx") |> 84 | apply_fmt_fun() 85 | } 86 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/add_calculated_row.md: -------------------------------------------------------------------------------- 1 | # add_calculated_row(x) 2 | 3 | Code 4 | apply_fmt_fun(add_calculated_row(ard_summary(mtcars, variables = mpg), expr = max - 5 | min, stat_name = "range")) 6 | Message 7 | {cards} data frame: 9 x 9 8 | Output 9 | variable context stat_name stat_label stat stat_fmt 10 | 1 mpg summary N N 32 32 11 | 2 mpg summary mean Mean 20.091 20.1 12 | 3 mpg summary sd SD 6.027 6.0 13 | 4 mpg summary median Median 19.2 19.2 14 | 5 mpg summary p25 Q1 15.35 15.4 15 | 6 mpg summary p75 Q3 22.8 22.8 16 | 7 mpg summary min Min 10.4 10.4 17 | 8 mpg summary max Max 33.9 33.9 18 | 9 mpg summary range range 23.5 23.5 19 | Message 20 | i 3 more variables: fmt_fun, warning, error 21 | 22 | --- 23 | 24 | Code 25 | apply_fmt_fun(add_calculated_row(ard_summary(mtcars, variables = mpg), expr = dplyr::case_when( 26 | mean > median ~ "Right Skew", mean < median ~ "Left Skew", .default = "Symmetric"), 27 | stat_name = "skew")) 28 | Message 29 | {cards} data frame: 9 x 9 30 | Output 31 | variable context stat_name stat_label stat stat_fmt 32 | 1 mpg summary N N 32 32 33 | 2 mpg summary mean Mean 20.091 20.1 34 | 3 mpg summary sd SD 6.027 6.0 35 | 4 mpg summary median Median 19.2 19.2 36 | 5 mpg summary p25 Q1 15.35 15.4 37 | 6 mpg summary p75 Q3 22.8 22.8 38 | 7 mpg summary min Min 10.4 10.4 39 | 8 mpg summary max Max 33.9 33.9 40 | 9 mpg summary skew skew Right Sk… Right Skew 41 | Message 42 | i 3 more variables: fmt_fun, warning, error 43 | 44 | # add_calculated_row(expr) messaging 45 | 46 | Code 47 | add_calculated_row(ard_summary(mtcars, variables = mpg), expr = not_a_stat * 2, 48 | stat_name = "this_doesnt_work") 49 | Condition 50 | Error in `add_calculated_row()`: 51 | ! There was an error calculating the new statistic. See below: 52 | x object 'not_a_stat' not found 53 | 54 | # add_calculated_row(by) messaging 55 | 56 | Code 57 | add_calculated_row(ard_summary(mtcars, variables = mpg, by = cyl), expr = max - 58 | min, stat_name = "range", by = "context") 59 | Condition 60 | Error in `add_calculated_row()`: 61 | ! Duplicate statistics present within `by` groups: "N", "mean", "sd", "median", "p25", "p75", "min", "max", "N", "mean", "sd", "median", "p25", "p75", "min", and "max" 62 | 63 | --------------------------------------------------------------------------------