Skip to content

Commit 65bdb06

Browse files
authored
Merge branch 'main' into 195-new-table-table-36
2 parents 53d52e6 + 33a5664 commit 65bdb06

30 files changed

+1945
-27
lines changed

NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export(alt_counts_df_preproc)
44
export(basic_table_annot)
55
export(make_fig_01)
66
export(make_fig_02)
7+
export(make_fig_14)
78
export(make_table_02)
89
export(make_table_02_gtsum)
910
export(make_table_02_tplyr)
@@ -33,6 +34,7 @@ export(make_table_33)
3334
export(make_table_34)
3435
export(make_table_35)
3536
export(make_table_36)
37+
export(make_table_38)
3638
export(split_cols_by_arm)
3739
import(Tplyr)
3840
import(dplyr)

NEWS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
`make_table_06`, `make_table_07`, `make_table_08`, `make_table_09`, `make_table_10`, `make_table_11`, `make_table_12`,
66
`make_table_13`, `make_table_14`, `make_table_15`, `make_table_16`, `make_table_17`, `make_table_18`, `make_table_20`,
77
`make_table_21`, `make_table_22`, `make_table_32`, `make_table_33`, `make_table_34`, `make_table_35`, `make_table_36`.
8-
* Added new functions for creating standard FDA figures: `make_fig_01`, `make_fig_02`.
8+
* Added new functions for creating standard FDA figures: `make_fig_01`, `make_fig_02`, `make_fig_14`.
99
* Added helper functions used within table functions:
1010
* `basic_table_annot` for adding basic table annotations (titles, footnotes, column counts).
1111
* `split_cols_by_arm` for splitting columns by arm with option to add total column.

R/argument_convention.R

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#' be included in the column labels by default. The length of `col_label` must be equal to the length of `arm_y`.
4141
#' * `pct`: (optional) whether the output should be returned as percentages. Defaults to `TRUE`.
4242
#' @param saffl_var (`character`)\cr flag variable used to indicate inclusion in safety population.
43+
#' @param trtemfl_var (`character`)\cr flag variable used to identify Treatment-emergent AE.
4344
#' @param sex_scope (`character`)\cr Level of `SEX` to output in table.
4445
#' @param soc_var (`character`)\cr flag variable used to indicate system organ class.
4546
#' @param show_colcounts (`flag`)\cr whether column counts should be printed.
@@ -51,6 +52,8 @@
5152
#' @param xticks (`vector` of `numeric`)\cr x-axis tick positions. If `NA` (default), tick mark positions are
5253
#' automatically calculated.
5354
#' @param x_lab (`character`)\cr x-axis label.
55+
#' @param yticks (`vector` of `numeric`)\cr y-axis tick positions. If `NA` (default), tick mark positions are
56+
#' automatically calculated.
5457
#' @param y_lab (`character`)\cr y-axis label.
5558
#'
5659
#' @name argument_convention

R/fda-fig_14.R

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#' FDA Figure 14: Mean and 95% Confidence Interval of Systolic Blood Pressure Over Time
2+
#' by Treatment Arm, Safety Population, Trial X
3+
#'
4+
#' @details
5+
#' * `df` must contain the variables `AVAL` and `PARAMCD`, and the variables specified by `arm_var`,
6+
#' `saffl_var`, `visit_var`, and `add_cond`.
7+
#' * Flag variables (i.e. `XXXFL`) are expected to have two levels: `"Y"` (true) and `"N"` (false). Missing values in
8+
#' flag variables are treated as `"N"`.
9+
#' * It is assumed that `df` contains one unique record per patient.
10+
#'
11+
#' @inheritParams argument_convention
12+
#' @param add_table (`flag`)\cr whether "Mean Value" and "Number of Patients" tables should be printed under the plot
13+
#' @param visit_var (`character`)\cr visit variable to put on the x-axis
14+
#' @param paramcd_val (`character`)\cr value of `PARAMCD` to plot
15+
#' @param add_cond (`expr`)\cr expression that provides additional filters for the analysis
16+
#' (for instance on `ATPT` or `VSPOS`)
17+
#' @param annotations (named `list` of `character`)\cr list of annotations to add to the figure. Valid annotation types
18+
#' are `title`, `subtitles`, and `caption`. Each name-value pair should use the annotation type as name and the
19+
#' desired string as value.
20+
#'
21+
#' @return A `ggplot2` object.
22+
#'
23+
#' @examples
24+
#' advs <- random.cdisc.data::cadvs
25+
#'
26+
#' fig <- make_fig_14(
27+
#' df = advs,
28+
#' add_cond = bquote("ONTRTFL == 'Y' | ABLFL == 'Y'"),
29+
#' add_table = TRUE,
30+
#' yticks = c(135, 140, 145, 150, 155, 160)
31+
#' )
32+
#' fig
33+
#'
34+
#' @export
35+
make_fig_14 <- function(df,
36+
arm_var = "ARM",
37+
saffl_var = "SAFFL",
38+
visit_var = "AVISIT",
39+
paramcd_val = "SYSBP",
40+
add_cond = NULL,
41+
x_lab = "",
42+
y_lab = NULL,
43+
yticks = NA,
44+
ggtheme = NULL,
45+
add_table = TRUE,
46+
annotations = NULL) {
47+
checkmate::assert_subset(c(arm_var, saffl_var, visit_var), names(df))
48+
assert_flag_variables(df, saffl_var)
49+
50+
df <- df %>%
51+
as_tibble() %>%
52+
filter(
53+
.data[[saffl_var]] == "Y",
54+
PARAMCD == {{ paramcd_val }},
55+
!is.na(AVAL)
56+
) %>%
57+
df_explicit_na()
58+
59+
if (!(is.null({{ add_cond }}))) {
60+
df <- df %>%
61+
filter(!!rlang::parse_expr(add_cond))
62+
}
63+
64+
if (is.null({{ y_lab }})) {
65+
y_param <- unique(df$PARAM)
66+
y_avalu <- unique(df$AVALU)
67+
68+
y_lab <- paste0("Mean Value (95% CI)", "\n", y_param, " (", y_avalu, ")")
69+
}
70+
71+
df <- df %>%
72+
group_by(!!sym(arm_var), !!sym(visit_var), .drop = TRUE) %>%
73+
summarise(
74+
mean = mean(AVAL, na.rm = TRUE),
75+
sd = sd(AVAL, na.rm = TRUE),
76+
n = n()
77+
) %>%
78+
mutate(
79+
se = sd / sqrt(n),
80+
lower_ci = mean - qt(1 - (0.05 / 2), n - 1) * se,
81+
upper_ci = mean + qt(1 - (0.05 / 2), n - 1) * se
82+
) %>%
83+
ungroup()
84+
85+
g <-
86+
ggplot(
87+
data = df,
88+
aes(
89+
x = !!sym(visit_var),
90+
y = mean,
91+
group = .data[[arm_var]],
92+
color = .data[[arm_var]]
93+
)
94+
) +
95+
geom_point(position = position_dodge(width = 0.5)) +
96+
geom_line(position = position_dodge(width = 0.5)) +
97+
geom_errorbar(
98+
aes(
99+
ymin = lower_ci,
100+
ymax = upper_ci
101+
),
102+
position = position_dodge(width = 0.5)
103+
) +
104+
labs(
105+
title = annotations[["title"]],
106+
subtitle = annotations[["subtitle"]],
107+
caption = annotations[["caption"]],
108+
x = x_lab,
109+
y = y_lab
110+
) +
111+
theme(
112+
legend.position = "bottom",
113+
legend.title = element_blank(),
114+
plot.margin = unit(c(0.05, 0.05, 0, 0.025), "npc")
115+
)
116+
117+
if (any(!is.na(yticks))) {
118+
g <- g +
119+
scale_y_continuous(breaks = yticks, limits = c(min(yticks), max(yticks)))
120+
}
121+
122+
if (!is.null(ggtheme)) g <- g + ggtheme
123+
124+
if (add_table) {
125+
g_legend <- cowplot::get_legend(g)
126+
g <- g + theme(legend.position = "none")
127+
128+
tbl_n <- df %>%
129+
mutate(meanr = sprintf("%.1f", mean)) %>%
130+
arrange(desc(!!sym(arm_var)))
131+
132+
g_tbl1 <- ggplot(tbl_n, aes(x = !!sym(visit_var), y = !!sym(arm_var))) +
133+
theme(
134+
axis.title.x = element_blank(),
135+
axis.title.y = element_blank(),
136+
axis.ticks.x = element_blank(),
137+
axis.ticks.y = element_blank(),
138+
panel.background = element_blank(),
139+
axis.text.x = element_blank(),
140+
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
141+
plot.margin = unit(c(0.1, 0.05, 0, 0.025), "npc"),
142+
plot.title = element_text(size = 10)
143+
) +
144+
labs(title = "Mean Value")
145+
146+
for (i in seq_len(nrow(tbl_n))) {
147+
g_tbl1 <- g_tbl1 +
148+
annotate("text", label = as.character(tbl_n$meanr[i]), x = tbl_n[[visit_var]][i], y = tbl_n[[arm_var]][i])
149+
}
150+
151+
g_tbl2 <- ggplot(tbl_n, aes(x = !!sym(visit_var), y = !!sym(arm_var))) +
152+
theme(
153+
axis.title.x = element_blank(),
154+
axis.title.y = element_blank(),
155+
axis.ticks.x = element_blank(),
156+
axis.ticks.y = element_blank(),
157+
panel.background = element_blank(),
158+
axis.text.x = element_blank(),
159+
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.2),
160+
plot.margin = unit(c(0.1, 0.05, 0, 0.025), "npc"),
161+
plot.title = element_text(size = 10)
162+
) +
163+
labs(title = "Number of Patients with Data")
164+
165+
for (i in seq_len(nrow(tbl_n))) {
166+
g_tbl2 <- g_tbl2 +
167+
annotate("text", label = as.character(tbl_n$n[i]), x = tbl_n[[visit_var]][i], y = tbl_n[[arm_var]][i])
168+
}
169+
170+
cowplot::plot_grid(
171+
g,
172+
g_tbl1,
173+
g_tbl2,
174+
g_legend,
175+
align = "v",
176+
axis = "l",
177+
ncol = 1,
178+
rel_heights = c(0.60, 0.15, 0.15, 0.1)
179+
)
180+
} else {
181+
g
182+
}
183+
}

R/fda-table_22.R

+1-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
#' AGE >= 65 & AGE < 75 ~ ">=65 to <75",
2626
#' AGE >= 75 ~ ">=75"
2727
#' )) %>% formatters::with_label("Age Group, years")) %>%
28-
#' formatters::var_relabel(
29-
#' AGE = "Age, years"
30-
#' )
28+
#' formatters::var_relabel(AGE = "Age, years")
3129
#'
3230
#' adae <- random.cdisc.data::cadae
3331
#'

R/fda-table_35.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#' Table 35. Patients With Adverse Events1 by System Organ Class,
1+
#' Table 35. Patients With Adverse Events by System Organ Class,
22
#' Safety Population, Pooled Analysis (or Trial X)
33
#'
44
#' @details

R/fda-table_38.R

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#' FDA Table 38: Patients With Adverse Events by System Organ Class, FDA Medical Query (Broad) and Preferred Term,
2+
#' Safety Population, Pooled Analysis (or Trial X)
3+
#'
4+
#' @details
5+
#' * `adae` must contain the variables `AEBODSYS`, `AESER`, and the variables specified by
6+
#' `arm_var`, `id_var`, `saffl_var`, `trtemfl_var`, `fmqsc_var`, `fmqnam_var`, and `pref_var`.
7+
#' * If specified, `alt_counts_df` must contain the variables specified by `arm_var`, `id_var`, and `saffl_var`.
8+
#' * Flag variables (i.e. `XXXFL`) are expected to have two levels: `"Y"` (true) and `"N"` (false). Missing values in
9+
#' flag variables are treated as `"N"`.
10+
#' * Columns are split by arm. Overall population column is excluded by default (see `lbl_overall` argument).
11+
#' * Numbers in table represent the absolute numbers of patients and fraction of `N`.
12+
#' * All-zero rows are removed by default (see `prune_0` argument).
13+
#'
14+
#' @inheritParams argument_convention
15+
#'
16+
#' @examples
17+
#' library(dplyr)
18+
#'
19+
#' adae <- random.cdisc.data::cadae
20+
#' adsl <- random.cdisc.data::cadsl
21+
#'
22+
#' set.seed(1)
23+
#' adae <- adae %>%
24+
#' rename(FMQ01SC = SMQ01SC) %>%
25+
#' mutate(
26+
#' AESER = sample(c("Y", "N"), size = nrow(adae), replace = TRUE),
27+
#' FMQ01NAM = sample(c("FMQ1", "FMQ2", "FMQ3"), size = nrow(adae), replace = TRUE)
28+
#' )
29+
#' adae$FMQ01SC[is.na(adae$FMQ01SC)] <- "Broad"
30+
#'
31+
#' tbl <- make_table_38(adae = adae, alt_counts_df = adsl)
32+
#' tbl
33+
#'
34+
#' @export
35+
make_table_38 <- function(adae,
36+
alt_counts_df = NULL,
37+
show_colcounts = TRUE,
38+
id_var = "USUBJID",
39+
arm_var = "ARM",
40+
saffl_var = "SAFFL",
41+
trtemfl_var = "TRTEMFL",
42+
fmqsc_var = "FMQ01SC",
43+
fmqnam_var = "FMQ01NAM",
44+
fmq_scope = "BROAD",
45+
pref_var = "AEDECOD",
46+
lbl_overall = NULL,
47+
lbl_pref_var = formatters::var_labels(adae, fill = TRUE)[pref_var],
48+
risk_diff = NULL,
49+
prune_0 = TRUE,
50+
na_level = "<Missing>",
51+
annotations = NULL) {
52+
checkmate::assert_subset(c(
53+
"AEBODSYS", arm_var, id_var, saffl_var, trtemfl_var, fmqsc_var,
54+
fmqnam_var, pref_var
55+
), names(adae))
56+
assert_flag_variables(adae, saffl_var, trtemfl_var)
57+
checkmate::assert_subset(toupper(fmq_scope), c("NARROW", "BROAD"))
58+
59+
adae <- adae %>%
60+
filter(.data[[saffl_var]] == "Y", .data[[trtemfl_var]] == "Y", .data[[fmqsc_var]] == fmq_scope) %>%
61+
df_explicit_na(na_level = na_level)
62+
adae[[fmqnam_var]] <- with_label(adae[[fmqnam_var]], paste0("FMQ (", tools::toTitleCase(tolower(fmq_scope)), ")"))
63+
64+
alt_counts_df <- alt_counts_df_preproc(alt_counts_df, id_var, arm_var, saffl_var)
65+
66+
lyt <- basic_table_annot(show_colcounts, annotations) %>%
67+
split_cols_by_arm(arm_var, lbl_overall, risk_diff) %>%
68+
split_rows_by(
69+
var = "AEBODSYS",
70+
split_fun = drop_split_levels,
71+
split_label = obj_label(adae$AEBODSYS),
72+
label_pos = "topleft"
73+
) %>%
74+
split_rows_by(
75+
fmqnam_var,
76+
child_labels = "hidden",
77+
label_pos = "topleft",
78+
split_label = obj_label(adae[[fmqnam_var]])
79+
) %>%
80+
summarize_num_patients(
81+
var = id_var,
82+
.stats = "unique",
83+
.labels = c(unique = NULL),
84+
riskdiff = !is.null(risk_diff)
85+
) %>%
86+
count_occurrences(
87+
vars = pref_var,
88+
riskdiff = !is.null(risk_diff)
89+
) %>%
90+
append_topleft(paste(" ", lbl_pref_var))
91+
92+
tbl <- build_table(lyt, df = adae, alt_counts_df = alt_counts_df)
93+
if (prune_0) tbl <- prune_table(tbl)
94+
95+
tbl
96+
}

_quarto.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ website:
106106
- quarto/table-templates/template-table_36.qmd
107107
- quarto/figure-templates/template-fig_01.qmd
108108
- quarto/figure-templates/template-fig_02.qmd
109-
109+
- quarto/figure-templates/template-fig_14.qmd
110110
- text: About
111111
file: quarto/about.qmd
112112
- text: Resources

man/argument_convention.Rd

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)