Skip to contents

[Stable]

We use the new S3 generic function s_compare() to implement comparisons for different x objects. This is used as Statistics Function in combination with the new Analyze Function compare_vars().

Usage

s_compare(x, .ref_group, .in_ref_col, ...)

# S3 method for numeric
s_compare(x, .ref_group, .in_ref_col, ...)

# S3 method for factor
s_compare(
  x,
  .ref_group,
  .in_ref_col,
  denom = "n",
  na.rm = TRUE,
  na_level = "<Missing>",
  ...
)

# S3 method for character
s_compare(
  x,
  .ref_group,
  .in_ref_col,
  denom = "n",
  na.rm = TRUE,
  na_level = "<Missing>",
  .var,
  verbose = TRUE,
  ...
)

# S3 method for logical
s_compare(x, .ref_group, .in_ref_col, na.rm = TRUE, denom = "n", ...)

a_compare(x, .ref_group, .in_ref_col, ..., .var)

# S3 method for numeric
a_compare(x, .ref_group, .in_ref_col, ...)

# S3 method for factor
a_compare(
  x,
  .ref_group,
  .in_ref_col,
  denom = "n",
  na.rm = TRUE,
  na_level = "<Missing>",
  ...
)

# S3 method for character
a_compare(
  x,
  .ref_group,
  .in_ref_col,
  denom = "n",
  na.rm = TRUE,
  na_level = "<Missing>",
  .var,
  verbose = TRUE,
  ...
)

# S3 method for logical
a_compare(x, .ref_group, .in_ref_col, na.rm = TRUE, denom = "n", ...)

compare_vars(
  lyt,
  vars,
  var_labels = vars,
  nested = TRUE,
  ...,
  show_labels = "default",
  table_names = vars,
  .stats = c("n", "mean_sd", "count_fraction", "pval"),
  .formats = NULL,
  .labels = NULL,
  .indent_mods = NULL
)

Arguments

x

(numeric)
vector of numbers we want to analyze.

.ref_group

(data.frame or vector)
the data corresponding to the reference group.

.in_ref_col

(logical)
TRUE when working with the reference level, FALSE otherwise.

...

arguments passed to s_compare().

denom

(string)
choice of denominator for factor proportions, can only be n (number of values in this row and column intersection).

na.rm

(flag)
whether NA values should be removed from x prior to analysis.

na_level

(string)
used to replace all NA or empty values in factors with custom string.

.var

(string)
single variable name that is passed by rtables when requested by a statistics function.

verbose

defaults to TRUE. It prints out warnings and messages. It is mainly used to print out information about factor casting.

lyt

(layout)
input layout where analyses will be added to.

vars

(character)
variable names for the primary analysis variable to be iterated over.

var_labels

character for label.

nested

boolean. Should this layout instruction be applied within the existing layout structure if possible (TRUE, the default) or as a new top-level element (`FALSE). Ignored if it would nest a split underneath analyses, which is not allowed.

show_labels

label visibility: one of "default", "visible" and "hidden".

table_names

(character)
this can be customized in case that the same vars are analyzed multiple times, to avoid warnings from rtables.

.stats

(character)
statistics to select for the table.

.formats

(named character or list)
formats for the statistics.

.labels

(named character)
labels for the statistics (without indent).

.indent_mods

(named integer)
indent modifiers for the labels.

Value

If x is of class numeric, returns a list with named items:

If x is of class factor or converted from character, returns a list with named items:

If x is of class logical, returns a list with named items:

Functions

  • s_compare(): s_compare is a S3 generic function to produce an object description and comparison versus the reference column in the form of p-values.

  • s_compare(numeric): Method for numeric class. This uses the standard t-test to calculate the p-value.

  • s_compare(factor): Method for factor class. This uses the chi-squared test to calculate the p-value. Note that the denom for factor proportions can only be n here since the usage is for comparing proportions between columns. Therefore a row-based proportion would not make sense. Also proportion based on N_col would be difficult since for the chi-squared test statistic we use the counts. Therefore missing values should be accounted for explicitly as factor levels.

  • s_compare(character): Method for character class. This makes an automatic conversion to factor (with a warning) and then forwards to the method for factors.

  • s_compare(logical): Method for logical class. A chi-squared test is used. If missing values are not removed, then they are counted as FALSE.

  • a_compare(): S3 generic Formatted Analysis function to produce an object description and comparison versus the reference column in the form of p-values. It is used as afun in rtables::analyze().

  • a_compare(numeric): Formatted Analysis function method for numeric.

  • a_compare(factor): Formatted Analysis function method for factor.

  • a_compare(character): Formatted Analysis function method for character.

  • a_compare(logical): Formatted Analysis function method for logical.

  • compare_vars(): Analyze Function to add a comparison of variables to rtables pipelines. The column split needs to have the reference group defined via ref_group so that the comparison is well defined. The ellipsis (...) conveys arguments to s_compare(). When factor variables contains NA, it is expected that NA have been conveyed to na_level appropriately beforehand with df_explicit_na().

Note

Automatic conversion of character to factor does not guarantee that the table can be generated correctly. In particular for sparse tables this very likely can fail. It is therefore better to always pre-process the dataset such that factors are manually created from character variables before passing the dataset to rtables::build_table().

Formatting arguments

These additional formatting arguments can be passed to the layout creating function:

.stats

(character)
names of the statistics to use

.indent_mods

(integer)
named vector of indent modifiers for the labels

.formats

(character or list)
named vector of formats for the statistics

.labels

(character)
named vector of labels for the statistics (without indent)

See also

Relevant constructor function create_afun_compare().

s_summary() which is used internally for the summary part per column.

Examples

# `s_compare.numeric`

## Usual case where both this and the reference group vector have more than 1 value.
s_compare(rnorm(10, 5, 1), .ref_group = rnorm(5, -5, 1), .in_ref_col = FALSE)
#> $n
#>  n 
#> 10 
#> 
#> $sum
#>      sum 
#> 49.42913 
#> 
#> $mean
#>     mean 
#> 4.942913 
#> 
#> $sd
#>        sd 
#> 0.8373918 
#> 
#> $se
#>        se 
#> 0.2648065 
#> 
#> $mean_sd
#>      mean        sd 
#> 4.9429131 0.8373918 
#> 
#> $mean_se
#>      mean        se 
#> 4.9429131 0.2648065 
#> 
#> $mean_ci
#> mean_ci_lwr mean_ci_upr 
#>    4.343879    5.541947 
#> attr(,"label")
#> [1] "Mean 95% CI"
#> 
#> $mean_sei
#> mean_sei_lwr mean_sei_upr 
#>     4.678107     5.207720 
#> attr(,"label")
#> [1] "Mean -/+ 1xSE"
#> 
#> $mean_sdi
#> mean_sdi_lwr mean_sdi_upr 
#>     4.105521     5.780305 
#> attr(,"label")
#> [1] "Mean -/+ 1xSD"
#> 
#> $mean_pval
#>      p_value 
#> 1.667497e-08 
#> attr(,"label")
#> [1] "Mean p-value (H0: mean = 0)"
#> 
#> $median
#>   median 
#> 5.125084 
#> 
#> $mad
#> mad 
#>   0 
#> 
#> $median_ci
#> median_ci_lwr median_ci_upr 
#>      4.645550      5.584917 
#> attr(,"conf_level")
#> [1] 0.9785156
#> attr(,"label")
#> [1] "Median 95% CI"
#> 
#> $quantiles
#> quantile_0.25 quantile_0.75 
#>      4.827461      5.503797 
#> attr(,"label")
#> [1] "25% and 75%-ile"
#> 
#> $iqr
#>       iqr 
#> 0.6763357 
#> 
#> $range
#>      min      max 
#> 2.754812 5.694993 
#> 
#> $min
#>      min 
#> 2.754812 
#> 
#> $max
#>      max 
#> 5.694993 
#> 
#> $cv
#>       cv 
#> 16.94126 
#> 
#> $geom_mean
#> geom_mean 
#>  4.859137 
#> 
#> $geom_mean_ci
#> mean_ci_lwr mean_ci_upr 
#>    4.182990    5.644576 
#> attr(,"label")
#> [1] "Geometric Mean 95% CI"
#> 
#> $geom_cv
#>  geom_cv 
#> 21.17724 
#> 
#> $pval
#> [1] 5.136138e-07
#> 

## If one group has not more than 1 value, then p-value is not calculated.
s_compare(rnorm(10, 5, 1), .ref_group = 1, .in_ref_col = FALSE)
#> $n
#>  n 
#> 10 
#> 
#> $sum
#>      sum 
#> 52.06279 
#> 
#> $mean
#>     mean 
#> 5.206279 
#> 
#> $sd
#>       sd 
#> 1.046725 
#> 
#> $se
#>        se 
#> 0.3310036 
#> 
#> $mean_sd
#>     mean       sd 
#> 5.206279 1.046725 
#> 
#> $mean_se
#>      mean        se 
#> 5.2062786 0.3310036 
#> 
#> $mean_ci
#> mean_ci_lwr mean_ci_upr 
#>    4.457496    5.955061 
#> attr(,"label")
#> [1] "Mean 95% CI"
#> 
#> $mean_sei
#> mean_sei_lwr mean_sei_upr 
#>     4.875275     5.537282 
#> attr(,"label")
#> [1] "Mean -/+ 1xSE"
#> 
#> $mean_sdi
#> mean_sdi_lwr mean_sdi_upr 
#>     4.159553     6.253004 
#> attr(,"label")
#> [1] "Mean -/+ 1xSD"
#> 
#> $mean_pval
#>      p_value 
#> 7.466498e-08 
#> attr(,"label")
#> [1] "Mean p-value (H0: mean = 0)"
#> 
#> $median
#>   median 
#> 5.478941 
#> 
#> $mad
#> mad 
#>   0 
#> 
#> $median_ci
#> median_ci_lwr median_ci_upr 
#>      4.151551      6.239289 
#> attr(,"conf_level")
#> [1] 0.9785156
#> attr(,"label")
#> [1] "Median 95% CI"
#> 
#> $quantiles
#> quantile_0.25 quantile_0.75 
#>      4.883857      5.865342 
#> attr(,"label")
#> [1] "25% and 75%-ile"
#> 
#> $iqr
#>      iqr 
#> 0.981485 
#> 
#> $range
#>      min      max 
#> 2.887095 6.343144 
#> 
#> $min
#>      min 
#> 2.887095 
#> 
#> $max
#>      max 
#> 6.343144 
#> 
#> $cv
#>       cv 
#> 20.10506 
#> 
#> $geom_mean
#> geom_mean 
#>  5.090995 
#> 
#> $geom_mean_ci
#> mean_ci_lwr mean_ci_upr 
#>    4.300937    6.026182 
#> attr(,"label")
#> [1] "Geometric Mean 95% CI"
#> 
#> $geom_cv
#>  geom_cv 
#> 23.90567 
#> 
#> $pval
#> character(0)
#> 

## Empty numeric does not fail, it returns NA-filled items and no p-value.
s_compare(numeric(), .ref_group = numeric(), .in_ref_col = FALSE)
#> $n
#> n 
#> 0 
#> 
#> $sum
#> sum 
#>  NA 
#> 
#> $mean
#> mean 
#>   NA 
#> 
#> $sd
#> sd 
#> NA 
#> 
#> $se
#> se 
#> NA 
#> 
#> $mean_sd
#> mean   sd 
#>   NA   NA 
#> 
#> $mean_se
#> mean   se 
#>   NA   NA 
#> 
#> $mean_ci
#> mean_ci_lwr mean_ci_upr 
#>          NA          NA 
#> attr(,"label")
#> [1] "Mean 95% CI"
#> 
#> $mean_sei
#> mean_sei_lwr mean_sei_upr 
#>           NA           NA 
#> attr(,"label")
#> [1] "Mean -/+ 1xSE"
#> 
#> $mean_sdi
#> mean_sdi_lwr mean_sdi_upr 
#>           NA           NA 
#> attr(,"label")
#> [1] "Mean -/+ 1xSD"
#> 
#> $mean_pval
#> p_value 
#>      NA 
#> attr(,"label")
#> [1] "Mean p-value (H0: mean = 0)"
#> 
#> $median
#> median 
#>     NA 
#> 
#> $mad
#> mad 
#>  NA 
#> 
#> $median_ci
#> median_ci_lwr median_ci_upr 
#>            NA            NA 
#> attr(,"conf_level")
#> [1] NA
#> attr(,"label")
#> [1] "Median 95% CI"
#> 
#> $quantiles
#> quantile_0.25 quantile_0.75 
#>            NA            NA 
#> attr(,"label")
#> [1] "25% and 75%-ile"
#> 
#> $iqr
#> iqr 
#>  NA 
#> 
#> $range
#> min max 
#>  NA  NA 
#> 
#> $min
#> min 
#>  NA 
#> 
#> $max
#> max 
#>  NA 
#> 
#> $cv
#> cv 
#> NA 
#> 
#> $geom_mean
#> geom_mean 
#>       NaN 
#> 
#> $geom_mean_ci
#> mean_ci_lwr mean_ci_upr 
#>          NA          NA 
#> attr(,"label")
#> [1] "Geometric Mean 95% CI"
#> 
#> $geom_cv
#> geom_cv 
#>      NA 
#> 
#> $pval
#> character(0)
#> 
# `s_compare.factor`

## Basic usage:
x <- factor(c("a", "a", "b", "c", "a"))
y <- factor(c("a", "b", "c"))
s_compare(x = x, .ref_group = y, .in_ref_col = FALSE)
#> $n
#> [1] 5
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0 0.6
#> 
#> $count_fraction$b
#> [1] 1.0 0.2
#> 
#> $count_fraction$c
#> [1] 1.0 0.2
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.7659283
#> 

## Management of NA values.
x <- explicit_na(factor(c("a", "a", "b", "c", "a", NA, NA)))
y <- explicit_na(factor(c("a", "b", "c", NA)))
s_compare(x = x, .ref_group = y, .in_ref_col = FALSE, na.rm = TRUE)
#> $n
#> [1] 5
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0 0.6
#> 
#> $count_fraction$b
#> [1] 1.0 0.2
#> 
#> $count_fraction$c
#> [1] 1.0 0.2
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.7659283
#> 
s_compare(x = x, .ref_group = y, .in_ref_col = FALSE, na.rm = FALSE)
#> $n
#> [1] 7
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> $count$`<Missing>`
#> [1] 2
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0000000 0.4285714
#> 
#> $count_fraction$b
#> [1] 1.0000000 0.1428571
#> 
#> $count_fraction$c
#> [1] 1.0000000 0.1428571
#> 
#> $count_fraction$`<Missing>`
#> [1] 2.0000000 0.2857143
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.9063036
#> 
# `s_compare.character`

## Basic usage:
x <- c("a", "a", "b", "c", "a")
y <- c("a", "b", "c")
s_compare(x, .ref_group = y, .in_ref_col = FALSE, .var = "x", verbose = FALSE)
#> $n
#> [1] 5
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0 0.6
#> 
#> $count_fraction$b
#> [1] 1.0 0.2
#> 
#> $count_fraction$c
#> [1] 1.0 0.2
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.7659283
#> 

## Note that missing values handling can make a large difference:
x <- c("a", "a", "b", "c", "a", NA)
y <- c("a", "b", "c", rep(NA, 20))
s_compare(x,
  .ref_group = y, .in_ref_col = FALSE,
  .var = "x", verbose = FALSE
)
#> $n
#> [1] 5
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0 0.6
#> 
#> $count_fraction$b
#> [1] 1.0 0.2
#> 
#> $count_fraction$c
#> [1] 1.0 0.2
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.7659283
#> 
s_compare(x,
  .ref_group = y, .in_ref_col = FALSE, .var = "x",
  na.rm = FALSE, verbose = FALSE
)
#> $n
#> [1] 6
#> 
#> $count
#> $count$a
#> [1] 3
#> 
#> $count$b
#> [1] 1
#> 
#> $count$c
#> [1] 1
#> 
#> $count$`<Missing>`
#> [1] 1
#> 
#> 
#> $count_fraction
#> $count_fraction$a
#> [1] 3.0 0.5
#> 
#> $count_fraction$b
#> [1] 1.0000000 0.1666667
#> 
#> $count_fraction$c
#> [1] 1.0000000 0.1666667
#> 
#> $count_fraction$`<Missing>`
#> [1] 1.0000000 0.1666667
#> 
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.005768471
#> 

# `s_compare.logical`

## Basic usage:
x <- c(TRUE, FALSE, TRUE, TRUE)
y <- c(FALSE, FALSE, TRUE)
s_compare(x, .ref_group = y, .in_ref_col = FALSE)
#> $n
#> [1] 4
#> 
#> $count
#> [1] 3
#> 
#> $count_fraction
#> [1] 3.00 0.75
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.2702894
#> 

## Management of NA values.
x <- c(NA, TRUE, FALSE)
y <- c(NA, NA, NA, NA, FALSE)
s_compare(x, .ref_group = y, .in_ref_col = FALSE, na.rm = TRUE)
#> $n
#> [1] 2
#> 
#> $count
#> [1] 1
#> 
#> $count_fraction
#> [1] 1.0 0.5
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.3864762
#> 
s_compare(x, .ref_group = y, .in_ref_col = FALSE, na.rm = FALSE)
#> $n
#> [1] 3
#> 
#> $count
#> [1] 1
#> 
#> $count_fraction
#> [1] 1.0000000 0.3333333
#> 
#> $n_blq
#> [1] 0
#> 
#> $pval
#> [1] 0.1675463
#> 
# `a_compare.numeric`
a_compare(
  rnorm(10, 5, 1),
  .ref_group = rnorm(20, -5, 1),
  .in_ref_col = FALSE,
  .var = "bla"
)
#> RowsVerticalSection (in_rows) object print method:
#> ----------------------------
#>        row_name                    formatted_cell indent_mod
#> 1             n                                10          0
#> 2           sum                              48.7          0
#> 3          mean                               4.9          0
#> 4            sd                               1.1          0
#> 5            se                               0.3          0
#> 6       mean_sd                         4.9 (1.1)          0
#> 7       mean_se                         4.9 (0.3)          0
#> 8       mean_ci                      (4.09, 5.65)          0
#> 9      mean_sei                      (4.52, 5.21)          0
#> 10     mean_sdi                      (3.77, 5.96)          0
#> 11    mean_pval                              0.00          0
#> 12       median                               5.1          0
#> 13          mad                              -0.0          0
#> 14    median_ci                      (3.78, 5.70)          0
#> 15    quantiles                         4.2 - 5.5          0
#> 16          iqr                               1.4          0
#> 17        range                         2.6 - 6.5          0
#> 18          min                               2.6          0
#> 19          max                               6.5          0
#> 20           cv                              22.5          0
#> 21    geom_mean                               4.7          0
#> 22 geom_mean_ci 3.93947172356727, 5.6984683842566          0
#> 23      geom_cv                              26.2          0
#> 24         pval                           <0.0001          0
#>                      row_label
#> 1                            n
#> 2                          Sum
#> 3                         Mean
#> 4                           SD
#> 5                           SE
#> 6                    Mean (SD)
#> 7                    Mean (SE)
#> 8                  Mean 95% CI
#> 9                Mean -/+ 1xSE
#> 10               Mean -/+ 1xSD
#> 11 Mean p-value (H0: mean = 0)
#> 12                      Median
#> 13   Median Absolute Deviation
#> 14               Median 95% CI
#> 15             25% and 75%-ile
#> 16                         IQR
#> 17                   Min - Max
#> 18                     Minimum
#> 19                     Maximum
#> 20                      CV (%)
#> 21              Geometric Mean
#> 22       Geometric Mean 95% CI
#> 23         CV % Geometric Mean
#> 24            p-value (t-test)
# `a_compare.factor`
# We need to ungroup `count` and `count_fraction` first so that the `rtables` formatting
# functions can be applied correctly.
afun <- make_afun(
  getS3method("a_compare", "factor"),
  .ungroup_stats = c("count", "count_fraction")
)
x <- factor(c("a", "a", "b", "c", "a"))
y <- factor(c("a", "a", "b", "c"))
afun(x, .ref_group = y, .in_ref_col = FALSE)
#> RowsVerticalSection (in_rows) object print method:
#> ----------------------------
#>   row_name formatted_cell indent_mod                  row_label
#> 1        n              5          0                          n
#> 2        a              3          0                          a
#> 3        b              1          0                          b
#> 4        c              1          0                          c
#> 5        a        3 (60%)          0                          a
#> 6        b        1 (20%)          0                          b
#> 7        c        1 (20%)          0                          c
#> 8    n_blq              0          0                      n_blq
#> 9     pval         0.9560          0 p-value (chi-squared test)
# `a_compare.character`
afun <- make_afun(
  getS3method("a_compare", "character"),
  .ungroup_stats = c("count", "count_fraction")
)
x <- c("A", "B", "A", "C")
y <- c("B", "A", "C")
afun(x, .ref_group = y, .in_ref_col = FALSE, .var = "x", verbose = FALSE)
#> RowsVerticalSection (in_rows) object print method:
#> ----------------------------
#>   row_name formatted_cell indent_mod                  row_label
#> 1        n              4          0                          n
#> 2        A              2          0                          A
#> 3        B              1          0                          B
#> 4        C              1          0                          C
#> 5        A        2 (50%)          0                          A
#> 6        B        1 (25%)          0                          B
#> 7        C        1 (25%)          0                          C
#> 8    n_blq              0          0                      n_blq
#> 9     pval         0.9074          0 p-value (chi-squared test)
# `a_compare.logical`
afun <- make_afun(
  getS3method("a_compare", "logical")
)
x <- c(TRUE, FALSE, FALSE, TRUE, TRUE)
y <- c(TRUE, FALSE)
afun(x, .ref_group = y, .in_ref_col = FALSE)
#> RowsVerticalSection (in_rows) object print method:
#> ----------------------------
#>         row_name formatted_cell indent_mod                  row_label
#> 1              n              5          0                          n
#> 2          count              3          0                      count
#> 3 count_fraction        3 (60%)          0             count_fraction
#> 4          n_blq              0          0                      n_blq
#> 5           pval         0.8091          0 p-value (chi-squared test)
# `compare_vars()` in `rtables` pipelines

## Default output within a `rtables` pipeline.
lyt <- basic_table() %>%
  split_cols_by("ARMCD", ref_group = "ARM B") %>%
  compare_vars(c("AGE", "SEX"))
build_table(lyt, tern_ex_adsl)
#>                                  ARM B        ARM A        ARM C   
#> ———————————————————————————————————————————————————————————————————
#> AGE                                                                
#>   n                                73           69           58    
#>   Mean (SD)                    35.8 (7.1)   34.1 (6.8)   36.1 (7.4)
#>   p-value (t-test)                            0.1446       0.8212  
#> SEX                                                                
#>   n                                73           69           58    
#>   F                            40 (54.8%)   38 (55.1%)   32 (55.2%)
#>   M                            33 (45.2%)   31 (44.9%)   26 (44.8%)
#>   p-value (chi-squared test)                  1.0000       1.0000  

## Select and format statistics output.
lyt <- basic_table() %>%
  split_cols_by("ARMCD", ref_group = "ARM C") %>%
  compare_vars(
    vars = "AGE",
    .stats = c("mean_sd", "pval"),
    .formats = c(mean_sd = "xx.x, xx.x"),
    .labels = c(mean_sd = "Mean, SD")
  )
build_table(lyt, df = tern_ex_adsl)
#>                      ARM C       ARM A       ARM B  
#> ————————————————————————————————————————————————————
#> Mean, SD           36.1, 7.4   34.1, 6.8   35.8, 7.1
#> p-value (t-test)                0.1176      0.8212