Skip to content

Commit ccb50a0

Browse files
committed
new table_02 function using gtsummary and ards
1 parent d622a76 commit ccb50a0

File tree

2 files changed

+33
-355
lines changed

2 files changed

+33
-355
lines changed

R/fda-table_02.R

+23-289
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#' FDA Table 2: Baseline Demographic and Clinical Characteristics, Safety Population, Pooled Analyses
22
#'
33
#' @details
4-
#' * `data` must contain the variables specified by `continuous_vars`, and `categorical_vars`.
5-
#' * `tbl_engine` must be one of `gtsummary`, `rtables`, `tplyr`.
4+
#' * `data` must contain the variables specified by `vars`.
65
#' * `return_ard` set to `TRUE` or `FALSE`; whether the intermediate ARD object should be returned.
76
#'
87
#' @inheritParams argument_convention
@@ -13,7 +12,7 @@ NULL
1312
#' @describeIn make_table_02 Create FDA table 2 using an ARD.
1413
#'
1514
#' @return
16-
#' * `make_table_02` returns an object matching the selected `tbl_engine` argument.
15+
#' * `make_table_02` returns a list containing a gtsummary table object.
1716
#' The intermediary `ARD` object can also be returned with `return_ard` set to `TRUE`.
1817
#'
1918
#' @examples
@@ -25,300 +24,35 @@ NULL
2524
#' AGE >= 65 ~ ">=65",
2625
#' AGE >= 65 & AGE < 75 ~ ">=65 to <75",
2726
#' AGE >= 75 ~ ">=75"
28-
#' ))
27+
#' )))
2928
#'
3029
#' tbl <- make_table_02(data = adsl)
3130
#' tbl
3231
#'
3332
#' @export
3433

3534
make_table_02 <- function(data,
35+
show_colcounts = TRUE,
36+
arm_var = "ARM",
37+
saffl_var = "SAFFL",
38+
vars = c("SEX", "AGE", "AGEGR1", "RACE", "ETHNIC", "COUNTRY"),
39+
lbl_vars = formatters::var_labels(data, fill = TRUE)[vars],
40+
lbl_overall = "Total Population",
41+
na_rm = FALSE,
3642
...,
37-
tbl_engine = "gtsummary",
3843
return_ard = TRUE) {
3944
# check data viability
40-
assert_subset(continuous_vars, names(data))
41-
assert_subset(categorical_vars, names(data))
45+
checkmate::assert_subset(c(vars, arm_var, saffl_var), names(data))
46+
assert_flag_variables(data, saffl_var)
4247

43-
if (tbl_engine == "rtables") {
44-
ard <- "ARD not available for {rtables}"
45-
tbl <- make_table_02_rtables(df = data)
46-
} else {
47-
ard <- make_ard_02(...)
48-
# commenting out this table building portion until package refactor
49-
# if (tbl_engine == "gtsummary") {
50-
# tbl <- make_table_02_gtsum(ard, ...)
51-
# } else {
52-
# tbl <- make_table_02_tplyr(ard, ...)
53-
# }
54-
}
55-
56-
if (return_ard) {
57-
res <- list(ard = ard)
58-
} else {
59-
res <- tbl
60-
}
61-
}
62-
63-
#' @keywords Internal
64-
make_ard_02 <- function(data = data,
65-
by = "ARM",
66-
continuous_vars = c("AGE"),
67-
categorical_vars = c("SEX", "AGEGR1", "RACE", "ETHNIC")) {
68-
ard <- cards::ard_stack(
69-
data = data,
70-
by = by,
71-
cards::ard_continuous(
72-
variables = continuous_vars,
73-
statistic = ~ continuous_summary_fns(c("mean", "sd", "median", "min", "max"))
74-
),
75-
cards::ard_categorical(variables = categorical_vars)
76-
)
77-
78-
return(ard)
79-
}
80-
81-
82-
#' @keywords Internal
83-
make_table_02_rtables <- function(df,
84-
alt_counts_df = NULL,
85-
show_colcounts = TRUE,
86-
arm_var = "ARM",
87-
saffl_var = "SAFFL",
88-
vars = c("SEX", "AGE", "AGEGR1", "RACE", "ETHNIC", "COUNTRY"),
89-
lbl_vars = formatters::var_labels(df, fill = TRUE)[vars],
90-
lbl_overall = "Total Population",
91-
na_rm = FALSE,
92-
prune_0 = TRUE,
93-
annotations = NULL) {
94-
assert_subset(c(vars, arm_var, saffl_var), names(df))
95-
assert_flag_variables(df, saffl_var)
96-
97-
df <- df %>%
98-
filter(.data[[saffl_var]] == "Y") %>%
99-
df_explicit_na()
100-
101-
alt_counts_df <- alt_counts_df_preproc(alt_counts_df, id_var, arm_var, saffl_var)
102-
103-
lyt <- basic_table_annot(show_colcounts, annotations) %>%
104-
split_cols_by_arm(arm_var, lbl_overall) %>%
105-
analyze_vars(
106-
vars = vars,
107-
var_labels = lbl_vars,
108-
show_labels = "visible",
109-
.stats = c("mean_sd", "median_range", "count_fraction"),
110-
.formats = NULL,
111-
na.rm = na_rm
112-
) %>%
113-
append_topleft("Characteristic")
114-
115-
tbl <- build_table(lyt, df = df, alt_counts_df = alt_counts_df)
116-
if (prune_0) tbl <- prune_table(tbl)
117-
118-
tbl
119-
}
120-
121-
#' @describeIn make_table_02 Create FDA table 2 using functions from `Tplyr` and `tfrmt`.
122-
#'
123-
#' @param tplyr_raw (`flag`)\cr whether the raw `tibble` created using `Tplyr` functions should be returned, or the
124-
#' table formatted using functions from `tfrmt` should be returned (default).
125-
#'
126-
#' @note
127-
#' * `make_table_02_tplyr` does not currently support footnote annotations
128-
#' * `make_table_02_tplyr` does not currently support `alt_counts_df` when `tplyr_raw = TRUE`.
129-
#'
130-
#' @return
131-
#' * `make_table_02_tplyr` returns a `gt_tbl` object when `tplyr_raw = FALSE` (default) and
132-
#' a `tibble` object when `tplyr_raw = TRUE`.
133-
#'
134-
#' @examples
135-
#' tbl <- make_table_02_tplyr(df = adsl)
136-
#' tbl
137-
#'
138-
#' @keywords Internal
139-
make_table_02_tplyr <- function(df,
140-
alt_counts_df = NULL,
141-
show_colcounts = TRUE,
142-
arm_var = "ARM",
143-
saffl_var = "SAFFL",
144-
vars = c("SEX", "AGE", "AGEGR1", "RACE", "ETHNIC", "COUNTRY"),
145-
lbl_vars = formatters::var_labels(df, fill = TRUE)[vars],
146-
lbl_overall = "Total Population",
147-
na_rm = FALSE,
148-
prune_0 = TRUE,
149-
annotations = NULL,
150-
tplyr_raw = FALSE) {
151-
assert_subset(c(saffl_var, vars, arm_var), names(df))
152-
assert_flag_variables(df, saffl_var)
153-
154-
df <- df %>% df_explicit_na()
155-
for (lbl in lbl_vars) {
156-
df[[lbl]] <- lbl
157-
}
158-
159-
var_types <- lapply(df[vars], function(x) if (is.numeric(x)) "numeric" else "count")
160-
161-
lyt <- tplyr_table(df, treat_var = !!sym(arm_var), where = !!sym(saffl_var) == "Y")
162-
163-
if (!is.null(lbl_overall)) lyt <- lyt %>% add_total_group(group_name = lbl_overall)
164-
165-
for (i in seq_along(vars)) {
166-
var <- vars[i]
167-
var_lbl <- lbl_vars[i]
168-
if (var_types[[var]] == "numeric") {
169-
if (tplyr_raw) {
170-
lyt <- lyt %>% add_layer(
171-
group_desc(vars(!!sym(var)), by = !!sym(var_lbl)) %>%
172-
set_format_strings(
173-
"Mean (SD)" = f_str("xx.x (xx.x)", mean, sd),
174-
"Median (Min - Max)" = f_str("xx.x (xx.x - xx.x)", median, min, max)
175-
)
176-
)
177-
} else {
178-
lyt <- lyt %>% add_layer(
179-
group_desc(vars(!!sym(var)), by = !!sym(var_lbl)) %>%
180-
set_format_strings(
181-
"Mean" = f_str("xx.xxxx", mean), "SD" = f_str("xx.xxxx", sd), "Median" = f_str("xx.xxxx", median),
182-
"Min" = f_str("xx.xxxx", min), "Max" = f_str("xx.xxxx", max)
183-
)
184-
)
185-
}
186-
} else {
187-
if (tplyr_raw) {
188-
lyt <- lyt %>% add_layer(
189-
group_count(vars(!!sym(var)), by = !!sym(var_lbl)) %>%
190-
set_format_strings(f_str("xx (xx.x%)", n, pct))
191-
)
192-
} else {
193-
lyt <- lyt %>% add_layer(
194-
group_count(vars(!!sym(var)), by = !!sym(var_lbl)) %>%
195-
set_format_strings(f_str("xx;xx.xxxx", n, pct))
196-
)
197-
}
198-
}
199-
}
200-
tbl <- lyt %>% build()
201-
202-
if (na_rm) {
203-
na_ind <- tbl[, 2] != "<Missing>"
204-
tbl <- tbl[na_ind, ]
205-
}
206-
207-
if (tplyr_raw) {
208-
tbl <- tbl %>%
209-
arrange(ord_layer_index, ord_layer_1, ord_layer_2) %>%
210-
apply_row_masks(row_breaks = TRUE) %>%
211-
select(starts_with("row_label"), starts_with("var1_")) %>%
212-
add_column_headers(
213-
paste0(
214-
"Characteristic | | ",
215-
paste0(
216-
levels(df[[arm_var]]), if (show_colcounts) paste0("\n(N=**", levels(df[[arm_var]]), "**)| ") else "| ",
217-
collapse = ""
218-
),
219-
ifelse(!is.null(lbl_overall), paste0(lbl_overall, ifelse(show_colcounts, c("\n(N=**Total**)"), ""), ""))
220-
),
221-
header_n = header_n(lyt)
222-
)
223-
224-
if (prune_0) {
225-
prune_ind <- apply(tbl, MARGIN = 1, function(x) all(x == "") || !all(gsub("[0()\\% ]", "", x[-c(1:2)]) == ""))
226-
tbl <- tbl[prune_ind, ]
227-
}
228-
} else {
229-
tbl <- tbl %>%
230-
tidyr::pivot_longer(head(names(.)[-c(1:2)], -3), names_to = "column", values_to = "value") %>%
231-
mutate(
232-
tbl_lbl = "Characteristic",
233-
column = gsub("var1_", "", column),
234-
param = ifelse(row_label2 %in% c("Mean", "SD", "Median", "Min", "Max"), row_label2, "n;pct"),
235-
row_label2 = case_when(
236-
row_label2 %in% c("Mean", "SD") ~ "Mean (SD)",
237-
row_label2 %in% c("Median", "Min", "Max") ~ "Median (Min - Max)",
238-
.default = row_label2
239-
)
240-
) %>%
241-
tidyr::separate_rows(c("param", "value"), sep = ";") %>%
242-
mutate(value = as.numeric(value))
243-
244-
if (show_colcounts) {
245-
colcounts <- summary(if (!is.null(alt_counts_df)) alt_counts_df[[arm_var]] else df[[arm_var]])
246-
big_ns <- tibble(
247-
column = c(levels(df[[arm_var]]), lbl_overall),
248-
param = "bigN",
249-
value = if (!is.null(lbl_overall)) c(colcounts, sum(colcounts)) else colcounts,
250-
)
251-
tbl <- bind_rows(tbl, big_ns)
252-
}
253-
254-
if (prune_0) {
255-
tbl <- tbl %>%
256-
group_by(ord_layer_index, ord_layer_2) %>%
257-
filter(sum(value) > 0) %>%
258-
ungroup()
259-
}
260-
big_n_tbl <- if (show_colcounts) big_n_structure(param_val = "bigN", n_frmt = frmt("\n(N=xx)")) else NULL
261-
262-
tbl <- tfrmt(
263-
group = c(tbl_lbl, row_label1),
264-
label = row_label2,
265-
column = column,
266-
param = param,
267-
value = value,
268-
title = annotations[["title"]],
269-
subtitle = paste(annotations[["subtitles"]], collapse = ", "),
270-
sorting_cols = c(ord_layer_1, ord_layer_2),
271-
body_plan = body_plan(
272-
frmt_structure(
273-
group_val = ".default", label_val = ".default",
274-
frmt_combine("{n} {pct}", n = frmt("xx"), pct = frmt_when("==0" ~ "", TRUE ~ frmt("(xx.x%)")))
275-
),
276-
frmt_structure(
277-
group_val = ".default", label_val = "Mean (SD)",
278-
frmt_combine("{Mean} ({SD})", Mean = frmt("xx.x"), SD = frmt("xx.x"))
279-
),
280-
frmt_structure(
281-
group_val = ".default", label_val = "Median (Min - Max)",
282-
frmt_combine("{Median} ({Min} - {Max})", Median = frmt("xx.x"), Min = frmt("xx.x"), Max = frmt("xx.x"))
283-
)
284-
),
285-
col_plan = col_plan(-starts_with("ord")),
286-
big_n = big_n_tbl
287-
) %>%
288-
print_to_gt(tbl)
289-
}
290-
tbl
291-
}
292-
293-
#' @describeIn make_table_02 Create FDA table 2 using functions from `gtsummary`.
294-
#'
295-
#' @return
296-
#' * `make_table_02_gtsum` returns a `tbl_summary` object.
297-
#'
298-
#' @examples
299-
#' tbl <- make_table_02_gtsum(df = adsl)
300-
#' tbl
301-
#'
302-
#' @keywords Internal
303-
make_table_02_gtsum <- function(df,
304-
show_colcounts = TRUE,
305-
arm_var = "ARM",
306-
saffl_var = "SAFFL",
307-
vars = c("SEX", "AGE", "AGEGR1", "RACE", "ETHNIC", "COUNTRY"),
308-
lbl_vars = formatters::var_labels(df, fill = TRUE)[vars],
309-
lbl_overall = "Total Population",
310-
na_rm = FALSE) {
311-
assert_subset(c(vars, arm_var, saffl_var), names(df))
312-
assert_flag_variables(df, saffl_var)
313-
314-
df <- df %>%
48+
df <- data %>%
31549
filter(.data[[saffl_var]] == "Y") %>%
31650
select(all_of(c(vars, arm_var)))
31751

31852
if (!na_rm) df <- df %>% df_explicit_na()
31953

32054
tbl <- df %>%
321-
tbl_summary(
55+
gtsummary::tbl_summary(
32256
by = arm_var,
32357
type = all_continuous() ~ "continuous2",
32458
statistic = list(
@@ -333,16 +67,16 @@ make_table_02_gtsum <- function(df,
33367
label = as.list(lbl_vars) %>% setNames(vars)
33468
) %>%
33569
gtsummary::bold_labels() %>%
336-
modify_header(all_stat_cols() ~ "**{level}** \nN = {n}") %>%
337-
add_overall(last = TRUE, col_label = paste0("**", lbl_overall, "** \nN = {n}")) %>%
70+
gtsummary::modify_header(all_stat_cols() ~ "**{level}** \nN = {n}") %>%
71+
gtsummary::add_overall(last = TRUE, col_label = paste0("**", lbl_overall, "** \nN = {n}")) %>%
33872
gtsummary::add_stat_label(label = all_continuous2() ~ c("Mean (SD)", "Median (min - max)")) %>%
339-
modify_footnote(update = everything() ~ NA) %>%
73+
gtsummary::modify_footnote(update = everything() ~ NA) %>%
34074
gtsummary::modify_column_alignment(columns = all_stat_cols(), align = "right")
34175

342-
gtsummary::with_gtsummary_theme(
343-
x = gtsummary::theme_gtsummary_compact(),
344-
expr = as_gt(tbl)
345-
)
76+
if (return_ard) {
77+
ard <- gtsummary::gather_ard(tbl)
78+
res <- list(tbl = tbl, ard = ard)
79+
} else {
80+
res <- list(tbl = tbl)
81+
}
34682
}
347-
348-

0 commit comments

Comments
 (0)