Formatting Statistics

When running the QC workflow with ARDs, there may be discrepancies due to rounding methodologies.

The {rtables} package uses R’s default “round-to-even” rounding (IEC 60559), while the {cards} package uses the standard rounding scheme (round-half-up: where values exactly halfway between two numbers are rounded up). If you are comparing rounded numbers in your analysis, we recommend applying a formatting function to your ARD statistics to match the {rtables} rounding scheme.

Make note of the median, and range values in the {rtables} result.

result <- rtables::basic_table(show_colcounts = TRUE) |>
  rtables::split_cols_by(var = "ARM") |>
  tern::analyze_vars(
    vars = "BMIBL",
    .formats = c("mean_sd" = "xx. / xx.", "median" = "xx.", "range" = "(xx., xx.)")
  ) |> # rounding stats to whole numbers
  rtables::build_table(cards::ADSL)
Registered S3 method overwritten by 'tern':
  method   from 
  tidy.glm broom
result_df <- rtables::as_result_df(result, make_ard = T)

result_df[, c("group1_level", "stat_name", "stat_string")]
           group1_level stat_name stat_string
1               Placebo         n          86
2               Placebo      mean          24
3               Placebo        sd           4
4               Placebo    median          23
5               Placebo       min          15
6               Placebo       max          33
7  Xanomeline High Dose         n          84
8  Xanomeline High Dose      mean          25
9  Xanomeline High Dose        sd           4
10 Xanomeline High Dose    median          25
11 Xanomeline High Dose       min          14
12 Xanomeline High Dose       max          34
13  Xanomeline Low Dose         n          83
14  Xanomeline Low Dose      mean          25
15  Xanomeline Low Dose        sd           4
16  Xanomeline Low Dose    median          24
17  Xanomeline Low Dose       min          18
18  Xanomeline Low Dose       max          40
library(cards)

ard_formatting <- ard_continuous(ADSL,
  by = TRT01A, variables = "BMIBL",
  fmt_fn = everything() ~ list(everything() ~ "xx")
) |>
  apply_fmt_fn() |>
  dplyr::filter(stat_name %in% c("N", "mean", "sd", "min", "max", "median"))

ard_formatting
{cards} data frame: 18 x 11
   group1 group1_level variable stat_name stat_label   stat stat_fmt
1  TRT01A      Placebo    BMIBL         N          N     86       86
2  TRT01A      Placebo    BMIBL      mean       Mean 23.636       24
3  TRT01A      Placebo    BMIBL        sd         SD  3.672        4
4  TRT01A      Placebo    BMIBL    median     Median   23.4       23
5  TRT01A      Placebo    BMIBL       min        Min   15.1       15
6  TRT01A      Placebo    BMIBL       max        Max   33.3       33
7  TRT01A    Xanomeli…    BMIBL         N          N     84       84
8  TRT01A    Xanomeli…    BMIBL      mean       Mean 25.348       25
9  TRT01A    Xanomeli…    BMIBL        sd         SD  4.158        4
10 TRT01A    Xanomeli…    BMIBL    median     Median   24.8       25
11 TRT01A    Xanomeli…    BMIBL       min        Min   13.7       14
12 TRT01A    Xanomeli…    BMIBL       max        Max   34.5       35
13 TRT01A    Xanomeli…    BMIBL         N          N     83       83
14 TRT01A    Xanomeli…    BMIBL      mean       Mean 25.063       25
15 TRT01A    Xanomeli…    BMIBL        sd         SD  4.271        4
16 TRT01A    Xanomeli…    BMIBL    median     Median   24.3       24
17 TRT01A    Xanomeli…    BMIBL       min        Min   17.7       18
18 TRT01A    Xanomeli…    BMIBL       max        Max   40.1       40
ℹ 4 more variables: context, fmt_fn, warning, error

Compare the {rtables} formatted string and the {cards} formatted string

compare_df <- cbind(
  ard_formatting[, c("group1_level", "stat_name", "stat", "stat_fmt")],
  result_df[, "stat_string"]
)

compare_df[11:15, ]
           group1_level stat_name     stat stat_fmt result_df[, "stat_string"]
11 Xanomeline High Dose       min     13.7       14                         14
12 Xanomeline High Dose       max     34.5       35                         34
13  Xanomeline Low Dose         N       83       83                         83
14  Xanomeline Low Dose      mean 25.06265       25                         25
15  Xanomeline Low Dose        sd 4.270509        4                          4

Notice how the rounded maximum value is “34” for the “Xanomeline High Dose” group using {rtables} while the stat is “35” when rounded in {cards}.

To apply the same rounding function as {rtables}, use the fmt_fn parameter in the ARD building function followed by the apply_fmt_fn. You can also apply the update_ard_fmt_fn.

ard_iec_rounding <- ard_formatting |>
  update_ard_fmt_fn(
    stat_names = "max",
    fmt_fn = round
  ) |>
  apply_fmt_fn(replace = T)

compare_df <- cbind(compare_df, ard_iec_rounding[, "stat_fmt"])

compare_df[11:15, ]
           group1_level stat_name     stat stat_fmt result_df[, "stat_string"]
11 Xanomeline High Dose       min     13.7       14                         14
12 Xanomeline High Dose       max     34.5       35                         34
13  Xanomeline Low Dose         N       83       83                         83
14  Xanomeline Low Dose      mean 25.06265       25                         25
15  Xanomeline Low Dose        sd 4.270509        4                          4
   stat_fmt
11       14
12       34
13       83
14       25
15        4

The easiest setting would be to set an option at the beginning of your script:

options(cards.round_type = "round-to-even")

This will update the global environment to apply this rounding method in cards. Below is the same code as the earlier ARD creation steps, but generates the rounded values similar to rtables directly.

ard_with_iec_round_option <- ard_continuous(ADSL,
  by = TRT01A, variables = "BMIBL",
  fmt_fn = everything() ~ list(everything() ~ "xx")
) |>
  apply_fmt_fn() |>
  dplyr::filter(stat_name %in% c("N", "mean", "sd", "min", "max", "median"))

ard_with_iec_round_option[11:15, ]
{cards} data frame: 5 x 11
  group1 group1_level variable stat_name stat_label   stat stat_fmt
1 TRT01A    Xanomeli…    BMIBL       min        Min   13.7       14
2 TRT01A    Xanomeli…    BMIBL       max        Max   34.5       34
3 TRT01A    Xanomeli…    BMIBL         N          N     83       83
4 TRT01A    Xanomeli…    BMIBL      mean       Mean 25.063       25
5 TRT01A    Xanomeli…    BMIBL        sd         SD  4.271        4
ℹ 4 more variables: context, fmt_fn, warning, error