Skip to contents

Analysis/statistical function for count and percentage in core columns and (optional) relative risk columns

Usage

s_freq_j(
  df,
  .var,
  .df_row,
  val = NULL,
  drop_levels = FALSE,
  excl_levels = NULL,
  alt_df,
  parent_df,
  id = "USUBJID",
  denom = c("n_df", "n_altdf", "N_col", "n_rowdf", "n_parentdf"),
  .N_col,
  countsource = c("df", "altdf", "altdf_subset")
)

a_freq_j(
  df,
  labelstr = NULL,
  .var = NA,
  val = NULL,
  drop_levels = FALSE,
  excl_levels = NULL,
  new_levels = NULL,
  new_levels_after = FALSE,
  addstr2levs = NULL,
  .df_row,
  .spl_context,
  .N_col,
  id = "USUBJID",
  denom = c("N_col", "n_df", "n_altdf", "N_colgroup", "n_rowdf", "n_parentdf"),
  riskdiff = TRUE,
  ref_path = NULL,
  variables = list(strata = NULL),
  conf_level = 0.95,
  method = c("wald", "waldcc", "cmh", "ha", "newcombe", "newcombecc", "strat_newcombe",
    "strat_newcombecc", "cmh_sato", "cmh_mn", "uncond_exact_diff"),
  weights_method = "cmh",
  label = NULL,
  label_fstr = NULL,
  label_map = NULL,
  .alt_df_full = NULL,
  denom_by = NULL,
  .stats = "count_unique_denom_fraction",
  .formats = NULL,
  .indent_mods = NULL,
  na_str = rep("NA", 3),
  .labels_n = NULL,
  extrablankline = FALSE,
  extrablanklineafter = NULL,
  restr_columns = NULL,
  colgroup = NULL,
  countsource = c("df", "altdf", "altdf_subset")
)

a_freq_j_with_exclude(
  df,
  labelstr = NULL,
  exclude_levels,
  .var = NA,
  .spl_context,
  .df_row,
  .N_col,
  .alt_df_full = NULL,
  .stats = "count_unique_denom_fraction",
  .formats = NULL,
  .indent_mods = NULL,
  .labels_n = NULL,
  ...
)

Arguments

df

(data.frame)
data set containing all analysis variables.

.var

(character(1))
Name of a categorical analysis variable in df. The variable must be a factor.

.df_row

(data.frame)
data frame across all of the columns for the given row split.

val

(character or NULL)
When NULL, all levels of the incoming variable (variable used in the analyze call) will be considered.
When a single string, only that current level/value of the incoming variable will be considered.
When multiple levels, only those levels/values of the incoming variable will be considered.
When no values are observed (eg zero row input df), a row with row-label No data to report will be included in the table.

drop_levels

(logical)
If TRUE non-observed levels (based upon .df_row) will not be included.
Cannot be used together with val.

excl_levels

(character or NULL)
When NULL, no levels of the incoming variable (variable used in the analyze call) will be excluded.
When multiple levels, those levels/values of the incoming variable will be excluded.
Cannot be used together with val.

alt_df

(dataframe)
Will be derived based upon alt_df_full and denom_by within a_freq_j.

parent_df

(dataframe)
Will be derived within a_freq_j based upon the input dataframe that goes into build_table (df) and denom_by.
It is a data frame in the higher row-space than the current input df (which underwent row-splitting by the rtables splitting machinery).

id

(string)
subject variable name.

denom

(string)
See Details.

.N_col

(integer)
column-wise N (column count) for the full column being analyzed that is typically passed by rtables.

countsource

Either df, altdf, or altdf_subset.
When altdf the counts will be based upon the alternative dataframe alt_df.
When altdf_subset the counts will be based upon alt_df but first restricted
to the levels/values of the current row split for .var (or to val when provided).
This is useful for subgroup processing, to present counts of subjects in a subgroup from the alternative dataframe.

labelstr

An argument to ensure this function can be used as a cfun in a summarize_row_groups call.
It is recommended not to utilize this argument for other purposes.
The label argument could be used instead (if val is a single string)
An another approach could be to utilize the label_map argument to control the row labels of the incoming analysis variable.

new_levels

(list(2) or NULL)
List of length 2.
First element : names of the new levels
Second element: list with values of the new levels.

new_levels_after

(logical)
If TRUE new levels will be added after last level.

addstr2levs

string, if not NULL will be appended to the rowlabel for that level, eg to add ",n (percent)" at the end of the rowlabels

.spl_context

(data.frame)
gives information about ancestor split states that is passed by rtables.

riskdiff

(logical)
When TRUE, risk difference calculations will be performed and presented (if required risk difference column splits are included).
When FALSE, risk difference columns will remain blank (if required risk difference column splits are included).

ref_path

(string)
Column path specifications for the control group for the relative risk derivation.

variables

Will be passed onto the risk difference function (internal function s_risk_diff_val_j), which is based upon tern::s_proportion_diff().
See ?tern::s_proportion_diff for details.

conf_level

(proportion)
confidence level of the interval.

method

Will be passed onto the risk difference function (internal function s_risk_diff_val_j).

weights_method

Will be passed onto the risk difference function (internal function s_risk_diff_val_j).

label

(string)
When val has length 1, the row label to be shown on the output can be specified using this argument.
When val is a character vector, the label_map argument can be specified to control the row-labels.

label_fstr

(string)
a sprintf style format string. It can contain up to one "%s", which takes the current split value and generates the row/column label.
It will be combined with the labelstr argument, when utilizing this function as a cfun in a summarize_row_groups call.
It is recommended not to utilize this argument for other purposes. The label argument could be used instead (if val is a single string)

label_map

(data.frame)
A mapping data frame used to translate levels of the analysis variable(s) into the row labels displayed in the table, optionally conditioned on the nearest row-split.

Four types of mappings are supported:

  1. Single-variable mapping. Column names: level, label.

  2. Multi-variable mapping. Column names: var, level, label.

  3. Conditional-on-row-split mapping. Column names: <split_var>, level, label.

  4. Conditional-on-row-split multi-variable mapping. Column names: <split_var>, var, level, label.

Here, <split_var> is a placeholder for the name of the variable used for the nearest row-split (e.g. SEX, PARAMCD).

Recommendation: ensure input variable(s) are of type factor, to avoid the error "got a label map that doesn't provide labels for all values".

See examples for more details.

.alt_df_full

(dataframe)
Denominator dataset for fraction and relative risk calculations.
this argument gets populated by the rtables split machinery (see rtables::additional_fun_params).

denom_by

(character)
Variables from row-split to be used in the denominator derivation.
This controls both denom = "n_parentdf" and denom = "n_altdf".
When denom = "n_altdf", the denominator is derived from .alt_df_full in combination with denom_by argument

.stats

(character)
Statistics to include in the table. May contain one or more of: "count", "count_unique", "count_unique_fraction", "count_unique_denom_fraction", "unique_fraction", "n_df", "n_altdf", "n_rowdf", "n_parentdf", "denom". See Value for the full list of available statistics.

.formats

(named 'character' or 'list')
formats for the statistics.

.indent_mods

(named integer)
indent modifiers for the labels. Defaults to 0, which corresponds to the unmodified default behavior. Can be negative.

na_str

(string)
string used to replace all NA or empty values in the output.

.labels_n

(named character)
String to control row labels for the 'n'-statistics.
Only useful when more than one 'n'-statistic is requested (rare situations only).

extrablankline

(logical)
When TRUE, an extra blank line will be added after the last value.
Avoid using this in template scripts, use section_div = " " instead (once PR for rtables is available)

extrablanklineafter

(string)
When the row-label matches the string, an extra blank line will be added after that value.

restr_columns

character
If not NULL, columns not defined in restr_columns will be blanked out.

colgroup

The name of the column group variable that is used as source for denominator calculation.
Required to be specified when denom = "N_colgroup".

exclude_levels

(list)
A named list where names correspond to split variables and values are vectors of levels to exclude.

...

additional arguments for the lower level functions.

Value

  • s_freq_j: returns a list of following statistics

    • n_df

    • n_rowdf

    • n_parentdf

    • n_altdf

    • denom

    • count

    • count_unique

    • count_unique_fraction

    • count_unique_denom_fraction

    • unique_fraction

  • a_freq_j: returns a list of requested statistics with formatted rtables::CellValue().
    Within the relative risk difference columns, the following stats are blanked out:

    • any of the n-statistics (n_df, n_altdf, n_parentdf, n_rowdf, denom)

    • count

    • count_unique

    For the others (count_unique_fraction, count_unique_denom_fraction, unique_fraction), the statistic is replaced by the relative risk difference + confidence interval.

Details

denom controls the denominator used to calculate proportions/percents. It must be one of

  • N_col Column count,

  • n_df Number of patients (based upon the main input dataframe df),

  • n_altdf Number of patients from the secondary dataframe (.alt_df_full),
    Note that argument denom_by will perform a row-split on the .alt_df_full dataframe.
    It is a requirement that variables specified in denom_by are part of the row split specifications.

  • N_colgroup Number of patients from the column group variable (note that this is based upon the input .alt_df_full dataframe).
    Note that the argument colgroup (column variable) needs to be provided, as it cannot be retrieved directly from the column layout definition.

  • n_rowdf Number of patients from the current row-level dataframe (.row_df from the rtables splitting machinery).

  • n_parentdf Number of patients from a higher row-level split than the current split.
    This higher row-level split is specified in the argument denom_by.

Functions

  • a_freq_j_with_exclude(): Wrapper for the afun which can exclude row split levels from producing the analysis. These have to be specified in the exclude_levels argument, see ?do_exclude_split for details.

Examples

library(dplyr)

adsl <- ex_adsl |> select("USUBJID", "SEX", "ARM")
adae <- ex_adae |> select("USUBJID", "AEBODSYS", "AEDECOD")
adae[["TRTEMFL"]] <- "Y"

trtvar <- "ARM"
ctrl_grp <- "B: Placebo"
adsl$colspan_trt <- factor(ifelse(adsl[[trtvar]] == ctrl_grp, " ", "Active Study Agent"),
  levels = c("Active Study Agent", " ")
)

adsl$rrisk_header <- "Risk Difference (%) (95% CI)"
adsl$rrisk_label <- paste(adsl[[trtvar]], paste("vs", ctrl_grp))

adae <- adae |> left_join(adsl)
#> Joining with `by = join_by(USUBJID)`

colspan_trt_map <- create_colspan_map(adsl,
  non_active_grp = "B: Placebo",
  non_active_grp_span_lbl = " ",
  active_grp_span_lbl = "Active Study Agent",
  colspan_var = "colspan_trt",
  trt_var = trtvar
)

ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)

lyt <- basic_table(show_colcounts = TRUE) |>
  split_cols_by("colspan_trt", split_fun = trim_levels_to_map(map = colspan_trt_map)) |>
  split_cols_by(trtvar) |>
  split_cols_by("rrisk_header", nested = FALSE) |>
  split_cols_by(trtvar, labels_var = "rrisk_label", split_fun = remove_split_levels(ctrl_grp))

lyt1 <- lyt |>
  analyze("TRTEMFL",
    show_labels = "hidden",
    afun = a_freq_j,
    extra_args = list(
      method = "wald",
      .stats = c("count_unique_denom_fraction"),
      ref_path = ref_path
    )
  )

result1 <- build_table(lyt1, adae, alt_counts_df = adsl)

result1
#>            Active Study Agent                                          Risk Difference (%) (95% CI)             
#>        A: Drug X      C: Combination      B: Placebo      A: Drug X vs B: Placebo   C: Combination vs B: Placebo
#>         (N=134)           (N=132)           (N=134)               (N=134)                     (N=132)           
#> ————————————————————————————————————————————————————————————————————————————————————————————————————————————————
#> Y   122/134 (91.0%)   120/132 (90.9%)   123/134 (91.8%)      -0.7 (-7.5, 6.0)             -0.9 (-7.6, 5.9)      

x_drug_x <- list(length(unique(subset(adae, adae[[trtvar]] == "A: Drug X")[["USUBJID"]])))
N_x_drug_x <- length(unique(subset(adsl, adsl[[trtvar]] == "A: Drug X")[["USUBJID"]]))
y_placebo <- list(length(unique(subset(adae, adae[[trtvar]] == ctrl_grp)[["USUBJID"]])))
N_y_placebo <- length(unique(subset(adsl, adsl[[trtvar]] == ctrl_grp)[["USUBJID"]]))

tern::stat_propdiff_ci(
  x = x_drug_x,
  N_x = N_x_drug_x,
  y = y_placebo,
  N_y = N_y_placebo
)
#> [[1]]
#> [1] -0.7462687 -7.4525893  5.9600520
#> 

x_combo <- list(length(unique(subset(adae, adae[[trtvar]] == "C: Combination")[["USUBJID"]])))
N_x_combo <- length(unique(subset(adsl, adsl[[trtvar]] == "C: Combination")[["USUBJID"]]))

tern::stat_propdiff_ci(
  x = x_combo,
  N_x = N_x_combo,
  y = y_placebo,
  N_y = N_y_placebo
)
#> [[1]]
#> [1] -0.8819539 -7.6386167  5.8747089
#> 


extra_args_rr <- list(
  denom = "n_altdf",
  denom_by = "SEX",
  riskdiff = FALSE,
  .stats = c("count_unique")
)

extra_args_rr2 <- list(
  denom = "n_altdf",
  denom_by = "SEX",
  riskdiff = TRUE,
  ref_path = ref_path,
  method = "wald",
  .stats = c("count_unique_denom_fraction"),
  na_str = rep("NA", 3)
)

lyt2 <- basic_table(
  top_level_section_div = " ",
  colcount_format = "N=xx"
) |>
  split_cols_by("colspan_trt", split_fun = trim_levels_to_map(map = colspan_trt_map)) |>
  split_cols_by(trtvar, show_colcounts = TRUE) |>
  split_cols_by("rrisk_header", nested = FALSE) |>
  split_cols_by(trtvar,
    labels_var = "rrisk_label", split_fun = remove_split_levels("B: Placebo"),
    show_colcounts = FALSE
  ) |>
  split_rows_by("SEX", split_fun = drop_split_levels) |>
  summarize_row_groups("SEX",
    cfun = a_freq_j,
    extra_args = append(extra_args_rr, list(label_fstr = "Gender: %s"))
  ) |>
  split_rows_by("TRTEMFL",
    split_fun = keep_split_levels("Y"),
    indent_mod = -1L,
    section_div = c(" ")
  ) |>
  summarize_row_groups("TRTEMFL",
    cfun = a_freq_j,
    extra_args = append(extra_args_rr2, list(
      label =
        "Subjects with >=1 AE", extrablankline = TRUE
    ))
  ) |>
  split_rows_by("AEBODSYS",
    split_label = "System Organ Class",
    split_fun = trim_levels_in_group("AEDECOD"),
    label_pos = "topleft",
    section_div = c(" "),
    nested = TRUE
  ) |>
  summarize_row_groups("AEBODSYS",
    cfun = a_freq_j,
    extra_args = extra_args_rr2
  ) |>
  analyze("AEDECOD",
    afun = a_freq_j,
    extra_args = extra_args_rr2
  )

result2 <- build_table(lyt2, adae, alt_counts_df = adsl)

# -------------------------------------------------------------------------
# Examples using the label_map argument
# -------------------------------------------------------------------------

adsl <- ex_adsl |> select("USUBJID", "SEX", "ARM")
adae <- ex_adae |> select("USUBJID", "AEBODSYS", "AEDECOD")
adae[["TRTEMFL"]] <- "Y"

trtvar <- "ARM"
adae <- adae |> left_join(adsl)
#> Joining with `by = join_by(USUBJID)`

# -------------------------------------------------------------------------
# 1. Simple mapping.
# Remaps factor levels (value) to custom new labels (label).
# -------------------------------------------------------------------------

label_map_simple <- data.frame(
  value = c("M", "F", "UNDIFFERENTIATED", "U"),
  label = c("Male", "Female", "Undifferentiated", "Unknown")
)

lyt_lmap1 <- basic_table(show_colcounts = TRUE) |>
  split_cols_by(trtvar) |>
  analyze("SEX",
    afun = a_freq_j,
    extra_args = list(
      .stats = "count_unique_fraction",
      label_map = label_map_simple
    )
  )

result_lmap1 <- build_table(lyt_lmap1, adsl, alt_counts_df = adsl)
result_lmap1
#>                    A: Drug X    B: Placebo   C: Combination
#>                     (N=134)      (N=134)        (N=132)    
#> ———————————————————————————————————————————————————————————
#> Female             79 (59.0%)   77 (57.5%)     66 (50.0%)  
#> Male               51 (38.1%)   55 (41.0%)     60 (45.5%)  
#> Unknown             3 (2.2%)     2 (1.5%)       4 (3.0%)   
#> Undifferentiated    1 (0.7%)        0           2 (1.5%)   

# -------------------------------------------------------------------------
# 2. Multi-variable mapping.
# Useful in situations where multiple Y/N variables can be analyzed in one
# [rtables::analyze()] call.
# -------------------------------------------------------------------------

multi_vars <- c("DTH30FL", "DTHA30FL", "DTHB30FL")
adslx <- pharmaverseadamjnj::adsl
# ensure the variables are factor with levels Y/N - and variable label is kept
adslx <- adslx |>
  mutate(across(all_of(multi_vars), \(x) structure(
    factor(x, levels = c("Y", "N")),
    label = attr(x, "label")
  )))

map_multi <- data.frame(
  var = multi_vars,
  value = "Y",
  label = c(
    "Death <=30D of Last Trt Flag",
    "Death >30D from Last Trt Flag",
    "Death <=30D of First Trt Flag"
  )
)

lyt_lmap1 <- basic_table(show_colcounts = TRUE) |>
  split_cols_by("ARM") |>
  analyze(multi_vars,
    afun = a_freq_j,
    extra_args = list(
      .stats = "count_unique_denom_fraction",
      label_map = map_multi,
      val = "Y"
    ),
    show_labels = "hidden"
  )

result_lmap2 <- build_table(lyt_lmap1, adslx)
result_lmap2
#>                                   Placebo     Screen Failure   Xanomeline High Dose   Xanomeline Low Dose
#>                                   (N=86)          (N=52)              (N=84)                (N=84)       
#> —————————————————————————————————————————————————————————————————————————————————————————————————————————
#> Death <=30D of Last Trt Flag    2/86 (2.3%)        0/52                0/84               2/84 (2.4%)    
#> Death >30D from Last Trt Flag      0/86            0/52                0/84                  0/84        
#> Death <=30D of First Trt Flag   1/86 (1.2%)        0/52                0/84                  0/84        

# -------------------------------------------------------------------------
# 3. Conditional-on-row-split mapping.
# -------------------------------------------------------------------------

map_rowsplit <- data.frame(
  SEX = c("M", "F"),
  value = c("Y", "Y"),
  label = c("Male subjects with >=1 TE AE", "Female subjects with >=1 TE AE")
)

adsl <- ex_adsl |> select("USUBJID", "ARM", "SEX")
adae <- ex_adae |> select("USUBJID", "AEBODSYS", "AEDECOD")
adae[["TRTEMFL"]] <- "Y"
adae <- adae |> left_join(adsl)
#> Joining with `by = join_by(USUBJID)`

lyt_lmap3 <- basic_table(show_colcounts = TRUE) |>
  split_cols_by("ARM") |>
  split_rows_by("SEX", split_fun = keep_split_levels(c("F", "M")), child_labels = "hidden") |>
  analyze("TRTEMFL",
    afun = a_freq_j,
    extra_args = list(
      .stats = "count_unique_denom_fraction",
      label_map = map_rowsplit,
      val = "Y"
    ),
    show_labels = "hidden"
  )

result_lmap3 <- build_table(lyt_lmap3, adae, alt_counts_df = adsl)
result_lmap3
#>                                    A: Drug X        B: Placebo     C: Combination
#>                                     (N=134)          (N=134)          (N=132)    
#> —————————————————————————————————————————————————————————————————————————————————
#> Female subjects with >=1 TE AE   72/134 (53.7%)   73/134 (54.5%)   61/132 (46.2%)
#> Male subjects with >=1 TE AE     46/134 (34.3%)   48/134 (35.8%)   53/132 (40.2%)

# -----------------------------------------------------------------------------------
# 4. Conditional-on-row-split multi-variable mapping.
# -----------------------------------------------------------------------------------
adsl_jnj <- pharmaverseadamjnj::adsl
advs_jnj <- pharmaverseadamjnj::advs

multi_vars <- c("CRIT1FL", "CRIT2FL", "CRIT3FL")

map_multi_rowsplit <- data.frame(
  PARAMCD = c(rep("DIABP", 3), rep("SYSBP", 3)),
  var = rep(multi_vars, 2),
  value = rep("Y", 6),
  label = c(
    "<50 mmHg and with >20 mmHg decrease from baseline",
    ">105 mmHg and with >30 mmHg increase from baseline",
    "Diastolic blood pressure<60",
    "<90 mmHg and with >30 mmHg decrease from baseline",
    ">180 mmHg and with >40 mmHg increase from baseline",
    "Systolic blood pressure<90"
  )
)

lyt_lmap4 <- basic_table(show_colcounts = TRUE) |>
  split_cols_by("TRT01A") |>
  split_rows_by("PARAMCD", split_fun = keep_split_levels(c("DIABP", "SYSBP"))) |>
  analyze(multi_vars,
    afun = a_freq_j,
    extra_args = list(
      .stats = "count_unique_denom_fraction",
      label_map = map_multi_rowsplit,
      val = "Y"
    ),
    show_labels = "hidden"
  )

result_lmap4 <- build_table(lyt_lmap4, advs_jnj, alt_counts_df = adsl_jnj)
result_lmap4
#>                                                        Xanomeline Low Dose   Xanomeline High Dose      Placebo   
#>                                                              (N=96)                 (N=72)             (N=86)    
#> —————————————————————————————————————————————————————————————————————————————————————————————————————————————————
#> DIABP                                                                                                            
#>   <50 mmHg and with >20 mmHg decrease from baseline           0/96                   0/72               0/86     
#>   >105 mmHg and with >30 mmHg increase from baseline          0/96                   0/72               0/86     
#>   Diastolic blood pressure<60                             57/96 (59.4%)         52/72 (72.2%)       56/86 (65.1%)
#> SYSBP                                                                                                            
#>   <90 mmHg and with >30 mmHg decrease from baseline           0/96                   0/72            1/86 (1.2%) 
#>   >180 mmHg and with >40 mmHg increase from baseline       1/96 (1.0%)               0/72            1/86 (1.2%) 
#>   Systolic blood pressure<90                              91/96 (94.8%)         69/72 (95.8%)       83/86 (96.5%)