Skip to content
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ cache
*_data
*_files
figures
*.tar.gz
11 changes: 6 additions & 5 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,18 @@ Depends:
gtsummary (>= 2.5.1),
R (>= 4.2)
Imports:
dplyr (>= 1.2.1)
Suggests:
cowplot (>= 1.2.0),
crane (>= 0.3.1),
forcats (>= 1.0.1),
dplyr (>= 1.2.1),
ggplot2 (>= 4.0),
ggsurvfit (>= 1.2.0),
lubridate (>= 1.9),
rlistings
Suggests:
crane (>= 0.3.1),
forcats (>= 1.0.1),
gt (>= 1.3.0),
knitr (>= 1.50),
labelled (>= 2.16.0),
lubridate (>= 1.9),
pharmaverseadam (>= 1.3.0),
purrr (>= 1.2),
random.cdisc.data (>= 0.3.16),
Expand Down
101 changes: 101 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,15 +1,116 @@
# Generated by roxygen2: do not edit by hand

export(make_fig_01)
export(make_fig_02)
export(make_fig_03)
export(make_fig_14)
export(make_table_02)
export(make_table_03)
export(make_table_04)
export(make_table_05)
export(make_table_06)
export(make_table_07)
export(make_table_08)
export(make_table_09)
export(make_table_10)
export(make_table_11)
export(make_table_12)
export(make_table_13)
export(make_table_14)
export(make_table_15)
export(make_table_16)
export(make_table_17)
export(make_table_18)
export(make_table_20)
export(make_table_21)
export(make_table_22)
export(make_table_29)
export(make_table_32)
export(make_table_33)
export(make_table_34)
export(make_table_35)
export(make_table_36)
export(make_table_37)
export(make_table_38)
export(make_table_43)
export(make_table_44)
export(make_table_45)
export(make_table_50)
export(make_table_51)
export(make_table_roche_lbt01)
importFrom(cards,ard_categorical)
importFrom(cards,ard_continuous)
importFrom(cards,ard_hierarchical)
importFrom(cards,ard_stack_hierarchical)
importFrom(cards,ard_tabulate)
importFrom(cards,ard_tabulate_value)
importFrom(cards,bind_ard)
importFrom(cards,tidy_ard_row_order)
importFrom(cardx,ard_continuous_ci)
importFrom(cowplot,get_plot_component)
importFrom(cowplot,plot_grid)
importFrom(dplyr,.data)
importFrom(dplyr,across)
importFrom(dplyr,all_of)
importFrom(dplyr,arrange)
importFrom(dplyr,case_when)
importFrom(dplyr,distinct)
importFrom(dplyr,everything)
importFrom(dplyr,filter)
importFrom(dplyr,group_by)
importFrom(dplyr,left_join)
importFrom(dplyr,mutate)
importFrom(dplyr,rowwise)
importFrom(dplyr,select)
importFrom(dplyr,slice_head)
importFrom(dplyr,slice_max)
importFrom(dplyr,slice_min)
importFrom(dplyr,summarise)
importFrom(dplyr,sym)
importFrom(dplyr,ungroup)
importFrom(ggplot2,aes)
importFrom(ggplot2,annotate)
importFrom(ggplot2,element_blank)
importFrom(ggplot2,element_rect)
importFrom(ggplot2,element_text)
importFrom(ggplot2,geom_errorbar)
importFrom(ggplot2,geom_line)
importFrom(ggplot2,geom_point)
importFrom(ggplot2,ggplot)
importFrom(ggplot2,ggplot_build)
importFrom(ggplot2,labs)
importFrom(ggplot2,position_dodge)
importFrom(ggplot2,scale_color_manual)
importFrom(ggplot2,scale_x_continuous)
importFrom(ggplot2,scale_y_continuous)
importFrom(ggplot2,theme)
importFrom(ggplot2,unit)
importFrom(ggsurvfit,Surv)
importFrom(ggsurvfit,ggsurvfit)
importFrom(ggsurvfit,survfit2)
importFrom(gtsummary,add_overall)
importFrom(gtsummary,all_categorical)
importFrom(gtsummary,all_continuous)
importFrom(gtsummary,all_stat_cols)
importFrom(gtsummary,gather_ard)
importFrom(gtsummary,modify_bold)
importFrom(gtsummary,modify_column_indent)
importFrom(gtsummary,modify_footnote_header)
importFrom(gtsummary,modify_header)
importFrom(gtsummary,modify_indent)
importFrom(gtsummary,modify_post_fmt_fun)
importFrom(gtsummary,modify_spanning_header)
importFrom(gtsummary,modify_table_body)
importFrom(gtsummary,remove_footnote_header)
importFrom(gtsummary,tbl_ard_hierarchical)
importFrom(gtsummary,tbl_ard_summary)
importFrom(gtsummary,tbl_hierarchical)
importFrom(gtsummary,tbl_merge)
importFrom(gtsummary,tbl_stack)
importFrom(gtsummary,tbl_strata_nested_stack)
importFrom(gtsummary,tbl_summary)
importFrom(lubridate,days)
importFrom(lubridate,interval)
importFrom(lubridate,ymd_hms)
importFrom(rlistings,as_listing)
importFrom(tidyr,pivot_wider)
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# cardinal 0.2.0.9001


# cardinal 0.2.0

* Initial release.
* Added `make_table_02()` to produce FDA Table 2 (Baseline Demographic and Clinical Characteristics, Safety Population). The function filters for the safety population, builds a `gtsummary` table with continuous (mean/SD, median/range) and categorical (n/%) summaries by treatment arm, and optionally returns the intermediate ARD (`return_ard = TRUE`).
* Moved `dplyr` from `Suggests` to `Imports` to support `make_table_02()`.
157 changes: 157 additions & 0 deletions R/fda-fig_01.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#' FDA Figure 1: Time to Permanent Discontinuation of Study Drug, Safety Population, Pooled Analyses
#'
#' @description
#' Creates FDA Figure 1 showing time to permanent discontinuation of study drug for the safety
#' population, with an optional "Number of Patients" table below the plot.
#'
#' @param df (`data.frame`)\cr dataset (typically ADSL) required to build the figure.
#' @param arm_var (`character`)\cr name of the treatment arm variable used to split figure into lines.
#' Defaults to `"TRT01A"`.
#' @param id_var (`character`)\cr name of the subject identifier variable. Defaults to `"USUBJID"`.
#' @param saffl_var (`character`)\cr name of the safety flag variable. Defaults to `"SAFFL"`.
#' @param trtsdtm_var (`character`)\cr name of the treatment start datetime variable.
#' Defaults to `"TRTSDTM"`.
#' @param trtedtm_var (`character`)\cr name of the treatment end datetime variable.
#' Defaults to `"TRTEDTM"`.
#' @param u_trtdur (`character`)\cr unit for treatment duration. One of `"days"`, `"weeks"`,
#' `"months"`, or `"years"`. Defaults to `"days"`.
#' @param x_lab (`character`)\cr x-axis label. Defaults to `"Time from first dose (<u_trtdur>)"`.
#' @param y_lab (`character`)\cr y-axis label. Defaults to `"Percent of Patients (%)"`.
#' @param xticks (`numeric` or `NA`)\cr x-axis tick mark positions. Defaults to `NA` (auto).
#' @param ggtheme (`ggplot2::theme` or `NULL`)\cr ggplot2 theme to apply. Defaults to `NULL`.
#' @param add_table (`flag`)\cr whether to add a "Number of Patients" table below the plot.
#' Defaults to `TRUE`.
#' @param annotations (named `list` of `character`)\cr list of annotations to add to the figure.
#' Valid types are `title`, `subtitles`, and `caption`.
#'
#' @details
#' * `df` must contain the variables specified by `arm_var`, `id_var`, `saffl_var`, `trtsdtm_var`,
#' and `trtedtm_var`.
#' * Flag variables (i.e. `XXXFL`) are expected to have two levels: `"Y"` (true) and `"N"` (false).
#' Missing values in flag variables are treated as `"N"`.
#' * Records with missing treatment start and/or end datetime are excluded from all calculations.
#' * Values in the "Number of Patients" table are the number of patients with treatment duration
#' equal to or greater than the given time.
#'
#' @return A `ggplot2` object.
#'
#' @examplesIf requireNamespace("pharmaverseadam", quietly = TRUE)
#' library(dplyr)
#'
#' adsl <- pharmaverseadam::adsl |>
#' filter(TRT01A != "Screen Failure")
#'
#' fig <- make_fig_01(df = adsl)
#' fig
#'
#' @importFrom dplyr filter mutate select distinct arrange all_of
#' @importFrom lubridate interval ymd_hms
#' @importFrom ggplot2 ggplot aes geom_line labs theme element_blank
#' @importFrom ggplot2 scale_x_continuous ggplot_build annotate unit element_rect
#' @importFrom cowplot get_plot_component plot_grid
#' @export
make_fig_01 <- function(df,
arm_var = "TRT01A",
id_var = "USUBJID",
saffl_var = "SAFFL",
trtsdtm_var = "TRTSDTM",
trtedtm_var = "TRTEDTM",
u_trtdur = "days",
x_lab = paste0("Time from first dose (", u_trtdur, ")"),
y_lab = "Percent of Patients (%)",
xticks = NA,
ggtheme = NULL,
add_table = TRUE,
annotations = NULL) {
stopifnot(is.data.frame(df))
stopifnot(all(c(arm_var, id_var, saffl_var, trtsdtm_var, trtedtm_var) %in% names(df)))
stopifnot(u_trtdur %in% c("days", "weeks", "months", "years"))

df <- df |>
dplyr::filter(.data[[saffl_var]] == "Y") |>
dplyr::mutate(
TRTDUR = as.numeric(
lubridate::interval(
lubridate::ymd_hms(.data[[trtsdtm_var]]),
lubridate::ymd_hms(.data[[trtedtm_var]])
),
u_trtdur
)
) |>
dplyr::filter(!is.na(TRTDUR)) |>
dplyr::select(dplyr::all_of(c(id_var, arm_var)), TRTDUR) |>
dplyr::distinct() |>
dplyr::arrange(desc(TRTDUR))

df$PT_PCT <- seq_len(nrow(df)) / nrow(df) * 100
max_time <- max(df$TRTDUR)

g <- ggplot2::ggplot(
data = df,
ggplot2::aes(x = TRTDUR, y = PT_PCT, group = .data[[arm_var]], color = .data[[arm_var]])
) +
ggplot2::geom_line() +
ggplot2::labs(
title = annotations[["title"]],
subtitle = annotations[["subtitles"]],
caption = annotations[["caption"]],
x = x_lab,
y = y_lab
) +
ggplot2::theme(
legend.position = "bottom",
legend.title = ggplot2::element_blank(),
plot.margin = ggplot2::unit(c(0.05, 0.05, 0, 0.025), "npc")
)

if (any(!is.na(xticks))) {
g <- g + ggplot2::scale_x_continuous(
breaks = xticks,
limits = c(min(xticks), max(c(xticks, max_time)))
)
}

if (!is.null(ggtheme)) g <- g + ggtheme

if (add_table) {
legend_pos <- paste0("guide-box-", ifelse(is.null(ggtheme), "bottom", ggtheme$legend.position))
g_legend <- cowplot::get_plot_component(g, legend_pos, return_all = TRUE)

g <- g + ggplot2::theme(legend.position = "none")

xtick_lbls <- ggplot2::ggplot_build(g)$layout$panel_params[[1]]$x$breaks
xtick_lbls <- xtick_lbls[!is.na(xtick_lbls)]
xlims <- ggplot2::ggplot_build(g)$layout$panel_params[[1]]$x$limits

tbl_n <- expand.grid(x = xtick_lbls, arm = rev(levels(df[[arm_var]])), n = 0)

g_tbl <- ggplot2::ggplot(tbl_n, ggplot2::aes(x = x, y = arm)) +
ggplot2::theme(
axis.title.x = ggplot2::element_blank(),
axis.title.y = ggplot2::element_blank(),
axis.ticks.x = ggplot2::element_blank(),
axis.ticks.y = ggplot2::element_blank(),
panel.background = ggplot2::element_blank(),
axis.text.x = ggplot2::element_blank(),
panel.border = ggplot2::element_rect(color = "black", fill = NA, linewidth = 0.5),
plot.margin = ggplot2::unit(c(0.1, 0.05, 0, 0.025), "npc")
) +
ggplot2::labs(title = "Number of Patients") +
ggplot2::scale_x_continuous(
breaks = xtick_lbls,
limits = c(min(xlims, xtick_lbls), max(xlims, xtick_lbls))
)

for (i in seq_len(nrow(tbl_n))) {
tbl_n$n[i] <- sum(df[[arm_var]] == tbl_n$arm[i] & df$TRTDUR >= tbl_n$x[i])
g_tbl <- g_tbl +
ggplot2::annotate("text", label = as.character(tbl_n$n[i]), x = tbl_n$x[i], y = tbl_n$arm[i])
}

if (!is.null(ggtheme)) g_tbl <- g_tbl + ggtheme

cowplot::plot_grid(g, g_tbl, g_legend, align = "v", axis = "l", ncol = 1, rel_heights = c(0.7, 0.25, 0.1))
} else {
g
}
}
Loading
Loading