| 1 |
#' [EXPERIMENTAL] Create new output function based on a template. |
|
| 2 |
#' |
|
| 3 |
#' We have separate templates for listings, tables, and graphs. |
|
| 4 |
#' There is also a template to set up the `run_all` script. |
|
| 5 |
#' |
|
| 6 |
#' @param template Must be one of `list_all_templates(package = "autoslider.core")`. |
|
| 7 |
#' @param function_name Name of the output function you want to create. Defaults to "default". |
|
| 8 |
#' @param save_path Path to save the function. Defaults to "./programs/R". |
|
| 9 |
#' @param overwrite Whether to overwrite an existing file. |
|
| 10 |
#' @param open Whether to open the file after creation. |
|
| 11 |
#' @param package Which package to search for the template file. Defaults to "autoslider.core". |
|
| 12 |
#' |
|
| 13 |
#' @return No return value. Called for side effects (writes a file). |
|
| 14 |
#' |
|
| 15 |
#' @details Use `list_all_templates(package = "autoslider.core")` to discover which templates are available. |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
#' |
|
| 19 |
#' @examples |
|
| 20 |
#' if (interactive()) {
|
|
| 21 |
#' use_template("t_dm_slide", function_name = "my_table", package = "autoslider.core")
|
|
| 22 |
#' } |
|
| 23 |
use_template <- function(template = "t_dm_slide", |
|
| 24 |
function_name = "default", |
|
| 25 |
save_path = "./programs/R", |
|
| 26 |
overwrite = FALSE, |
|
| 27 |
open = interactive(), |
|
| 28 |
package = "autoslider.core") {
|
|
| 29 | 40x |
assert_that(assertthat::is.string(template)) |
| 30 | 40x |
assert_that(assertthat::is.string(package)) |
| 31 | 40x |
assert_that(assertthat::is.flag(overwrite)) |
| 32 | 40x |
assert_that(assertthat::is.flag(open)) |
| 33 | 40x |
assert_that(!is.null(save_path)) |
| 34 |
# assert_that(template %in% list_all_templates(package) || |
|
| 35 |
# paste0(system.file("templates", package = package), "/", template) %in%
|
|
| 36 |
# list_all_templates(package)) |
|
| 37 | ||
| 38 | 40x |
if (!dir.exists(save_path)) {
|
| 39 | 2x |
dir.create(save_path, recursive = TRUE) |
| 40 |
} |
|
| 41 | ||
| 42 | 40x |
save_path <- file.path(save_path, paste0(function_name, ".R")) |
| 43 | ||
| 44 |
# Original validation logic for when save_path is provided |
|
| 45 | 40x |
assertthat::has_extension(save_path, ext = "R") |
| 46 | 40x |
assertthat::is.writeable(save_path %>% dirname()) |
| 47 | ||
| 48 | ||
| 49 |
# Build expected full path |
|
| 50 | 40x |
expected_path <- file.path(system.file("templates", package = package), template)
|
| 51 | 40x |
expected_core_path <- file.path(system.file("templates", package = "autoslider.core"), template)
|
| 52 | ||
| 53 |
# Validation logic |
|
| 54 | 40x |
valid <- FALSE |
| 55 | 40x |
if (package == "autoslider.core") {
|
| 56 | 38x |
valid <- expected_path %in% list_all_templates(package) |
| 57 | 2x |
} else if (package == "autoslideR") {
|
| 58 | 2x |
valid <- (expected_path %in% list_all_templates(package)) || (expected_core_path %in% list_all_templates(package)) |
| 59 |
} |
|
| 60 | ||
| 61 |
# Error if invalid |
|
| 62 | 40x |
if (!valid) {
|
| 63 | 3x |
err_msg <- sprintf( |
| 64 | 3x |
"Template '%s' not found in package '%s'. Use list_all_templates('%s') to see available templates.",
|
| 65 | 3x |
template, package, package |
| 66 |
) |
|
| 67 | 3x |
abort(err_msg) |
| 68 |
} |
|
| 69 | ||
| 70 | ||
| 71 | 37x |
if (file.exists(save_path) && !overwrite) {
|
| 72 | 1x |
err_msg <- paste( |
| 73 | 1x |
sprintf("A file named '%s' already exists.", save_path),
|
| 74 | 1x |
"\u2139 Set `overwrite = TRUE` to force overwriting it.", |
| 75 | 1x |
sep = "\n" |
| 76 |
) |
|
| 77 | 1x |
abort(err_msg) |
| 78 |
} |
|
| 79 | ||
| 80 | 36x |
if (package == "autoslider.core") {
|
| 81 | 35x |
file_list <- get_template_filepath(package = package, full.names = TRUE) |
| 82 | 1x |
} else if (package == "autoslideR") {
|
| 83 | 1x |
file_list <- c( |
| 84 | 1x |
get_template_filepath(package = "autoslideR", full.names = TRUE), |
| 85 | 1x |
get_template_filepath(package = "autoslider.core", full.names = TRUE) |
| 86 |
) |
|
| 87 |
} |
|
| 88 | ||
| 89 | 36x |
template_file <- file_list[basename(file_list) == paste0(template, ".R")] |
| 90 | ||
| 91 | ||
| 92 | 36x |
if (file.copy(template_file, save_path, overwrite = TRUE)) {
|
| 93 | 36x |
rlang::inform(sprintf("\u2713 File '%s' has been created successfully", save_path))
|
| 94 | 36x |
file_lines <- readLines(save_path) |
| 95 | ||
| 96 | 36x |
file_lines <- file_lines[!grepl("^#'", file_lines)]
|
| 97 | 36x |
file_lines <- file_lines[nzchar(file_lines)] |
| 98 | ||
| 99 |
# Replace function name with numbering |
|
| 100 | 36x |
file_lines <- gsub(tolower(template), function_name, file_lines) |
| 101 | 36x |
writeLines(file_lines, save_path) |
| 102 |
} |
|
| 103 | ||
| 104 | 36x |
if (open) {
|
| 105 | ! |
file.edit(save_path) |
| 106 |
} |
|
| 107 | ||
| 108 | 36x |
invisible(TRUE) |
| 109 |
} |
|
| 110 | ||
| 111 |
#' [EXPERIMENTAL] List All Available Templates |
|
| 112 |
#' |
|
| 113 |
#' @param package Which package to search for the template files. Defaults to "autoslider.core". |
|
| 114 |
#' |
|
| 115 |
#' @return A character vector of available template names in the specified package. |
|
| 116 |
#' |
|
| 117 |
#' @export |
|
| 118 |
#' |
|
| 119 |
#' @examples |
|
| 120 |
#' list_all_templates(package = "autoslider.core") |
|
| 121 |
list_all_templates <- function(package = "autoslider.core") {
|
|
| 122 | 45x |
if (package == "autoslideR") {
|
| 123 | 4x |
c( |
| 124 | 4x |
get_template_filepath(package = "autoslideR", full.names = TRUE), |
| 125 | 4x |
get_template_filepath(package = "autoslider.core", full.names = TRUE) |
| 126 |
) |> |
|
| 127 | 4x |
stringr::str_remove("\\.R$") |>
|
| 128 | 4x |
structure(package = package) |
| 129 | 41x |
} else if (package == "autoslider.core") {
|
| 130 | 41x |
get_template_filepath(package = package, full.names = TRUE) |> |
| 131 | 41x |
stringr::str_remove("\\.R$") |>
|
| 132 | 41x |
structure(package = package) |
| 133 |
} |
|
| 134 |
} |
|
| 135 | ||
| 136 | ||
| 137 |
#' Retrieve Template File Paths |
|
| 138 |
#' |
|
| 139 |
#' @param package A character string specifying the name of the package to search. |
|
| 140 |
#' @param full.names If `TRUE`, returns the full path to each file. |
|
| 141 |
#' If `FALSE`, returns only the file names. |
|
| 142 |
#' |
|
| 143 |
#' @return A character vector of template file names or paths, depending on `full.names`. |
|
| 144 |
#' |
|
| 145 |
#' @export |
|
| 146 |
#' |
|
| 147 |
#' @keywords internal |
|
| 148 |
get_template_filepath <- function(package = "autoslider.core", full.names = FALSE) {
|
|
| 149 |
# Installed-package path |
|
| 150 | 86x |
template_dir <- system.file("templates", package = package)
|
| 151 | ||
| 152 | 86x |
pattern <- "^(t_|l_|g_)" |
| 153 | 86x |
if (full.names == TRUE) {
|
| 154 | 86x |
pattern <- paste0(paste0(template_dir, "/"), c("t_", "g_", "l_"),
|
| 155 | 86x |
collapse = "|" |
| 156 |
) |
|
| 157 |
} |
|
| 158 | ||
| 159 | 86x |
list.files(template_dir, pattern = "\\.R$", full.names = full.names) |> |
| 160 | 86x |
stringr::str_subset(pattern) |
| 161 |
} |
| 1 |
#' Read yaml spec file |
|
| 2 |
#' |
|
| 3 |
#' Read yaml spec file and split according to filter lists |
|
| 4 |
#' |
|
| 5 |
#' @param spec_file `character`. Path to a yaml spec file |
|
| 6 |
#' @param metadata Metadata of study |
|
| 7 |
#' |
|
| 8 |
#' @return |
|
| 9 |
#' An object of class `spec` which is a `list` where each element corresponds |
|
| 10 |
#' to one output, e.g. `t_dm_IT`. |
|
| 11 |
#' |
|
| 12 |
#' @author |
|
| 13 |
#' - Liming Li (`Lil128`) |
|
| 14 |
#' - Thomas Neitmann (`neitmant`) |
|
| 15 |
#' - Joe Zhu |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
#' |
|
| 19 |
#' @examples |
|
| 20 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 21 |
#' |
|
| 22 |
#' ## Take a look at the 'raw' content of the spec file |
|
| 23 |
#' cat(readLines(spec_file)[1:24], sep = "\n") |
|
| 24 |
#' |
|
| 25 |
#' ## This is how it looks once read into R |
|
| 26 |
#' spec <- read_spec(spec_file) |
|
| 27 |
#' spec[1:3] |
|
| 28 |
#' |
|
| 29 |
read_spec <- function(spec_file = "spec.yml", |
|
| 30 |
metadata = NULL) {
|
|
| 31 | 4x |
spec <- yaml::read_yaml(spec_file, eval.expr = TRUE) |
| 32 | 4x |
ret <- lapply(spec, function(s) {
|
| 33 | 156x |
lapply(s$suffix, function(su) {
|
| 34 | 156x |
ret <- s |
| 35 | 156x |
ret$suffix <- su |
| 36 | 156x |
c(ret, metadata) |
| 37 |
}) |
|
| 38 |
}) |
|
| 39 | 4x |
spec_obj <- unlist(ret, recursive = FALSE) |
| 40 | 4x |
as_spec(spec_obj) |
| 41 |
} |
|
| 42 | ||
| 43 |
#' validate spec file |
|
| 44 |
#' @description not implemented yet |
|
| 45 |
#' @param spec specification |
|
| 46 |
#' @noRd |
|
| 47 |
validate_spec <- function(spec) {
|
|
| 48 | ! |
message <- NULL |
| 49 | ! |
if (is.null(spec$dataset)) {
|
| 50 | ! |
message <- c(message, "Spec must not assign dataset argument!") |
| 51 |
} |
|
| 52 | ! |
if (is.null(spec$func)) {
|
| 53 | ! |
message <- c(message, "Spec must include func argument!") |
| 54 |
} |
|
| 55 | ! |
if (is.null(spec$outpath)) {
|
| 56 | ! |
message <- c(message, "Spec must include outpath argument!") |
| 57 |
} |
|
| 58 |
} |
|
| 59 | ||
| 60 |
#' Filter a spec object |
|
| 61 |
#' |
|
| 62 |
#' @param spec A `spec` object as returned by `read_spec()` |
|
| 63 |
#' @param filter_expr A `logical` expression indicating outputs to keep |
|
| 64 |
#' @param verbose Should a message about the number of outputs matching |
|
| 65 |
#' `filter_spec` be printed? Defaults to `TRUE`. |
|
| 66 |
#' |
|
| 67 |
#' @return |
|
| 68 |
#' A `spec` object containing only the outputs matching `filter_expr` |
|
| 69 |
#' |
|
| 70 |
#' @author Thomas Neitmann (`neitmant`) |
|
| 71 |
#' |
|
| 72 |
#' @export |
|
| 73 |
#' |
|
| 74 |
#' @examples |
|
| 75 |
#' library(dplyr) |
|
| 76 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 77 |
#' spec <- spec_file %>% read_spec() |
|
| 78 |
#' |
|
| 79 |
#' ## Keep only the t_dm_IT output |
|
| 80 |
#' filter_spec(spec, output == "t_dm_IT") |
|
| 81 |
#' |
|
| 82 |
#' ## Same as above but more verbose |
|
| 83 |
#' filter_spec(spec, program == "t_dm" && suffix == "IT") |
|
| 84 |
#' |
|
| 85 |
#' ## Keep all t_ae outputs |
|
| 86 |
#' filter_spec(spec, program == "t_ae") |
|
| 87 |
#' |
|
| 88 |
#' ## Keep all output run on safety population |
|
| 89 |
#' filter_spec(spec, "SE" %in% suffix) |
|
| 90 |
#' |
|
| 91 |
#' ## Keep t_dm_CHN_IT and t_dm_CHN_SE |
|
| 92 |
#' filter_spec(spec, program == "t_dm" && suffix %in% c("CHN_IT", "CHN_SE"))
|
|
| 93 |
#' |
|
| 94 |
#' ## Keep all tables |
|
| 95 |
#' filter_spec(spec, grepl("^t_", program))
|
|
| 96 |
#' |
|
| 97 |
filter_spec <- function(spec, filter_expr, verbose = TRUE) {
|
|
| 98 | 3x |
if (is.character(substitute(filter_expr))) {
|
| 99 | ! |
warn_about_legacy_filtering(filter_expr) |
| 100 | ! |
condition <- bquote(output == .(filter_expr)) |
| 101 |
} else {
|
|
| 102 | 3x |
condition <- substitute(filter_expr) |
| 103 |
} |
|
| 104 | 3x |
stopifnot(is_spec(spec), is.language(condition), is.logical(verbose)) |
| 105 | 3x |
vars <- all.vars(condition) |
| 106 | ||
| 107 | 3x |
filtered_spec <- Filter(function(output) {
|
| 108 | 105x |
assert_exists_in_spec_or_calling_env(vars, output) |
| 109 | 105x |
p <- eval(condition, envir = output) |
| 110 | 105x |
assert_is_valid_filter_result(p) |
| 111 | 105x |
p |
| 112 | 3x |
}, spec) |
| 113 | ||
| 114 | 3x |
if (verbose) {
|
| 115 | 3x |
log_number_of_matched_records(spec, filtered_spec, condition) |
| 116 |
} |
|
| 117 | ||
| 118 | 3x |
as_spec(filtered_spec) |
| 119 |
} |
|
| 120 | ||
| 121 |
is_spec <- function(x) {
|
|
| 122 | 3x |
"spec" %in% class(x) |
| 123 |
} |
|
| 124 | ||
| 125 |
as_spec <- function(x) {
|
|
| 126 | 7x |
spec <- lapply(x, function(elem) {
|
| 127 | 176x |
if (is.null(elem$suffix)) {
|
| 128 | ! |
elem$suffix <- "" |
| 129 |
} |
|
| 130 | ||
| 131 | 176x |
if (elem$suffix == "") {
|
| 132 | ! |
elem$output <- elem$program |
| 133 |
} else {
|
|
| 134 | 176x |
elem$output <- paste(elem$program, elem$suffix, sep = "_") |
| 135 |
} |
|
| 136 | ||
| 137 | 176x |
if (is.null(elem$paper)) {
|
| 138 | 12x |
elem$paper <- default_paper_size(elem$program) |
| 139 | 164x |
} else if (elem$paper == "a4r") {
|
| 140 | ! |
warn_about_legacy_paper_size("a4r", "L11")
|
| 141 | ! |
elem$paper <- "L11" |
| 142 | 164x |
} else if (elem$paper == "a4") {
|
| 143 | ! |
warn_about_legacy_paper_size("a4", "P11")
|
| 144 | ! |
elem$paper <- "P11" |
| 145 |
} else {
|
|
| 146 | 164x |
validate_paper_size(elem$paper) |
| 147 |
} |
|
| 148 | ||
| 149 | 176x |
elem |
| 150 |
}) |
|
| 151 | ||
| 152 | 7x |
structure( |
| 153 | 7x |
.Data = spec, |
| 154 | 7x |
names = map_chr(spec, `[[`, "output"), |
| 155 | 7x |
class = union("spec", class(x))
|
| 156 |
) |
|
| 157 |
} |
| 1 |
format_xx <- function(str) {
|
|
| 2 | 2x |
tern::format_xx(str) |
| 3 |
} |
|
| 4 | ||
| 5 |
#' Assert function to check the cutoff |
|
| 6 |
#' |
|
| 7 |
#' @param data dataframe |
|
| 8 |
#' @param cutoff cutoff threshold |
|
| 9 |
#' @return Set the cutoff value |
|
| 10 |
#' @export |
|
| 11 |
check_and_set_cutoff <- function(data, cutoff) {
|
|
| 12 | 27x |
if (is.na(cutoff)) {
|
| 13 | 5x |
cutoff <- 0 |
| 14 |
} else { # check cutoff is the same with the filter
|
|
| 15 | 22x |
suffix <- attr(data, "filters") |
| 16 | 22x |
cutoff_suffix <- str_extract(string = paste(suffix, collapse = "_"), pattern = "(\\d+)(?=PER)") %>% |
| 17 | 22x |
as.numeric() |
| 18 | 22x |
if (!is.na(cutoff_suffix)) {
|
| 19 | 4x |
assert_that(are_equal(cutoff, cutoff_suffix)) |
| 20 |
} |
|
| 21 |
} |
|
| 22 | ||
| 23 | 27x |
cutoff |
| 24 |
} |
|
| 25 | ||
| 26 |
#' Replace NAs to NA |
|
| 27 |
#' |
|
| 28 |
#' @param table_df Table dataframe |
|
| 29 |
#' @return Input dataframe with both column replaced to NA |
|
| 30 |
#' @export |
|
| 31 |
na_replace <- function(table_df) {
|
|
| 32 | 2x |
if (length(colnames(table_df)) == 2) {
|
| 33 | 2x |
col1_na <- which(is.na(table_df[1])) |
| 34 | 2x |
if (length(col1_na) > 0) {
|
| 35 | 1x |
for (i in 1:length(col1_na)) {
|
| 36 | 1x |
table_df[col1_na[i], 1] <- table_df[col1_na[i], 2] |
| 37 | 1x |
table_df[col1_na[i], 2] <- NA |
| 38 |
} |
|
| 39 |
} |
|
| 40 |
} |
|
| 41 | ||
| 42 | 2x |
table_df |
| 43 |
} |
|
| 44 | ||
| 45 |
#' Concatenate arguments into a string |
|
| 46 |
#' |
|
| 47 |
#' @param ... arguments passed to program |
|
| 48 |
#' @return No return value, called for side effects |
|
| 49 |
#' @export |
|
| 50 |
dec_paste <- function(...) {
|
|
| 51 | 2x |
arguments <- list( |
| 52 |
... |
|
| 53 |
) |
|
| 54 | ||
| 55 | 2x |
if (!any(is.na(arguments))) {
|
| 56 | 2x |
do.call("paste", arguments)
|
| 57 |
} |
|
| 58 |
} |
|
| 59 | ||
| 60 |
#' Convert list of numbers to vectors |
|
| 61 |
#' |
|
| 62 |
#' @param num_list list of numbers |
|
| 63 |
#' @return No return value, called for side effects |
|
| 64 |
#' @export |
|
| 65 |
to_vector <- function(num_list) {
|
|
| 66 | 1x |
sapply(num_list, function(x) {
|
| 67 | 2x |
y <- unlist(x) |
| 68 | 2x |
if (is.null(y)) {
|
| 69 | ! |
y <- NA |
| 70 |
} |
|
| 71 | 2x |
y |
| 72 |
}) |
|
| 73 |
} |
|
| 74 | ||
| 75 |
#' Founding method |
|
| 76 |
#' @param x number need to be rounded |
|
| 77 |
#' @param digits number of digits |
|
| 78 |
#' @return rounded value |
|
| 79 |
#' @export |
|
| 80 |
new_round <- function(x, digits = 1) {
|
|
| 81 | 22157x |
posneg <- sign(x) |
| 82 | 22157x |
z <- abs(x) * 10^digits |
| 83 | 22157x |
z <- z + 0.5 + sqrt(.Machine$double.eps) |
| 84 | 22157x |
z <- trunc(z) |
| 85 | 22157x |
z <- z / 10^digits |
| 86 | 22157x |
z * posneg |
| 87 |
} |
|
| 88 | ||
| 89 |
#' Format of xx.xx (xx.xx) |
|
| 90 |
#' |
|
| 91 |
#' @param x input array |
|
| 92 |
#' @param output output handle |
|
| 93 |
#' @return formatted values |
|
| 94 |
#' @export |
|
| 95 |
trim_perc1 <- function(x, output) {
|
|
| 96 | 149x |
paste0(x[1], " (", new_round(x[2] * 100, 1), ")")
|
| 97 |
} |
|
| 98 | ||
| 99 |
#' Format of xx.xx (xx.x) |
|
| 100 |
#' |
|
| 101 |
#' @param x input array |
|
| 102 |
#' @param output output handle |
|
| 103 |
#' @return formatted values |
|
| 104 |
#' @export |
|
| 105 |
trim_perc <- function(x, output) {
|
|
| 106 | 1x |
paste0(x[1], " (", new_round(x[2] * 100, 2), ")")
|
| 107 |
} |
|
| 108 | ||
| 109 |
#' Format of (xx\%, xx\%) |
|
| 110 |
#' |
|
| 111 |
#' @param x input array |
|
| 112 |
#' @param output output handle |
|
| 113 |
#' @return formatted values |
|
| 114 |
#' @export |
|
| 115 |
perc_perc <- function(x, output) {
|
|
| 116 | 1x |
paste0(new_round(x[1] * 100, 0), "% (", new_round(x[2] * 100, 0), "%)")
|
| 117 |
} |
|
| 118 | ||
| 119 |
#' Format of xx.xx (xx.xx, xx.xx) |
|
| 120 |
#' |
|
| 121 |
#' @param x input array |
|
| 122 |
#' @param output output handle |
|
| 123 |
#' @return formatted values |
|
| 124 |
#' @export |
|
| 125 |
format_3d <- function(x, output) {
|
|
| 126 | 1x |
paste0(new_round(x[1], 2), " (", new_round(x[2], 2), ", ", new_round(x[3], 2), ")")
|
| 127 |
} |
|
| 128 | ||
| 129 | ||
| 130 |
#' survival time afun |
|
| 131 |
#' |
|
| 132 |
#' @param df data |
|
| 133 |
#' @param .var variable of interest |
|
| 134 |
#' @param is_event vector indicating event |
|
| 135 |
#' @param control `control_surv_time()` by default |
|
| 136 |
#' @return A function suitable for use in rtables::analyze() with element selection, |
|
| 137 |
#' reformatting, and relabeling performed automatically. |
|
| 138 |
#' @export |
|
| 139 |
s_surv_time_1 <- function(df, .var, is_event, control = control_surv_time()) {
|
|
| 140 |
# assert_that(is_df_with_variables(df, list(tte = .var, is_event = is_event)), |
|
| 141 |
# is.string(.var), is_numeric_vector(df[[.var]]), is_logical_vector(df[[is_event]])) |
|
| 142 | ||
| 143 | 6x |
conf_type <- control$conf_type |
| 144 | 6x |
conf_level <- control$conf_level |
| 145 | 6x |
quantiles <- control$quantiles |
| 146 | 6x |
formula <- as.formula(paste0( |
| 147 | 6x |
"Surv(", .var, ", ", is_event,
|
| 148 | 6x |
") ~ 1" |
| 149 |
)) |
|
| 150 | 6x |
srv_fit <- survfit( |
| 151 | 6x |
formula = formula, data = df, conf.int = conf_level, |
| 152 | 6x |
conf.type = conf_type |
| 153 |
) |
|
| 154 | 6x |
srv_tab <- summary(srv_fit, extend = TRUE)$table |
| 155 |
# srv_qt_tab <- quantile(srv_fit, probs = quantiles)$quantile |
|
| 156 |
# range_censor <- range_noinf(df[[.var]][!df[[is_event]]], |
|
| 157 |
# na.rm = TRUE) |
|
| 158 |
# range_event <- range_noinf(df[[.var]][df[[is_event]]], na.rm = TRUE) |
|
| 159 |
# range <- range_noinf(df[[.var]], na.rm = TRUE) |
|
| 160 | 6x |
new_label <- paste0("Median (Months, ", conf_level * 100, "% CI)")
|
| 161 | ||
| 162 | 6x |
list( |
| 163 | 6x |
median_ci = formatters::with_label(c( |
| 164 | 6x |
unname(srv_tab["median"]), |
| 165 | 6x |
unname(srv_tab[paste0(srv_fit$conf.int, c("LCL", "UCL"))])
|
| 166 | 6x |
), new_label) |
| 167 |
) |
|
| 168 |
} |
|
| 169 | ||
| 170 | ||
| 171 |
s_coxph_pairwise_1 <- function(df, .ref_group, .in_ref_col, .var, is_event, strat = NULL, |
|
| 172 |
control = control_coxph()) {
|
|
| 173 |
# assert_that(is_df_with_variables(df, list(tte = .var, is_event = is_event)), |
|
| 174 |
# is.string(.var), is_numeric_vector(df[[.var]]), is_logical_vector(df[[is_event]])) |
|
| 175 | ! |
pval_method <- control$pval_method |
| 176 | ! |
ties <- control$ties |
| 177 | ! |
conf_level <- control$conf_level |
| 178 | ||
| 179 | ! |
strat_type <- ifelse(is.null(strat), "Unstratified", "Stratified") |
| 180 | ! |
if (.in_ref_col) {
|
| 181 | ! |
return( |
| 182 | ! |
in_rows( |
| 183 | ! |
rcell(""),
|
| 184 | ! |
rcell(""),
|
| 185 | ! |
.labels = c(paste0(strat_type, " HR (", conf_level * 100, "% CI)"), paste0("p-value (", pval_method, ")"))
|
| 186 |
) |
|
| 187 |
# list(hr_ci = formatters::with_label("", paste0("Stratified HR (", conf_level*100, "% CI)")),
|
|
| 188 |
# pvalue = formatters::with_label("", paste0("p-value (", pval_method, ")"))
|
|
| 189 |
# ) |
|
| 190 |
) |
|
| 191 |
} |
|
| 192 | ! |
data <- rbind(.ref_group, df) |
| 193 | ! |
group <- factor(rep(c("ref", "x"), c(nrow(.ref_group), nrow(df))),
|
| 194 | ! |
levels = c("ref", "x")
|
| 195 |
) |
|
| 196 | ! |
df_cox <- data.frame( |
| 197 | ! |
tte = data[[.var]], is_event = data[[is_event]], |
| 198 | ! |
arm = group |
| 199 |
) |
|
| 200 | ! |
if (is.null(strat)) {
|
| 201 | ! |
formula_cox <- Surv(tte, is_event) ~ arm |
| 202 |
} else {
|
|
| 203 | ! |
formula_cox <- as.formula(paste0( |
| 204 | ! |
"Surv(tte, is_event) ~ arm + strata(",
|
| 205 | ! |
paste(strat, collapse = ","), ")" |
| 206 |
)) |
|
| 207 | ! |
df_cox <- cbind(df_cox, data[strat]) |
| 208 |
} |
|
| 209 | ! |
cox_fit <- coxph(formula = formula_cox, data = df_cox, ties = ties) |
| 210 | ! |
sum_cox <- summary(cox_fit, conf.int = conf_level, extend = TRUE) |
| 211 | ! |
pval <- switch(pval_method, |
| 212 | ! |
wald = sum_cox$waldtest["pvalue"], |
| 213 | ! |
`log-rank` = sum_cox$sctest["pvalue"], |
| 214 | ! |
likelihood = sum_cox$logtest["pvalue"] |
| 215 |
) |
|
| 216 | ! |
list( |
| 217 |
# hr = formatters::with_label(sum_cox$conf.int[1, 1], "Hazard Ratio"), |
|
| 218 |
# hr_ci = formatters::with_label(unname(sum_cox$conf.int[1, 3:4]), f_conf_level(conf_level)), |
|
| 219 | ! |
hr_ci = formatters::with_label( |
| 220 | ! |
c(sum_cox$conf.int[1, 1], unname(sum_cox$conf.int[1, 3:4])), |
| 221 | ! |
paste0("Stratified HR (", conf_level * 100, "% CI)")
|
| 222 |
), |
|
| 223 | ! |
pvalue = formatters::with_label(unname(pval), paste0("p-value (", pval_method, ")"))
|
| 224 |
) |
|
| 225 | ||
| 226 | ! |
in_rows( |
| 227 | ! |
rcell(c(sum_cox$conf.int[1, 1], unname(sum_cox$conf.int[1, 3:4])), format = format_3d), |
| 228 | ! |
rcell(unname(pval), format = "x.xxxx | (<0.0001)"), |
| 229 | ! |
.labels = c(paste0("Stratified HR (", conf_level * 100, "% CI)"), paste0("p-value (", pval_method, ")"))
|
| 230 |
) |
|
| 231 |
} |
|
| 232 | ||
| 233 |
is_in_repository <- function() {
|
|
| 234 | 29x |
system("git status", ignore.stdout = TRUE, ignore.stderr = TRUE) == 0
|
| 235 |
} |
|
| 236 | ||
| 237 |
get_remote_url <- function() {
|
|
| 238 | 1x |
repos <- system("git remote -v", intern = TRUE)
|
| 239 | 1x |
str_extract(repos, "(https://|git@).*.git") |
| 240 |
} |
|
| 241 | ||
| 242 |
get_last_gitcommit_sha <- function() {
|
|
| 243 | 1x |
system("git rev-parse HEAD", intern = TRUE)
|
| 244 |
} |
|
| 245 | ||
| 246 |
get_repo_head_name <- function() {
|
|
| 247 | 1x |
system("git rev-parse --abbrev-ref HEAD", intern = TRUE)
|
| 248 |
} |
|
| 249 | ||
| 250 |
warn <- function(...) {
|
|
| 251 | 4x |
warning(..., call. = FALSE, immediate. = TRUE) |
| 252 |
} |
|
| 253 | ||
| 254 |
git_footnote <- function(for_test = FALSE) {
|
|
| 255 | 29x |
if (is_in_repository()) {
|
| 256 | ! |
remote_url <- get_remote_url()[1] |
| 257 | ! |
if (grepl("^https", remote_url)) {
|
| 258 | ! |
https_url <- gsub("\\.git$", "", remote_url)
|
| 259 |
} else {
|
|
| 260 | ! |
https_url <- gsub("^git@", "https://", gsub(":", "/", remote_url))
|
| 261 |
} |
|
| 262 | ||
| 263 | ! |
repo <- paste("GitHub repository:", https_url)
|
| 264 | ! |
commit <- paste( |
| 265 | ! |
"Git hash:", |
| 266 | ! |
get_last_gitcommit_sha() |
| 267 |
) |
|
| 268 | ! |
ret <- c(repo, commit) |
| 269 |
} else {
|
|
| 270 | 29x |
ret <- NULL |
| 271 |
} |
|
| 272 | ||
| 273 | 29x |
if (for_test == TRUE) {
|
| 274 | 11x |
ret <- NULL |
| 275 |
} |
|
| 276 | ||
| 277 | 29x |
ret |
| 278 |
} |
|
| 279 | ||
| 280 |
datetime <- function() {
|
|
| 281 |
# eICE like format, e.g. 23SEP2020 12:40 |
|
| 282 | 3x |
toupper(format(Sys.time(), "%d%b%Y %H:%M")) |
| 283 |
} |
|
| 284 | ||
| 285 |
enumerate <- function(x, quote = "`") {
|
|
| 286 | 3x |
n <- length(x) |
| 287 | 3x |
if (n == 1L) {
|
| 288 | 2x |
paste0(quote, x, quote) |
| 289 |
} else {
|
|
| 290 | 1x |
paste( |
| 291 | 1x |
paste(paste0(quote, x[-n], quote), collapse = ", "), |
| 292 | 1x |
paste("and", paste0(quote, x[n], quote))
|
| 293 |
) |
|
| 294 |
} |
|
| 295 |
} |
|
| 296 | ||
| 297 |
map_lgl <- function(x, f, ...) {
|
|
| 298 | 109x |
vapply(x, f, logical(1L), ..., USE.NAMES = FALSE) |
| 299 |
} |
|
| 300 | ||
| 301 |
map_num <- function(x, f, ...) {
|
|
| 302 | 1x |
vapply(x, f, numeric(1L), ..., USE.NAMES = FALSE) |
| 303 |
} |
|
| 304 | ||
| 305 |
map_chr <- function(x, f, ...) {
|
|
| 306 | 7x |
vapply(x, f, character(1L), ..., USE.NAMES = FALSE) |
| 307 |
} |
|
| 308 | ||
| 309 | ||
| 310 |
on_master_branch <- function() {
|
|
| 311 | ! |
get_repo_head_name() %in% c("master", "main")
|
| 312 |
} |
|
| 313 | ||
| 314 | ||
| 315 |
create_new_reporting_event <- function(name) {
|
|
| 316 | 1x |
dir.create(name) |
| 317 | 1x |
file.create(file.path(name, "metadata.yml")) |
| 318 |
} |
|
| 319 | ||
| 320 |
create_output_name <- function(program, suffix) {
|
|
| 321 | 4x |
ifelse(is.na(suffix) | suffix == "", program, paste(program, suffix, sep = "_")) |
| 322 |
} |
|
| 323 | ||
| 324 |
default_paper_size <- function(program) {
|
|
| 325 | 12x |
output_type <- substr(program, 1L, 1L) |
| 326 | 12x |
defaults <- c(l = "L8", t = "P8", g = "L11") |
| 327 | 12x |
if (output_type %in% names(defaults)) {
|
| 328 | 12x |
unname(defaults[output_type]) |
| 329 |
} else {
|
|
| 330 | ! |
"P8" |
| 331 |
} |
|
| 332 |
} |
|
| 333 | ||
| 334 |
vbar2newline <- function(x) {
|
|
| 335 | 5x |
gsub("\\s*\\|\\s*", "\n", x)
|
| 336 |
} |
|
| 337 | ||
| 338 |
munge_spaces <- function(text, wordboundary = "(\\t|\\n|\\x0b|\\x0c|\\r| )") {
|
|
| 339 | 12x |
stringr::str_replace_all(text, wordboundary, " ") |
| 340 |
} |
|
| 341 | ||
| 342 |
# split_chunk <- function(text, whitespace = "[\\t\\n\\x0b\\x0c\\r\\ ]") {
|
|
| 343 |
# wordsep_re <- sprintf("(%s+)", whitespace)
|
|
| 344 |
# strsplit(text, split = wordsep_re, perl = TRUE) |
|
| 345 |
# } |
|
| 346 |
split_chunk <- function(text, whitespace = "\\s+") {
|
|
| 347 |
# Split the string by one or more whitespace characters. |
|
| 348 | 12x |
chunks <- strsplit(text, split = whitespace, perl = TRUE)[[1]] |
| 349 |
# Remove any empty strings that result from leading/trailing whitespace. |
|
| 350 | 12x |
chunks[chunks != ""] |
| 351 |
} |
|
| 352 | ||
| 353 |
wrap_chunk <- function(chunks, width) {
|
|
| 354 | 13x |
if (length(chunks) == 0) {
|
| 355 | 1x |
return(list()) |
| 356 |
} |
|
| 357 | ||
| 358 | 12x |
lines <- list() |
| 359 | 12x |
current_line <- "" |
| 360 | ||
| 361 | 12x |
while (length(chunks) > 0) {
|
| 362 | 57x |
word <- chunks[1] |
| 363 | 57x |
if (nchar(word) > width) {
|
| 364 |
# If there's content on the current line, bank it first. |
|
| 365 | 2x |
if (current_line != "") {
|
| 366 | 2x |
lines <- append(lines, current_line) |
| 367 |
} |
|
| 368 | ||
| 369 | 2x |
lines <- append(lines, substr(word, 1, width)) |
| 370 | 2x |
chunks[1] <- substr(word, width + 1, nchar(word)) |
| 371 | 2x |
current_line <- "" |
| 372 | 2x |
next |
| 373 |
} |
|
| 374 | ||
| 375 | 55x |
potential_line <- if (current_line == "") word else paste(current_line, word, sep = " ") |
| 376 | ||
| 377 | 55x |
if (nchar(potential_line) <= width) {
|
| 378 | 39x |
current_line <- potential_line |
| 379 | 39x |
chunks <- chunks[-1] |
| 380 |
} else {
|
|
| 381 |
# If it doesn't fit, bank the current line and start a new one with the word. |
|
| 382 | 16x |
lines <- append(lines, current_line) |
| 383 | 16x |
current_line <- word |
| 384 | 16x |
chunks <- chunks[-1] |
| 385 |
} |
|
| 386 |
} |
|
| 387 | ||
| 388 | 12x |
if (current_line != "") {
|
| 389 | 12x |
lines <- append(lines, current_line) |
| 390 |
} |
|
| 391 | ||
| 392 | 12x |
lines |
| 393 |
} |
|
| 394 | ||
| 395 | ||
| 396 | ||
| 397 |
text_wrap_cut <- function(text, width) {
|
|
| 398 | 8x |
width <- as.integer(width) |
| 399 | 8x |
if (width <= 0) {
|
| 400 | 1x |
return("")
|
| 401 |
} |
|
| 402 | 7x |
munged_text <- munge_spaces(text) |
| 403 | 7x |
chunks <- split_chunk(munged_text) |
| 404 | 7x |
wrapped_list <- wrap_chunk(chunks, width = width) |
| 405 | 7x |
paste(unlist(wrapped_list), collapse = "\n") |
| 406 |
} |
|
| 407 | ||
| 408 |
text_wrap_cut_keepreturn <- function(text, width) {
|
|
| 409 | 2x |
if (is.na(width)) {
|
| 410 | ! |
width <- 0 |
| 411 |
} |
|
| 412 | 2x |
lines <- strsplit(text, "\n")[[1]] |
| 413 | 2x |
wrapped_lines_list <- lapply(lines, function(line) {
|
| 414 | 5x |
if (line == "") {
|
| 415 |
"" |
|
| 416 |
} else {
|
|
| 417 | 4x |
text_wrap_cut(line, width) |
| 418 |
} |
|
| 419 |
}) |
|
| 420 | 2x |
paste(wrapped_lines_list, collapse = "\n") |
| 421 |
} |
|
| 422 | ||
| 423 | ||
| 424 |
#' @noRd |
|
| 425 |
fs <- function(paper) {
|
|
| 426 | 4x |
fontsize <- as.integer(substr(paper, 2, nchar(paper))) |
| 427 | 4x |
orientation <- substr(paper, 1, 1) |
| 428 | 4x |
list(fontsize = fontsize, orientation = orientation) |
| 429 |
} |
|
| 430 | ||
| 431 |
validate_paper_size <- function(paper) {
|
|
| 432 | 171x |
assert_is_character_scalar(paper) |
| 433 | 171x |
if (!grepl("^[P|L][1-9][0-9]{0,1}$", paper)) {
|
| 434 | 3x |
abort( |
| 435 | 3x |
"Page size must be starting with `L` or `P` to indicate the orientation of the page, ", |
| 436 | 3x |
"followed by an integer to indicate the fontsize" |
| 437 |
) |
|
| 438 |
} |
|
| 439 | 168x |
fontsize <- as.integer(substr(paper, 2, nchar(paper))) |
| 440 | 168x |
if (fontsize > 14) {
|
| 441 | 1x |
abort("Fontsize should be less or equal than 14")
|
| 442 |
} |
|
| 443 |
} |
|
| 444 | ||
| 445 |
get_output_file_ext <- function(output, file_path) {
|
|
| 446 | 15x |
ret <- "" |
| 447 | 15x |
if (tools::file_ext(file_path) != "") {
|
| 448 | ! |
ret <- file_path |
| 449 |
} else {
|
|
| 450 | 15x |
file_ext <- ifelse(is_rtable(output) || "dVTableTree" %in% class(output), "out", "pdf") |
| 451 | 15x |
ret <- sprintf("%s.%s", file_path, file_ext)
|
| 452 |
} |
|
| 453 | ||
| 454 | 15x |
ret |
| 455 |
} |
|
| 456 | ||
| 457 |
# make config global so that test-util recognizes it |
|
| 458 |
.autoslider_config <- new.env(parent = emptyenv()) |
|
| 459 | ||
| 460 |
warn_about_legacy_filtering <- function(output) {
|
|
| 461 | 2x |
if (.autoslider_config$filter_warning_issued) {
|
| 462 | 1x |
return(invisible()) |
| 463 |
} else {
|
|
| 464 | 1x |
.autoslider_config$filter_warning_issued <- TRUE |
| 465 |
} |
|
| 466 | ||
| 467 | 1x |
msg <- sprintf( |
| 468 | 1x |
paste( |
| 469 | 1x |
"Filtering based upon a character scalar is deprecated.", |
| 470 | 1x |
"Please use `output == '%s'` instead." |
| 471 |
), |
|
| 472 | 1x |
output |
| 473 |
) |
|
| 474 | 1x |
warn(msg) |
| 475 |
} |
|
| 476 | ||
| 477 |
warn_about_legacy_paper_size <- function(old_paper_size, |
|
| 478 |
new_paper_size) {
|
|
| 479 | 3x |
if (.autoslider_config$paper_size_warning_issued[old_paper_size]) {
|
| 480 | 1x |
return(invisible()) |
| 481 |
} else {
|
|
| 482 | 2x |
.autoslider_config$paper_size_warning_issued[old_paper_size] <- TRUE |
| 483 |
} |
|
| 484 | ||
| 485 | 2x |
msg <- sprintf( |
| 486 | 2x |
"Paper size '%s' is deprecated. Please use '%s' instead.", |
| 487 | 2x |
old_paper_size, |
| 488 | 2x |
new_paper_size |
| 489 |
) |
|
| 490 | 2x |
warn(msg) |
| 491 |
} |
|
| 492 | ||
| 493 | ||
| 494 | ||
| 495 |
#' Build side by side layout by cbind |
|
| 496 |
#' |
|
| 497 |
#' @param lyt layout object |
|
| 498 |
#' @param anl analysis data object |
|
| 499 |
#' @param side_by_side A logical value indicating whether to display the data side by side. |
|
| 500 |
#' @return An `rtables` layout |
|
| 501 |
#' @export |
|
| 502 |
lyt_to_side_by_side <- function(lyt, anl, side_by_side = NULL) {
|
|
| 503 | 28x |
result <- build_table(lyt = lyt, df = anl) |
| 504 | ||
| 505 | 27x |
if (!is.null(side_by_side)) {
|
| 506 | 10x |
if (grepl("Asia", side_by_side)) {
|
| 507 | 1x |
tmp_anl <- anl %>% filter(COUNTRY %in% c("CHN", "HKG", "TWN", "KOR", "SGP", "THA", "MYS"))
|
| 508 | 1x |
tmp_anl$lvl <- "Asia" |
| 509 | 1x |
result <- cbind_rtables( |
| 510 | 1x |
result, |
| 511 | 1x |
build_table( |
| 512 | 1x |
lyt = lyt, |
| 513 | 1x |
df = tmp_anl |
| 514 |
) |
|
| 515 |
) |
|
| 516 |
} |
|
| 517 | ||
| 518 | 10x |
if (grepl("China", side_by_side)) {
|
| 519 | 8x |
tmp_anl <- anl %>% filter(COUNTRY == "CHN") |
| 520 | 8x |
tmp_anl$lvl <- "China" |
| 521 | 8x |
result <- cbind_rtables(result, build_table(lyt = lyt, df = tmp_anl)) |
| 522 |
} |
|
| 523 |
} |
|
| 524 | 27x |
return(result) |
| 525 |
} |
|
| 526 | ||
| 527 |
#' Build side by side layout by cbind |
|
| 528 |
#' @param lyt layout object |
|
| 529 |
#' @param anl analysis data object |
|
| 530 |
#' @param side_by_side A logical value indicating whether to display the data side by side. |
|
| 531 |
#' @param alt_counts_df alternative data frame for counts |
|
| 532 |
#' @return An `rtables` layout |
|
| 533 |
#' @export |
|
| 534 |
lyt_to_side_by_side_two_data <- function(lyt, anl, alt_counts_df, side_by_side = NULL) {
|
|
| 535 | 28x |
result <- build_table(lyt = lyt, df = anl, alt_counts_df = alt_counts_df) |
| 536 | ||
| 537 | 28x |
if (!is.null(side_by_side)) {
|
| 538 | 7x |
if (grepl("Asia", side_by_side)) {
|
| 539 | 7x |
countries <- c("CHN", "HKG", "TWN", "KOR", "SGP", "THA", "MYS")
|
| 540 | 7x |
tmp_anl <- anl %>% filter(COUNTRY %in% countries) |
| 541 | 7x |
tmp_anl$lvl <- "Asia" |
| 542 | 7x |
tmp_alt <- alt_counts_df %>% filter(COUNTRY %in% countries) |
| 543 | 7x |
tmp_alt$lvl <- "Asia" |
| 544 | ||
| 545 | 7x |
result <- cbind_rtables( |
| 546 | 7x |
result, |
| 547 | 7x |
build_table( |
| 548 | 7x |
lyt = lyt, |
| 549 | 7x |
df = tmp_anl, |
| 550 | 7x |
alt_counts_df = tmp_alt |
| 551 |
) |
|
| 552 |
) |
|
| 553 |
} |
|
| 554 | ||
| 555 | 7x |
if (grepl("China", side_by_side)) {
|
| 556 | ! |
tmp_anl <- anl %>% filter(COUNTRY == "CHN") |
| 557 | ! |
tmp_anl$lvl <- "China" |
| 558 | ! |
tmp_alt <- alt_counts_df %>% filter(COUNTRY == "CHN") |
| 559 | ! |
tmp_alt$lvl <- "China" |
| 560 | ! |
result <- cbind_rtables(result, build_table( |
| 561 | ! |
lyt = lyt, df = tmp_anl, |
| 562 | ! |
alt_counts_df = tmp_alt |
| 563 |
)) |
|
| 564 |
} |
|
| 565 |
} |
|
| 566 | 28x |
return(result) |
| 567 |
} |
|
| 568 | ||
| 569 | ||
| 570 |
do_call <- function(fun, ...) {
|
|
| 571 | 617x |
args <- list(...) |
| 572 | 617x |
do.call(fun, args[intersect(names(args), formalArgs(fun))]) |
| 573 |
} |
|
| 574 | ||
| 575 | ||
| 576 |
#' Build table header, a utility function to help with construct structured header for table layout |
|
| 577 |
#' @param anl analysis data object |
|
| 578 |
#' @param arm Arm variable for column split |
|
| 579 |
#' @param split_by_study, if true, construct structured header with the study ID |
|
| 580 |
#' @param side_by_side A logical value indicating whether to display the data side by side. |
|
| 581 |
#' @return A `rtables` layout with desired header. |
|
| 582 |
#' @export |
|
| 583 |
build_table_header <- function(anl, |
|
| 584 |
arm, |
|
| 585 |
split_by_study, |
|
| 586 |
side_by_side) {
|
|
| 587 | 56x |
lyt <- basic_table() |
| 588 | 56x |
if (is.null(side_by_side)) {
|
| 589 | 39x |
if (split_by_study) {
|
| 590 | 6x |
assert_that(length(unique(anl$STUDYID)) > 1) |
| 591 | 6x |
lyt <- lyt %>% |
| 592 | 6x |
split_cols_by(var = "STUDYID") %>% |
| 593 | 6x |
split_cols_by(var = arm) |
| 594 |
} else {
|
|
| 595 | 33x |
lyt <- lyt %>% |
| 596 | 33x |
split_cols_by(var = arm) %>% |
| 597 | 33x |
add_overall_col("All Patients")
|
| 598 |
} |
|
| 599 |
} else {
|
|
| 600 | 17x |
if (split_by_study) {
|
| 601 | 10x |
warning("split_by_study argument will be ignored")
|
| 602 |
} |
|
| 603 | 17x |
lyt <- lyt %>% |
| 604 | 17x |
split_cols_by(var = "lvl") %>% |
| 605 | 17x |
split_cols_by(var = arm) %>% |
| 606 | 17x |
add_overall_col("All Patients")
|
| 607 |
} |
|
| 608 | ||
| 609 | 56x |
lyt |
| 610 |
} |
|
| 611 | ||
| 612 | ||
| 613 |
get_version_label_output <- function() {
|
|
| 614 | 1x |
NULL |
| 615 |
} |
|
| 616 | ||
| 617 | ||
| 618 |
strip_NA <- function(input) {
|
|
| 619 | 21x |
input[which(input != "NA")] |
| 620 |
} |
| 1 |
#' generate slides based on output |
|
| 2 |
#' |
|
| 3 |
#' @param outputs List of output |
|
| 4 |
#' @param template Template file path |
|
| 5 |
#' @param outfile Out file path |
|
| 6 |
#' @param fig_width figure width in inch |
|
| 7 |
#' @param fig_height figure height in inch |
|
| 8 |
#' @param t_lpp An integer specifying the table lines per page \cr |
|
| 9 |
#' Specify this optional argument to modify the length of all of the table displays |
|
| 10 |
#' @param t_cpp An integer specifying the table columns per page\cr |
|
| 11 |
#' Specify this optional argument to modify the width of all of the table displays |
|
| 12 |
#' @param l_lpp An integer specifying the listing lines per page\cr |
|
| 13 |
#' Specify this optional argument to modify the length of all of the listings display |
|
| 14 |
#' @param l_cpp An integer specifying the listing columns per page\cr |
|
| 15 |
#' Specify this optional argument to modify the width of all of the listings display |
|
| 16 |
#' @param fig_editable whether we want the figure to be editable in pptx viewers, defaults to FALSE |
|
| 17 |
#' @param ... arguments passed to program |
|
| 18 |
#' @return No return value, called for side effects |
|
| 19 |
#' @export |
|
| 20 |
#' @examplesIf require(filters) |
|
| 21 |
#' |
|
| 22 |
#' # Example 1. When applying to the whole pipeline |
|
| 23 |
#' library(dplyr) |
|
| 24 |
#' data <- list( |
|
| 25 |
#' adsl = eg_adsl %>% dplyr::mutate(FASFL = SAFFL), |
|
| 26 |
#' adae = eg_adae |
|
| 27 |
#' ) |
|
| 28 |
#' |
|
| 29 |
#' |
|
| 30 |
#' filters::load_filters( |
|
| 31 |
#' yaml_file = system.file("filters.yml", package = "autoslider.core"),
|
|
| 32 |
#' overwrite = TRUE |
|
| 33 |
#' ) |
|
| 34 |
#' |
|
| 35 |
#' |
|
| 36 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 37 |
#' spec_file %>% |
|
| 38 |
#' read_spec() %>% |
|
| 39 |
#' filter_spec(program %in% c("t_dm_slide")) %>%
|
|
| 40 |
#' generate_outputs(datasets = data) %>% |
|
| 41 |
#' decorate_outputs() %>% |
|
| 42 |
#' generate_slides() |
|
| 43 |
#' |
|
| 44 |
#' # Example 2. When applying to an rtable object or an rlisting object |
|
| 45 |
#' adsl <- eg_adsl |
|
| 46 |
#' t_dm_slide(adsl, "TRT01P", c("SEX", "AGE")) %>%
|
|
| 47 |
#' generate_slides() |
|
| 48 |
generate_slides <- function(outputs, |
|
| 49 |
outfile = paste0(tempdir(), "/output.pptx"), |
|
| 50 |
template = file.path(system.file(package = "autoslider.core"), "theme/basic.pptx"), |
|
| 51 |
fig_width = 9, fig_height = 5, t_lpp = 20, t_cpp = 200, |
|
| 52 |
l_lpp = 20, l_cpp = 150, fig_editable = FALSE, ...) {
|
|
| 53 | 10x |
if (any(c( |
| 54 | 10x |
is(outputs, "VTableTree"), |
| 55 | 10x |
is(outputs, "listing_df") |
| 56 |
))) {
|
|
| 57 | 2x |
if (is(outputs, "listing_df")) {
|
| 58 | ! |
current_title <- main_title(outputs) |
| 59 |
} else {
|
|
| 60 | 2x |
current_title <- outputs@main_title |
| 61 |
} |
|
| 62 | 2x |
outputs <- list( |
| 63 | 2x |
decorate(outputs, titles = current_title, footnotes = "Confidential and for internal use only") |
| 64 |
) |
|
| 65 | 8x |
} else if (any(c( |
| 66 | 8x |
is(outputs, "data.frame"), |
| 67 | 8x |
is(outputs, "ggplot"), |
| 68 | 8x |
is(outputs, "gtsummary"), |
| 69 | 8x |
is(outputs, "dVTableTree"), |
| 70 | 8x |
is(outputs, "dlisting"), |
| 71 | 8x |
is(outputs, "grob") |
| 72 |
))) {
|
|
| 73 | ! |
if (is(outputs, "ggplot")) {
|
| 74 | ! |
current_title <- outputs$labels$title |
| 75 | ! |
if (is.null(current_title)) {
|
| 76 | ! |
current_title <- "" |
| 77 |
} |
|
| 78 | ! |
outputs <- decorate.ggplot(outputs, titles = current_title) |
| 79 | ! |
} else if (is(outputs, "grob")) {
|
| 80 | ! |
outputs <- decorate.grob(outputs) |
| 81 |
} |
|
| 82 | ||
| 83 | ! |
outputs <- list(outputs) |
| 84 |
} |
|
| 85 | ||
| 86 | 10x |
assert_that(is.list(outputs)) |
| 87 | ||
| 88 |
# ======== generate slides =======# |
|
| 89 |
# set slides layout |
|
| 90 | 10x |
ppt <- read_pptx(path = template) |
| 91 | 10x |
location_ <- officer::fortify_location(ph_location_fullsize(), doc = ppt) |
| 92 | 10x |
width <- location_$width |
| 93 | 10x |
height <- location_$height |
| 94 | ||
| 95 |
# add content to slides template |
|
| 96 | 10x |
for (x in outputs) {
|
| 97 | 24x |
if (is(x, "dVTableTree") || is(x, "VTableTree")) {
|
| 98 | 18x |
y <- to_flextable(x, lpp = t_lpp, cpp = t_cpp, ...) |
| 99 | 18x |
usernotes <- x@usernotes |
| 100 | 18x |
for (tt in y) {
|
| 101 | 20x |
table_to_slide(ppt, |
| 102 | 20x |
content = tt, |
| 103 | 20x |
table_loc = center_table_loc(tt$ft, ppt_width = width, ppt_height = height), |
| 104 | 20x |
usernotes = usernotes, ... |
| 105 |
) |
|
| 106 |
} |
|
| 107 | 6x |
} else if (is(x, "dlisting")) {
|
| 108 | 1x |
y <- to_flextable(x, cpp = l_cpp, lpp = l_lpp, ...) |
| 109 | 1x |
for (tt in y) {
|
| 110 | 272x |
table_to_slide(ppt, |
| 111 | 272x |
content = tt, |
| 112 | 272x |
table_loc = center_table_loc(tt$ft, ppt_width = width, ppt_height = height), ... |
| 113 |
) |
|
| 114 |
} |
|
| 115 | 5x |
} else if (is(x, "data.frame")) { # this is dedicated for small data frames without pagination
|
| 116 | ! |
y <- to_flextable(x, ...) |
| 117 | ! |
table_to_slide(ppt, content = y, decor = FALSE, ...) |
| 118 | 5x |
} else if (is(x, "gtsummary") || is(x, "dgtsummary")) {
|
| 119 | 1x |
y <- to_flextable(x, ...) |
| 120 | 1x |
table_to_slide(ppt, |
| 121 | 1x |
content = y, decor = FALSE, ... |
| 122 |
) |
|
| 123 |
} else {
|
|
| 124 | 4x |
if (any(class(x) %in% c("decoratedGrob", "decoratedGrobSet", "ggplot"))) {
|
| 125 | 4x |
if (is(x, "ggplot")) {
|
| 126 | ! |
x <- decorate.ggplot(x) |
| 127 |
} |
|
| 128 | ||
| 129 | 4x |
assertthat::assert_that(is(x, "decoratedGrob") || is(x, "decoratedGrobSet")) |
| 130 | ||
| 131 | 4x |
figure_to_slide(ppt, |
| 132 | 4x |
content = x, fig_width = fig_width, fig_height = fig_height, |
| 133 | 4x |
figure_loc = center_figure_loc(fig_width, fig_height, ppt_width = width, ppt_height = 1.17 * height), |
| 134 | 4x |
fig_editable = fig_editable, ... |
| 135 |
) |
|
| 136 |
} else {
|
|
| 137 | ! |
if (is(x, "autoslider_error")) {
|
| 138 | ! |
message(x) |
| 139 |
} else {
|
|
| 140 | ! |
next |
| 141 |
} |
|
| 142 |
} |
|
| 143 |
} |
|
| 144 |
} |
|
| 145 | 9x |
print(ppt, target = outfile) |
| 146 |
} |
|
| 147 | ||
| 148 |
#' Generate flextable for preview first page |
|
| 149 |
#' |
|
| 150 |
#' @param x rtables or data.frame |
|
| 151 |
#' @return A flextable or a ggplot object depending to the input. |
|
| 152 |
#' @export |
|
| 153 |
#' @examples |
|
| 154 |
#' # Example 1. preview table |
|
| 155 |
#' library(dplyr) |
|
| 156 |
#' adsl <- eg_adsl |
|
| 157 |
#' t_dm_slide(adsl, "TRT01P", c("SEX", "AGE")) %>% slides_preview()
|
|
| 158 |
slides_preview <- function(x) {
|
|
| 159 | 1x |
if (is(x, "VTableTree")) {
|
| 160 | 1x |
ret <- to_flextable(paginate_table(x, lpp = 20)[[1]]) |
| 161 | ! |
} else if (is(x, "listing_df")) {
|
| 162 | ! |
new_colwidth <- formatters::propose_column_widths(x) |
| 163 | ! |
ret <- to_flextable(old_paginate_listing(x, cpp = 150, lpp = 20)[[1]], |
| 164 | ! |
col_width = new_colwidth |
| 165 |
) |
|
| 166 | ! |
} else if (is(x, "ggplot")) {
|
| 167 | ! |
ret <- x |
| 168 |
} else {
|
|
| 169 | ! |
stop("Unintended usage!")
|
| 170 |
} |
|
| 171 | 1x |
ret |
| 172 |
} |
|
| 173 | ||
| 174 |
get_body_bottom_location <- function(ppt) {
|
|
| 175 | ! |
location_ <- officer::fortify_location(ph_location_fullsize(), doc = ppt) |
| 176 | ! |
width <- location_$width |
| 177 | ! |
height <- location_$height |
| 178 | ! |
top <- 0.7 * height |
| 179 | ! |
left <- 0.1 * width |
| 180 | ! |
ph <- ph_location(left = left, top = top) |
| 181 | ! |
ph |
| 182 |
} |
|
| 183 | ||
| 184 | ||
| 185 |
#' create location container to center the table |
|
| 186 |
#' |
|
| 187 |
#' @param ft Flextable object |
|
| 188 |
#' @param ppt_width Powerpoint width |
|
| 189 |
#' @param ppt_height Powerpoint height |
|
| 190 |
#' @return Location for a placeholder |
|
| 191 |
center_table_loc <- function(ft, ppt_width, ppt_height) {
|
|
| 192 | 291x |
top <- (ppt_height - sum(dim(ft)$heights)) / 2 |
| 193 | 291x |
left <- (ppt_width - sum(dim(ft)$widths)) / 2 |
| 194 | 291x |
ph <- ph_location(left = left, top = top) |
| 195 | 291x |
ph |
| 196 |
} |
|
| 197 | ||
| 198 |
#' Adjust title line break and font size |
|
| 199 |
#' |
|
| 200 |
#' @param title Character string |
|
| 201 |
#' @param max_char Integer specifying the maximum number of characters in one line |
|
| 202 |
#' @param title_color Title color |
|
| 203 |
get_proper_title <- function(title, max_char = 60, title_color = "#1C2B39") {
|
|
| 204 |
# cat(nchar(title), " ", as.integer(24-nchar(title)/para), "\n") |
|
| 205 | 295x |
title <- gsub("\\n", "\\s", title)
|
| 206 | 295x |
new_title <- "" |
| 207 | ||
| 208 | 295x |
while (nchar(title) > max_char) {
|
| 209 | 278x |
spaces <- gregexpr("\\s", title)
|
| 210 | 278x |
new_title <- paste0(new_title, "\n", substring(title, 1, max(spaces[[1]][spaces[[1]] <= max_char]))) |
| 211 | 278x |
title <- substring(title, max(spaces[[1]][spaces[[1]] <= max_char]) + 1, nchar(title)) |
| 212 |
} |
|
| 213 | ||
| 214 | 295x |
new_title <- paste0(new_title, "\n", title) |
| 215 | ||
| 216 | 295x |
ftext( |
| 217 | 295x |
trimws(new_title), |
| 218 | 295x |
fp_text( |
| 219 | 295x |
font.size = floor(26 - nchar(title) / max_char), |
| 220 | 295x |
color = title_color |
| 221 |
) |
|
| 222 |
) |
|
| 223 |
} |
|
| 224 | ||
| 225 |
#' Add decorated flextable to slides |
|
| 226 |
#' |
|
| 227 |
#' @param ppt Slide |
|
| 228 |
#' @param content Content to be added |
|
| 229 |
#' @param table_loc Table location |
|
| 230 |
#' @param usernotes User notes |
|
| 231 |
#' @param decor Should table be decorated |
|
| 232 |
#' @param layout layout from theme |
|
| 233 |
#' @param ... additional arguments |
|
| 234 |
#' @return Slide with added content |
|
| 235 |
table_to_slide <- function(ppt, content, decor = TRUE, layout = "Title and Content", table_loc = ph_location_type("body"),
|
|
| 236 |
usernotes = "", ...) {
|
|
| 237 | 293x |
layt_summary <- layout_summary(ppt) |
| 238 | 293x |
assertthat::assert_that(layout %in% layt_summary$layout) |
| 239 | 292x |
ppt_master <- layt_summary$master[1] |
| 240 | 292x |
args <- list(...) |
| 241 | 292x |
ppt <- layout_default(ppt, layout) |
| 242 | ||
| 243 | 292x |
if (decor) {
|
| 244 | 291x |
print(content$header) |
| 245 | 291x |
out <- content$ft |
| 246 | ||
| 247 | 291x |
if (length(content$footnotes) > 1) {
|
| 248 | 9x |
content$footnotes <- paste(content$footnotes, collapse = "\n") |
| 249 |
} |
|
| 250 |
# print(content_footnotes) |
|
| 251 | 291x |
if (content$footnotes != "") {
|
| 252 | 284x |
out <- footnote(out, |
| 253 | 284x |
i = 1, j = 1, |
| 254 | 284x |
value = as_paragraph(content$footnotes), |
| 255 | 284x |
ref_symbols = " ", part = "header", inline = TRUE |
| 256 |
) |
|
| 257 |
} |
|
| 258 | ||
| 259 | 291x |
args$arg_header <- list( |
| 260 | 291x |
value = fpar(get_proper_title(content$header)), |
| 261 | 291x |
location = ph_location_type("title")
|
| 262 |
) |
|
| 263 |
} else {
|
|
| 264 | 1x |
out <- content |
| 265 | 1x |
out <- footnote(out, |
| 266 | 1x |
i = 1, j = 1, |
| 267 | 1x |
value = as_paragraph("Confidential and for internal use only"),
|
| 268 | 1x |
ref_symbols = " ", part = "header", inline = TRUE |
| 269 |
) |
|
| 270 |
} |
|
| 271 | ||
| 272 | 292x |
ppt <- do_call(add_slide, x = ppt, master = ppt_master, ...) |
| 273 | 292x |
ppt <- ph_with(ppt, value = out, location = table_loc) |
| 274 | 292x |
ppt <- set_notes(ppt, value = usernotes, |
| 275 | 292x |
location = notes_location_type("body"))
|
| 276 | 292x |
ph_with_args <- args[unlist(lapply(args, function(x) all(c("location", "value") %in% names(x))))]
|
| 277 | 292x |
res <- lapply(ph_with_args, function(x) {
|
| 278 | 291x |
ppt <- ph_with(ppt, value = x$value, location = x$location) |
| 279 |
}) |
|
| 280 | ||
| 281 | 292x |
return(res) |
| 282 |
} |
|
| 283 | ||
| 284 |
#' Create location container to center the figure, based on ppt size and |
|
| 285 |
#' user specified figure size |
|
| 286 |
#' |
|
| 287 |
#' @param fig_width Figure width |
|
| 288 |
#' @param fig_height Figure height |
|
| 289 |
#' @param ppt_width Slide width |
|
| 290 |
#' @param ppt_height Slide height |
|
| 291 |
#' |
|
| 292 |
#' @return Location for a placeholder from scratch |
|
| 293 |
center_figure_loc <- function(fig_width, fig_height, ppt_width, ppt_height) {
|
|
| 294 |
# center figure |
|
| 295 | 1x |
top <- (ppt_height - fig_height) / 2 |
| 296 | 1x |
left <- (ppt_width - fig_width) / 2 |
| 297 | 1x |
ph_location(top = top, left = left) |
| 298 |
} |
|
| 299 | ||
| 300 |
#' Placeholder for ph_with_img |
|
| 301 |
#' |
|
| 302 |
#' @param ppt power point file |
|
| 303 |
#' @param figure image object |
|
| 304 |
#' @param fig_width width of figure |
|
| 305 |
#' @param fig_height height of figure |
|
| 306 |
#' @param figure_loc location of figure |
|
| 307 |
#' @return Location for a placeholder |
|
| 308 |
#' @export |
|
| 309 |
ph_with_img <- function(ppt, figure, fig_width, fig_height, figure_loc) {
|
|
| 310 | 1x |
file_name <- tempfile(fileext = ".svg") |
| 311 | 1x |
svg(filename = file_name, width = fig_width, height = fig_height, onefile = TRUE) |
| 312 | 1x |
grid.draw(figure$grob) |
| 313 | 1x |
dev.off() |
| 314 | 1x |
on.exit(unlink(file_name)) |
| 315 | 1x |
ext_img <- external_img(file_name, width = fig_width, height = fig_height) |
| 316 | ||
| 317 | 1x |
ppt %>% ph_with(value = ext_img, location = figure_loc, use_loc_size = FALSE) |
| 318 |
} |
|
| 319 | ||
| 320 |
#' Add figure to slides |
|
| 321 |
#' |
|
| 322 |
#' @param ppt slide page |
|
| 323 |
#' @param content content to be added |
|
| 324 |
#' @param decor should decoration be added |
|
| 325 |
#' @param fig_width user specified figure width |
|
| 326 |
#' @param fig_height user specified figure height |
|
| 327 |
#' @param figure_loc location of the figure. Defaults to `ph_location_type("body")`
|
|
| 328 |
#' @param layout theme layout |
|
| 329 |
#' @param fig_editable whether we want the figure to be editable in pptx viewers |
|
| 330 |
#' @param ... arguments passed to program |
|
| 331 |
#' |
|
| 332 |
#' @return slide with the added content |
|
| 333 |
figure_to_slide <- function(ppt, content, |
|
| 334 |
decor = TRUE, |
|
| 335 |
fig_width, |
|
| 336 |
fig_height, |
|
| 337 |
layout = "Title and Content", |
|
| 338 |
figure_loc = ph_location_type("body"),
|
|
| 339 |
fig_editable = FALSE, |
|
| 340 |
...) {
|
|
| 341 | 4x |
layt_summary <- layout_summary(ppt) |
| 342 | 4x |
assertthat::assert_that(layout %in% layt_summary$layout) |
| 343 | 4x |
ppt_master <- layt_summary$master[1] |
| 344 | 4x |
ppt <- layout_default(ppt, layout) |
| 345 | 4x |
args <- list(...) |
| 346 | ||
| 347 | ||
| 348 | 4x |
if (decor) {
|
| 349 | 4x |
args$arg_header <- list( |
| 350 | 4x |
value = fpar(get_proper_title(content$titles)), |
| 351 | 4x |
location = ph_location_type("title")
|
| 352 |
) |
|
| 353 |
} |
|
| 354 | ||
| 355 | 4x |
if ("decoratedGrob" %in% class(content)) {
|
| 356 | 4x |
ppt <- do_call(add_slide, x = ppt, master = ppt_master, ...) |
| 357 | 4x |
if (fig_editable) {
|
| 358 | 4x |
content_list <- g_export(content) |
| 359 | 4x |
ppt <- ph_with(ppt, content_list$dml, location = ph_location_type(type = "body")) |
| 360 |
} else {
|
|
| 361 | ! |
ppt <- ph_with_img(ppt, content, fig_width, fig_height, figure_loc) |
| 362 |
} |
|
| 363 | ||
| 364 | 4x |
ph_with_args <- args[unlist(lapply(args, function(x) all(c("location", "value") %in% names(x))))]
|
| 365 | 4x |
res <- lapply(ph_with_args, function(x) {
|
| 366 | 4x |
ppt <- ph_with(ppt, value = x$value, location = x$location) |
| 367 |
}) |
|
| 368 | 4x |
return(res) |
| 369 | ! |
} else if ("decoratedGrobSet" %in% class(content)) { # for decoratedGrobSet, a list of figures are created and added
|
| 370 |
# revisit, to make more efficent |
|
| 371 | ! |
for (figure in content) {
|
| 372 | ! |
ppt <- do_call(add_slide, x = ppt, master = ppt_master, ...) |
| 373 | ! |
ppt <- ph_with_img(ppt, figure, fig_width, fig_height, figure_loc) |
| 374 |
} |
|
| 375 | ! |
return(ppt) |
| 376 |
} else {
|
|
| 377 | ! |
stop("Should not reach here")
|
| 378 |
} |
|
| 379 |
} |
| 1 |
#' Adverse event summary table |
|
| 2 |
#' |
|
| 3 |
#' @param adsl ADSL dataset, dataframe |
|
| 4 |
#' @param adae ADAE dataset, dataframe |
|
| 5 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 6 |
#' @param dose_adjust_flags Character or a vector of characters. Each character is a variable |
|
| 7 |
#' name in adae dataset. These variables are Logical vectors which flag AEs |
|
| 8 |
#' leading to dose adjustment, such as drug discontinuation, dose interruption |
|
| 9 |
#' and reduction. The flag can be related to any drug, or a specific drug. |
|
| 10 |
#' @param dose_adjust_labels Character or a vector of characters. Each character represents |
|
| 11 |
#' a label displayed in the AE summary table (e.g. AE leading to discontinuation |
|
| 12 |
#' from drug X). The order of the labels should match the order of variable |
|
| 13 |
#' names in \code{dose_adjust_flags}.
|
|
| 14 |
#' @param gr34_highest_grade_only A logical value. Default is TRUE, such that |
|
| 15 |
#' only patients with the highest AE grade as 3 or 4 are included for the count of the "Grade 3-4 AE" and |
|
| 16 |
#' "Treatment-related Grade 3-4 AE" ; set it to FALSE if |
|
| 17 |
#' you want to include patients with the highest AE grade as 5. |
|
| 18 |
#' |
|
| 19 |
#' @return an rtables object |
|
| 20 |
#' @export |
|
| 21 |
#' |
|
| 22 |
#' @examples |
|
| 23 |
#' library(dplyr) |
|
| 24 |
#' ADSL <- eg_adsl |
|
| 25 |
#' ADAE <- eg_adae |
|
| 26 |
#' |
|
| 27 |
#' ADAE <- ADAE %>% |
|
| 28 |
#' dplyr::mutate(ATOXGR = AETOXGR) |
|
| 29 |
#' t_ae_summ_slide(adsl = ADSL, adae = ADAE) |
|
| 30 |
#' |
|
| 31 |
#' # add flag for ae leading to dose reduction |
|
| 32 |
#' ADAE$reduce_flg <- ifelse(ADAE$AEACN == "DOSE REDUCED", TRUE, FALSE) |
|
| 33 |
#' t_ae_summ_slide( |
|
| 34 |
#' adsl = ADSL, adae = ADAE, |
|
| 35 |
#' dose_adjust_flags = c("reduce_flg"),
|
|
| 36 |
#' dose_adjust_labels = c("AE leading to dose reduction of drug X")
|
|
| 37 |
#' ) |
|
| 38 |
#' # add flgs for ae leading to dose reduction, drug withdraw and drug interruption |
|
| 39 |
#' ADAE$withdraw_flg <- ifelse(ADAE$AEACN == "DRUG WITHDRAWN", TRUE, FALSE) |
|
| 40 |
#' ADAE$interrup_flg <- ifelse(ADAE$AEACN == "DRUG INTERRUPTED", TRUE, FALSE) |
|
| 41 |
#' out <- t_ae_summ_slide( |
|
| 42 |
#' adsl = ADSL, adae = ADAE, arm = "TRT01A", |
|
| 43 |
#' dose_adjust_flags = c("withdraw_flg", "reduce_flg", "interrup_flg"),
|
|
| 44 |
#' dose_adjust_labels = c( |
|
| 45 |
#' "AE leading to discontinuation from drug X", |
|
| 46 |
#' "AE leading to drug X reduction", |
|
| 47 |
#' "AE leading to drug X interruption" |
|
| 48 |
#' ) |
|
| 49 |
#' ) |
|
| 50 |
#' print(out) |
|
| 51 |
#' generate_slides(out, paste0(tempdir(), "/ae_summary.pptx")) |
|
| 52 |
t_ae_summ_slide <- function(adsl, adae, arm = "TRT01A", |
|
| 53 |
dose_adjust_flags = NA, |
|
| 54 |
dose_adjust_labels = NA, |
|
| 55 |
gr34_highest_grade_only = TRUE) {
|
|
| 56 |
# The gr3-4 only count the patients whose highest ae grade is 3 or 4 |
|
| 57 | 5x |
assert_that(has_name(adae, "TRT01A")) |
| 58 | 5x |
assert_that(has_name(adae, "AEDECOD")) |
| 59 | 5x |
assert_that(has_name(adae, "AEBODSYS")) |
| 60 | 5x |
assert_that(has_name(adae, "ATOXGR")) |
| 61 | 5x |
assert_that(has_name(adae, "AEREL")) |
| 62 | 5x |
assert_that(has_name(adae, "ANL01FL")) |
| 63 | 5x |
assert_that(has_name(adae, "SAFFL")) |
| 64 | 5x |
assert_that(has_name(adae, "TRTEMFL")) |
| 65 | 5x |
assert_that(has_name(adae, "AESER")) |
| 66 | 5x |
assert_that(length(dose_adjust_flags) == length(dose_adjust_labels)) |
| 67 | 5x |
assert_that(assertthat::is.flag(gr34_highest_grade_only)) |
| 68 | ||
| 69 | ||
| 70 | 4x |
if (sum(is.na(dose_adjust_flags)) == 0 & sum(is.na(dose_adjust_labels)) == 0) {
|
| 71 | 3x |
for (txt in dose_adjust_flags) {
|
| 72 | 9x |
assert_that(all(unlist(adae[txt]) %in% c(TRUE, FALSE))) |
| 73 | 9x |
assert_that(has_name(adae, txt)) |
| 74 |
} |
|
| 75 |
} |
|
| 76 | ||
| 77 | 4x |
adsl1 <- adsl %>% |
| 78 | 4x |
select("STUDYID", "USUBJID", "TRT01A")
|
| 79 | ||
| 80 | 4x |
pts_gr5 <- adae %>% filter(ATOXGR %in% c(5)) |
| 81 | ||
| 82 | 4x |
anl <- adae %>% |
| 83 | 4x |
mutate_at( |
| 84 | 4x |
c("AEDECOD", "AEBODSYS"),
|
| 85 | 4x |
~ explicit_na(sas_na(.)) # Replace blank arm with <Missing> |
| 86 |
) %>% |
|
| 87 | 4x |
mutate( |
| 88 | 4x |
ATOXGR = sas_na(ATOXGR) %>% as.factor(), |
| 89 | 4x |
ATOXGR2 = case_when( |
| 90 | 4x |
ATOXGR %in% c(1, 2) ~ "1 - 2", |
| 91 | 4x |
ATOXGR %in% c(3, 4) ~ "3 - 4", |
| 92 | 4x |
ATOXGR %in% c(5) ~ "5", |
| 93 | 4x |
) %>% as.factor(), |
| 94 | 4x |
TRT01A = sas_na(TRT01A) %>% as.factor() |
| 95 |
) %>% |
|
| 96 | 4x |
semi_join(., adsl1, by = c("STUDYID", "USUBJID")) %>%
|
| 97 | 4x |
filter(ANL01FL == "Y" & TRTEMFL == "Y" & SAFFL == "Y") %>% |
| 98 | 4x |
formatters::var_relabel( |
| 99 | 4x |
ATOXGR2 = "AE Grade 3 groups", |
| 100 | 4x |
ATOXGR = "AE Grade", |
| 101 | 4x |
TRT01A = "Actual Treatment 01" |
| 102 |
) %>% |
|
| 103 |
# ---------- ADAE: Treatment related flags --------- |
|
| 104 | 4x |
mutate( |
| 105 | 4x |
TMPFL1_REL0 = AEREL == "Y" |
| 106 |
) %>% |
|
| 107 | 4x |
formatters::var_relabel( |
| 108 | 4x |
TMPFL1_REL0 = "Any treatment" |
| 109 |
) %>% |
|
| 110 |
# ---------- ADAE: Grade 5 and related flags --------- |
|
| 111 | 4x |
mutate( |
| 112 | 4x |
TMPFL1_G5 = ATOXGR %in% c(5), |
| 113 | 4x |
TMPFL1_G5_REL = ATOXGR %in% c(5) & AEREL == "Y" |
| 114 |
) %>% |
|
| 115 | 4x |
formatters::var_relabel( |
| 116 | 4x |
TMPFL1_G5 = "Grade 5 AE", |
| 117 | 4x |
TMPFL1_G5_REL = "Treatment-related Grade 5 AE" |
| 118 |
) %>% |
|
| 119 |
# ---------- ADAE: SAE and related flags --------- |
|
| 120 | 4x |
mutate( |
| 121 | 4x |
TMPFL1_SER = AESER == "Y", |
| 122 | 4x |
TMPFL1_SER_REL = AESER == "Y" & AEREL == "Y" |
| 123 |
) %>% |
|
| 124 | 4x |
formatters::var_relabel( |
| 125 | 4x |
TMPFL1_SER = "Serious AE", |
| 126 | 4x |
TMPFL1_SER_REL = "Treatment-related Serious AE" |
| 127 |
) |
|
| 128 | ||
| 129 |
# ---------- ADAE: Grade 3/4 and related flags --------- |
|
| 130 | 4x |
if (gr34_highest_grade_only == TRUE) {
|
| 131 | 3x |
anl <- anl %>% |
| 132 | 3x |
mutate( |
| 133 | 3x |
TMPFL1_G34 = ATOXGR %in% c(3, 4) & !(USUBJID %in% pts_gr5$USUBJID), # Only count the highest grade is 3 or 4 |
| 134 | 3x |
TMPFL1_G34_REL = ATOXGR %in% c(3, 4) & AEREL == "Y" & !(USUBJID %in% pts_gr5$USUBJID) |
| 135 |
) %>% |
|
| 136 | 3x |
formatters::var_relabel( |
| 137 | 3x |
TMPFL1_G34 = "Grade 3-4 AE", |
| 138 | 3x |
TMPFL1_G34_REL = "Treatment-related Grade 3-4 AE" |
| 139 |
) |
|
| 140 |
} else {
|
|
| 141 | 1x |
anl <- anl %>% |
| 142 | 1x |
mutate( |
| 143 | 1x |
TMPFL1_G34 = ATOXGR %in% c(3, 4), |
| 144 | 1x |
TMPFL1_G34_REL = ATOXGR %in% c(3, 4) & AEREL == "Y" |
| 145 |
) %>% |
|
| 146 | 1x |
formatters::var_relabel( |
| 147 | 1x |
TMPFL1_G34 = "Grade 3-4 AE", |
| 148 | 1x |
TMPFL1_G34_REL = "Treatment-related Grade 3-4 AE" |
| 149 |
) |
|
| 150 |
} |
|
| 151 | ||
| 152 | 4x |
if (nrow(anl) == 0) {
|
| 153 | 1x |
return(null_report()) |
| 154 |
} else {
|
|
| 155 | 3x |
lyt <- basic_table() %>% |
| 156 | 3x |
split_cols_by(arm, split_fun = add_overall_level("All Patients", first = FALSE)) %>%
|
| 157 | 3x |
add_colcounts() %>% |
| 158 | 3x |
count_patients_with_event( |
| 159 | 3x |
vars = "USUBJID", |
| 160 | 3x |
filters = c("SAFFL" = "Y"),
|
| 161 | 3x |
denom = "N_col", |
| 162 | 3x |
.stats = "count_fraction", |
| 163 | 3x |
.labels = c(count_fraction = "All grade AEs, any cause"), |
| 164 | 3x |
table_names = "U", |
| 165 |
# .formats = list(trim_perc1) |
|
| 166 |
) %>% |
|
| 167 | 3x |
count_patients_with_flags( |
| 168 | 3x |
"USUBJID", |
| 169 | 3x |
flag_variables = c(TMPFL1_REL0 = "Related"), |
| 170 | 3x |
denom = "N_col", |
| 171 | 3x |
.indent_mods = 1L, |
| 172 | 3x |
var_labels = "TMPFL1 Related" |
| 173 |
# .format = list(trim_perc1) |
|
| 174 |
) %>% |
|
| 175 | 3x |
count_patients_with_flags( |
| 176 | 3x |
"USUBJID", |
| 177 | 3x |
flag_variables = c(TMPFL1_G34 = "Grade 3-4 AEs"), |
| 178 | 3x |
denom = "N_col", |
| 179 | 3x |
.indent_mods = 0L, |
| 180 | 3x |
var_labels = "Grade 3-4 AEs" |
| 181 |
# .format = list(trim_perc1) |
|
| 182 |
) %>% |
|
| 183 | 3x |
count_patients_with_flags( |
| 184 | 3x |
"USUBJID", |
| 185 | 3x |
flag_variables = c(TMPFL1_G34_REL = "Related"), |
| 186 | 3x |
denom = "N_col", |
| 187 | 3x |
.indent_mods = 1L, |
| 188 | 3x |
var_labels = "TMPFL1_G34 Related" |
| 189 |
# .format = list(trim_perc1) |
|
| 190 |
) %>% |
|
| 191 | 3x |
count_patients_with_flags( |
| 192 | 3x |
"USUBJID", |
| 193 | 3x |
flag_variables = c(TMPFL1_G5 = "Grade 5 AE"), |
| 194 | 3x |
denom = "N_col", |
| 195 | 3x |
.indent_mods = 0L, |
| 196 | 3x |
var_labels = "Grade 5 AE" |
| 197 |
# .format = list(trim_perc1) |
|
| 198 |
) %>% |
|
| 199 | 3x |
count_patients_with_flags( |
| 200 | 3x |
"USUBJID", |
| 201 | 3x |
flag_variables = c(TMPFL1_G5_REL = "Related"), |
| 202 | 3x |
denom = "N_col", |
| 203 | 3x |
.indent_mods = 1L, |
| 204 | 3x |
var_labels = "TMPFL1_G5 Related" |
| 205 |
# .format = list(trim_perc1) |
|
| 206 |
) %>% |
|
| 207 | 3x |
count_patients_with_flags( |
| 208 | 3x |
"USUBJID", |
| 209 | 3x |
flag_variables = c(TMPFL1_SER = "SAEs"), |
| 210 | 3x |
denom = "N_col", |
| 211 | 3x |
.indent_mods = 0L, |
| 212 | 3x |
var_labels = "SAEs" |
| 213 |
# .format = list(trim_perc1) |
|
| 214 |
) %>% |
|
| 215 | 3x |
count_patients_with_flags( |
| 216 | 3x |
"USUBJID", |
| 217 | 3x |
flag_variables = c(TMPFL1_SER_REL = "Related"), |
| 218 | 3x |
denom = "N_col", |
| 219 | 3x |
.indent_mods = 1L, |
| 220 | 3x |
var_labels = "TMPFL1_SEA Related" |
| 221 |
# .format = list(trim_perc1) |
|
| 222 |
) |
|
| 223 | ||
| 224 | 3x |
if (sum(is.na(dose_adjust_flags)) == 0 & sum(is.na(dose_adjust_labels)) == 0) {
|
| 225 | 3x |
for (i in 1:length(dose_adjust_flags)) {
|
| 226 | 9x |
text <- paste0( |
| 227 | 9x |
' lyt <- lyt %>% |
| 228 | 9x |
count_patients_with_flags( |
| 229 | 9x |
"USUBJID", |
| 230 | 9x |
flag_variables = c(', dose_adjust_flags[i], "='", dose_adjust_labels[i],
|
| 231 |
"'), |
|
| 232 | 9x |
denom = 'N_col', |
| 233 | 9x |
var_labels = paste('dose adjust',i),
|
| 234 | 9x |
.indent_mods = 0L)" |
| 235 |
) |
|
| 236 | 9x |
eval(parse(text = text)) |
| 237 |
} |
|
| 238 |
} |
|
| 239 | ||
| 240 | 3x |
result <- build_table( |
| 241 | 3x |
lyt, |
| 242 | 3x |
df = anl, |
| 243 | 3x |
alt_counts_df = adsl |
| 244 |
) |
|
| 245 | 3x |
result@main_title <- "AE summary table" |
| 246 |
} |
|
| 247 | ||
| 248 | 3x |
return(result) |
| 249 |
} |
| 1 |
#' Discontinue table |
|
| 2 |
#' @param adsl ADSL data |
|
| 3 |
#' @param arm Arm variable, character, "`TRT01P" by default. |
|
| 4 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 5 |
#' @param side_by_side "GlobalAsia" or "GlobalAsiaChina" to define the side by side requirement |
|
| 6 |
#' @inherit gen_notes note |
|
| 7 |
#' @export |
|
| 8 |
#' @examples |
|
| 9 |
#' library(dplyr) |
|
| 10 |
#' adsl <- eg_adsl %>% |
|
| 11 |
#' mutate(DISTRTFL = sample(c("Y", "N"), size = nrow(eg_adsl), replace = TRUE, prob = c(.1, .9))) %>%
|
|
| 12 |
#' preprocess_t_ds() |
|
| 13 |
#' out1 <- t_ds_slide(adsl, "TRT01P") |
|
| 14 |
#' print(out1) |
|
| 15 |
#' generate_slides(out1, paste0(tempdir(), "/ds.pptx")) |
|
| 16 |
#' |
|
| 17 |
#' out2 <- t_ds_slide(adsl, "TRT01P", split_by_study = TRUE) |
|
| 18 |
#' print(out2) |
|
| 19 |
#' |
|
| 20 |
t_ds_slide <- function(adsl, arm = "TRT01P", |
|
| 21 |
split_by_study = FALSE, |
|
| 22 |
side_by_side = NULL) {
|
|
| 23 | 8x |
assert_that(has_name(adsl, arm)) |
| 24 | 8x |
assert_that(has_name(adsl, "SAFFL")) |
| 25 | 8x |
assert_that(has_name(adsl, "STDONS"), |
| 26 | 8x |
msg = "`STDONS` variable is needed for this output, please use `preprocess_t_ds` function to derive." |
| 27 |
) |
|
| 28 | 8x |
assert_that(has_name(adsl, "DCSREAS")) |
| 29 | 8x |
assert_that(length(levels(adsl$STDONS)) <= 3) |
| 30 | ||
| 31 | 8x |
adsl1 <- adsl %>% |
| 32 | 8x |
mutate( |
| 33 | 8x |
STDONS = factor(explicit_na(sas_na(STDONS)), |
| 34 | 8x |
levels = c("Alive: On Treatment", "Alive: In Follow-up", "<Missing>"),
|
| 35 | 8x |
labels = c("On Treatment", "In Follow-up", "<Missing>")
|
| 36 |
), |
|
| 37 | 8x |
DCSREAS = str_to_title(factor(sas_na(DCSREAS))), |
| 38 | 8x |
DCSflag = ifelse(is.na(DCSREAS), "N", "Y"), |
| 39 | 8x |
STDONSflag = ifelse(STDONS == "<Missing>", "N", "Y") |
| 40 |
) %>% |
|
| 41 | 8x |
mutate_at(c("STDONS", "DCSREAS"), ~ as.factor(explicit_na(.))) %>%
|
| 42 | 8x |
formatters::var_relabel( |
| 43 | 8x |
STDONS = "On-study Status", |
| 44 | 8x |
DCSflag = "Discontinued the study" |
| 45 |
) |
|
| 46 | ||
| 47 | 8x |
if (!is.null(side_by_side)) {
|
| 48 | 3x |
adsl1$lvl <- "Global" |
| 49 |
} |
|
| 50 | ||
| 51 | 8x |
lyt <- build_table_header(adsl1, arm, split_by_study = split_by_study, side_by_side = side_by_side) |
| 52 | ||
| 53 | 8x |
lyt <- lyt %>% |
| 54 | 8x |
count_values("SAFFL",
|
| 55 | 8x |
values = "Y", |
| 56 | 8x |
.labels = c(count_fraction = "Received Treatment") |
| 57 |
) %>% |
|
| 58 | 8x |
split_rows_by( |
| 59 | 8x |
"STDONSflag", |
| 60 | 8x |
split_fun = keep_split_levels("Y"),
|
| 61 |
) %>% |
|
| 62 | 8x |
summarize_row_groups(label_fstr = "On-study Status") %>% |
| 63 | 8x |
analyze_vars( |
| 64 | 8x |
"STDONS", |
| 65 | 8x |
.stats = "count_fraction", |
| 66 | 8x |
denom = "N_col", |
| 67 | 8x |
na.rm = TRUE, |
| 68 |
# var_labels = formatters::var_labels(adsl1)["STDONS"] |
|
| 69 |
) %>% |
|
| 70 | 8x |
split_rows_by( |
| 71 | 8x |
"DCSflag", |
| 72 | 8x |
split_fun = keep_split_levels("Y"),
|
| 73 |
) %>% |
|
| 74 | 8x |
summarize_row_groups(label_fstr = "Discontinued the study") %>% |
| 75 | 8x |
analyze_vars( |
| 76 | 8x |
"DCSREAS", |
| 77 | 8x |
.stats = "count_fraction", |
| 78 | 8x |
denom = "N_col" |
| 79 |
) |
|
| 80 | ||
| 81 | 8x |
result <- lyt_to_side_by_side(lyt, adsl1, side_by_side) |
| 82 | 8x |
result@main_title <- "Discontinue table" |
| 83 | 8x |
return(result) |
| 84 |
} |
| 1 |
#' s3 method for to_flextable |
|
| 2 |
#' @param x object to to_flextable |
|
| 3 |
#' @param ... additional arguments passed to methods |
|
| 4 |
to_flextable <- function(x, ...) {
|
|
| 5 | 316x |
UseMethod("to_flextable")
|
| 6 |
} |
|
| 7 | ||
| 8 | ||
| 9 |
#' default method to to_flextable |
|
| 10 |
#' @param x object to to_flextable |
|
| 11 |
#' @param ... additional arguments. not used. |
|
| 12 |
#' |
|
| 13 |
#' @export |
|
| 14 |
to_flextable.default <- function(x, ...) {
|
|
| 15 | ! |
stop("default to_flextable function does not exist")
|
| 16 |
} |
|
| 17 | ||
| 18 | ||
| 19 |
#' To flextable |
|
| 20 |
#' |
|
| 21 |
#' @details convert the dataframe object into flextable, and merge the cells |
|
| 22 |
#' that have colspan > 1. align the columns to the middle, and the row.names to |
|
| 23 |
#' the left. indent the row.names by 10 times indention. titles are added in headerlines, |
|
| 24 |
#' footnotes are added in footer lines, |
|
| 25 |
#' The width of the columns are aligned based on autofit() of officer function. |
|
| 26 |
#' For paginated table, the width of the 1st column are set as the widest 1st column among paginated tables |
|
| 27 |
#' @param x Decorated dataframe with title and footnote as attributes |
|
| 28 |
#' @param lpp \{lpp\} from \{paginate_table\}. numeric. Maximum lines per page
|
|
| 29 |
#' @param ... arguments passed to program |
|
| 30 |
#' |
|
| 31 |
#' @export |
|
| 32 |
#' |
|
| 33 |
to_flextable.Ddataframe <- function(x, lpp, ...) {
|
|
| 34 |
# paginate VTableTree |
|
| 35 |
Ddf <- x |
|
| 36 |
df <- Ddf@df |
|
| 37 | ||
| 38 |
page_max <- ceiling(nrow(df) / lpp) |
|
| 39 |
pag_df <- split(df, rep(1:page_max, each = lpp)) |
|
| 40 | ||
| 41 |
ft_list <- lapply(1:length(pag_df), function(x) {
|
|
| 42 |
ft <- to_flextable(pag_df[[x]], ...) |
|
| 43 |
list( |
|
| 44 |
ft = ft, |
|
| 45 |
header = ifelse(x == 1, Ddf@titles, paste(Ddf@titles, "(cont.)")), |
|
| 46 |
footnotes = Ddf@footnotes |
|
| 47 |
) |
|
| 48 |
}) |
|
| 49 | ||
| 50 |
# force the width of the 1st column to be the widest of all paginated table |
|
| 51 |
ft_list_resize <- set_width_widest(ft_list) |
|
| 52 |
class(ft_list_resize) <- "dflextable" |
|
| 53 | ||
| 54 |
ft_list_resize |
|
| 55 |
} |
|
| 56 | ||
| 57 |
#' To flextable |
|
| 58 |
#' |
|
| 59 |
#' Convert the dataframe into flextable, and merge the cells |
|
| 60 |
#' that have colspan > 1. align the columns to the middle, and the row.names to |
|
| 61 |
#' the left. indent the row.names by 10 times indention. |
|
| 62 |
#' |
|
| 63 |
#' @param x dataframe |
|
| 64 |
#' @param lpp \{lpp\} from \{paginate_table\}. numeric. Maximum lines per page
|
|
| 65 |
#' @param table_format Table format |
|
| 66 |
#' @export |
|
| 67 |
to_flextable.Ddataframe <- function(x, lpp, table_format = table_format, ...) {
|
|
| 68 | ! |
df <- x |
| 69 | ! |
if (all(is.na(formatters::var_labels(df)))) {
|
| 70 | ! |
formatters::var_labels(df) <- names(df) |
| 71 |
} |
|
| 72 | ! |
ft <- flextable(df) |
| 73 | ! |
ft <- set_header_labels(ft, values = as.list(formatters::var_labels(df))) |
| 74 | ||
| 75 |
# if(!is.null(apply_theme)){
|
|
| 76 |
# ft <- ft %>% |
|
| 77 |
# apply_theme() |
|
| 78 |
# } |
|
| 79 | ||
| 80 | ! |
ft <- ft %>% |
| 81 | ! |
align_text_col(align = "center", header = TRUE) %>% |
| 82 | ! |
align(i = seq_len(nrow(df)), j = 1, align = "left") %>% # row names align to left |
| 83 | ! |
border(border = fp_border(color = border_color, width = 1), part = "all") %>% |
| 84 | ! |
padding(padding.top = 3, padding.bottom = 3, part = "all") %>% |
| 85 | ! |
autofit(add_h = 0) %>% |
| 86 | ! |
table_format() |
| 87 | ||
| 88 | ! |
ft <- ft %>% |
| 89 | ! |
width(width = c( |
| 90 | ! |
dim(ft)$widths[1], |
| 91 | ! |
dim(ft)$widths[-1] - dim(ft)$widths[-1] + sum(dim(ft)$widths[-1]) / (ncol(df) - 1) |
| 92 | ! |
)) # even the non-label column width |
| 93 | ||
| 94 | ! |
if (flextable_dim(ft)$widths > 10) {
|
| 95 | ! |
pgwidth <- 10.5 |
| 96 | ! |
ft <- ft %>% |
| 97 | ! |
width(width = dim(ft)$widths * pgwidth / flextable_dim(ft)$widths) |
| 98 |
# adjust width of each column as percentage of total width |
|
| 99 |
} |
|
| 100 | ||
| 101 | ! |
return(ft) |
| 102 |
} |
|
| 103 | ||
| 104 |
#' convert gtsummary to flextable |
|
| 105 |
#' @export |
|
| 106 |
to_flextable.gtsummary <- function(x, ...) {
|
|
| 107 | ! |
ft <- x %>% |
| 108 | ! |
gtsummary::as_flex_table() |
| 109 | ||
| 110 | ! |
ft |
| 111 |
} |
|
| 112 | ||
| 113 |
#' convert dgtsummary to flextable |
|
| 114 |
#' @export |
|
| 115 |
to_flextable.dgtsummary <- function(x, ...) {
|
|
| 116 | 1x |
ft <- x %>% |
| 117 | 1x |
gtsummary::as_flex_table() |
| 118 |
} |
|
| 119 | ||
| 120 | ||
| 121 | ||
| 122 | ||
| 123 |
#' convert data.frame to flextable |
|
| 124 |
#' @export |
|
| 125 |
to_flextable.data.frame <- function(x, col_width = NULL, table_format = orange_format, |
|
| 126 |
dose_template = FALSE, font_size = 9, ...) {
|
|
| 127 | 272x |
df <- x |
| 128 | 272x |
ft <- do_call(flextable, data = df, ...) |
| 129 | ||
| 130 | 272x |
if (dose_template) {
|
| 131 | ! |
ft <- ft %>% |
| 132 | ! |
autofit() %>% |
| 133 | ! |
fit_to_width(10) |
| 134 |
} else {
|
|
| 135 | 272x |
if (all(is.na(formatters::var_labels(df)))) {
|
| 136 | ! |
formatters::var_labels(df) <- names(df) |
| 137 |
} |
|
| 138 | ||
| 139 | 272x |
ft <- set_header_labels(ft, values = as.list(formatters::var_labels(df))) |
| 140 | 272x |
ft <- ft %>% width(width = col_width) |
| 141 | 272x |
if (flextable_dim(ft)$widths > 10) {
|
| 142 | 272x |
pgwidth <- 10.5 |
| 143 | 272x |
ft <- ft %>% |
| 144 | 272x |
width(width = dim(ft)$widths * pgwidth / flextable_dim(ft)$widths) |
| 145 |
# adjust width of each column as percentage of total width |
|
| 146 |
} |
|
| 147 |
} |
|
| 148 | ||
| 149 | 272x |
ft <- ft %>% |
| 150 | 272x |
table_format(...) %>% |
| 151 | 272x |
fontsize(size = font_size, part = "all") |
| 152 | ||
| 153 | 272x |
return(ft) |
| 154 |
} |
|
| 155 | ||
| 156 | ||
| 157 |
old_paginate_listing <- function(lsting, |
|
| 158 |
page_type = "letter", |
|
| 159 |
font_family = "Courier", |
|
| 160 |
font_size = 8, |
|
| 161 |
lineheight = 1, |
|
| 162 |
landscape = FALSE, |
|
| 163 |
pg_width = NULL, |
|
| 164 |
pg_height = NULL, |
|
| 165 |
margins = c(top = .5, bottom = .5, left = .75, right = .75), |
|
| 166 |
lpp = NA_integer_, |
|
| 167 |
cpp = NA_integer_, |
|
| 168 |
colwidths = formatters::propose_column_widths(lsting), |
|
| 169 |
tf_wrap = !is.null(max_width), |
|
| 170 |
max_width = NULL, |
|
| 171 |
verbose = FALSE) {
|
|
| 172 | 1x |
checkmate::assert_class(lsting, "listing_df") |
| 173 | 1x |
checkmate::assert_numeric(colwidths, lower = 0, len = length(listing_dispcols(lsting)), null.ok = TRUE) |
| 174 | 1x |
checkmate::assert_flag(tf_wrap) |
| 175 | 1x |
checkmate::assert_count(max_width, null.ok = TRUE) |
| 176 | 1x |
checkmate::assert_flag(verbose) |
| 177 | ||
| 178 | 1x |
indx <- formatters::paginate_indices(lsting, |
| 179 | 1x |
page_type = page_type, |
| 180 | 1x |
font_family = font_family, |
| 181 | 1x |
font_size = font_size, |
| 182 | 1x |
lineheight = lineheight, |
| 183 | 1x |
landscape = landscape, |
| 184 | 1x |
pg_width = pg_width, |
| 185 | 1x |
pg_height = pg_height, |
| 186 | 1x |
margins = margins, |
| 187 | 1x |
lpp = lpp, |
| 188 | 1x |
cpp = cpp, |
| 189 | 1x |
colwidths = colwidths, |
| 190 | 1x |
tf_wrap = tf_wrap, |
| 191 | 1x |
max_width = max_width, |
| 192 | 1x |
rep_cols = length(get_keycols(lsting)), |
| 193 | 1x |
verbose = verbose |
| 194 |
) |
|
| 195 | ||
| 196 | 1x |
vert_pags <- lapply( |
| 197 | 1x |
indx$pag_row_indices, |
| 198 | 1x |
function(ii) lsting[ii, ] |
| 199 |
) |
|
| 200 | 1x |
dispnames <- listing_dispcols(lsting) |
| 201 | 1x |
full_pag <- lapply( |
| 202 | 1x |
vert_pags, |
| 203 | 1x |
function(onepag) {
|
| 204 | 272x |
if (!is.null(indx$pag_col_indices)) {
|
| 205 | 272x |
lapply( |
| 206 | 272x |
indx$pag_col_indices, |
| 207 | 272x |
function(jj) {
|
| 208 | 272x |
res <- onepag[, dispnames[jj], drop = FALSE] |
| 209 | 272x |
listing_dispcols(res) <- intersect(dispnames, names(res)) |
| 210 | 272x |
res |
| 211 |
} |
|
| 212 |
) |
|
| 213 |
} else {
|
|
| 214 | ! |
list(onepag) |
| 215 |
} |
|
| 216 |
} |
|
| 217 |
) |
|
| 218 | ||
| 219 | 1x |
ret <- unlist(full_pag, recursive = FALSE) |
| 220 | 1x |
ret |
| 221 |
} |
|
| 222 | ||
| 223 | ||
| 224 |
#' convert listing to flextable |
|
| 225 |
#' @export |
|
| 226 |
to_flextable.dlisting <- function(x, cpp, lpp, ...) {
|
|
| 227 | 1x |
ddf <- x |
| 228 | 1x |
df <- ddf@lst |
| 229 | 1x |
col_width <- ddf@width |
| 230 | 1x |
pag_df <- old_paginate_listing(df, cpp = cpp, lpp = lpp) |
| 231 | 1x |
ft_list <- lapply(1:length(pag_df), function(x) {
|
| 232 | 272x |
ft <- to_flextable(pag_df[[x]], col_width = col_width, ...) |
| 233 | 272x |
if (length(prov_footer(df)) == 0) {
|
| 234 | 272x |
cat_foot <- main_footer(df) |
| 235 |
} else {
|
|
| 236 | ! |
cat_foot <- paste0(prov_footer(df), "\n", main_footer(df)) |
| 237 |
} |
|
| 238 | ||
| 239 | 272x |
if (length(cat_foot) == 0) {
|
| 240 | ! |
cat_foot <- "" |
| 241 |
} |
|
| 242 | 272x |
list( |
| 243 | 272x |
ft = ft, |
| 244 | 272x |
header = ifelse(x == 1, main_title(df), paste(main_title(df), "(cont.)")), |
| 245 | 272x |
footnotes = cat_foot |
| 246 |
) |
|
| 247 |
}) |
|
| 248 |
# force the width of the 1st column to be the widest of all paginated table |
|
| 249 |
# ft_list_resize <- set_width_widest(ft_list) |
|
| 250 | 1x |
class(ft_list) <- "dflextable" |
| 251 | ||
| 252 | 1x |
ft_list |
| 253 |
} |
|
| 254 | ||
| 255 | ||
| 256 | ||
| 257 |
#' Covert rtables object to flextable |
|
| 258 |
#' |
|
| 259 |
#' @param x rtable(VTableTree) object |
|
| 260 |
#' @param table_format a function that decorate a flextable and return a flextable |
|
| 261 |
#' @export |
|
| 262 |
to_flextable.VTableTree <- function(x, table_format = orange_format, ...) {
|
|
| 263 | 24x |
tbl <- x |
| 264 | 24x |
mf <- formatters::matrix_form(tbl, indent_rownames = TRUE) |
| 265 | 24x |
nr_header <- attr(mf, "nrow_header") |
| 266 | 24x |
non_total_coln <- c(TRUE, !grepl("All Patients", names(tbl)))
|
| 267 | 24x |
df <- as.data.frame(mf$strings[(nr_header + 1):(nrow(mf$strings)), , drop = FALSE]) |
| 268 | ||
| 269 | 24x |
header_df <- as.data.frame(mf$strings[1:(nr_header), , drop = FALSE]) |
| 270 | ||
| 271 |
# if(concat_header){
|
|
| 272 |
# header_df <- lapply(header_df, function(x) {paste0(x, collapse = "\n")}) %>% as.data.frame
|
|
| 273 |
# } |
|
| 274 | ||
| 275 |
# if(!total_col){
|
|
| 276 |
# df <- df[non_total_coln] |
|
| 277 |
# header_df <- header_df[non_total_coln] |
|
| 278 |
# } |
|
| 279 | 24x |
ft <- do_call(flextable, data = df, ...) |
| 280 | 24x |
ft <- ft %>% |
| 281 | 24x |
delete_part(part = "header") %>% |
| 282 | 24x |
add_header(values = header_df) |
| 283 | ||
| 284 |
# if(!is.null(apply_theme)){
|
|
| 285 |
# ft <- ft %>% |
|
| 286 |
# apply_theme() |
|
| 287 |
# } |
|
| 288 | ||
| 289 | 24x |
ft <- do_call(table_format, ft = ft, ...) |
| 290 | 24x |
ft <- ft %>% |
| 291 | 24x |
merge_at_indice(lst = get_merge_index(mf$spans[(nr_header + 1):nrow(mf$spans), , drop = FALSE]), part = "body") %>% |
| 292 | 24x |
merge_at_indice(lst = get_merge_index(mf$spans[1:nr_header, , drop = FALSE]), part = "header") %>% |
| 293 | 24x |
align_text_col(align = "center", header = TRUE) %>% |
| 294 | 24x |
align(i = seq_len(nrow(tbl)), j = 1, align = "left") %>% # row names align to left |
| 295 | 24x |
padding_lst(mf$row_info$indent) %>% |
| 296 | 24x |
padding(padding.top = 3, padding.bottom = 3, part = "all") %>% |
| 297 | 24x |
autofit(add_h = 0) |
| 298 | ||
| 299 | ||
| 300 | 24x |
ft <- ft %>% |
| 301 | 24x |
width(width = c( |
| 302 | 24x |
dim(ft)$widths[1], |
| 303 | 24x |
dim(ft)$widths[-1] - dim(ft)$widths[-1] + sum(dim(ft)$widths[-1]) / (ncol(mf$strings) - 1) |
| 304 | 24x |
)) # even the non-label column width |
| 305 | ||
| 306 | 24x |
if (flextable_dim(ft)$widths > 10) {
|
| 307 | 11x |
pgwidth <- 10.5 |
| 308 | 11x |
ft <- ft %>% |
| 309 | 11x |
width(width = dim(ft)$widths * pgwidth / flextable_dim(ft)$widths) |
| 310 |
# adjust width of each column as percentage of total width |
|
| 311 |
} |
|
| 312 | ||
| 313 | 24x |
return(ft) |
| 314 |
} |
|
| 315 | ||
| 316 | ||
| 317 |
#' To flextable |
|
| 318 |
#' |
|
| 319 |
#' @param x decorated rtable(dVTableTree) object |
|
| 320 |
#' @param lpp \{lpp\} from \link[rtables]{paginate_table}. numeric. Maximum lines per page
|
|
| 321 |
#' @param ... argument parameters |
|
| 322 |
#' @details convert the VTableTree object into flextable, and merge the cells |
|
| 323 |
#' that have colspan > 1. align the columns to the middle, and the row.names to |
|
| 324 |
#' the left. indent the row.names by 10 times indention. titles are added in headerlines, |
|
| 325 |
#' footnotes are added in footer lines, |
|
| 326 |
#' The width of the columns are aligned based on autofit() of officer function. |
|
| 327 |
#' For paginated table, the width of the 1st column are set as the widest 1st column among paginated tables |
|
| 328 |
to_flextable.dVTableTree <- function(x, lpp, cpp, ...) {
|
|
| 329 | 19x |
dtbl <- x |
| 330 |
# paginate VTableTree |
|
| 331 | 19x |
pag_tbl <- paginate_table(dtbl@tbl, lpp = lpp, cpp = cpp) |
| 332 | 19x |
ft_list <- lapply(1:length(pag_tbl), function(x) {
|
| 333 | 23x |
ft <- to_flextable(pag_tbl[[x]], ...) |
| 334 | 23x |
if (length(dtbl@tbl@provenance_footer) == 0) {
|
| 335 | 23x |
cat_foot <- dtbl@footnotes |
| 336 |
} else {
|
|
| 337 | ! |
cat_foot <- paste0(dtbl@tbl@provenance_footer, "\n", dtbl@footnotes) |
| 338 |
} |
|
| 339 | ||
| 340 | 23x |
list( |
| 341 | 23x |
ft = ft, |
| 342 | 23x |
header = ifelse(x == 1, dtbl@titles, paste(dtbl@titles, "(cont.)")), |
| 343 | 23x |
footnotes = cat_foot |
| 344 |
) |
|
| 345 |
}) |
|
| 346 |
# force the width of the 1st column to be the widest of all paginated table |
|
| 347 | 19x |
ft_list_resize <- set_width_widest(ft_list) |
| 348 | 19x |
class(ft_list_resize) <- "dflextable" |
| 349 | ||
| 350 | 19x |
ft_list_resize |
| 351 |
} |
|
| 352 | ||
| 353 |
g_export <- function(decorated_p) {
|
|
| 354 | 4x |
ret <- list() |
| 355 | ||
| 356 | 4x |
ret$dml <- rvg::dml( |
| 357 | 4x |
ggobj = ggpubr::as_ggplot(decorated_p$grob), |
| 358 | 4x |
bg = "white", |
| 359 | 4x |
pointsize = 12, |
| 360 | 4x |
editable = TRUE |
| 361 |
) |
|
| 362 | 4x |
ret$footnote <- decorated_p$footnotes |
| 363 | 4x |
ret$spec <- attributes(decorated_p)$spec |
| 364 | ||
| 365 | 4x |
ret |
| 366 |
} |
|
| 367 | ||
| 368 |
set_width_widest <- function(ft_list) {
|
|
| 369 | 19x |
width1st <- max(unlist(lapply(ft_list, function(x) {
|
| 370 | 23x |
x$ft$body$colwidths[1] |
| 371 |
}))) |
|
| 372 | 19x |
for (i in 1:length(ft_list)) {
|
| 373 | 23x |
ft_list[[i]]$ft <- width(ft_list[[i]]$ft, 1, width = width1st) |
| 374 |
} |
|
| 375 | ||
| 376 | 19x |
ft_list |
| 377 |
} |
|
| 378 | ||
| 379 |
get_merge_index_single <- function(span) {
|
|
| 380 | 323x |
ret <- list() |
| 381 | 323x |
j <- 1 |
| 382 | 323x |
while (j < length(span)) {
|
| 383 | 1279x |
if (span[j] != 1) {
|
| 384 | 1x |
ret <- c(ret, list(j:(j + span[j] - 1))) |
| 385 |
} |
|
| 386 | 1279x |
j <- j + span[j] |
| 387 |
} |
|
| 388 | ||
| 389 | 323x |
ret |
| 390 |
} |
|
| 391 | ||
| 392 |
get_merge_index <- function(spans) {
|
|
| 393 | 48x |
ret <- lapply(seq_len(nrow(spans)), function(i) {
|
| 394 | 323x |
ri <- spans[i, ] |
| 395 | 323x |
r <- get_merge_index_single(ri) |
| 396 | 323x |
lapply(r, function(s) {
|
| 397 | 1x |
list(j = s, i = i) |
| 398 |
}) |
|
| 399 |
}) |
|
| 400 | 48x |
unlist(ret, recursive = FALSE, use.names = FALSE) |
| 401 |
} |
|
| 402 | ||
| 403 |
merge_at_indice <- function(ft, lst, part) {
|
|
| 404 | 48x |
Reduce(function(ft, ij) {
|
| 405 | 1x |
merge_at(ft, i = ij$i, j = ij$j, part = part) |
| 406 | 48x |
}, lst, ft) |
| 407 |
} |
|
| 408 | ||
| 409 |
padding_lst <- function(ft, indents) {
|
|
| 410 | 24x |
Reduce(function(ft, s) {
|
| 411 | 295x |
padding(ft, s, 1, padding.left = (indents[s] + 1) * 10) |
| 412 | 24x |
}, seq_len(length(indents)), ft) |
| 413 |
} |
| 1 |
get_deepseek_key <- function(filename = "DEEPSEEK_KEY") {
|
|
| 2 | ! |
scan(filename, what = "character", sep = NULL) |
| 3 |
} |
|
| 4 | ||
| 5 |
get_portkey_key <- function(filename = "PORTKEY_KEY") {
|
|
| 6 | ! |
scan(filename, what = "character", sep = NULL) |
| 7 |
} |
|
| 8 | ||
| 9 |
get_system_prompt <- function(text = "you are a Clinical data scientist expert") {
|
|
| 10 | ! |
return(text) |
| 11 |
} |
|
| 12 | ||
| 13 |
#' Get an `ellmer` chat API with given platform |
|
| 14 |
#' |
|
| 15 |
#' @param platform Platform provider |
|
| 16 |
#' @param base_url Base url |
|
| 17 |
#' @param api_key API key |
|
| 18 |
#' @param model Model of choice |
|
| 19 |
#' |
|
| 20 |
#' @export |
|
| 21 |
#' |
|
| 22 |
get_ellmer_chat <- function(platform = "deepseek", |
|
| 23 |
base_url = "https://api.deepseek.com", |
|
| 24 |
api_key = get_deepseek_key(), |
|
| 25 |
model = "deepseek-chat") {
|
|
| 26 | ! |
chat <- NULL |
| 27 | ! |
if (platform == "deepseek") {
|
| 28 | ! |
chat <- ellmer::chat_deepseek( |
| 29 | ! |
system_prompt = get_system_prompt(), |
| 30 | ! |
base_url = base_url, |
| 31 | ! |
api_key = api_key, |
| 32 | ! |
model = model |
| 33 |
) |
|
| 34 | ! |
} else if (platform == "galileo") {
|
| 35 | ! |
chat <- ellmer::chat_portkey( |
| 36 | ! |
system_prompt = get_system_prompt(), |
| 37 | ! |
base_url = base_url, |
| 38 | ! |
api_key = api_key, |
| 39 | ! |
model = model |
| 40 |
) |
|
| 41 | ! |
} else if (platform == "openai") {
|
| 42 | ! |
chat <- ellmer::chat_openai( |
| 43 | ! |
system_prompt = get_system_prompt(), |
| 44 | ! |
base_url = base_url, |
| 45 | ! |
api_key = api_key, |
| 46 | ! |
model = model |
| 47 |
) |
|
| 48 | ! |
} else if (platform == "ollama") {
|
| 49 | ! |
chat <- ellmer::chat_ollama( |
| 50 | ! |
system_prompt = get_system_prompt(), |
| 51 | ! |
base_url = base_url, |
| 52 | ! |
model = model |
| 53 |
) |
|
| 54 |
} |
|
| 55 | ||
| 56 | ! |
return(chat) |
| 57 |
} |
|
| 58 | ||
| 59 |
#' Read prompt list from yaml file |
|
| 60 |
#' |
|
| 61 |
#' @param filename File name |
|
| 62 |
#' |
|
| 63 |
#' @export |
|
| 64 |
get_prompt_list <- function(filename) {
|
|
| 65 | ! |
prompt <- yaml::read_yaml(filename, eval.expr = TRUE) |
| 66 | ! |
structure( |
| 67 | ! |
.Data = prompt, |
| 68 | ! |
names = map_chr(prompt, `[[`, "output"), |
| 69 | ! |
class = union("spec", class(prompt))
|
| 70 |
) |
|
| 71 |
} |
|
| 72 | ||
| 73 | ||
| 74 |
integrate_prompt <- function(base_prompt, tlg) {
|
|
| 75 |
# let's do figures in the future |
|
| 76 | ! |
ret <- paste( |
| 77 | ! |
"As a Clinical data scientist expert, here is a", |
| 78 | ! |
tlg@main_title, ":\n\n" |
| 79 |
) |
|
| 80 | ! |
ret <- gsub("\\{table_text\\}", export_as_txt(tlg), base_prompt)
|
| 81 | ! |
ret |
| 82 |
} |
|
| 83 | ||
| 84 |
#' Update footnote with AI response |
|
| 85 |
#' |
|
| 86 |
#' @param outputs Output objects |
|
| 87 |
#' @param prompt_list List of prompt |
|
| 88 |
#' @param platform Platform provider |
|
| 89 |
#' @param base_url Base url |
|
| 90 |
#' @param api_key API key |
|
| 91 |
#' @param model Model of choice |
|
| 92 |
#' |
|
| 93 |
#' @export |
|
| 94 |
get_ai_notes <- function(outputs, prompt_list, platform, base_url, api_key, model) {
|
|
| 95 | ! |
chat <- get_ellmer_chat(platform, base_url, api_key, model) |
| 96 | ! |
names_outputs <- names(outputs) |
| 97 | ! |
ret <- lapply(names_outputs, function(output_name) {
|
| 98 | ! |
output <- outputs[[output_name]] |
| 99 | ! |
if (is(output, "autoslider_error")) {
|
| 100 | ! |
return(output) |
| 101 |
} |
|
| 102 | ! |
if (output_name %in% names(prompt_list)) {
|
| 103 | ! |
base_prompt <- prompt_list[[output_name]]$prompt |
| 104 | ! |
current_prompt <- integrate_prompt(base_prompt, output@tbl) |
| 105 | ! |
raw_response <- chat$chat(current_prompt) |
| 106 | ! |
clean_response <- sub(".*?</think>\\s*", "", raw_response)
|
| 107 | ! |
output@usernotes <- paste(platform, model, "generated notes:", clean_response) |
| 108 |
} |
|
| 109 | ! |
output |
| 110 |
}) |
|
| 111 | ! |
names(ret) <- names_outputs |
| 112 | ! |
ret |
| 113 |
} |
| 1 |
#' Plot mean values of LB |
|
| 2 |
#' |
|
| 3 |
#' Wrapper for `g_mean_general()`. |
|
| 4 |
#' Requires filtering of the datasets (e.g. using SUFFIX in spec.yml) |
|
| 5 |
#' |
|
| 6 |
#' @param adsl ADSL data |
|
| 7 |
#' @param adlb ADLB data |
|
| 8 |
#' @param arm `"TRT01P"` by default |
|
| 9 |
#' @param paramcd character scalar. defaults to By default `"PARAM"` |
|
| 10 |
#' Which variable to use for plotting. |
|
| 11 |
#' @param y character scalar. Variable to plot on the Y axis. By default `"AVAL"` |
|
| 12 |
#' @inheritParams g_mean_general |
|
| 13 |
#' @param ... | |
|
| 14 |
#' Gets forwarded to `tern::g_lineplot()`. |
|
| 15 |
#' This lets you specify additional arguments to `tern::g_lineplot()` |
|
| 16 |
#' @author Stefan Thoma (`thomas7`) |
|
| 17 |
#' @export |
|
| 18 |
#' @examplesIf require('rsvg')
|
|
| 19 |
#' library(dplyr) |
|
| 20 |
#' |
|
| 21 |
#' adlb_filtered <- eg_adlb %>% filter( |
|
| 22 |
#' PARAMCD == "CRP" |
|
| 23 |
#' ) |
|
| 24 |
#' plot_lb <- g_lb_slide( |
|
| 25 |
#' adsl = eg_adsl, |
|
| 26 |
#' adlb = adlb_filtered, |
|
| 27 |
#' paramcd = "PARAM", |
|
| 28 |
#' subtitle_add_unit = FALSE |
|
| 29 |
#' ) + |
|
| 30 |
#' ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) |
|
| 31 |
#' generate_slides(plot_lb, paste0(tempdir(), "/g_lb.pptx")) |
|
| 32 |
#' |
|
| 33 |
#' # Let's plot change values: |
|
| 34 |
#' plot_lb_chg <- g_lb_slide( |
|
| 35 |
#' adsl = eg_adsl, |
|
| 36 |
#' adlb = adlb_filtered, |
|
| 37 |
#' paramcd = "PARAM", |
|
| 38 |
#' y = "CHG", |
|
| 39 |
#' subtitle = "Plot of change from baseline and 95% Confidence Limit by Visit." |
|
| 40 |
#' ) |
|
| 41 |
#' generate_slides(plot_lb_chg, paste0(tempdir(), "/g_lb_chg.pptx")) |
|
| 42 |
#' |
|
| 43 |
g_lb_slide <- function(adsl, adlb, arm = "TRT01P", paramcd = "PARAM", y = "AVAL", |
|
| 44 |
subtitle = "Plot of Mean and 95% Confidence Limits by Visit.", ...) {
|
|
| 45 |
# tern 0.9.4 added facet_var in control_lineplot_vars |
|
| 46 | 3x |
variables <- control_lineplot_vars(group_var = arm, paramcd = paramcd, y = y) %>% |
| 47 | 3x |
strip_NA() |
| 48 | ||
| 49 | 2x |
by_vars <- c("USUBJID", "STUDYID")
|
| 50 | 2x |
assert_that(is.string(arm)) |
| 51 | 2x |
assert_that(is.string(paramcd)) |
| 52 | 2x |
assert_that(is.string(y)) |
| 53 | 2x |
assert_that(has_name(adlb, c(by_vars, variables) %>% unique())) |
| 54 | 2x |
assert_that(has_name(adsl, c(by_vars, arm) %>% unique())) |
| 55 | 2x |
assert_that(is.string(subtitle)) |
| 56 | ||
| 57 | 2x |
g_mean_general( |
| 58 | 2x |
adsl = adsl, data = adlb, variables = variables, by_vars = by_vars, |
| 59 | 2x |
subtitle = subtitle, ... |
| 60 |
) |
|
| 61 |
} |
| 1 |
#' Adverse event table |
|
| 2 |
#' |
|
| 3 |
#' @param adae ADAE data set, dataframe |
|
| 4 |
#' @param adsl ADSL data set, dataframe |
|
| 5 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 6 |
#' @param cutoff Cutoff threshold |
|
| 7 |
#' @param prune_by_total Prune according total column |
|
| 8 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 9 |
#' @param side_by_side A logical value indicating whether to display the data side by side. |
|
| 10 |
#' @return rtables object |
|
| 11 |
#' @inherit gen_notes note |
|
| 12 |
#' @export |
|
| 13 |
#' @examples |
|
| 14 |
#' |
|
| 15 |
#' library(dplyr) |
|
| 16 |
#' # Example 1 |
|
| 17 |
#' adsl <- eg_adsl %>% |
|
| 18 |
#' dplyr::mutate(TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")))
|
|
| 19 |
#' adae <- eg_adae %>% |
|
| 20 |
#' dplyr::mutate( |
|
| 21 |
#' TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")),
|
|
| 22 |
#' ATOXGR = AETOXGR |
|
| 23 |
#' ) |
|
| 24 |
#' out <- t_ae_pt_slide(adsl, adae, "TRT01A", 2) |
|
| 25 |
#' print(out) |
|
| 26 |
#' generate_slides(out, paste0(tempdir(), "/ae.pptx")) |
|
| 27 |
#' |
|
| 28 |
#' # Example 2, prune by total column |
|
| 29 |
#' out2 <- t_ae_pt_slide(adsl, adae, "TRT01A", 25, prune_by_total = TRUE) |
|
| 30 |
#' print(out2) |
|
| 31 |
#' generate_slides(out, paste0(tempdir(), "/ae2.pptx")) |
|
| 32 |
t_ae_pt_slide <- function(adsl, adae, arm = "TRT01A", cutoff = NA, prune_by_total = FALSE, |
|
| 33 |
split_by_study = FALSE, |
|
| 34 |
side_by_side = NULL) {
|
|
| 35 | 6x |
cutoff <- check_and_set_cutoff(adae, cutoff) |
| 36 | 6x |
result <- t_ae_pt_core(adsl, adae, arm, cutoff, |
| 37 | 6x |
diff = FALSE, soc = "NULL", |
| 38 | 6x |
prune_by_total = prune_by_total, |
| 39 | 6x |
split_by_study, |
| 40 | 6x |
side_by_side |
| 41 |
) |
|
| 42 | 6x |
result@main_title <- "Adverse Events table" |
| 43 | ||
| 44 | 6x |
if (is.null(side_by_side)) {
|
| 45 |
# adding "N" attribute |
|
| 46 | 6x |
arm <- col_paths(result)[[1]][1] |
| 47 | ||
| 48 | 6x |
n_r <- data.frame( |
| 49 | 6x |
ARM = toupper(names(result@col_info)), |
| 50 | 6x |
N = col_counts(result) %>% as.numeric() |
| 51 |
) %>% |
|
| 52 | 6x |
`colnames<-`(c(paste(arm), "N")) %>% |
| 53 | 6x |
arrange(get(arm)) |
| 54 | ||
| 55 | 6x |
attr(result, "N") <- n_r |
| 56 |
} |
|
| 57 | 6x |
result |
| 58 |
} |
| 1 |
abort <- function(...) {
|
|
| 2 | 13x |
stop(..., call. = FALSE) |
| 3 |
} |
|
| 4 | ||
| 5 |
assert_is_character_scalar <- function(x) {
|
|
| 6 | 178x |
if (length(x) != 1L || is.na(x) || !is.character(x) || x == "") {
|
| 7 | 5x |
abort("`", deparse(substitute(x)), "` must be a character scalar.")
|
| 8 |
} |
|
| 9 |
} |
|
| 10 | ||
| 11 |
assert_is_valid_version_label <- function(x) {
|
|
| 12 | 2x |
if (!(x %in% c("DRAFT", "APPROVED") || is.null(x))) {
|
| 13 | ! |
abort("Version label must be 'DRAFT', 'APPROVED' or `NULL` but is '", x, "'.")
|
| 14 |
} |
|
| 15 |
} |
|
| 16 | ||
| 17 | ||
| 18 |
assert_exists_in_spec_or_calling_env <- function(vars, output, env = parent.frame()) {
|
|
| 19 | 108x |
exist_in_spec <- vars %in% names(output) |
| 20 |
# explicitly define env to use, better practice for testing |
|
| 21 | 108x |
exist_in_calling_env <- map_lgl(vars, exists, envir = env) |
| 22 | ||
| 23 | 108x |
non_existing_vars <- vars[!(exist_in_spec | exist_in_calling_env)] |
| 24 | ||
| 25 | ||
| 26 | 108x |
n <- length(non_existing_vars) |
| 27 | 108x |
if (n >= 1L) {
|
| 28 | 1x |
err_msg <- sprintf( |
| 29 | 1x |
paste( |
| 30 | 1x |
"Cannot filter based upon the %s %s as %s not contained in", |
| 31 | 1x |
"`spec` or the surrounding environment." |
| 32 |
), |
|
| 33 | 1x |
if (n == 1L) "variable" else "variables", |
| 34 | 1x |
enumerate(non_existing_vars), |
| 35 | 1x |
if (n == 1L) "it is" else "they are" |
| 36 |
) |
|
| 37 | 1x |
stop(err_msg, call. = FALSE) |
| 38 |
} |
|
| 39 |
} |
|
| 40 | ||
| 41 |
assert_is_valid_filter_result <- function(x) {
|
|
| 42 | 113x |
if (length(x) != 1L || is.na(x) || !is.logical(x)) {
|
| 43 | 6x |
stop( |
| 44 | 6x |
"`filter_expr` must evaluate to a logical scalar but returned `", |
| 45 | 6x |
deparse(x), "`.", |
| 46 | 6x |
call. = FALSE |
| 47 |
) |
|
| 48 |
} |
|
| 49 |
} |
| 1 |
#' Table of AEs of Special Interest |
|
| 2 |
#' adapted from https://insightsengineering.github.io/tlg-catalog/stable/tables/adverse-events/aet01_aesi.html |
|
| 3 |
#' @param adsl ADSL data set, dataframe |
|
| 4 |
#' @param adae ADAE data set, dataframe. |
|
| 5 |
#' @param aesi AESI variable which will act as a filter to select the rows required to create the table. |
|
| 6 |
#' An example of AESI variable is CQ01NAM. |
|
| 7 |
#' @param arm Arm variable, character, `"ACTARM"` by default. |
|
| 8 |
#' @param grad_var Grading variable, character, `"AETOXGR"` by default. |
|
| 9 |
#' |
|
| 10 |
#' @return rtables object |
|
| 11 |
#' @export |
|
| 12 |
#' @author Kai Xiang Lim (`limk43`) |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' library(dplyr) |
|
| 16 |
#' adsl <- eg_adsl |
|
| 17 |
#' adae <- eg_adae |
|
| 18 |
#' adae_atoxgr <- adae %>% dplyr::mutate(ATOXGR = AETOXGR) |
|
| 19 |
#' t_aesi_slide(adsl, adae, aesi = "CQ01NAM") |
|
| 20 |
#' t_aesi_slide(adsl, adae, aesi = "CQ01NAM", arm = "ARM", grad_var = "AESEV") |
|
| 21 |
#' t_aesi_slide(adsl, adae_atoxgr, aesi = "CQ01NAM", grad_var = "ATOXGR") |
|
| 22 |
#' |
|
| 23 |
t_aesi_slide <- function(adsl, adae, aesi, arm = "ACTARM", grad_var = "AETOXGR") {
|
|
| 24 | 6x |
assert_that(has_name(adsl, arm)) |
| 25 | 6x |
assert_that(has_name(adae, "AEACN")) |
| 26 | 6x |
assert_that(has_name(adae, "AEOUT")) |
| 27 | 6x |
assert_that(has_name(adae, "AECONTRT")) |
| 28 | 6x |
assert_that(has_name(adae, "AESER")) |
| 29 | 6x |
assert_that(has_name(adae, "AEREL")) |
| 30 | 6x |
assert_that(has_name(adae, grad_var)) |
| 31 | 6x |
assert_that(has_name(adae, "AECONTRT")) |
| 32 | ||
| 33 | 6x |
aesi_sym <- rlang::sym(aesi) |
| 34 | ||
| 35 | ||
| 36 | 5x |
adae2 <- filter(adae, is.na(!!aesi_sym)) |
| 37 | ||
| 38 | 4x |
adsl <- df_explicit_na(adsl) |
| 39 | 4x |
adae2 <- df_explicit_na(adae2) |
| 40 | ||
| 41 |
# Merge ADAE with ADSL and ensure character variables are converted to factors and empty |
|
| 42 |
# strings and NAs are explicit missing levels. |
|
| 43 | 4x |
adae2 <- adsl %>% |
| 44 | 4x |
inner_join(adae2, by = c("USUBJID", "TRT01A", "TRT01P", "ARM", "ARMCD", "ACTARM", "ACTARMCD")) %>%
|
| 45 | 4x |
df_explicit_na() |
| 46 | ||
| 47 | 4x |
not_resolved <- adae2 %>% |
| 48 | 4x |
filter(!(AEOUT %in% c("RECOVERED/RESOLVED", "FATAL", "RECOVERED/RESOLVED WITH SEQUELAE"))) %>%
|
| 49 | 4x |
distinct(USUBJID) %>% |
| 50 | 4x |
mutate(NOT_RESOLVED = "Y") |
| 51 | ||
| 52 | 4x |
adae2 <- adae2 %>% |
| 53 | 4x |
left_join(not_resolved, by = c("USUBJID")) %>%
|
| 54 | 4x |
mutate( |
| 55 | 4x |
ALL_RESOLVED = formatters::with_label( |
| 56 | 4x |
is.na(NOT_RESOLVED), |
| 57 | 4x |
"Total number of patients with all non-fatal AESIs resolved" |
| 58 |
), |
|
| 59 | 4x |
NOT_RESOLVED = formatters::with_label( |
| 60 | 4x |
!is.na(NOT_RESOLVED), |
| 61 | 4x |
"Total number of patients with at least one unresolved or ongoing non-fatal AESI" |
| 62 |
) |
|
| 63 |
) |
|
| 64 | ||
| 65 | 4x |
adae2 <- adae2 %>% |
| 66 | 4x |
mutate( |
| 67 | 4x |
AEDECOD = as.character(AEDECOD), |
| 68 | 4x |
WD = formatters::with_label( |
| 69 | 4x |
AEACN == "DRUG WITHDRAWN", "Total number of patients with study drug withdrawn due to AESI" |
| 70 |
), |
|
| 71 | 4x |
DSM = formatters::with_label( |
| 72 | 4x |
AEACN %in% c("DRUG INTERRUPTED", "DOSE INCREASED", "DOSE REDUCED"),
|
| 73 | 4x |
"Total number of patients with dose modified/interrupted due to AESI" |
| 74 |
), |
|
| 75 | 4x |
CONTRT = formatters::with_label(AECONTRT == "Y", "Total number of patients with treatment received for AESI"), |
| 76 | 4x |
SER = formatters::with_label(AESER == "Y", "Total number of patients with at least one serious AESI"), |
| 77 | 4x |
REL = formatters::with_label(AEREL == "Y", "Total number of patients with at least one related AESI"), |
| 78 | 4x |
ALL_RESOLVED_WD = formatters::with_label( |
| 79 | 4x |
WD == TRUE & ALL_RESOLVED == TRUE, |
| 80 | 4x |
"No. of patients with study drug withdrawn due to resolved AESI" |
| 81 |
), |
|
| 82 | 4x |
ALL_RESOLVED_DSM = formatters::with_label( |
| 83 | 4x |
DSM == TRUE & ALL_RESOLVED == TRUE, |
| 84 | 4x |
"No. of patients with dose modified/interrupted due to resolved AESI" |
| 85 |
), |
|
| 86 | 4x |
ALL_RESOLVED_CONTRT = formatters::with_label( |
| 87 | 4x |
CONTRT == TRUE & ALL_RESOLVED == TRUE, |
| 88 | 4x |
"No. of patients with treatment received for resolved AESI" |
| 89 |
), |
|
| 90 | 4x |
NOT_RESOLVED_WD = formatters::with_label( |
| 91 | 4x |
WD == TRUE & NOT_RESOLVED == TRUE, |
| 92 | 4x |
"No. of patients with study drug withdrawn due to unresolved or ongoing AESI" |
| 93 |
), |
|
| 94 | 4x |
NOT_RESOLVED_DSM = formatters::with_label( |
| 95 | 4x |
DSM == TRUE & NOT_RESOLVED == TRUE, |
| 96 | 4x |
"No. of patients with dose modified/interrupted due to unresolved or ongoing AESI" |
| 97 |
), |
|
| 98 | 4x |
NOT_RESOLVED_CONTRT = formatters::with_label( |
| 99 | 4x |
CONTRT == TRUE & NOT_RESOLVED == TRUE, |
| 100 | 4x |
"No. of patients with treatment received for unresolved or ongoing AESI" |
| 101 |
), |
|
| 102 | 4x |
SERWD = formatters::with_label( |
| 103 | 4x |
AESER == "Y" & AEACN == "DRUG WITHDRAWN", |
| 104 | 4x |
"No. of patients with study drug withdrawn due to serious AESI" |
| 105 |
), |
|
| 106 | 4x |
SERCONTRT = formatters::with_label( |
| 107 | 4x |
AECONTRT == "Y" & AESER == "Y", |
| 108 | 4x |
"No. of patients with dose modified/interrupted due to serious AESI" |
| 109 |
), |
|
| 110 | 4x |
SERDSM = formatters::with_label( |
| 111 | 4x |
AESER == "Y" & AEACN %in% c("DRUG INTERRUPTED", "DOSE INCREASED", "DOSE REDUCED"),
|
| 112 | 4x |
"No. of patients with treatment received for serious AESI" |
| 113 |
), |
|
| 114 | 4x |
RELWD = formatters::with_label( |
| 115 | 4x |
AEREL == "Y" & AEACN == "DRUG WITHDRAWN", |
| 116 | 4x |
"No. of patients with study drug withdrawn due to related AESI" |
| 117 |
), |
|
| 118 | 4x |
RELDSM = formatters::with_label( |
| 119 | 4x |
AEREL == "Y" & AEACN %in% c("DRUG INTERRUPTED", "DOSE INCREASED", "DOSE REDUCED"),
|
| 120 | 4x |
"No. of patients with dose modified/interrupted due to related AESI" |
| 121 |
), |
|
| 122 | 4x |
RELCONTRT = formatters::with_label( |
| 123 | 4x |
AECONTRT == "Y" & AEREL == "Y", |
| 124 | 4x |
"No. of patients with treatment received for related AESI" |
| 125 |
), |
|
| 126 | 4x |
RELSER = formatters::with_label(AESER == "Y" & AEREL == "Y", "No. of patients with serious, related AESI") |
| 127 |
) |
|
| 128 | ||
| 129 | 4x |
if (grad_var %in% c("AETOXGR", "ATOXGR")) {
|
| 130 | 2x |
adae2 <- adae2 %>% |
| 131 | 2x |
mutate( |
| 132 | 2x |
{{ grad_var }} := forcats::fct_recode(get(grad_var),
|
| 133 | 2x |
"Grade 1" = "1", |
| 134 | 2x |
"Grade 2" = "2", |
| 135 | 2x |
"Grade 3" = "3", |
| 136 | 2x |
"Grade 4" = "4", |
| 137 | 2x |
"Grade 5 (fatal outcome)" = "5" |
| 138 |
) |
|
| 139 |
) |
|
| 140 | 2x |
} else if (grad_var %in% c("AESEV", "ASEV")) {
|
| 141 | 1x |
adae2 <- adae2 %>% |
| 142 | 1x |
mutate( |
| 143 | 1x |
{{ grad_var }} := forcats::fct_recode(stringr::str_to_title(get(grad_var), locale = "en"))
|
| 144 |
) |
|
| 145 |
} |
|
| 146 | ||
| 147 | 4x |
aesi_vars <- c("WD", "DSM", "CONTRT", "ALL_RESOLVED", "NOT_RESOLVED", "SER", "REL")
|
| 148 | ||
| 149 | 4x |
lyt_adae <- basic_table(show_colcounts = TRUE) %>% |
| 150 | 4x |
split_cols_by(arm) %>% |
| 151 | 4x |
count_patients_with_event( |
| 152 | 4x |
vars = "USUBJID", |
| 153 | 4x |
filters = c("ANL01FL" = "Y"),
|
| 154 | 4x |
denom = "N_col", |
| 155 | 4x |
.labels = c(count_fraction = "Total number of patients with at least one AESI") |
| 156 |
) %>% |
|
| 157 | 4x |
count_values( |
| 158 | 4x |
"ANL01FL", |
| 159 | 4x |
values = "Y", |
| 160 | 4x |
.stats = "count", |
| 161 | 4x |
.labels = c(count = "Total number of AESIs"), |
| 162 | 4x |
table_names = "total_aes" |
| 163 |
) %>% |
|
| 164 | 4x |
count_occurrences_by_grade( |
| 165 | 4x |
var = grad_var, |
| 166 | 4x |
var_labels = "Total number of patients with at least one AESI by worst grade", |
| 167 | 4x |
show_labels = "visible" |
| 168 |
) %>% |
|
| 169 | 4x |
count_patients_with_flags("USUBJID", flag_variables = aesi_vars, denom = "N_col")
|
| 170 | ||
| 171 | 4x |
result <- build_table(lyt_adae, df = adae2, alt_counts_df = adsl) |
| 172 | ||
| 173 | ||
| 174 | 4x |
result |
| 175 |
} |
| 1 |
#' Demographic table |
|
| 2 |
#' |
|
| 3 |
#' @param adsl ADSL data set, dataframe |
|
| 4 |
#' @param arm Arm variable, character, "`TRT01P" by default. |
|
| 5 |
#' @param vars Characters of variables |
|
| 6 |
#' @param stats see `.stats` from [tern::analyze_vars()] |
|
| 7 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 8 |
#' @param side_by_side "GlobalAsia" or "GlobalAsiaChina" to define the side by side requirement |
|
| 9 |
#' @return rtables object |
|
| 10 |
#' @inherit gen_notes note |
|
| 11 |
#' @export |
|
| 12 |
#' @examples |
|
| 13 |
#' library(dplyr) |
|
| 14 |
#' adsl <- eg_adsl |
|
| 15 |
#' out1 <- t_dm_slide(adsl, "TRT01P", c("SEX", "AGE", "RACE", "ETHNIC", "COUNTRY"))
|
|
| 16 |
#' print(out1) |
|
| 17 |
#' generate_slides(out1, paste0(tempdir(), "/dm.pptx")) |
|
| 18 |
#' |
|
| 19 |
#' out2 <- t_dm_slide(adsl, "TRT01P", c("SEX", "AGE", "RACE", "ETHNIC", "COUNTRY"),
|
|
| 20 |
#' split_by_study = TRUE |
|
| 21 |
#' ) |
|
| 22 |
#' print(out2) |
|
| 23 |
#' |
|
| 24 |
t_dm_slide <- function(adsl, |
|
| 25 |
arm = "TRT01P", |
|
| 26 |
vars = c("AGE", "SEX", "RACE"),
|
|
| 27 |
stats = c("median", "range", "count_fraction"),
|
|
| 28 |
split_by_study = FALSE, |
|
| 29 |
side_by_side = NULL) {
|
|
| 30 | 13x |
if (is.null(side_by_side)) {
|
| 31 | 8x |
extra <- NULL |
| 32 |
} else {
|
|
| 33 | 5x |
extra <- c("COUNTRY")
|
| 34 |
} |
|
| 35 | ||
| 36 | 13x |
for (v in c(vars, extra)) {
|
| 37 | 60x |
assert_that(has_name(adsl, v)) |
| 38 |
} |
|
| 39 | ||
| 40 | 13x |
adsl1 <- adsl %>% |
| 41 | 13x |
select(all_of(c("STUDYID", "USUBJID", arm, vars, extra)))
|
| 42 | ||
| 43 | 13x |
if (!is.null(side_by_side)) {
|
| 44 | 5x |
adsl1$lvl <- "Global" |
| 45 |
} |
|
| 46 | ||
| 47 | 13x |
lyt <- build_table_header(adsl1, arm, |
| 48 | 13x |
split_by_study = split_by_study, |
| 49 | 13x |
side_by_side = side_by_side |
| 50 |
) |
|
| 51 | ||
| 52 | 13x |
lyt <- lyt %>% |
| 53 | 13x |
analyze_vars( |
| 54 | 13x |
na.rm = TRUE, |
| 55 | 13x |
.stats = stats, |
| 56 | 13x |
denom = "n", |
| 57 | 13x |
vars = vars, |
| 58 | 13x |
.formats = c(mean_sd = "xx.xx (xx.xx)", median = "xx.xx"), |
| 59 | 13x |
var_labels = formatters::var_labels(adsl1)[vars] |
| 60 |
) |
|
| 61 | ||
| 62 | 13x |
result <- lyt_to_side_by_side(lyt, adsl1, side_by_side) |
| 63 | ||
| 64 | 13x |
if (is.null(side_by_side)) {
|
| 65 |
# adding "N" attribute |
|
| 66 | 8x |
arm <- col_paths(result)[[1]][1] |
| 67 | ||
| 68 | 8x |
n_r <- data.frame( |
| 69 | 8x |
ARM = toupper(names(result@col_info)), |
| 70 | 8x |
N = col_counts(result) %>% as.numeric() |
| 71 |
) %>% |
|
| 72 | 8x |
`colnames<-`(c(paste(arm), "N")) %>% |
| 73 | 8x |
dplyr::arrange(get(arm)) |
| 74 | ||
| 75 | 8x |
attr(result, "N") <- n_r |
| 76 |
} |
|
| 77 | 13x |
result@main_title <- "Demographic slide" |
| 78 | 13x |
result |
| 79 |
} |
| 1 |
#' Adverse event table |
|
| 2 |
#' |
|
| 3 |
#' @param adae ADAE data set, dataframe |
|
| 4 |
#' @param adsl ADSL data set, dataframe |
|
| 5 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 6 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 7 |
#' @param side_by_side should table be displayed side by side |
|
| 8 |
#' @return rtables object |
|
| 9 |
#' @inherit gen_notes note |
|
| 10 |
#' @export |
|
| 11 |
#' @examples |
|
| 12 |
#' library(dplyr) |
|
| 13 |
#' adsl <- eg_adsl %>% |
|
| 14 |
#' dplyr::mutate(TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")))
|
|
| 15 |
#' adae <- eg_adae %>% |
|
| 16 |
#' dplyr::mutate( |
|
| 17 |
#' TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")),
|
|
| 18 |
#' ATOXGR = AETOXGR |
|
| 19 |
#' ) |
|
| 20 |
#' out <- t_ae_slide(adsl, adae, "TRT01A") |
|
| 21 |
#' print(out) |
|
| 22 |
#' generate_slides(out, paste0(tempdir(), "/ae.pptx")) |
|
| 23 |
t_ae_slide <- function(adsl, adae, arm = "TRT01A", |
|
| 24 |
split_by_study = FALSE, side_by_side = NULL) {
|
|
| 25 | 3x |
assert_that(has_name(adae, "AEDECOD")) |
| 26 | 3x |
assert_that(has_name(adae, "ATOXGR")) |
| 27 | 3x |
assert_that(has_name(adae, "AEBODSYS")) |
| 28 | ||
| 29 | 3x |
slref_arm <- sort(unique(adsl[[arm]])) |
| 30 | 3x |
anl_arm <- sort(unique(adae[[arm]])) |
| 31 | 3x |
assert_that(identical(slref_arm, anl_arm), |
| 32 | 3x |
msg = "The adsl and the analysis datasets should have the same treatment arm levels" |
| 33 |
) |
|
| 34 | ||
| 35 | 3x |
anl <- adae %>% |
| 36 | 3x |
mutate_at( |
| 37 | 3x |
c("AEDECOD", "AEBODSYS"),
|
| 38 | 3x |
~ explicit_na(sas_na(.)) # Replace blank arm with <Missing> |
| 39 |
) %>% |
|
| 40 | 3x |
semi_join(., adsl, by = c("STUDYID", "USUBJID")) %>%
|
| 41 | 3x |
mutate( |
| 42 | 3x |
AETOXGR = sas_na(AETOXGR) %>% as.factor() |
| 43 |
) %>% |
|
| 44 | 3x |
formatters::var_relabel( |
| 45 | 3x |
AEBODSYS = "MedDRA System Organ Class", |
| 46 | 3x |
AEDECOD = "MedDRA Preferred Term" |
| 47 |
) |
|
| 48 | ||
| 49 | 3x |
if (!is.null(side_by_side)) {
|
| 50 | ! |
anl$lvl <- "Global" |
| 51 |
} |
|
| 52 | ||
| 53 | 3x |
if (nrow(anl) == 0) {
|
| 54 | 1x |
return(null_report()) |
| 55 |
} else {
|
|
| 56 | 2x |
lyt <- build_table_header(adsl, arm, |
| 57 | 2x |
split_by_study = split_by_study, |
| 58 | 2x |
side_by_side = side_by_side |
| 59 |
) |
|
| 60 | ||
| 61 | 2x |
lyt <- lyt %>% |
| 62 | 2x |
split_rows_by( |
| 63 | 2x |
"AEBODSYS", |
| 64 | 2x |
child_labels = "hidden", |
| 65 | 2x |
nested = FALSE, |
| 66 | 2x |
indent_mod = 0L, |
| 67 | 2x |
split_fun = drop_split_levels, |
| 68 | 2x |
label_pos = "topleft", |
| 69 | 2x |
split_label = obj_label(anl$AEBODSYS) |
| 70 |
) %>% |
|
| 71 | 2x |
summarize_num_patients( |
| 72 | 2x |
var = "USUBJID", |
| 73 | 2x |
.stats = c("unique"),
|
| 74 | 2x |
.labels = c( |
| 75 | 2x |
unique = "Total number of patients" |
| 76 |
), |
|
| 77 | 2x |
.formats = list(trim_perc1) |
| 78 |
) %>% |
|
| 79 | 2x |
count_occurrences( |
| 80 | 2x |
vars = "AEBODSYS", |
| 81 | 2x |
.indent_mods = -1L |
| 82 |
# , .formats = list(trim_perc1) |
|
| 83 |
) %>% |
|
| 84 | 2x |
count_occurrences( |
| 85 | 2x |
vars = "AEDECOD", |
| 86 | 2x |
.indent_mods = 1L |
| 87 |
# , .formats = list(trim_perc1) |
|
| 88 |
) %>% |
|
| 89 |
# append_varlabels(anl, "AEDECOD", indent = TRUE) |
|
| 90 | 2x |
append_topleft(paste(" ", formatters::var_labels(anl["AEDECOD"]), "N (%)"))
|
| 91 | ||
| 92 | 2x |
result <- lyt_to_side_by_side_two_data(lyt, anl, adsl, side_by_side) |
| 93 | ||
| 94 | 2x |
result1 <- result %>% |
| 95 | 2x |
prune_table() %>% |
| 96 | 2x |
sort_at_path( |
| 97 | 2x |
path = c("AEBODSYS"),
|
| 98 | 2x |
scorefun = cont_n_allcols |
| 99 |
) %>% |
|
| 100 | 2x |
sort_at_path( |
| 101 | 2x |
path = c("AEBODSYS", "*", "AEDECOD"),
|
| 102 | 2x |
scorefun = score_occurrences |
| 103 |
) |
|
| 104 | ||
| 105 | 2x |
t_aesi_trim_rows <- function(tt) {
|
| 106 | 2x |
rows <- collect_leaves(tt, TRUE, TRUE) |
| 107 | ||
| 108 | 2x |
tbl <- tt[!grepl("unique", names(rows)), , keep_topleft = TRUE]
|
| 109 | ||
| 110 | 2x |
tbl |
| 111 |
} |
|
| 112 | 2x |
result1 <- result1 %>% |
| 113 | 2x |
t_aesi_trim_rows() |
| 114 | 2x |
result1@main_title <- "AE event table" |
| 115 | 2x |
return(result1) |
| 116 |
} |
|
| 117 |
} |
| 1 |
#' Adverse event table |
|
| 2 |
#' |
|
| 3 |
#' @param adae ADAE data set, dataframe |
|
| 4 |
#' @param adsl ADSL data set, dataframe |
|
| 5 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 6 |
#' @param cutoff Cutoff threshold |
|
| 7 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 8 |
#' @param side_by_side "GlobalAsia" or "GlobalAsiaChina" to define the side by side requirement |
|
| 9 |
#' @return rtables object |
|
| 10 |
#' @inherit gen_notes note |
|
| 11 |
#' @export |
|
| 12 |
#' @examples |
|
| 13 |
#' library(dplyr) |
|
| 14 |
#' adsl <- eg_adsl %>% |
|
| 15 |
#' dplyr::mutate(TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")))
|
|
| 16 |
#' adae <- eg_adae %>% |
|
| 17 |
#' dplyr::mutate( |
|
| 18 |
#' TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")),
|
|
| 19 |
#' ATOXGR = AETOXGR |
|
| 20 |
#' ) |
|
| 21 |
#' out <- t_ae_pt_diff_slide(adsl, adae, "TRT01A", 2) |
|
| 22 |
#' print(out) |
|
| 23 |
#' generate_slides(out, paste0(tempdir(), "/ae_diff.pptx")) |
|
| 24 |
t_ae_pt_diff_slide <- function(adsl, adae, arm = "TRT01A", cutoff = NA, |
|
| 25 |
split_by_study = FALSE, side_by_side = NULL) {
|
|
| 26 | 9x |
cutoff <- check_and_set_cutoff(adae, cutoff) |
| 27 | 9x |
result <- t_ae_pt_core(adsl, adae, arm, cutoff, |
| 28 | 9x |
diff = TRUE, soc = "NULL", |
| 29 | 9x |
prune_by_total = FALSE, split_by_study, side_by_side |
| 30 |
) |
|
| 31 | 8x |
result@main_title <- "Adverse Events with Difference" |
| 32 | ||
| 33 | 8x |
if (!all(dim(result@rowspans) == c(0, 0))) {
|
| 34 | ! |
if (is.null(side_by_side)) {
|
| 35 |
# adding "N" attribute |
|
| 36 | ! |
arm <- col_paths(result)[[1]][1] |
| 37 | ||
| 38 | ! |
n_r <- data.frame( |
| 39 | ! |
ARM = toupper(names(result@col_info)), |
| 40 | ! |
N = col_counts(result) %>% as.numeric() |
| 41 |
) %>% |
|
| 42 | ! |
`colnames<-`(c(paste(arm), "N")) %>% |
| 43 | ! |
arrange(get(arm)) |
| 44 | ||
| 45 | ! |
attr(result, "N") <- n_r |
| 46 |
} |
|
| 47 |
} |
|
| 48 | ||
| 49 | 8x |
result |
| 50 |
} |
|
| 51 | ||
| 52 | ||
| 53 |
t_ae_pt_core <- function(adsl, adae, arm, cutoff, diff = FALSE, soc = "NULL", |
|
| 54 |
prune_by_total = FALSE, |
|
| 55 |
split_by_study, side_by_side) {
|
|
| 56 | 27x |
assert_that(has_name(adae, "AEDECOD")) |
| 57 | 27x |
assert_that(has_name(adae, "ATOXGR")) |
| 58 | 27x |
assert_that(has_name(adae, "AEBODSYS")) |
| 59 | 27x |
assert_that(has_name(adae, "ANL01FL")) |
| 60 | 27x |
assert_that((diff + prune_by_total) < 2) |
| 61 | 27x |
assert_that(cutoff <= 100 & cutoff >= 0) |
| 62 | ||
| 63 | 27x |
if (!is.null(side_by_side)) {
|
| 64 | 7x |
assert_that(has_name(adsl, "RACE")) |
| 65 | 7x |
assert_that(has_name(adsl, "COUNTRY")) |
| 66 |
} |
|
| 67 | ||
| 68 | 27x |
slref_arm <- sort(unique(adsl[[arm]])) |
| 69 | 27x |
anl_arm <- sort(unique(adae[[arm]])) |
| 70 | 27x |
assert_that(identical(slref_arm, anl_arm), |
| 71 | 27x |
msg = "The adsl and the analysis datasets should have the same treatment arm levels" |
| 72 |
) |
|
| 73 | ||
| 74 | 27x |
if (is.null(side_by_side)) {
|
| 75 | 20x |
adsl1 <- adsl %>% |
| 76 | 20x |
select("STUDYID", "USUBJID", all_of(arm))
|
| 77 | 7x |
} else if (side_by_side != TRUE) {
|
| 78 | 7x |
adsl1 <- adsl %>% |
| 79 | 7x |
select("STUDYID", "USUBJID", "RACE", "COUNTRY", all_of(arm))
|
| 80 | 7x |
adsl1$lvl <- "Global" |
| 81 |
} else {
|
|
| 82 | ! |
adsl1 <- adsl %>% |
| 83 | ! |
select("STUDYID", "USUBJID", all_of(arm))
|
| 84 | ! |
adsl1$lvl <- "Global" |
| 85 |
} |
|
| 86 | ||
| 87 | 27x |
anl <- adae %>% |
| 88 | 27x |
mutate_at( |
| 89 | 27x |
c("AEDECOD", "AEBODSYS"),
|
| 90 | 27x |
~ explicit_na(sas_na(.)) # Replace blank arm with <Missing> |
| 91 |
) %>% |
|
| 92 | 27x |
semi_join(., adsl1, by = c("STUDYID", "USUBJID")) %>%
|
| 93 | 27x |
mutate( |
| 94 | 27x |
ATOXGR = sas_na(ATOXGR) %>% as.factor(), |
| 95 | 27x |
ATOXGR2 = case_when( |
| 96 | 27x |
ATOXGR %in% c(1, 2) ~ "1 - 2", |
| 97 | 27x |
ATOXGR %in% c(3, 4) ~ "3 - 4", |
| 98 | 27x |
ATOXGR %in% c(5) ~ "5", |
| 99 | 27x |
) %>% as.factor() |
| 100 |
) |
|
| 101 | ||
| 102 | 27x |
if (!is.null(side_by_side)) {
|
| 103 | 7x |
anl$lvl <- "Global" |
| 104 |
} |
|
| 105 | ||
| 106 | 27x |
if (soc == "soc") {
|
| 107 | 12x |
anl <- anl %>% |
| 108 | 12x |
mutate( |
| 109 | 12x |
AEBODSYS = sas_na(AEBODSYS) %>% as.factor() |
| 110 |
) |
|
| 111 |
} |
|
| 112 | ||
| 113 | 27x |
anl <- anl %>% |
| 114 | 27x |
formatters::var_relabel( |
| 115 | 27x |
AEBODSYS = "MedDRA System Organ Class", |
| 116 | 27x |
AEDECOD = "MedDRA Preferred Term" |
| 117 |
) %>% |
|
| 118 | 27x |
filter(ANL01FL == "Y") |
| 119 | ||
| 120 | 27x |
if (nrow(anl) == 0) {
|
| 121 | 1x |
return(null_report()) |
| 122 |
} else {
|
|
| 123 | 26x |
lyt <- build_table_header(adsl1, arm, split_by_study = split_by_study, side_by_side = side_by_side) |
| 124 | ||
| 125 |
# lyt <- basic_table() %>% |
|
| 126 |
# split_cols_by(var = arm, split_fun = add_overall_level("All Patients", first = FALSE)) %>%
|
|
| 127 |
# add_colcounts() |
|
| 128 | ||
| 129 | 26x |
if (soc == "soc") {
|
| 130 | 12x |
lyt <- lyt %>% |
| 131 | 12x |
split_rows_by( |
| 132 | 12x |
"AEBODSYS", |
| 133 | 12x |
child_labels = "visible", |
| 134 | 12x |
nested = FALSE, |
| 135 | 12x |
indent_mod = -1L, |
| 136 | 12x |
split_fun = drop_split_levels |
| 137 |
) %>% |
|
| 138 | 12x |
append_varlabels(anl, "AEBODSYS") |
| 139 |
} |
|
| 140 | ||
| 141 | 26x |
lyt <- lyt %>% |
| 142 | 26x |
count_occurrences( |
| 143 | 26x |
vars = "AEDECOD", |
| 144 | 26x |
.indent_mods = c(count_fraction = 1L) |
| 145 |
# , .formats = list(trim_perc1) |
|
| 146 |
) %>% |
|
| 147 | 26x |
append_topleft(paste(" ", formatters::var_labels(anl["AEDECOD"]), "N (%)"))
|
| 148 | ||
| 149 | 26x |
if (soc == "soc") {
|
| 150 | 12x |
sort_path <- c("AEBODSYS", "*", "AEDECOD")
|
| 151 |
} else {
|
|
| 152 | 14x |
sort_path <- c("AEDECOD")
|
| 153 |
} |
|
| 154 | ||
| 155 |
# this is an add hoc test check |
|
| 156 | 26x |
myh_col_indices <- function(table_row, col_names) {
|
| 157 | ! |
NULL |
| 158 |
} |
|
| 159 |
# environment(myh_col_indices) <- asNamespace("tern")
|
|
| 160 |
# assignInNamespace("h_col_indices", myh_col_indices, ns = "tern")
|
|
| 161 |
# result <- build_table(lyt = lyt, df = anl, alt_counts_df = adsl1) |
|
| 162 | ||
| 163 | 26x |
result <- lyt_to_side_by_side_two_data(lyt, anl, adsl1, side_by_side) |
| 164 | ||
| 165 | 26x |
result <- result %>% |
| 166 | 26x |
sort_at_path( |
| 167 | 26x |
path = sort_path, |
| 168 | 26x |
scorefun = score_occurrences |
| 169 |
) |
|
| 170 | ||
| 171 |
# criteria_fun <- function(tr) is(tr, "ContentRow") |
|
| 172 |
# result <- trim_rows(result, criteria = criteria_fun) |
|
| 173 | ||
| 174 | 26x |
if (diff) {
|
| 175 | 15x |
row_condition <- has_fractions_difference( |
| 176 | 15x |
atleast = cutoff / 100, |
| 177 |
# col_names = levels(adsl1$TRT01A) |
|
| 178 | 15x |
col_indices = 1:2 |
| 179 |
) |
|
| 180 | 15x |
if (length(levels(adsl1[[arm]])) > 2) {
|
| 181 | 2x |
stop("More than two arms, not implemented yet")
|
| 182 |
} |
|
| 183 | 11x |
} else if (prune_by_total) {
|
| 184 | 4x |
if (is.null(side_by_side)) {
|
| 185 | 4x |
row_condition <- has_fraction_in_any_col( |
| 186 | 4x |
atleast = cutoff / 100, |
| 187 | 4x |
col_indices = ncol(result) |
| 188 |
) |
|
| 189 | ! |
} else if (!is.null(side_by_side)) {
|
| 190 | ! |
stop("I am not implemented yet")
|
| 191 |
} else {
|
|
| 192 | ! |
row_condition <- has_fraction_in_any_col( |
| 193 | ! |
atleast = cutoff / 100, |
| 194 | ! |
col_indices = ncol(result) |
| 195 |
) |
|
| 196 |
} |
|
| 197 |
} else {
|
|
| 198 | 7x |
row_condition <- has_fraction_in_any_col( |
| 199 | 7x |
atleast = cutoff / 100, |
| 200 | 7x |
col_names = levels(adsl1[[arm]]) |
| 201 |
) |
|
| 202 |
} |
|
| 203 | ||
| 204 | 24x |
result1 <- prune_table(result, keep_rows(row_condition)) |
| 205 |
# Viewer(result1) |
|
| 206 | ||
| 207 | 24x |
if (is.null(result1)) {
|
| 208 | ! |
return(null_report()) |
| 209 |
} else {
|
|
| 210 | 24x |
return(result1) |
| 211 |
} |
|
| 212 |
} |
|
| 213 |
} |
| 1 |
#' Plot mean values of EG |
|
| 2 |
#' |
|
| 3 |
#' Wrapper for `g_mean_general()`. |
|
| 4 |
#' Requires filtering of the datasets (e.g. using SUFFIX in spec.yml) |
|
| 5 |
#' |
|
| 6 |
#' @param adsl ADSL data |
|
| 7 |
#' @param adeg ADVS data |
|
| 8 |
#' @param arm `"TRT01P"` by default |
|
| 9 |
#' @param paramcd Which variable to use for plotting. By default `"PARAM"` |
|
| 10 |
#' @inheritParams g_mean_general |
|
| 11 |
#' @param ... | |
|
| 12 |
#' Gets forwarded to `tern::g_lineplot()`. |
|
| 13 |
#' This lets you specify additional arguments to `tern::g_lineplot()` |
|
| 14 |
#' @author Stefan Thoma (`thomas7`) |
|
| 15 |
#' @importFrom forcats fct_reorder |
|
| 16 |
#' @export |
|
| 17 |
#' @examplesIf require('rsvg')
|
|
| 18 |
#' library(dplyr) |
|
| 19 |
#' |
|
| 20 |
#' adeg_filtered <- eg_adeg %>% filter( |
|
| 21 |
#' PARAMCD == "HR" |
|
| 22 |
#' ) |
|
| 23 |
#' plot_eg <- g_eg_slide( |
|
| 24 |
#' adsl = eg_adsl, |
|
| 25 |
#' adeg = adeg_filtered, |
|
| 26 |
#' arm = "TRT01P", |
|
| 27 |
#' paramcd = "PARAM", |
|
| 28 |
#' subtitle_add_unit = FALSE |
|
| 29 |
#' ) + |
|
| 30 |
#' ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) |
|
| 31 |
#' |
|
| 32 |
#' generate_slides(plot_eg, paste0(tempdir(), "/g_eg.pptx")) |
|
| 33 |
g_eg_slide <- function(adsl, adeg, arm = "TRT01P", paramcd = "PARAM", |
|
| 34 |
subtitle = "Plot of Mean and 95% Confidence Limits by Visit.", ...) {
|
|
| 35 |
# tern 0.9.4 added facet_var in control_lineplot_vars |
|
| 36 | 5x |
variables <- control_lineplot_vars(group_var = arm, paramcd = paramcd) %>% strip_NA() |
| 37 | 3x |
by_vars <- c("USUBJID", "STUDYID")
|
| 38 | 3x |
assert_that(is.string(arm)) |
| 39 | 3x |
assert_that(has_name(adeg, c(by_vars, variables) %>% unique())) |
| 40 | 3x |
assert_that(has_name(adsl, c(by_vars, arm) %>% unique())) |
| 41 | 3x |
assert_that(is.string(subtitle)) |
| 42 | ||
| 43 | 3x |
g_mean_general( |
| 44 | 3x |
adsl = adsl, data = adeg, variables = variables, by_vars = by_vars, |
| 45 | 3x |
subtitle = subtitle, ... |
| 46 |
) |
|
| 47 |
} |
| 1 |
#' Determine Slide Insertion Page Number |
|
| 2 |
#' |
|
| 3 |
#' Computes the appropriate page number at which to insert a new slide into the PowerPoint deck. |
|
| 4 |
#' Defaults to appending to the end if no value is provided. |
|
| 5 |
#' |
|
| 6 |
#' @param doc_original An `officer::rpptx` object representing the PowerPoint file. |
|
| 7 |
#' @param to_page Desired slide index to insert the new slide. If `NA`, appends to the last page. |
|
| 8 |
#' |
|
| 9 |
#' @return A single integer value indicating the validated page number for slide insertion. |
|
| 10 |
#' |
|
| 11 |
#' @export |
|
| 12 |
#' |
|
| 13 |
#' @examples |
|
| 14 |
#' tmp <- tempfile(fileext = ".pptx") |
|
| 15 |
#' doc <- officer::read_pptx() |
|
| 16 |
#' doc <- officer::add_slide(doc, layout = "Title Slide", master = "Office Theme") |
|
| 17 |
#' print(doc, target = tmp) |
|
| 18 |
#' doc <- officer::read_pptx(tmp) |
|
| 19 |
#' initialize_to_page(doc, NA) # append to end |
|
| 20 |
#' initialize_to_page(doc, 1) # insert at page 1 |
|
| 21 |
initialize_to_page <- function(doc_original, to_page) {
|
|
| 22 | 11x |
max_pages <- length(doc_original) |
| 23 | 11x |
if (max_pages == 0) {
|
| 24 | 4x |
max_pages <- 1 |
| 25 |
} |
|
| 26 | 11x |
if (is.na(to_page)) {
|
| 27 | 1x |
to_page <- max_pages |
| 28 |
} |
|
| 29 | ||
| 30 | 11x |
assert_that((max_pages + 1) >= to_page) |
| 31 | 9x |
to_page |
| 32 |
} |
|
| 33 | ||
| 34 |
#' Post-process PowerPoint Document |
|
| 35 |
#' |
|
| 36 |
#' Performs final actions on the PowerPoint object, including optionally saving the updated file. |
|
| 37 |
#' The saved filename includes a suffix indicating the slide type that was appended. |
|
| 38 |
#' |
|
| 39 |
#' @param doc An `officer::rpptx` object to finalize. |
|
| 40 |
#' @param save_file A boolean indicating whether to save the file to disk. |
|
| 41 |
#' @param doc_o Original PowerPoint file path. |
|
| 42 |
#' @param type A string suffix to label the output file, e.g., `"cohort_sec"` or `"safety_sum_sec"`. |
|
| 43 |
#' |
|
| 44 |
#' @return The modified `officer::rpptx` object. |
|
| 45 |
#' |
|
| 46 |
#' @export |
|
| 47 |
#' |
|
| 48 |
#' @examples |
|
| 49 |
#' tmp <- tempfile(fileext = ".pptx") |
|
| 50 |
#' doc <- officer::read_pptx() |
|
| 51 |
#' doc <- officer::add_slide(doc, layout = "Title Slide", master = "Office Theme") |
|
| 52 |
#' print(doc, target = tmp) |
|
| 53 |
#' doc <- officer::read_pptx(tmp) |
|
| 54 |
#' # Call postprocessing_doc to save a modified version of doc |
|
| 55 |
#' postprocessing_doc(doc, TRUE, tmp, type = "final") |
|
| 56 |
postprocessing_doc <- function(doc, save_file, doc_o, type = "") {
|
|
| 57 | 12x |
if (save_file) {
|
| 58 | 4x |
doc_dir <- dirname(doc_o) |
| 59 | 4x |
doc_base_name <- tools::file_path_sans_ext(basename(doc_o)) |
| 60 | ||
| 61 | 4x |
doc_ext <- tools::file_ext(basename(doc_o)) |
| 62 | 4x |
outfile_name <- paste0(doc_base_name, "_", type, ".", doc_ext) |
| 63 | ||
| 64 |
# Combine the directory with the new filename to get the full output path |
|
| 65 | 4x |
outfile_final <- file.path(doc_dir, outfile_name) |
| 66 | 4x |
print(doc, outfile_final) |
| 67 |
} |
|
| 68 | ||
| 69 | 11x |
return(doc) |
| 70 |
} |
|
| 71 | ||
| 72 | ||
| 73 |
#' Initialize PowerPoint Document Object |
|
| 74 |
#' |
|
| 75 |
#' This function ensures a PowerPoint document (`officer::rpptx` object) is loaded. |
|
| 76 |
#' If a `doc_original` is provided, it is directly returned. Otherwise, the function reads |
|
| 77 |
#' the presentation from the given file path. |
|
| 78 |
#' |
|
| 79 |
#' @param doc_original An existing `officer::rpptx` object, or `NULL` to read from file. |
|
| 80 |
#' @param doc_o Path to a PowerPoint (`.pptx`) file. Used only if `doc_original` is `NULL`. |
|
| 81 |
#' |
|
| 82 |
#' @return An `officer::rpptx` PowerPoint object. |
|
| 83 |
#' |
|
| 84 |
#' @export |
|
| 85 |
#' |
|
| 86 |
#' @examples |
|
| 87 |
#' example <- tempfile(fileext = ".pptx") |
|
| 88 |
#' doc <- officer::read_pptx() |
|
| 89 |
#' doc <- officer::add_slide(doc, layout = "Title and Content") |
|
| 90 |
#' print(doc, target = example) |
|
| 91 |
#' doc <- initialize_doc_original(NULL, example) |
|
| 92 |
initialize_doc_original <- function(doc_original, doc_o) {
|
|
| 93 | 13x |
if (is.null(doc_original)) {
|
| 94 | 6x |
doc_original <- officer::read_pptx(doc_o) |
| 95 |
} |
|
| 96 | 13x |
doc_original |
| 97 |
} |
|
| 98 | ||
| 99 | ||
| 100 |
#' Append Title Slides to a PowerPoint Document |
|
| 101 |
#' |
|
| 102 |
#' This function adds a new title slide using a "Title and Content" layout |
|
| 103 |
#' from the "Office Theme". |
|
| 104 |
#' |
|
| 105 |
#' @param doc_o Path to a PowerPoint (`.pptx`) file. Used to initialize the document |
|
| 106 |
#' if `doc_original` is `NULL`. |
|
| 107 |
#' @param study_id A character string that represent your study identifier. |
|
| 108 |
#' @param to_page An integer specifying the page number where the new slide should be moved. |
|
| 109 |
#' @param doc_original An optional existing `officer::rpptx` object. If `NULL`, |
|
| 110 |
#' the document is initialized from `doc_o`. |
|
| 111 |
#' @param save_file A logical value. If `TRUE`, the modified document is saved to a file |
|
| 112 |
#' after adding the slide. |
|
| 113 |
#' |
|
| 114 |
#' @return An `officer::rpptx` object with the new title slide appended. |
|
| 115 |
#' |
|
| 116 |
#' @export |
|
| 117 |
#' |
|
| 118 |
#' @examples |
|
| 119 |
#' |
|
| 120 |
#' tmp <- tempfile(fileext = ".pptx") |
|
| 121 |
#' doc <- officer::read_pptx() |
|
| 122 |
#' doc <- officer::add_slide(doc, layout = "Title Slide", master = "Office Theme") |
|
| 123 |
#' print(doc, target = tmp) |
|
| 124 |
#' |
|
| 125 |
#' doc <- append_title_slides( |
|
| 126 |
#' doc_o = tmp, |
|
| 127 |
#' study_id = "My Study #13", |
|
| 128 |
#' to_page = 1, |
|
| 129 |
#' save_file = TRUE |
|
| 130 |
#' ) |
|
| 131 |
append_title_slides <- function( |
|
| 132 |
doc_o, |
|
| 133 |
study_id = "XXXX change me", |
|
| 134 |
to_page = NA, |
|
| 135 |
doc_original = NULL, |
|
| 136 |
save_file = FALSE) {
|
|
| 137 | 4x |
doc_original <- initialize_doc_original(doc_original, doc_o) |
| 138 | ||
| 139 | 4x |
to_page <- initialize_to_page(doc_original, to_page) |
| 140 | ||
| 141 | 4x |
doc <- doc_original %>% |
| 142 | 4x |
officer::add_slide(layout = "Title and Content", master = "Office Theme") %>% |
| 143 | 4x |
officer::ph_with( |
| 144 | 4x |
value = paste0(study_id, "Meeting"), |
| 145 | 4x |
location = officer::ph_location_type(type = "title") |
| 146 |
) %>% |
|
| 147 | 4x |
officer::ph_with( |
| 148 | 4x |
value = paste0("meeting"),
|
| 149 | 4x |
location = officer::ph_location_type(type = "body") |
| 150 |
) %>% |
|
| 151 | 4x |
officer::move_slide(to = to_page) |
| 152 | ||
| 153 | 4x |
postprocessing_doc(doc, save_file, doc_o, type = "title") |
| 154 | ||
| 155 | 4x |
return(doc) |
| 156 |
} |
|
| 157 | ||
| 158 | ||
| 159 | ||
| 160 |
#' Append Section Header Slides to a PowerPoint Document |
|
| 161 |
#' |
|
| 162 |
#' This function adds a new section header slide to an existing PowerPoint document |
|
| 163 |
#' using a "Section Header" layout from the "Office Theme". |
|
| 164 |
#' It populates the title placeholder with the provided section title. |
|
| 165 |
#' |
|
| 166 |
#' @param doc_o Path to a PowerPoint (`.pptx`) file. Used to initialize the document |
|
| 167 |
#' if `doc_original` is `NULL`, and for post-processing. |
|
| 168 |
#' @param section_title A character string for the title of the section header slide. |
|
| 169 |
#' Defaults to "New Section". |
|
| 170 |
#' @param to_page An integer specifying the page number where the new slide should be moved. |
|
| 171 |
#' If `NA`, the slide is added at the end and `initialize_to_page` determines its final position. |
|
| 172 |
#' @param doc_original An optional existing `officer::rpptx` object. If `NULL`, |
|
| 173 |
#' the document is initialized from `doc_o`. |
|
| 174 |
#' @param save_file A logical value. If `TRUE`, the modified document is saved to a file |
|
| 175 |
#' after adding the slide. |
|
| 176 |
#' |
|
| 177 |
#' @return An `officer::rpptx` object with the new section header slide appended. |
|
| 178 |
#' |
|
| 179 |
#' @export |
|
| 180 |
#' |
|
| 181 |
#' @examples |
|
| 182 |
#' |
|
| 183 |
#' tmp <- tempfile(fileext = ".pptx") |
|
| 184 |
#' doc <- officer::read_pptx() |
|
| 185 |
#' print(doc, target = tmp) |
|
| 186 |
#' |
|
| 187 |
#' append_section_header_slides( |
|
| 188 |
#' doc_o = tmp, |
|
| 189 |
#' section_title = "My Section", |
|
| 190 |
#' to_page = 1, |
|
| 191 |
#' save_file = TRUE |
|
| 192 |
#' ) |
|
| 193 |
append_section_header_slides <- function( |
|
| 194 |
doc_o, |
|
| 195 |
section_title = "New Section", |
|
| 196 |
to_page = NA, |
|
| 197 |
doc_original = NULL, |
|
| 198 |
save_file = FALSE) {
|
|
| 199 | 4x |
doc_original <- initialize_doc_original(doc_original, doc_o) |
| 200 | ||
| 201 | 4x |
to_page <- initialize_to_page(doc_original, to_page) |
| 202 | ||
| 203 | 3x |
doc <- doc_original %>% |
| 204 | 3x |
officer::add_slide(layout = "Section Header", master = "Office Theme") %>% |
| 205 | 3x |
officer::ph_with( |
| 206 | 3x |
value = section_title, |
| 207 | 3x |
location = officer::ph_location_type(type = "title") |
| 208 |
) %>% |
|
| 209 | 3x |
officer::move_slide(to = to_page) |
| 210 | ||
| 211 | 3x |
postprocessing_doc(doc, save_file, doc_o, type = "section_header") |
| 212 | ||
| 213 | 3x |
return(doc) |
| 214 |
} |
|
| 215 | ||
| 216 |
#' Append All Predefined Slides to a PowerPoint Document |
|
| 217 |
#' |
|
| 218 |
#' This function orchestrates the appending of a series of predefined slides |
|
| 219 |
#' (including title and section header slides) to a PowerPoint document based |
|
| 220 |
#' on a provided page list. |
|
| 221 |
#' |
|
| 222 |
#' @param doc_o Path to a PowerPoint (`.pptx`) file. Used to initialize the document |
|
| 223 |
#' if `doc_original` is `NULL`, and for final post-processing. |
|
| 224 |
#' @param page_list A list of slide definitions. Each element in the list should be |
|
| 225 |
#' another list containing: |
|
| 226 |
#' - `type`: A character string indicating the type of slide ("title" or "section").
|
|
| 227 |
#' - `to_page`: An integer specifying the target page number for the slide. |
|
| 228 |
#' - Other arguments specific to the slide type (e.g., `study_id` for "title" slides, |
|
| 229 |
#' `section_title` for "section" slides). |
|
| 230 |
#' @param doc_original An optional existing `officer::rpptx` object. If `NULL`, |
|
| 231 |
#' the document is initialized from `doc_o`. |
|
| 232 |
#' @param save_file A logical value. If `TRUE`, the final modified document is saved |
|
| 233 |
#' to a file after all slides have been appended. |
|
| 234 |
#' |
|
| 235 |
#' @return An `officer::rpptx` object with all specified slides appended. |
|
| 236 |
#' |
|
| 237 |
#' @export |
|
| 238 |
#' @examples |
|
| 239 |
#' tmp <- tempfile(fileext = ".pptx") |
|
| 240 |
#' doc <- officer::read_pptx() |
|
| 241 |
#' print(doc, target = tmp) |
|
| 242 |
#' |
|
| 243 |
#' my_page_list <- list( |
|
| 244 |
#' list(type = "title", to_page = 1, study_id = "My Project"), |
|
| 245 |
#' list(type = "section", to_page = 2, section_title = "Introduction"), |
|
| 246 |
#' list(type = "title", to_page = 3, study_id = "Mid-Term Review"), |
|
| 247 |
#' list(type = "section", to_page = 4, section_title = "Key Findings") |
|
| 248 |
#' ) |
|
| 249 |
#' |
|
| 250 |
#' # Append all slides using the dynamic page_list |
|
| 251 |
#' doc <- append_all_slides( |
|
| 252 |
#' doc_o = tmp, |
|
| 253 |
#' page_list = my_page_list, |
|
| 254 |
#' save_file = TRUE |
|
| 255 |
#' ) |
|
| 256 |
append_all_slides <- function( |
|
| 257 |
doc_o, |
|
| 258 |
page_list = list(), # Default to an empty list |
|
| 259 |
doc_original = NULL, |
|
| 260 |
save_file = FALSE) {
|
|
| 261 | 3x |
doc <- initialize_doc_original(doc_original, doc_o) |
| 262 | ||
| 263 | ||
| 264 | 3x |
for (page in page_list) {
|
| 265 | 6x |
current_to_page <- page$to_page |
| 266 | ||
| 267 | ||
| 268 | 6x |
if (page$type == "title") {
|
| 269 | 3x |
doc <- append_title_slides( |
| 270 | 3x |
doc_o = doc_o, |
| 271 | 3x |
doc_original = doc, |
| 272 | 3x |
to_page = current_to_page, |
| 273 | 3x |
study_id = page$study_id, |
| 274 | 3x |
save_file = FALSE |
| 275 |
) |
|
| 276 | 3x |
} else if (page$type == "section") {
|
| 277 | 3x |
doc <- append_section_header_slides( |
| 278 | 3x |
doc_o = doc_o, |
| 279 | 3x |
doc_original = doc, |
| 280 | 3x |
to_page = current_to_page, |
| 281 | 3x |
section_title = page$section_title, |
| 282 | 3x |
save_file = FALSE |
| 283 |
) |
|
| 284 |
} |
|
| 285 |
} |
|
| 286 | ||
| 287 | 2x |
postprocessing_doc(doc, save_file, doc_o, type = "final") |
| 288 | ||
| 289 | 2x |
return(doc) |
| 290 |
} |
| 1 |
#' Generate output and apply filters, titles, and footnotes |
|
| 2 |
#' |
|
| 3 |
#' @param program program name |
|
| 4 |
#' @param datasets list of datasets |
|
| 5 |
#' @param spec spec |
|
| 6 |
#' @param verbose_level Verbose level of messages be displayed. See details for further information. |
|
| 7 |
#' @return No return value, called for side effects |
|
| 8 |
#' @details |
|
| 9 |
#' `verbose_level` is used to control how many messages are printed out. |
|
| 10 |
#' By default, `2` will show all filter messages and show output generation message. |
|
| 11 |
#' `1` will show output generation message only. |
|
| 12 |
#' `0` will display no message. |
|
| 13 |
#' @param ... arguments passed to program |
|
| 14 |
#' |
|
| 15 |
#' @author Liming Li (`Lil128`) |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
#' |
|
| 19 |
#' @examplesIf require(filters) |
|
| 20 |
#' library(dplyr) |
|
| 21 |
#' filters::load_filters( |
|
| 22 |
#' yaml_file = system.file("filters.yml", package = "autoslider.core"),
|
|
| 23 |
#' overwrite = TRUE |
|
| 24 |
#' ) |
|
| 25 |
#' |
|
| 26 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 27 |
#' spec <- spec_file %>% read_spec() |
|
| 28 |
#' |
|
| 29 |
#' data <- list( |
|
| 30 |
#' adsl = eg_adsl, |
|
| 31 |
#' adae = eg_adae |
|
| 32 |
#' ) |
|
| 33 |
#' generate_output("t_ae_slide", data, spec$t_ae_slide_SE)
|
|
| 34 |
#' |
|
| 35 |
generate_output <- |
|
| 36 |
function(program, |
|
| 37 |
datasets, |
|
| 38 |
spec, |
|
| 39 |
verbose_level = 2, |
|
| 40 |
...) {
|
|
| 41 | 18x |
suffix <- spec$suffix |
| 42 | 18x |
if (verbose_level > 0) {
|
| 43 | 18x |
cat_bullet( |
| 44 | 18x |
sprintf( |
| 45 | 18x |
"Running program `%s` with suffix '%s'.", |
| 46 | 18x |
program, |
| 47 | 18x |
suffix |
| 48 |
), |
|
| 49 | 18x |
bullet = "pointer", |
| 50 | 18x |
bullet_col = "green" |
| 51 |
) |
|
| 52 |
} |
|
| 53 | 18x |
func <- tryCatch( |
| 54 |
{
|
|
| 55 | 18x |
func_wrapper( |
| 56 | 18x |
func = match.fun(program), |
| 57 | 18x |
datasets = datasets, |
| 58 | 18x |
spec = spec, |
| 59 | 18x |
verbose = verbose_level > 1 |
| 60 |
) |
|
| 61 |
}, |
|
| 62 | 18x |
error = function(e) {
|
| 63 | ! |
info <- e$message |
| 64 | ! |
if (verbose_level > 0) {
|
| 65 | ! |
cat_bullet(paste0("Error: ", info), bullet = "warning", bullet_col = "red")
|
| 66 |
} |
|
| 67 | ! |
autoslider_error(info, spec = spec, step = "filter dataset") |
| 68 |
} |
|
| 69 |
) |
|
| 70 | 18x |
if (is(func, "autoslider_error")) {
|
| 71 | ! |
return(func) |
| 72 |
} |
|
| 73 | 18x |
ret <- tryCatch( |
| 74 |
{
|
|
| 75 | 18x |
func(...) |
| 76 |
}, |
|
| 77 | 18x |
error = function(e) {
|
| 78 | ! |
info <- e$message |
| 79 | ! |
if (verbose_level > 0) {
|
| 80 | ! |
cat_bullet(paste0("Error: ", info), bullet = "warning", bullet_col = "red")
|
| 81 |
} |
|
| 82 | ! |
autoslider_error(info, spec = spec, step = "user program") |
| 83 |
} |
|
| 84 |
) |
|
| 85 | 18x |
return(ret) |
| 86 |
} |
|
| 87 | ||
| 88 |
#' Generate all outputs from a spec |
|
| 89 |
#' |
|
| 90 |
#' @param spec Specification list generated by `read_spec` |
|
| 91 |
#' @param datasets A `list` of datasets |
|
| 92 |
#' @param verbose_level Verbose level of messages be displayed. See details for further information. |
|
| 93 |
#' @return No return value, called for side effects |
|
| 94 |
#' @details |
|
| 95 |
#' `verbose_level` is used to control how many messages are printed out. |
|
| 96 |
#' By default, `2` will show all filter messages and show output generation message. |
|
| 97 |
#' `1` will show output generation message only. |
|
| 98 |
#' `0` will display no message. |
|
| 99 |
#' |
|
| 100 |
#' @author |
|
| 101 |
#' - Thomas Neitmann (`neitmant`) |
|
| 102 |
#' - Liming Li (`Lil128`) |
|
| 103 |
#' |
|
| 104 |
#' @export |
|
| 105 |
#' |
|
| 106 |
#' @examplesIf require(filters) |
|
| 107 |
#' library(dplyr, warn.conflicts = FALSE) |
|
| 108 |
#' data <- list( |
|
| 109 |
#' adsl = eg_adsl, |
|
| 110 |
#' adae = eg_adae |
|
| 111 |
#' ) |
|
| 112 |
#' filters::load_filters( |
|
| 113 |
#' yaml_file = system.file("filters.yml", package = "autoslider.core"),
|
|
| 114 |
#' overwrite = TRUE |
|
| 115 |
#' ) |
|
| 116 |
#' |
|
| 117 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 118 |
#' spec_file %>% |
|
| 119 |
#' read_spec() %>% |
|
| 120 |
#' filter_spec(output %in% c("t_dm_slide_IT", "t_ae_slide_SE")) %>%
|
|
| 121 |
#' generate_outputs(datasets = data) |
|
| 122 |
#' |
|
| 123 |
generate_outputs <- function(spec, datasets, verbose_level = 2) {
|
|
| 124 | 2x |
lapply(spec, function(s) {
|
| 125 | 18x |
args <- c( |
| 126 | 18x |
list( |
| 127 | 18x |
program = s$program, |
| 128 | 18x |
spec = s, |
| 129 | 18x |
datasets = datasets, |
| 130 | 18x |
verbose_level = verbose_level |
| 131 |
), |
|
| 132 | 18x |
s$args # ... arguments passed onto the output-generating function |
| 133 |
) |
|
| 134 | 18x |
output <- fastDoCall(generate_output, args) |
| 135 | 18x |
attr(output, "spec") <- s |
| 136 | 18x |
output |
| 137 |
}) |
|
| 138 |
} |
| 1 |
#' generic function decorate |
|
| 2 |
#' @return No return value, called for side effects |
|
| 3 |
#' @export |
|
| 4 |
setGeneric("decorate", function(x, ...) standardGeneric("decorate"))
|
|
| 5 | ||
| 6 |
#' s3 method for decorate |
|
| 7 |
#' @param x object to decorate |
|
| 8 |
#' @param ... additional arguments passed to methods |
|
| 9 |
decorate <- function(x, ...) {
|
|
| 10 | 1x |
UseMethod("decorate")
|
| 11 |
} |
|
| 12 | ||
| 13 |
#' default method to decorate |
|
| 14 |
#' @param x object to decorate |
|
| 15 |
#' @param ... additional arguments. not used. |
|
| 16 |
#' @return No return value, called for side effects |
|
| 17 |
#' @export |
|
| 18 |
decorate.default <- function(x, ...) {
|
|
| 19 | 1x |
stop("default decorate function does not exist")
|
| 20 |
} |
|
| 21 | ||
| 22 |
#' decorate method for autoslider_error class |
|
| 23 |
#' @param x object to decorate |
|
| 24 |
#' @param ... additional arguments. not used. |
|
| 25 |
#' @return No return value, called for side effects |
|
| 26 |
#' @export |
|
| 27 |
decorate.autoslider_error <- function(x, ...) {
|
|
| 28 | 1x |
x |
| 29 |
} |
|
| 30 | ||
| 31 |
#' Decorate TableTree |
|
| 32 |
#' |
|
| 33 |
#' @param x A VTableTree object representing the data to be decorated. |
|
| 34 |
#' @param titles Title to be added to the table. |
|
| 35 |
#' @param footnotes Footnote to be added to the table |
|
| 36 |
#' @param paper Orientation and font size as string, e.g. "P8"; "L11" |
|
| 37 |
#' @param for_test `logic` CICD parameter |
|
| 38 |
#' @param ... Additional arguments passed to the decoration function. |
|
| 39 |
#' @return No return value, called for side effects |
|
| 40 |
#' @export |
|
| 41 |
setMethod( |
|
| 42 |
"decorate", "VTableTree", |
|
| 43 |
decorate.VTableTree <- function(x, titles = "", footnotes = "", paper = "P8", for_test = FALSE, ...) {
|
|
| 44 | 18x |
width_set <- attr(x, "width") |
| 45 | 18x |
tmp_x <- formatters::matrix_form(x) |
| 46 | ||
| 47 | 18x |
if (is.null(width_set)) {
|
| 48 | 18x |
width <- formatters::propose_column_widths(tmp_x) |
| 49 |
} else {
|
|
| 50 | ! |
width <- ifelse(is.na(width_set), formatters::propose_column_widths(tmp_x), width_set) |
| 51 |
} |
|
| 52 | ||
| 53 | 18x |
glued_title <- glue::glue(paste(titles, collapse = "\n")) |
| 54 | 18x |
main_title(x) <- glued_title |
| 55 | ||
| 56 | 18x |
git_fn <- git_footnote(for_test) |
| 57 | 18x |
glued_footnotes <- glue::glue(paste(c(footnotes, git_fn), collapse = "\n")) |
| 58 | 18x |
main_footer(x) <- glued_footnotes |
| 59 | ||
| 60 | 18x |
new( |
| 61 | 18x |
"dVTableTree", |
| 62 | 18x |
tbl = x, |
| 63 | 18x |
titles = glued_title, |
| 64 | 18x |
footnotes = footnotes, |
| 65 | 18x |
usernotes = "", |
| 66 | 18x |
paper = paper, |
| 67 | 18x |
width = width |
| 68 |
) |
|
| 69 |
} |
|
| 70 |
) |
|
| 71 | ||
| 72 | ||
| 73 |
#' Decorate ggplot object |
|
| 74 |
#' |
|
| 75 |
#' @param x An object to decorate |
|
| 76 |
#' @param titles Plot titles |
|
| 77 |
#' @param footnotes Plot footnotes |
|
| 78 |
#' @param paper Paper size, by default "L11" |
|
| 79 |
#' @param for_test `logic` CICD parameter |
|
| 80 |
#' @param ... additional arguments. not used. |
|
| 81 |
#' @return No return value, called for side effects |
|
| 82 |
#' @export |
|
| 83 |
#' @details |
|
| 84 |
#' The paper default paper size, `L11`, indicate that the fontsize is 11. |
|
| 85 |
#' The fontsize of the footnotes, is the fontsize of the titles minus 2. |
|
| 86 |
decorate.ggplot <- function(x, titles = "", footnotes = "", paper = "L11", for_test = FALSE, ...) {
|
|
| 87 | 5x |
glued_title <- glue::glue(paste(titles, collapse = "\n")) |
| 88 |
# main_title(x) <- glued_title |
|
| 89 | ||
| 90 | 5x |
git_fn <- git_footnote(for_test) |
| 91 | 5x |
glued_footnotes <- glue::glue(paste(c(footnotes, git_fn), collapse = "\n")) |
| 92 |
# main_footer(x) <- glued_footnotes |
|
| 93 | ||
| 94 | 5x |
ret <- list( |
| 95 | 5x |
grob = ggplot2::ggplotGrob(x), |
| 96 | 5x |
titles = glued_title, |
| 97 | 5x |
footnotes = footnotes, |
| 98 | 5x |
usernotes = "", |
| 99 | 5x |
paper = paper, |
| 100 | 5x |
for_test = for_test |
| 101 |
) |
|
| 102 | 5x |
class(ret) <- "decoratedGrob" |
| 103 | 5x |
return(ret) |
| 104 |
} |
|
| 105 | ||
| 106 | ||
| 107 |
#' decorate listing |
|
| 108 |
#' |
|
| 109 |
#' @param x A listing_df object representing the data to be decorated. |
|
| 110 |
#' @param titles Title to be added to the table. |
|
| 111 |
#' @param footnotes Footnote to be added to the table |
|
| 112 |
#' @param paper Orientation and font size as string, e.g. "P8"; "L11" |
|
| 113 |
#' @param for_test `logic` CICD parameter |
|
| 114 |
#' @param ... Additional arguments. not used. |
|
| 115 |
#' @return No return value, called for side effects |
|
| 116 |
#' @export |
|
| 117 |
setMethod( |
|
| 118 |
"decorate", "listing_df", |
|
| 119 |
decorate.listing_df <- function(x, titles = "", footnotes = "", paper = "P8", for_test = FALSE, ...) {
|
|
| 120 | 1x |
width_set <- attr(x, "width") |
| 121 | 1x |
tmp_x <- formatters::matrix_form(x) |
| 122 | ||
| 123 | 1x |
if (is.null(width_set)) {
|
| 124 | 1x |
width <- formatters::propose_column_widths(tmp_x) |
| 125 |
} else {
|
|
| 126 | ! |
width <- ifelse(is.na(width_set), formatters::propose_column_widths(tmp_x), width_set) |
| 127 |
} |
|
| 128 | ||
| 129 | 1x |
glued_title <- glue::glue(paste(titles, collapse = "\n")) |
| 130 | 1x |
main_title(x) <- glued_title |
| 131 | ||
| 132 | 1x |
git_fn <- git_footnote(for_test) |
| 133 | 1x |
glued_footnotes <- glue::glue(paste(c(footnotes, git_fn), collapse = "\n")) |
| 134 | 1x |
main_footer(x) <- glued_footnotes |
| 135 | 1x |
new( |
| 136 | 1x |
"dlisting", |
| 137 | 1x |
lst = x, |
| 138 | 1x |
titles = glued_title, |
| 139 | 1x |
footnotes = footnotes, |
| 140 | 1x |
usernotes = "", |
| 141 | 1x |
paper = paper, |
| 142 | 1x |
width = width |
| 143 |
) |
|
| 144 |
} |
|
| 145 |
) |
|
| 146 | ||
| 147 | ||
| 148 | ||
| 149 | ||
| 150 |
#' decorate grob |
|
| 151 |
#' @param x object to decorate |
|
| 152 |
#' @param titles graph titles |
|
| 153 |
#' @param footnotes graph footnotes |
|
| 154 |
#' @param paper paper size. default is "L8". |
|
| 155 |
#' @param for_test `logic` CICD parameter |
|
| 156 |
#' @param ... Additional arguments. not used. |
|
| 157 |
#' @return No return value, called for side effects |
|
| 158 |
#' @details |
|
| 159 |
#' The paper default paper size, `L11`, indicate that the fontsize is 11. |
|
| 160 |
#' The fontsize of the footnotes, is the fontsize of the titles minus 2. |
|
| 161 |
#' @export |
|
| 162 |
#' |
|
| 163 |
decorate.grob <- |
|
| 164 |
function(x, titles = "", footnotes = "", paper = "L11", for_test = FALSE, ...) {
|
|
| 165 | 2x |
size <- fs(paper) |
| 166 | 2x |
grob <- tern::decorate_grob( |
| 167 | 2x |
grob = x, |
| 168 | 2x |
titles = glue::glue(paste(titles, collapse = "\n")), |
| 169 | 2x |
footnotes = c(glue::glue(paste(footnotes, collapse = "\n")), git_footnote(for_test), datetime()), |
| 170 | 2x |
border = FALSE, |
| 171 | 2x |
gp_titles = gpar(fontsize = size$fontsize), |
| 172 | 2x |
gp_footnotes = gpar(fontsize = size$fontsize - 2) |
| 173 |
) |
|
| 174 | 2x |
attr(grob, "paper") <- ifelse(size$orientation == "P", "a4", "a4r") |
| 175 | 2x |
grob |
| 176 |
} |
|
| 177 | ||
| 178 |
#' decorate gtsummary |
|
| 179 |
#' |
|
| 180 |
#' @param x gtsummary object to decorate |
|
| 181 |
#' @param titles graph titles |
|
| 182 |
#' @param footnotes graph footnotes |
|
| 183 |
#' @param paper paper size. default is "L8". |
|
| 184 |
#' @param for_test `logic` CICD parameter |
|
| 185 |
#' @param ... Additional arguments. not used. |
|
| 186 |
#' @return No return value, called for side effects |
|
| 187 |
#' @details |
|
| 188 |
#' The paper default paper size, `L11`, indicate that the fontsize is 11. |
|
| 189 |
#' The fontsize of the footnotes, is the fontsize of the titles minus 2.#' |
|
| 190 |
#' @export |
|
| 191 |
decorate.gtsummary <- |
|
| 192 |
function(x, titles = "", footnotes = "", paper = "L11", for_test = FALSE, ...) {
|
|
| 193 | 1x |
size <- fs(paper) |
| 194 | 1x |
glued_title <- glue::glue(paste(titles, collapse = "\n")) |
| 195 | 1x |
x <- x %>% modify_caption(caption = "") |
| 196 | 1x |
structure( |
| 197 | 1x |
.Data = x, |
| 198 | 1x |
titles = glued_title, |
| 199 | 1x |
paper = paper, |
| 200 | 1x |
class = union("dgtsummary", class(x))
|
| 201 |
) |
|
| 202 |
} |
|
| 203 |
# ) |
|
| 204 | ||
| 205 |
#' decorate list of grobs |
|
| 206 |
#' @param x object to decorate |
|
| 207 |
#' @param titles graph titles |
|
| 208 |
#' @param footnotes graph footnotes |
|
| 209 |
#' @param paper paper size. default is "L11". |
|
| 210 |
#' @param for_test `logic` CICD parameter |
|
| 211 |
#' @param ... additional arguments. not used |
|
| 212 |
#' @details |
|
| 213 |
#' The paper default paper size, `L11`, indicate that the fontsize is 11. |
|
| 214 |
#' The fontsize of the footnotes, is the fontsize of the titles minus 2. |
|
| 215 |
#' @return No return value, called for side effects |
|
| 216 |
#' @export |
|
| 217 |
#' |
|
| 218 |
decorate.list <- |
|
| 219 |
function(x, titles, footnotes, paper = "L11", for_test = FALSE, ...) {
|
|
| 220 | 1x |
stopifnot(all(vapply(x, function(x) {
|
| 221 | 2x |
"grob" %in% class(x) || "ggplot" %in% class(x) |
| 222 | 1x |
}, FUN.VALUE = TRUE))) |
| 223 | 1x |
size <- fs(paper) |
| 224 | 1x |
x <- lapply(x, function(g) {
|
| 225 | 2x |
ret <- g |
| 226 | 2x |
if ("ggplot" %in% class(g)) {
|
| 227 | 2x |
ret <- ggplot2::ggplotGrob(g) |
| 228 |
} |
|
| 229 | 2x |
ret |
| 230 |
}) |
|
| 231 | 1x |
grobs <- decorate_grob_set( |
| 232 | 1x |
grobs = x, |
| 233 | 1x |
titles = glue::glue(paste(titles, collapse = "\n")), |
| 234 | 1x |
footnotes = c(glue::glue(paste(footnotes, collapse = "\n")), git_footnote(for_test), datetime()), |
| 235 | 1x |
border = FALSE, |
| 236 | 1x |
gp_titles = gpar(fontsize = size$fontsize), |
| 237 | 1x |
gp_footnotes = gpar(fontsize = size$fontsize - 2) |
| 238 |
) |
|
| 239 | 1x |
structure( |
| 240 | 1x |
.Data = grobs, |
| 241 | 1x |
paper = ifelse(size$orientation == "P", "a4", "a4r"), |
| 242 | 1x |
class = union("decoratedGrobSet", class(grobs))
|
| 243 |
) |
|
| 244 |
} |
|
| 245 | ||
| 246 |
#' Decorate outputs |
|
| 247 |
#' |
|
| 248 |
#' Decorate outputs with titles and footnotes |
|
| 249 |
#' |
|
| 250 |
#' @param outputs `list` of output objects as created by `generate_outputs` |
|
| 251 |
#' @param generic_title `character` vector of titles |
|
| 252 |
#' @param generic_footnote `character` vector of footnotes |
|
| 253 |
#' @param version_label `character`. A version label to be added to the title. |
|
| 254 |
#' @param for_test `logic` CICD parameter |
|
| 255 |
#' @return No return value, called for side effects |
|
| 256 |
#' @details |
|
| 257 |
#' `generic_title` and `generic_footnote` will be added to *all* outputs. The use |
|
| 258 |
#' case is to add information such as protocol number and snapshot date defined |
|
| 259 |
#' in a central place (e.g. metadata.yml) to *every* output. |
|
| 260 |
#' |
|
| 261 |
#' `version_label` must be either `"DRAFT"`, `"APPROVED"` or `NULL`. By default, |
|
| 262 |
#' when outputs are created on the master branch it is set to `NULL`, i.e. no |
|
| 263 |
#' version label will be displayed. Otherwise `"DRAFT"` will be added. To add |
|
| 264 |
#' `"APPROVED"` to the title you will need to explicitly set `version_label = "APPROVED"`. |
|
| 265 |
#' |
|
| 266 |
#' @export |
|
| 267 |
decorate_outputs <- function(outputs, |
|
| 268 |
generic_title = NULL, |
|
| 269 |
generic_footnote = "Confidential and for internal use only", |
|
| 270 |
version_label = get_version_label_output(), |
|
| 271 |
for_test = FALSE) {
|
|
| 272 | 2x |
assert_is_valid_version_label(version_label) |
| 273 | ||
| 274 | 2x |
lapply(outputs, function(output) {
|
| 275 | 18x |
if (is(output, "autoslider_error")) {
|
| 276 | ! |
return(output) |
| 277 |
} |
|
| 278 | ||
| 279 | 18x |
spec <- attr(output, "spec") |
| 280 | ||
| 281 | 18x |
filter_titles <- function(...) {
|
| 282 | 18x |
if (length(c(...)) == 0 || "all" %in% c(...)) {
|
| 283 | ! |
r <- vapply( |
| 284 | ! |
filters::get_filters(spec$suffix), |
| 285 | ! |
FUN = `[[`, |
| 286 | ! |
FUN.VALUE = character(1L), |
| 287 | ! |
"title" |
| 288 |
) |
|
| 289 |
} else {
|
|
| 290 | 18x |
r <- vapply( |
| 291 | 18x |
Filter( |
| 292 | 18x |
f = function(x) any(x$target %in% toupper(c(...))), |
| 293 | 18x |
x = filters::get_filters(spec$suffix) |
| 294 |
), |
|
| 295 | 18x |
FUN = `[[`, |
| 296 | 18x |
FUN.VALUE = character(1L), |
| 297 | 18x |
"title" |
| 298 |
) |
|
| 299 |
} |
|
| 300 | 18x |
paste(r, collapse = ", ") |
| 301 |
} |
|
| 302 | ||
| 303 | 18x |
pattern <- "\\{filter_titles\\(((\"\\w+\")(,\\s*\"\\w+\")*){0,1}\\)\\}"
|
| 304 | 18x |
if (grepl(pattern, spec$titles)) {
|
| 305 | 1x |
m <- regmatches(spec$titles, regexpr(pattern, spec$titles)) |
| 306 | 1x |
full_title <- paste( |
| 307 | 1x |
version_label, |
| 308 | 1x |
sub(pattern = pattern, eval(parse(text = m)), spec$titles) |
| 309 |
) |
|
| 310 |
} else {
|
|
| 311 | 17x |
full_title <- paste( |
| 312 | 17x |
paste(version_label, spec$titles), |
| 313 | 17x |
filter_titles("ADSL"),
|
| 314 | 17x |
sep = ", " |
| 315 |
) |
|
| 316 |
} |
|
| 317 | ||
| 318 | 18x |
if ("ggplot" %in% class(output)) {
|
| 319 | 4x |
decorate.ggplot(output, titles = full_title) |
| 320 | 14x |
} else if ("grob" %in% class(output)) {
|
| 321 | ! |
decorate.grob(output) |
| 322 |
} else {
|
|
| 323 | 14x |
structure( |
| 324 | 14x |
.Data = decorate( |
| 325 | 14x |
x = output, |
| 326 | 14x |
title = c(full_title, generic_title), |
| 327 | 14x |
footnotes = c(spec$footnotes, generic_footnote), |
| 328 | 14x |
paper = spec$paper, |
| 329 | 14x |
for_test = for_test |
| 330 |
), |
|
| 331 | 14x |
spec = modifyList(spec, list(titles = glue::glue(paste0(c(full_title, generic_title), collapse = "\n")))) |
| 332 |
) |
|
| 333 |
} |
|
| 334 |
}) |
|
| 335 |
} |
|
| 336 | ||
| 337 |
#' Print decorated grob |
|
| 338 |
#' |
|
| 339 |
#' @param x An object of class `decoratedGrob` |
|
| 340 |
#' @param ... not used. |
|
| 341 |
#' @return No return value, called for side effects |
|
| 342 |
#' @export |
|
| 343 |
print.decoratedGrob <- function(x, ...) {
|
|
| 344 | ! |
grid::grid.newpage() |
| 345 | ! |
grid::grid.draw(x) |
| 346 |
} |
|
| 347 | ||
| 348 |
#' Print decorated grob set |
|
| 349 |
#' |
|
| 350 |
#' @param x An object of class `decoratedGrobSet` |
|
| 351 |
#' @param ... not used. |
|
| 352 |
#' @return No return value, called for side effects |
|
| 353 |
#' @export |
|
| 354 |
print.decoratedGrobSet <- function(x, ...) {
|
|
| 355 | ! |
for (plot in x) {
|
| 356 | ! |
grid::grid.newpage() |
| 357 | ! |
grid::grid.draw(plot) |
| 358 |
} |
|
| 359 |
} |
| 1 |
#' Death table |
|
| 2 |
#' |
|
| 3 |
#' @param adsl ADSL data set, dataframe |
|
| 4 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 5 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 6 |
#' @param side_by_side used for studies in China. "GlobalAsia" or "GlobalAsiaChina" to define |
|
| 7 |
#' the side by side requirement. |
|
| 8 |
#' @return rtables object |
|
| 9 |
#' @inherit gen_notes note |
|
| 10 |
#' @export |
|
| 11 |
#' @examples |
|
| 12 |
#' library(dplyr) |
|
| 13 |
#' adsl <- eg_adsl %>% preprocess_t_dd() |
|
| 14 |
#' out1 <- t_dd_slide(adsl, "TRT01A") |
|
| 15 |
#' print(out1) |
|
| 16 |
#' generate_slides(out1, paste0(tempdir(), "/dd.pptx")) |
|
| 17 |
#' |
|
| 18 |
#' out2 <- t_dd_slide(adsl, "TRT01A", split_by_study = TRUE) |
|
| 19 |
#' print(out2) |
|
| 20 |
t_dd_slide <- function(adsl, |
|
| 21 |
arm = "TRT01A", |
|
| 22 |
split_by_study = FALSE, |
|
| 23 |
side_by_side = NULL) {
|
|
| 24 | 8x |
assert_that(has_name(adsl, "DTHCAT")) |
| 25 | 8x |
assert_that(has_name(adsl, "DTHFL")) |
| 26 | ||
| 27 | 8x |
anl <- adsl |
| 28 | ||
| 29 | 8x |
if (!is.null(side_by_side)) {
|
| 30 | 2x |
anl$lvl <- "Global" |
| 31 |
} |
|
| 32 | ||
| 33 | 8x |
if (nrow(anl) == 0) {
|
| 34 | 1x |
return(null_report()) |
| 35 |
} else {
|
|
| 36 | 7x |
lyt <- build_table_header(adsl, arm, split_by_study = split_by_study, side_by_side = side_by_side) |
| 37 | ||
| 38 | 7x |
lyt <- lyt %>% |
| 39 | 7x |
count_values( |
| 40 | 7x |
"DTHFL", |
| 41 | 7x |
values = "Y", |
| 42 | 7x |
denom = c("N_col"),
|
| 43 | 7x |
.labels = c(count_fraction = "All Deaths") |
| 44 |
) %>% |
|
| 45 | 7x |
analyze_vars( |
| 46 | 7x |
vars = "DTHCAT", .stats = "count_fraction", |
| 47 | 7x |
na_str = "<Missing>", |
| 48 | 7x |
var_labels = " ", |
| 49 | 7x |
na.rm = TRUE |
| 50 |
) %>% |
|
| 51 |
# count_patients_with_flags( |
|
| 52 |
# "USUBJID", |
|
| 53 |
# flag_variables = formatters::var_labels(anl[,c("DTHCAT1", "DTHCAT2", "DTHCAT3")]),
|
|
| 54 |
# .indent_mods = 1L, |
|
| 55 |
# .format = list(trim_perc1), |
|
| 56 |
# denom = "n" |
|
| 57 |
# ) %>% |
|
| 58 | 7x |
append_topleft("N (%)")
|
| 59 | ||
| 60 | 7x |
result <- lyt_to_side_by_side(lyt, anl, side_by_side) |
| 61 | 6x |
result@main_title <- "Death table" |
| 62 | 6x |
result |
| 63 |
} |
|
| 64 |
} |
| 1 |
#' function wrapper to pass filtered data |
|
| 2 |
#' @param func function name |
|
| 3 |
#' @param datasets list of raw datasets |
|
| 4 |
#' @param spec spec |
|
| 5 |
#' @param verbose whether to show verbose information |
|
| 6 |
#' @return a wrapped function using filtered adam |
|
| 7 |
func_wrapper <- |
|
| 8 |
function(func, datasets, spec, verbose = TRUE) {
|
|
| 9 | 23x |
suffix <- spec$suffix |
| 10 | 23x |
function_args <- names(formals(func)) |
| 11 | 23x |
datasets_filtered <- filters::apply_filter( |
| 12 | 23x |
data = datasets, |
| 13 | 23x |
id = suffix, |
| 14 | 23x |
verbose = verbose |
| 15 |
) |
|
| 16 | ||
| 17 | 23x |
if ("datasets" %in% function_args) {
|
| 18 | 2x |
if ("spec" %in% function_args) {
|
| 19 | 1x |
return({
|
| 20 | 1x |
function(...) {
|
| 21 | 1x |
fastDoCall(func, list(datasets_filtered, spec = spec, ...)) |
| 22 |
} |
|
| 23 |
}) |
|
| 24 |
} else {
|
|
| 25 | 1x |
return({
|
| 26 | 1x |
function(...) {
|
| 27 | 1x |
fastDoCall(func, list(datasets_filtered, ...)) |
| 28 |
} |
|
| 29 |
}) |
|
| 30 |
} |
|
| 31 |
} else {
|
|
| 32 |
# to keep compatibility with previous version |
|
| 33 | 21x |
data_used <- |
| 34 | 21x |
function_args[function_args %in% names(datasets)] |
| 35 | 21x |
if ("spec" %in% function_args) {
|
| 36 | 1x |
return({
|
| 37 | 1x |
function(...) {
|
| 38 | 1x |
fastDoCall(func, c( |
| 39 | 1x |
datasets_filtered[data_used], |
| 40 | 1x |
list(spec = spec), |
| 41 | 1x |
list(...) |
| 42 |
)) |
|
| 43 |
} |
|
| 44 |
}) |
|
| 45 |
} else {
|
|
| 46 | 20x |
return({
|
| 47 | 20x |
function(...) {
|
| 48 | 20x |
fastDoCall(func, c(datasets_filtered[data_used], list(...))) |
| 49 |
} |
|
| 50 |
}) |
|
| 51 |
} |
|
| 52 |
} |
|
| 53 |
} |
| 1 |
#' Adverse event table |
|
| 2 |
#' |
|
| 3 |
#' @param adae ADAE data set, dataframe |
|
| 4 |
#' @param adsl ADSL data set, dataframe |
|
| 5 |
#' @param arm Arm variable, character, "`TRT01A" by default. |
|
| 6 |
#' @param cutoff Cutoff threshold |
|
| 7 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 8 |
#' @param side_by_side "GlobalAsia" or "GlobalAsiaChina" to define the side by side requirement |
|
| 9 |
#' @return rtables object |
|
| 10 |
#' @inherit gen_notes note |
|
| 11 |
#' @export |
|
| 12 |
#' @examples |
|
| 13 |
#' library(dplyr) |
|
| 14 |
#' adsl <- eg_adsl %>% |
|
| 15 |
#' dplyr::mutate(TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")))
|
|
| 16 |
#' adae <- eg_adae %>% |
|
| 17 |
#' dplyr::mutate( |
|
| 18 |
#' TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")),
|
|
| 19 |
#' ATOXGR = AETOXGR |
|
| 20 |
#' ) |
|
| 21 |
#' out <- t_ae_pt_soc_diff_slide(adsl, adae, "TRT01A", 2) |
|
| 22 |
#' print(out) |
|
| 23 |
#' generate_slides(out, paste0(tempdir(), "/ae_diff.pptx")) |
|
| 24 |
t_ae_pt_soc_diff_slide <- function(adsl, adae, arm = "TRT01A", cutoff = NA, |
|
| 25 |
split_by_study = FALSE, side_by_side = NULL) {
|
|
| 26 | 7x |
cutoff <- check_and_set_cutoff(adae, cutoff) |
| 27 | 7x |
result <- t_ae_pt_core(adsl, adae, arm, cutoff, |
| 28 | 7x |
diff = TRUE, soc = "soc", |
| 29 | 7x |
prune_by_total = FALSE, |
| 30 | 7x |
split_by_study, side_by_side |
| 31 |
) |
|
| 32 | 6x |
result@main_title <- "Adverse Events with Difference" |
| 33 | ||
| 34 | 6x |
if (is.null(side_by_side)) {
|
| 35 |
# adding "N" attribute |
|
| 36 | 3x |
arm <- col_paths(result)[[1]][1] |
| 37 | ||
| 38 | 3x |
n_r <- data.frame( |
| 39 | 3x |
ARM = toupper(names(result@col_info)), |
| 40 | 3x |
N = col_counts(result) %>% as.numeric() |
| 41 |
) %>% |
|
| 42 | 3x |
`colnames<-`(c(paste(arm), "N")) %>% |
| 43 | 3x |
arrange(get(arm)) |
| 44 | ||
| 45 | 3x |
attr(result, "N") <- n_r |
| 46 |
} |
|
| 47 | 6x |
result |
| 48 |
} |
| 1 |
#' Does do.call quicker, and avoids issues with debug mode within do.call |
|
| 2 |
#' @description copied from ms showcase app |
|
| 3 |
#' @param what either a function or a non-empty character string naming the function to be called. |
|
| 4 |
#' @param args a list of arguments to the function call. The names attribute of args gives the argument names. |
|
| 5 |
#' @param quote a logical value indicating whether to quote the arguments. |
|
| 6 |
#' @param envir an environment within which to evaluate the call. This will be most useful if what is a character |
|
| 7 |
#' string and the arguments are symbols or quoted expressions. |
|
| 8 |
#' @return No return value, called for side effects |
|
| 9 |
#' @export |
|
| 10 |
fastDoCall <- |
|
| 11 |
function(what, |
|
| 12 |
args, |
|
| 13 |
quote = FALSE, |
|
| 14 |
envir = parent.frame()) {
|
|
| 15 | 41x |
if (quote) {
|
| 16 | ! |
args <- lapply(args, enquote) |
| 17 |
} |
|
| 18 | ||
| 19 | 41x |
if (is.null(names(args))) {
|
| 20 | ! |
argn <- args |
| 21 | ! |
args <- list() |
| 22 |
} else {
|
|
| 23 |
# Add all the named arguments |
|
| 24 | 41x |
argn <- lapply(names(args)[names(args) != ""], as.name) |
| 25 | 41x |
names(argn) <- names(args)[names(args) != ""] |
| 26 |
# Add the unnamed arguments |
|
| 27 | 41x |
argn <- c(argn, args[names(args) == ""]) |
| 28 | 41x |
args <- args[names(args) != ""] |
| 29 |
} |
|
| 30 | ||
| 31 | 41x |
if (is(what, "character")) {
|
| 32 | 1x |
if (is.character(what)) {
|
| 33 | 1x |
fn <- strsplit(what, "[:]{2,3}")[[1]]
|
| 34 | 1x |
what <- if (length(fn) == 1) {
|
| 35 | 1x |
get(fn[[1]], envir = envir, mode = "function") |
| 36 |
} else {
|
|
| 37 | ! |
get(fn[[2]], envir = asNamespace(fn[[1]]), mode = "function") |
| 38 |
} |
|
| 39 |
} |
|
| 40 | 1x |
call <- as.call(c(list(what), argn)) |
| 41 | 40x |
} else if (is(what, "function")) {
|
| 42 | 40x |
f_name <- deparse(substitute(what)) |
| 43 | 40x |
call <- as.call(c(list(as.name(f_name)), argn)) |
| 44 | 40x |
args[[f_name]] <- what |
| 45 | ! |
} else if (is(what, "name")) {
|
| 46 | ! |
call <- as.call(c(list(what, argn))) |
| 47 |
} |
|
| 48 | ||
| 49 | 41x |
eval(call, |
| 50 | 41x |
envir = args, |
| 51 | 41x |
enclos = envir |
| 52 |
) |
|
| 53 |
} |
| 1 |
#' Save an Output |
|
| 2 |
#' |
|
| 3 |
#' @param output Output object, e.g. an `rtable` or `grob` |
|
| 4 |
#' @param file_name Full path of the new file *excluding* the extension |
|
| 5 |
#' @param save_rds Saved as an `.rds` files |
|
| 6 |
#' @details |
|
| 7 |
#' Tables are saved as RDS file |
|
| 8 |
#' |
|
| 9 |
#' @return The input `object` invisibly |
|
| 10 |
#' @export |
|
| 11 |
#' |
|
| 12 |
#' @examples |
|
| 13 |
#' library(dplyr) |
|
| 14 |
#' adsl <- eg_adsl %>% |
|
| 15 |
#' filter(SAFFL == "Y") %>% |
|
| 16 |
#' mutate(TRT01P = factor(TRT01P, levels = c("A: Drug X", "B: Placebo")))
|
|
| 17 |
#' output_dir <- tempdir() |
|
| 18 |
#' t_dm_slide(adsl, "TRT01P", c("SEX", "AGE", "RACE", "ETHNIC", "COUNTRY")) %>%
|
|
| 19 |
#' decorate( |
|
| 20 |
#' title = "Demographic table", |
|
| 21 |
#' footnote = "" |
|
| 22 |
#' ) %>% |
|
| 23 |
#' save_output( |
|
| 24 |
#' file_name = file.path(output_dir, "t_dm_SE"), |
|
| 25 |
#' save_rds = TRUE |
|
| 26 |
#' ) |
|
| 27 |
#' |
|
| 28 |
setGeneric("save_output", function(output, file_name, save_rds) {
|
|
| 29 |
standardGeneric("save_output")
|
|
| 30 |
}) |
|
| 31 | ||
| 32 |
#' @rdname save_output |
|
| 33 |
save_output <- function(output, file_name, save_rds = TRUE) {
|
|
| 34 | 6x |
UseMethod("save_output")
|
| 35 |
} |
|
| 36 | ||
| 37 |
#' @rdname save_output |
|
| 38 |
#' @return No return value, called for side effects |
|
| 39 |
#' @export |
|
| 40 |
save_output.autoslider_error <- function(output, |
|
| 41 |
file_name, |
|
| 42 |
save_rds = TRUE) {
|
|
| 43 | ! |
output |
| 44 |
} |
|
| 45 | ||
| 46 |
#' @rdname save_output |
|
| 47 |
#' @aliases save_output, dVTableTree, dVTableTree-method |
|
| 48 |
setMethod("save_output", "dVTableTree", save_output.dVTableTree <- function(output, file_name, save_rds = TRUE) {
|
|
| 49 | 12x |
if (save_rds) {
|
| 50 | 12x |
saveRDS(output, file = paste0(file_name, ".rds")) |
| 51 |
} |
|
| 52 | ||
| 53 | 12x |
invisible(output) |
| 54 |
}) |
|
| 55 | ||
| 56 |
#' @rdname save_output |
|
| 57 |
#' @return The input `object` invisibly |
|
| 58 |
#' @export |
|
| 59 |
save_output.decoratedGrob <- function(output, |
|
| 60 |
file_name, |
|
| 61 |
save_rds = TRUE) {
|
|
| 62 | 4x |
if (save_rds) {
|
| 63 | 4x |
saveRDS(output, file = paste0(file_name, ".rds")) |
| 64 |
} |
|
| 65 | ||
| 66 | 4x |
invisible(output) |
| 67 |
} |
|
| 68 | ||
| 69 |
#' @rdname save_output |
|
| 70 |
#' @return The input `object` invisibly |
|
| 71 |
#' @export |
|
| 72 |
save_output.decoratedGrobSet <- function(output, file_name, save_rds = TRUE) {
|
|
| 73 | ! |
if (save_rds) {
|
| 74 | ! |
saveRDS(output, file = paste0(file_name, ".rds")) |
| 75 |
} |
|
| 76 | ||
| 77 | ! |
invisible(output) |
| 78 |
} |
|
| 79 | ||
| 80 | ||
| 81 |
#' @rdname save_output |
|
| 82 |
#' @return The input `object` invisibly |
|
| 83 |
#' @export |
|
| 84 |
save_output.dgtsummary <- function(output, file_name, save_rds = TRUE) {
|
|
| 85 | 1x |
if (save_rds) {
|
| 86 | 1x |
saveRDS(output, file = paste0(file_name, ".rds")) |
| 87 |
} |
|
| 88 | ||
| 89 | 1x |
invisible(output) |
| 90 |
} |
|
| 91 | ||
| 92 |
#' @rdname save_output |
|
| 93 |
#' @return The input `object` invisibly |
|
| 94 |
#' @export |
|
| 95 |
save_output.dlisting <- function(output, file_name, save_rds = TRUE) {
|
|
| 96 | 1x |
if (save_rds) {
|
| 97 | 1x |
saveRDS(output, file = paste0(file_name, ".rds")) |
| 98 |
} |
|
| 99 | ||
| 100 | 1x |
invisible(output) |
| 101 |
} |
|
| 102 | ||
| 103 | ||
| 104 |
#' Save a list of outputs |
|
| 105 |
#' |
|
| 106 |
#' @param outputs `list` of outputs as created by `generate_outputs` |
|
| 107 |
#' @param outfolder Folder in which to store the `outputs`` |
|
| 108 |
#' @param verbose_level Level of verbose information displayed. |
|
| 109 |
#' Default set to `1`. |
|
| 110 |
#' @param save_rds Should the input `outputs` be saved as `.rds` files in |
|
| 111 |
#' in addition to `.out` or `.pdf` files? Defaults to `FALSE`. |
|
| 112 |
#' @param generic_suffix generic suffix. must be length 1 character or NULL. |
|
| 113 |
#' @export |
|
| 114 |
#' @return The input `object` invisibly |
|
| 115 |
#' @examplesIf require(filters) |
|
| 116 |
#' ## As `save_outputs` is the last step in the pipeline we have to run |
|
| 117 |
#' ## the 'whole machinery' in order to show its functionality. |
|
| 118 |
#' library(dplyr, warn.conflicts = FALSE) |
|
| 119 |
#' |
|
| 120 |
#' data <- list( |
|
| 121 |
#' adsl = eg_adsl, |
|
| 122 |
#' adae = eg_adae, |
|
| 123 |
#' adtte = eg_adtte |
|
| 124 |
#' ) |
|
| 125 |
#' |
|
| 126 |
#' filters::load_filters( |
|
| 127 |
#' yaml_file = system.file("filters.yml", package = "autoslider.core"),
|
|
| 128 |
#' overwrite = TRUE |
|
| 129 |
#' ) |
|
| 130 |
#' |
|
| 131 |
#' ## For this example the outputs will be saved in a temporary directory. In a |
|
| 132 |
#' ## production run this should be the reporting event's 'output' folder instead. |
|
| 133 |
#' output_dir <- tempdir() |
|
| 134 |
#' |
|
| 135 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 136 |
#' read_spec(spec_file) %>% |
|
| 137 |
#' filter_spec(program == "t_dm_slide") %>% |
|
| 138 |
#' generate_outputs(datasets = data) %>% |
|
| 139 |
#' decorate_outputs() %>% |
|
| 140 |
#' save_outputs(outfolder = output_dir) |
|
| 141 |
#' |
|
| 142 |
save_outputs <- function(outputs, |
|
| 143 |
outfolder = file.path("output"),
|
|
| 144 |
generic_suffix = NULL, |
|
| 145 |
save_rds = TRUE, |
|
| 146 |
verbose_level = 1) {
|
|
| 147 | 1x |
stopifnot(is.list(outputs)) |
| 148 | ||
| 149 | 1x |
if (!dir.exists(outfolder)) {
|
| 150 | ! |
dir.create(outfolder) |
| 151 |
} |
|
| 152 | 1x |
if (!is.null(generic_suffix)) {
|
| 153 | ! |
if (!(is.character(generic_suffix) & length(generic_suffix) == 1)) {
|
| 154 | ! |
stop("generic suffix must be length 1 character!")
|
| 155 |
} |
|
| 156 |
} |
|
| 157 | 1x |
ret <- lapply(outputs, function(output) {
|
| 158 | 15x |
spec <- attr(output, "spec") |
| 159 | 15x |
file_path <- file.path(outfolder, spec$output) |
| 160 | 15x |
file_path <- paste0(c(file_path, generic_suffix), collapse = "_") |
| 161 | 15x |
output <- save_output( |
| 162 | 15x |
output = output, |
| 163 | 15x |
file_name = file_path, |
| 164 | 15x |
save_rds = save_rds |
| 165 |
) |
|
| 166 | ||
| 167 | 15x |
if (verbose_level > 0) {
|
| 168 | 15x |
if (is(output, "autoslider_error")) {
|
| 169 | ! |
cat_bullet( |
| 170 | ! |
"Saving output ", |
| 171 | ! |
attr(output, "spec")$output, |
| 172 | ! |
" failed in step ", |
| 173 | ! |
attr(output, "step"), |
| 174 | ! |
" with error message: ", |
| 175 | ! |
toString(output), |
| 176 | ! |
bullet = "cross", |
| 177 | ! |
bullet_col = "red" |
| 178 |
) |
|
| 179 |
} else {
|
|
| 180 | 15x |
cat_bullet( |
| 181 | 15x |
"Output saved in path ", |
| 182 | 15x |
file_path, |
| 183 | 15x |
bullet = "tick", |
| 184 | 15x |
bullet_col = "green" |
| 185 |
) |
|
| 186 |
} |
|
| 187 |
} |
|
| 188 | ||
| 189 | 15x |
attr(output, "outpath") <- get_output_file_ext(output, file_path) |
| 190 | 15x |
output |
| 191 |
}) |
|
| 192 | ||
| 193 | 1x |
if (verbose_level > 0) {
|
| 194 | 1x |
total_number <- length(ret) |
| 195 | 1x |
fail_number <- sum(map_lgl(ret, is, class2 = "autoslider_error")) |
| 196 | 1x |
log_success_infomation(total_number - fail_number, fail_number) |
| 197 |
} |
|
| 198 | ||
| 199 | 1x |
ret |
| 200 |
} |
|
| 201 | ||
| 202 | ||
| 203 |
#' Generate slides from rds files |
|
| 204 |
#' @param filenames List of file names |
|
| 205 |
#' @param template Template file path |
|
| 206 |
#' @param outfile Out file path |
|
| 207 |
#' @return No return value, called for side effects |
|
| 208 |
#' |
|
| 209 |
#' @export |
|
| 210 |
#' @examplesIf require(filters) |
|
| 211 |
#' library(dplyr, warn.conflicts = FALSE) |
|
| 212 |
#' |
|
| 213 |
#' data <- list( |
|
| 214 |
#' adsl = eg_adsl, |
|
| 215 |
#' adae = eg_adae, |
|
| 216 |
#' adtte = eg_adtte |
|
| 217 |
#' ) |
|
| 218 |
#' |
|
| 219 |
#' filters::load_filters( |
|
| 220 |
#' yaml_file = system.file("filters.yml", package = "autoslider.core"),
|
|
| 221 |
#' overwrite = TRUE |
|
| 222 |
#' ) |
|
| 223 |
#' |
|
| 224 |
#' ## For this example the outputs will be saved in a temporary directory. In a |
|
| 225 |
#' ## production run this should be the reporting event's 'output' folder instead. |
|
| 226 |
#' output_dir <- tempdir() |
|
| 227 |
#' |
|
| 228 |
#' spec_file <- system.file("spec.yml", package = "autoslider.core")
|
|
| 229 |
#' read_spec(spec_file) %>% |
|
| 230 |
#' filter_spec(program == "t_dm_slide") %>% |
|
| 231 |
#' generate_outputs(datasets = data) %>% |
|
| 232 |
#' decorate_outputs() %>% |
|
| 233 |
#' save_outputs(outfolder = output_dir) |
|
| 234 |
#' |
|
| 235 |
#' slides_from_rds(list.files(file.path(output_dir, "t_dm_slide_FAS.rds"))) |
|
| 236 |
slides_from_rds <- function(filenames, outfile = paste0(tempdir(), "/output.pptx"), |
|
| 237 |
template = file.path(system.file(package = "autoslider.core"), "theme/basic.pptx")) {
|
|
| 238 | 2x |
outputs <- lapply(filenames, readRDS) |
| 239 | 2x |
generate_slides(outputs, outfile, template) |
| 240 |
} |
| 1 |
log_success_infomation <- function(success, failure) {
|
|
| 2 | 3x |
total_number <- success + failure |
| 3 | 3x |
cat_bullet( |
| 4 | 3x |
"Total number of success ", |
| 5 | 3x |
success, |
| 6 |
"/", |
|
| 7 | 3x |
total_number, |
| 8 | 3x |
bullet = "tick", |
| 9 | 3x |
bullet_col = "green" |
| 10 |
) |
|
| 11 | 3x |
if (failure > 0) {
|
| 12 | 1x |
cat_bullet( |
| 13 | 1x |
"Total number of failures ", |
| 14 | 1x |
failure, |
| 15 |
"/", |
|
| 16 | 1x |
total_number, |
| 17 | 1x |
bullet = "cross", |
| 18 | 1x |
bullet_col = "red" |
| 19 |
) |
|
| 20 |
} |
|
| 21 |
} |
|
| 22 | ||
| 23 |
log_number_of_matched_records <- function(original_spec, |
|
| 24 |
filtered_spec, |
|
| 25 |
condition) {
|
|
| 26 | 3x |
if (length(filtered_spec)) {
|
| 27 | 3x |
msg <- sprintf( |
| 28 | 3x |
"%d/%d outputs matched the filter condition `%s`.", |
| 29 | 3x |
length(filtered_spec), |
| 30 | 3x |
length(original_spec), |
| 31 | 3x |
deparse(condition) |
| 32 |
) |
|
| 33 | 3x |
cat_bullet(msg, bullet = "tick", bullet_col = "green") |
| 34 |
} else {
|
|
| 35 | ! |
msg <- sprintf( |
| 36 | ! |
"No output matched the filter condition `%s`", |
| 37 | ! |
deparse(condition) |
| 38 |
) |
|
| 39 | ! |
cat_bullet(msg, bullet = "cross", bullet_col = "red") |
| 40 |
} |
|
| 41 |
} |
| 1 |
#' DOR table |
|
| 2 |
#' @param adsl ADSL dataset |
|
| 3 |
#' @param adtte ADTTE dataset |
|
| 4 |
#' @param arm Arm variable, character, "`TRT01P" by default. |
|
| 5 |
#' @param refgroup Reference group |
|
| 6 |
#' @inherit gen_notes note |
|
| 7 |
#' @return An `rtables` object |
|
| 8 |
#' @export |
|
| 9 |
#' @examples |
|
| 10 |
#' library(dplyr) |
|
| 11 |
#' adsl <- eg_adsl %>% |
|
| 12 |
#' dplyr::mutate(TRT01P = factor(TRT01P, levels = c("A: Drug X", "B: Placebo", "C: Combination")))
|
|
| 13 |
#' adtte <- eg_adtte %>% |
|
| 14 |
#' dplyr::filter(PARAMCD == "OS") %>% |
|
| 15 |
#' dplyr::mutate(TRT01P = factor(TRT01P, levels = c("A: Drug X", "B: Placebo", "C: Combination")))
|
|
| 16 |
#' out <- t_dor_slide(adsl, adtte) |
|
| 17 |
#' print(out) |
|
| 18 |
#' generate_slides(out, paste0(tempdir(), "/dor.pptx")) |
|
| 19 |
t_dor_slide <- function(adsl, adtte, arm = "TRT01P", refgroup = NULL) {
|
|
| 20 | 2x |
assert_that(has_name(adsl, arm)) |
| 21 | 2x |
assert_that(has_name(adtte, "CNSR")) |
| 22 | 2x |
assert_that(has_name(adtte, "EVNTDESC")) |
| 23 | 2x |
assert_that(has_name(adtte, "AVALU")) |
| 24 | 2x |
assert_that(has_name(adtte, "AVAL")) |
| 25 | 2x |
assert_that(all(!is.na(adtte[["AVALU"]]))) |
| 26 | ||
| 27 | 2x |
slref_arm <- sort(unique(adsl[[arm]])) |
| 28 | 2x |
anl_arm <- sort(unique(adtte[[arm]])) |
| 29 | 2x |
assert_that(identical(slref_arm, anl_arm), |
| 30 | 2x |
msg = "The adsl and the analysis datasets should have the same treatment arm levels" |
| 31 |
) |
|
| 32 | ||
| 33 | ||
| 34 | 2x |
time_unit <- unique(adtte[["AVALU"]]) |
| 35 | 2x |
assert_that(length(time_unit) == 1) |
| 36 | ||
| 37 | 2x |
if (toupper(time_unit) == "DAYS") {
|
| 38 | 1x |
adtte <- adtte %>% |
| 39 | 1x |
dplyr::mutate(AVAL = day2month(AVAL)) |
| 40 | 1x |
} else if (toupper(time_unit) == "YEARS") {
|
| 41 | 1x |
adtte <- adtte %>% |
| 42 | 1x |
dplyr::mutate(AVAL = AVAL * 12) |
| 43 |
} |
|
| 44 | ||
| 45 | 2x |
adtte_f <- adtte %>% |
| 46 | 2x |
dplyr::mutate( |
| 47 | 2x |
is_event = CNSR == 0, |
| 48 | 2x |
is_not_event = CNSR == 1, |
| 49 | 2x |
EVNT1 = factor( |
| 50 | 2x |
case_when( |
| 51 | 2x |
is_event == TRUE ~ "Responders with subsequent event (%)", |
| 52 | 2x |
is_event == FALSE ~ "Responders without subsequent event (%)" |
| 53 |
) |
|
| 54 |
), |
|
| 55 | 2x |
EVNTDESC = factor(EVNTDESC) |
| 56 |
) %>% |
|
| 57 | 2x |
semi_join(., adsl, by = c("STUDYID", "USUBJID")) %>%
|
| 58 | 2x |
select(STUDYID, USUBJID, {{ arm }}, AVAL, is_event, is_not_event, EVNT1, EVNTDESC) %>%
|
| 59 | 2x |
df_explicit_na(char_as_factor = FALSE) |
| 60 | ||
| 61 | 2x |
lyt_02 <- basic_table() %>% |
| 62 | 2x |
split_cols_by( |
| 63 | 2x |
var = arm, |
| 64 | 2x |
ref_group = refgroup |
| 65 |
) %>% |
|
| 66 | 2x |
add_colcounts() %>% |
| 67 | 2x |
count_values( |
| 68 | 2x |
vars = "USUBJID", |
| 69 | 2x |
values = unique(adtte$USUBJID), |
| 70 | 2x |
.labels = c(count = "Responders"), |
| 71 | 2x |
.stats = "count" |
| 72 |
) %>% |
|
| 73 | 2x |
analyze_vars( |
| 74 | 2x |
vars = "is_event", |
| 75 | 2x |
.stats = "count_fraction", |
| 76 | 2x |
.labels = c(count_fraction = "With subsequent event (%)"), |
| 77 | 2x |
.indent_mods = c(count_fraction = 1L), |
| 78 | 2x |
show_labels = "hidden", |
| 79 |
) %>% |
|
| 80 | 2x |
analyze( |
| 81 | 2x |
vars = "AVAL", |
| 82 | 2x |
afun = s_surv_time_1, |
| 83 | 2x |
extra_args = list(is_event = "is_event"), |
| 84 | 2x |
table_names = "est_prop", |
| 85 | 2x |
format = format_xx("xx.x (xx.x, xx.x)"),
|
| 86 | 2x |
show_labels = "hidden", |
| 87 | 2x |
indent_mod = 1 |
| 88 |
) |
|
| 89 | ||
| 90 | 2x |
result <- build_table(lyt_02, df = adtte_f, alt_counts_df = adsl) |
| 91 | 2x |
result@main_title <- "DOR slide" |
| 92 | 2x |
result |
| 93 |
} |
| 1 |
#' Table color and font |
|
| 2 |
#' |
|
| 3 |
#' @description Zebra themed color |
|
| 4 |
#' |
|
| 5 |
#' @name autoslider_format |
|
| 6 |
NULL |
|
| 7 | ||
| 8 |
#' @describeIn autoslider_format |
|
| 9 |
#' |
|
| 10 |
#' User defined color code and font size |
|
| 11 |
#' |
|
| 12 |
#' @param ft flextable object |
|
| 13 |
#' @param odd_header Hex color code, default to deep sky blue |
|
| 14 |
#' @param odd_body Hex color code, default to alice blue |
|
| 15 |
#' @param even_header Hex color code, default to slate gray |
|
| 16 |
#' @param even_body Hex color code, default to slate gray |
|
| 17 |
#' @param font_name Font name, default to arial |
|
| 18 |
#' @param body_font_size Font size of the table content, default to 12 |
|
| 19 |
#' @param header_font_size Font size of the table header, default to 14 |
|
| 20 |
#' @return A flextable with applied theme. |
|
| 21 |
#' @export |
|
| 22 |
autoslider_format <- function(ft, |
|
| 23 |
odd_header = "#0EAED5", # "deepskyblue2", |
|
| 24 |
odd_body = "#EBF5FA", # "aliceblue", |
|
| 25 |
even_header = "#0EAED5", # "slategray1", |
|
| 26 |
even_body = "#D0E4F2", # "slategray1" # slategray1, |
|
| 27 |
font_name = "arial", |
|
| 28 |
body_font_size = 12, |
|
| 29 |
header_font_size = 14) {
|
|
| 30 | 296x |
ft %>% |
| 31 | 296x |
theme_zebra( |
| 32 | 296x |
odd_header = odd_header, |
| 33 | 296x |
odd_body = odd_body, |
| 34 | 296x |
even_header = odd_header, |
| 35 | 296x |
even_body = even_body |
| 36 |
) %>% |
|
| 37 | 296x |
font(fontname = font_name, part = "all") %>% |
| 38 | 296x |
fontsize(size = body_font_size, part = "body") %>% |
| 39 | 296x |
color(color = "white", part = "header") %>% |
| 40 | 296x |
fontsize(size = header_font_size, part = "header") %>% |
| 41 | 296x |
bold(part = "header") |
| 42 |
} |
|
| 43 | ||
| 44 | ||
| 45 |
#' @describeIn autoslider_format |
|
| 46 |
#' |
|
| 47 |
#' Blue color theme |
|
| 48 |
#' |
|
| 49 |
#' @param ft flextable object |
|
| 50 |
#' @param ... arguments passed to program |
|
| 51 |
#' |
|
| 52 |
#' @export |
|
| 53 |
blue_format <- function(ft, ...) {
|
|
| 54 | 1x |
ft %>% autoslider_format( |
| 55 | 1x |
odd_header = "#0B41CD", |
| 56 | 1x |
odd_body = "#1482FA", |
| 57 | 1x |
even_body = "#BDE3FF", |
| 58 |
... |
|
| 59 |
) |
|
| 60 |
} |
|
| 61 | ||
| 62 |
#' @describeIn autoslider_format |
|
| 63 |
#' |
|
| 64 |
#' Orange color theme |
|
| 65 |
#' |
|
| 66 |
#' @param ft flextable object |
|
| 67 |
#' @param ... arguments passed to program |
|
| 68 |
#' |
|
| 69 |
#' @export |
|
| 70 |
orange_format <- function(ft, ...) {
|
|
| 71 | 292x |
ft %>% autoslider_format( |
| 72 | 292x |
odd_header = "#ED4A0D", |
| 73 | 292x |
odd_body = "#FF7D29", |
| 74 | 292x |
even_body = "#FFBD69", |
| 75 |
... |
|
| 76 |
) |
|
| 77 |
} |
|
| 78 | ||
| 79 |
#' @describeIn autoslider_format |
|
| 80 |
#' |
|
| 81 |
#' Red color theme |
|
| 82 |
#' |
|
| 83 |
#' @param ft flextable object |
|
| 84 |
#' @param ... arguments passed to program |
|
| 85 |
#' |
|
| 86 |
#' @export |
|
| 87 |
red_format <- function(ft, ...) {
|
|
| 88 | 1x |
ft %>% autoslider_format( |
| 89 | 1x |
odd_header = "#C40000", |
| 90 | 1x |
odd_body = "#FF1F26", |
| 91 | 1x |
even_body = "#FF8782", |
| 92 |
... |
|
| 93 |
) |
|
| 94 |
} |
|
| 95 | ||
| 96 | ||
| 97 |
#' @describeIn autoslider_format |
|
| 98 |
#' |
|
| 99 |
#' Purple color theme |
|
| 100 |
#' |
|
| 101 |
#' @param ft flextable object |
|
| 102 |
#' @param ... arguments passed to program |
|
| 103 |
#' |
|
| 104 |
#' @export |
|
| 105 |
purple_format <- function(ft, ...) {
|
|
| 106 | 1x |
ft %>% autoslider_format( |
| 107 | 1x |
odd_header = "#BC36F0", |
| 108 | 1x |
odd_body = "#E085FC", |
| 109 | 1x |
even_body = "#F2D4FF", |
| 110 |
... |
|
| 111 |
) |
|
| 112 |
} |
|
| 113 | ||
| 114 |
#' @describeIn autoslider_format |
|
| 115 |
#' |
|
| 116 |
#' `AutoslideR` dose formats |
|
| 117 |
#' |
|
| 118 |
#' @param ft flextable object |
|
| 119 |
#' @param header_vals Header |
|
| 120 |
#' |
|
| 121 |
#' @export |
|
| 122 |
autoslider_dose_format <- function(ft, header_vals = names(ft$body$dataset)) {
|
|
| 123 |
# The original implementation used delete_rows and add_header_row, which can be |
|
| 124 |
# brittle. Using set_header_labels is the idiomatic and more robust way |
|
| 125 |
# to simply change the text of the header row. This avoids the colwidths error. |
|
| 126 | 1x |
ft %>% |
| 127 | 1x |
theme_booktabs() %>% |
| 128 |
# set_header_labels(values = header_vals) %>% |
|
| 129 | 1x |
bold(part = "header") %>% |
| 130 | 1x |
border_remove() |
| 131 |
} |
|
| 132 | ||
| 133 | ||
| 134 |
#' @describeIn autoslider_format |
|
| 135 |
#' |
|
| 136 |
#' Black color theme |
|
| 137 |
#' @author Nina Qi and Jasmina Uzunovic |
|
| 138 |
#' @param ft flextable object |
|
| 139 |
#' @param ... arguments passed to program |
|
| 140 |
#' |
|
| 141 |
#' @export |
|
| 142 |
black_format_tb <- function(ft, body_font_size = 8, header_font_size = 8, ...) {
|
|
| 143 | 1x |
ft %>% |
| 144 | 1x |
theme_booktabs() %>% |
| 145 | 1x |
fontsize(size = body_font_size, part = "body") %>% |
| 146 | 1x |
fontsize(size = header_font_size, part = "header") %>% |
| 147 | 1x |
bold(part = "header") %>% |
| 148 | 1x |
color(color = "blue", part = "header") %>% |
| 149 | 1x |
border_inner_h(part = "all", border = fp_border(color = "black")) %>% |
| 150 | 1x |
hline_top(part = "all", border = fp_border(color = "black", width = 2)) %>% |
| 151 | 1x |
hline_bottom(part = "all", border = fp_border(color = "black", width = 2)) |
| 152 |
} |
| 1 |
#' Plot mean values general function |
|
| 2 |
#' used by wrappers `g_vs_slide`,`g_lb_slide`, & `g_eg_slide` |
|
| 3 |
#' |
|
| 4 |
#' adapted from https://insightsengineering.github.io/tlg-catalog/stable/graphs/other/mng01.html |
|
| 5 |
#' |
|
| 6 |
#' @param adsl ADSL dataset |
|
| 7 |
#' @param data dataset containing the variable of interest in PARAMCD and AVAL |
|
| 8 |
#' @inheritParams tern::g_lineplot |
|
| 9 |
#' @param by_vars variables to merge the two datasets by |
|
| 10 |
#' @param subtitle character scalar forwarded to g_lineplot |
|
| 11 |
#' @param ... additional arguments passed to `tern::g_lineplot` |
|
| 12 |
#' @author Stefan Thoma (`thomas7`) |
|
| 13 |
#' @importFrom forcats fct_reorder |
|
| 14 |
#' @import ggplot2 |
|
| 15 |
#' @import dplyr tern assertthat |
|
| 16 |
#' @export |
|
| 17 |
#' @examplesIf require('rsvg')
|
|
| 18 |
#' library(dplyr) |
|
| 19 |
#' advs_filtered <- eg_advs %>% filter( |
|
| 20 |
#' PARAMCD == "SYSBP" |
|
| 21 |
#' ) |
|
| 22 |
#' out1 <- g_mean_general(eg_adsl, advs_filtered) |
|
| 23 |
#' generate_slides(out1, paste0(tempdir(), "/g_mean.pptx")) |
|
| 24 |
g_mean_general <- function(adsl, |
|
| 25 |
data, |
|
| 26 |
variables = control_lineplot_vars(group_var = "TRT01P"), |
|
| 27 |
by_vars = c("USUBJID", "STUDYID"),
|
|
| 28 |
subtitle = "Plot of Mean and 95% Confidence Limits by Visit.", |
|
| 29 |
...) {
|
|
| 30 | 9x |
assert_that(is.string(subtitle)) |
| 31 | 9x |
variables <- variables %>% strip_NA() # tern 0.9.4 added facet_var in control_lineplot_vars |
| 32 | 9x |
assert_that(has_name(data, c(by_vars, variables))) |
| 33 | 9x |
assert_that(has_name(adsl, c(by_vars, variables["group_var"]))) |
| 34 | ||
| 35 | 9x |
adsl_f <- adsl %>% |
| 36 | 9x |
df_explicit_na() |
| 37 | ||
| 38 | 9x |
data_f <- data %>% |
| 39 | 9x |
mutate(AVISIT = forcats::fct_reorder(AVISIT, AVISITN, min)) %>% |
| 40 | 9x |
dplyr::filter( |
| 41 | 9x |
AVISIT != "SCREENING" |
| 42 |
) %>% |
|
| 43 | 9x |
droplevels() %>% |
| 44 | 9x |
df_explicit_na() %>% |
| 45 | 9x |
semi_join(adsl_f, by_vars) |
| 46 | ||
| 47 | ||
| 48 | 9x |
plot <- g_lineplot( |
| 49 | 9x |
df = data_f, |
| 50 | 9x |
alt_counts_df = adsl_f, |
| 51 | 9x |
variables = variables, |
| 52 | 9x |
title = "", |
| 53 | 9x |
subtitle = subtitle, |
| 54 |
... |
|
| 55 |
) |
|
| 56 | 9x |
plot |
| 57 |
} |
| 1 |
#' Refactor active arm |
|
| 2 |
#' |
|
| 3 |
#' @param df Input dataframe |
|
| 4 |
#' @param arm_var Arm variable |
|
| 5 |
#' @param levels factor levels |
|
| 6 |
#' @param labels factor labels |
|
| 7 |
#' @return Dataframe with re-level and re-labelled arm variable. |
|
| 8 |
#' @export |
|
| 9 |
mutate_actarm <- function(df, |
|
| 10 |
arm_var = "TRT01A", |
|
| 11 |
levels = c( |
|
| 12 |
"PLACEBO + PACLITAXEL + CISPLATIN", |
|
| 13 |
"ATEZOLIZUMAB + TIRAGOLUMAB + PACLITAXEL + CISPLATIN" |
|
| 14 |
), |
|
| 15 |
labels = c("Pbo+Pbo+PC", "Tira+Atezo+PC")) {
|
|
| 16 | 2x |
df %>% |
| 17 | 2x |
mutate_at(arm_var, ~ factor(explicit_na(sas_na(.)), |
| 18 | 2x |
levels = levels, |
| 19 | 2x |
labels = labels |
| 20 |
)) |
|
| 21 |
} |
|
| 22 | ||
| 23 |
#' Preprocess t_dd function |
|
| 24 |
#' |
|
| 25 |
#' @param df Input dataframe |
|
| 26 |
#' @param levels factor levels |
|
| 27 |
#' @param labels factor labels |
|
| 28 |
#' @return dataframe |
|
| 29 |
#' @export |
|
| 30 |
preprocess_t_dd <- function(df, |
|
| 31 |
levels = c("PROGRESSIVE DISEASE", "ADVERSE EVENT", "OTHER", "<Missing>"),
|
|
| 32 |
labels = c("Progressive Disease", "Adverse Events", "Other", "<Missing>")) {
|
|
| 33 | 1x |
noNA(levels) |
| 34 | 1x |
noNA(labels) |
| 35 | 1x |
assert_that(length(levels) >= 3) |
| 36 | 1x |
assert_that(length(labels) >= 3) |
| 37 | ||
| 38 | 1x |
df %>% |
| 39 | 1x |
mutate( |
| 40 | 1x |
DTHCAT1 = DTHCAT == levels[1], |
| 41 | 1x |
DTHCAT2 = DTHCAT == levels[2], |
| 42 | 1x |
DTHCAT3 = DTHCAT == levels[3], |
| 43 | 1x |
DTHCAT = factor(explicit_na(sas_na(DTHCAT)), levels = levels, labels = labels) |
| 44 |
) %>% |
|
| 45 | 1x |
formatters::var_relabel( |
| 46 | 1x |
DTHCAT1 = labels[1], |
| 47 | 1x |
DTHCAT2 = labels[2], |
| 48 | 1x |
DTHCAT3 = labels[3] |
| 49 |
) |
|
| 50 |
} |
|
| 51 | ||
| 52 | ||
| 53 |
#' Preprocess t_ds function |
|
| 54 |
#' |
|
| 55 |
#' @param df Input dataframe |
|
| 56 |
#' @param levels factor levels |
|
| 57 |
#' @param labels factor labels |
|
| 58 |
#' @return dataframe |
|
| 59 |
#' @export |
|
| 60 |
preprocess_t_ds <- function(df, |
|
| 61 |
levels = c("Alive: On Treatment", "Alive: In Follow-up", "<Missing>"),
|
|
| 62 |
labels = c("Alive: On Treatment", "Alive: In Follow-up", "<Missing>")) {
|
|
| 63 | 2x |
assert_that(has_name(df, "DISTRTFL"), |
| 64 | 2x |
msg = "`DISTRTFL` variable is needed for deriving `STDONS` variable, |
| 65 | 2x |
suggest to use `DTRTxxFL` to create `DISTRTFL`." |
| 66 |
) |
|
| 67 | 2x |
noNA(levels) |
| 68 | 2x |
noNA(labels) |
| 69 | 2x |
assert_that(length(levels) >= 3) |
| 70 | 2x |
assert_that(length(labels) >= 3) |
| 71 | ||
| 72 | 2x |
data_adsl <- df %>% |
| 73 |
# Calculate STDONS |
|
| 74 | 2x |
mutate(STDONS = case_when( |
| 75 | 2x |
toupper(EOSSTT) == "ONGOING" & DTHFL == "" & DISTRTFL == "N" ~ "Alive: On Treatment", |
| 76 | 2x |
toupper(EOSSTT) == "ONGOING" & DISTRTFL == "Y" ~ "Alive: In Follow-up", |
| 77 | 2x |
TRUE ~ "" |
| 78 |
)) %>% |
|
| 79 |
# Process variable |
|
| 80 | 2x |
mutate(STDONS = factor(explicit_na(sas_na(STDONS)), levels = levels, labels = labels)) |
| 81 |
} |
| 1 |
#' Plot mean values of VS |
|
| 2 |
#' |
|
| 3 |
#' Wrapper for `g_mean_general()`. |
|
| 4 |
#' Requires filtering of the datasets (e.g. using SUFFIX in spec.yml) |
|
| 5 |
#' |
|
| 6 |
#' @param adsl ADSL data |
|
| 7 |
#' @param advs ADVS data |
|
| 8 |
#' @param arm `"TRT01P"` by default |
|
| 9 |
#' @inheritParams g_mean_general |
|
| 10 |
#' @param paramcd Which variable to use for plotting. By default `"PARAM"` |
|
| 11 |
#' @param ... | |
|
| 12 |
#' Gets forwarded to `tern::g_lineplot()`. |
|
| 13 |
#' This lets you specify additional arguments to `tern::g_lineplot()` |
|
| 14 |
#' @author Stefan Thoma (`thomas7`) |
|
| 15 |
#' @export |
|
| 16 |
#' @examplesIf require('rsvg')
|
|
| 17 |
#' library(dplyr) |
|
| 18 |
#' advs_filtered <- eg_advs %>% filter( |
|
| 19 |
#' PARAMCD == "SYSBP" |
|
| 20 |
#' ) |
|
| 21 |
#' plot_vs <- g_vs_slide( |
|
| 22 |
#' adsl = eg_adsl, |
|
| 23 |
#' advs = advs_filtered, |
|
| 24 |
#' paramcd = "PARAM", |
|
| 25 |
#' subtitle_add_unit = FALSE |
|
| 26 |
#' ) + |
|
| 27 |
#' ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) |
|
| 28 |
#' # makes editable plots |
|
| 29 |
#' generate_slides(plot_vs, paste0(tempdir(), "/g_vs.pptx"), fig_editable = TRUE) |
|
| 30 |
#' # not editable plots, which appear as images |
|
| 31 |
#' generate_slides(plot_vs, paste0(tempdir(), "/g_vs.pptx"), fig_editable = FALSE) |
|
| 32 |
g_vs_slide <- function(adsl, advs, arm = "TRT01P", paramcd = "PARAM", |
|
| 33 |
subtitle = "Plot of Mean and 95% Confidence Limits by Visit.", ...) {
|
|
| 34 |
# tern 0.9.4 added facet_var in control_lineplot_vars |
|
| 35 | 4x |
variables <- control_lineplot_vars(group_var = arm, paramcd = paramcd) %>% strip_NA() |
| 36 | ||
| 37 | 3x |
by_vars <- c("USUBJID", "STUDYID")
|
| 38 | 3x |
assert_that(is.string(arm)) |
| 39 | 3x |
assert_that(has_name(advs, c(by_vars, variables) %>% unique())) |
| 40 | 3x |
assert_that(has_name(adsl, c(by_vars, arm) %>% unique())) |
| 41 | ||
| 42 | 3x |
g_mean_general( |
| 43 | 3x |
adsl = adsl, data = advs, variables = variables, by_vars = by_vars, |
| 44 | 3x |
subtitle = subtitle, ... |
| 45 |
) |
|
| 46 |
} |
| 1 |
#' Adverse Events listing |
|
| 2 |
#' adapted from https://insightsengineering.github.io/tlg-catalog/stable/listings/adverse-events/ael02.html |
|
| 3 |
#' @param adsl ADSL data |
|
| 4 |
#' @param adae ADAE data |
|
| 5 |
#' @export |
|
| 6 |
#' @examples |
|
| 7 |
#' library(dplyr) |
|
| 8 |
#' library(rlistings) |
|
| 9 |
#' adsl <- eg_adsl |
|
| 10 |
#' adae <- eg_adae |
|
| 11 |
#' |
|
| 12 |
#' out <- l_ae_slide(adsl = adsl, adae = adae) |
|
| 13 |
#' head(out) |
|
| 14 |
l_ae_slide <- function(adsl, adae) {
|
|
| 15 | 2x |
assert_that(has_name(adae, c( |
| 16 | 2x |
"SITEID", "SUBJID", "AGE", "SEX", "RACE", "TRTSDTM", "AETOXGR", |
| 17 | 2x |
"AENDY", "ASTDY", "AESER", "AEREL", "AEOUT", "AECONTRT", "AEACN" |
| 18 |
))) |
|
| 19 | ||
| 20 |
# Preprocess data |
|
| 21 | 2x |
adsl_f <- adsl %>% |
| 22 | 2x |
df_explicit_na() |
| 23 | ||
| 24 | 2x |
adae_f <- adae %>% |
| 25 | 2x |
semi_join(., adsl_f, by = c("STUDYID", "USUBJID")) %>%
|
| 26 | 2x |
df_explicit_na() %>% |
| 27 | 2x |
mutate( |
| 28 | 2x |
CPID = paste(SITEID, SUBJID, sep = "/"), |
| 29 | 2x |
ASR = paste(AGE, SEX, RACE, sep = "/"), |
| 30 | 2x |
Date_First = toupper(format(as.Date(TRTSDTM), "%d%b%Y")), |
| 31 | 2x |
Duration = AENDY - ASTDY + 1, |
| 32 | 2x |
Serious = ifelse(AESER == "Y", "Yes", ifelse(AESER == "N", "No", "")), |
| 33 | 2x |
Related = ifelse(AEREL == "Y", "Yes", ifelse(AEREL == "N", "No", "")), |
| 34 | 2x |
Outcome = case_when( |
| 35 | 2x |
AEOUT == "FATAL" ~ 1, |
| 36 | 2x |
AEOUT == "NOT RECOVERED/NOT RESOLVED" ~ 2, |
| 37 | 2x |
AEOUT == "RECOVERED/RESOLVED" ~ 3, |
| 38 | 2x |
AEOUT == "RECOVERED/RESOLVED WITH SEQUELAE" ~ 4, |
| 39 | 2x |
AEOUT == "RECOVERING/RESOLVING" ~ 5, |
| 40 | 2x |
AEOUT == "UNKNOWN" ~ 6 |
| 41 |
), |
|
| 42 | 2x |
Treated = ifelse(AECONTRT == "Y", "Yes", ifelse(AECONTRT == "N", "No", "")), |
| 43 | 2x |
Action = case_when( |
| 44 | 2x |
AEACN == "DOSE INCREASED" ~ 1, |
| 45 | 2x |
AEACN == "DOSE NOT CHANGED" ~ 2, |
| 46 | 2x |
AEACN == "DOSE REDUCED" | AEACN == "DOSE RATE REDUCED" ~ 3, |
| 47 | 2x |
AEACN == "DRUG INTERRUPTED" ~ 4, |
| 48 | 2x |
AEACN == "DRUG WITHDRAWN" ~ 5, |
| 49 | 2x |
AEACN == "NOT APPLICABLE" | AEACN == "NOT EVALUABLE" ~ 6, |
| 50 | 2x |
AEACN == "UNKNOWN" ~ 7 |
| 51 |
) |
|
| 52 |
) %>% |
|
| 53 | 2x |
select( |
| 54 | 2x |
CPID, |
| 55 |
# ASR, |
|
| 56 |
# TRT01A, |
|
| 57 | 2x |
AEDECOD, |
| 58 | 2x |
Date_First, |
| 59 |
# ASTDY, |
|
| 60 |
# Duration, |
|
| 61 | 2x |
Serious, |
| 62 |
# AESEV, |
|
| 63 | 2x |
Related, |
| 64 |
# Outcome, |
|
| 65 |
# Treated, |
|
| 66 | 2x |
AETOXGR, |
| 67 | 2x |
Action |
| 68 |
) |
|
| 69 | ||
| 70 | ||
| 71 | 2x |
formatters::var_labels(adae_f) <- c( |
| 72 | 2x |
CPID = "Center/Patient ID", # keep |
| 73 |
# ASR = "Age/Sex/Race", |
|
| 74 |
# TRT01A = "Treatment", #keep |
|
| 75 | 2x |
AEDECOD = "Adverse\nEvent MedDRA\nPreferred Term", # keep |
| 76 | 2x |
Date_First = "Date of\nFirst Study\nDrug\nAdministration", # keep |
| 77 |
# ASTDY = "Study\nDay of\nOnset", |
|
| 78 |
# Duration = "AE\nDuration\nin Days", |
|
| 79 | 2x |
Serious = "Serious", # keep |
| 80 |
# AESEV = "Most\nExtreme\nIntensity", |
|
| 81 | 2x |
Related = "Caused by\nStudy\nDrug", # keep |
| 82 |
# Outcome = "Outcome\n(1)", |
|
| 83 |
# Treated = "Treatment\nfor AE", |
|
| 84 | 2x |
AETOXGR = "Analysis Toxicity Grade", # keep |
| 85 | 2x |
Action = "Action\nTaken\n(2)" # keep |
| 86 |
) |
|
| 87 | ||
| 88 |
# Set up listing |
|
| 89 | ||
| 90 | 2x |
lsting <- as_listing( |
| 91 | 2x |
adae_f, |
| 92 | 2x |
key_cols = c("CPID"),
|
| 93 | 2x |
disp_cols = names(adae_f) |
| 94 |
) |
|
| 95 | ||
| 96 | 2x |
lsting |
| 97 |
} |
| 1 |
#' Adverse event table |
|
| 2 |
#' |
|
| 3 |
#' @param adae ADAE data set, dataframe |
|
| 4 |
#' @param adsl ADSL data set, dataframe |
|
| 5 |
#' @param arm Arm variable, character |
|
| 6 |
#' @param cutoff Cutoff threshold |
|
| 7 |
#' @param prune_by_total Prune according total column |
|
| 8 |
#' @param split_by_study Split by study, building structured header for tables |
|
| 9 |
#' @param side_by_side "GlobalAsia" or "GlobalAsiaChina" to define the side by side requirement |
|
| 10 |
#' @return rtables object |
|
| 11 |
#' |
|
| 12 |
#' @export |
|
| 13 |
#' @examples |
|
| 14 |
#' library(dplyr) |
|
| 15 |
#' # Example 1 |
|
| 16 |
#' adsl <- eg_adsl %>% |
|
| 17 |
#' dplyr::mutate(TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")))
|
|
| 18 |
#' adae <- eg_adae %>% |
|
| 19 |
#' dplyr::mutate( |
|
| 20 |
#' TRT01A = factor(TRT01A, levels = c("A: Drug X", "B: Placebo")),
|
|
| 21 |
#' ATOXGR = AETOXGR |
|
| 22 |
#' ) |
|
| 23 |
#' out <- t_ae_pt_soc_slide(adsl, adae, "TRT01A", 2) |
|
| 24 |
#' print(out) |
|
| 25 |
#' generate_slides(out, paste0(tempdir(), "/ae.pptx")) |
|
| 26 |
#' |
|
| 27 |
#' |
|
| 28 |
#' # Example 2, prune by total column |
|
| 29 |
#' out2 <- t_ae_pt_soc_slide(adsl, adae, "TRT01A", 25, prune_by_total = TRUE) |
|
| 30 |
#' print(out2) |
|
| 31 |
#' generate_slides(out2, paste0(tempdir(), "/ae2.pptx")) |
|
| 32 |
t_ae_pt_soc_slide <- function(adsl, adae, arm, cutoff = NA, |
|
| 33 |
prune_by_total = FALSE, |
|
| 34 |
split_by_study = FALSE, |
|
| 35 |
side_by_side = NULL) {
|
|
| 36 | 5x |
cutoff <- check_and_set_cutoff(adae, cutoff) |
| 37 | 5x |
result <- t_ae_pt_core(adsl, adae, arm, cutoff, |
| 38 | 5x |
diff = FALSE, soc = "soc", |
| 39 | 5x |
prune_by_total = prune_by_total, |
| 40 | 5x |
split_by_study, side_by_side |
| 41 |
) |
|
| 42 | 5x |
result@main_title <- "Adverse Events table" |
| 43 | ||
| 44 | 5x |
if (is.null(side_by_side)) {
|
| 45 |
# adding "N" attribute |
|
| 46 | 5x |
arm <- col_paths(result)[[1]][1] |
| 47 | ||
| 48 | 5x |
n_r <- data.frame( |
| 49 | 5x |
ARM = toupper(names(result@col_info)), |
| 50 | 5x |
N = col_counts(result) %>% as.numeric() |
| 51 |
) %>% |
|
| 52 | 5x |
`colnames<-`(c(paste(arm), "N")) %>% |
| 53 | 5x |
arrange(get(arm)) |
| 54 | ||
| 55 | 5x |
attr(result, "N") <- n_r |
| 56 |
} |
|
| 57 | 5x |
result |
| 58 |
} |
| 1 |
#' Convert dates from `yyyy-mm-dd` format into 20APR2019 format |
|
| 2 |
#' `Datetime` format removes the time and outputs date in the same way |
|
| 3 |
#' Able to handle truncated dates as well (e.g. just the year or year and month) |
|
| 4 |
#' |
|
| 5 |
#' `dplyr::case_when()` will check all RHS expressions on the input, this means if |
|
| 6 |
#' these expressions return warnings, they will happen even then the input doesn't |
|
| 7 |
#' doesn't satisfy the LHS. For this reason, I had to 'quiet' all `lubridate` functions. |
|
| 8 |
#' This `format_date()` function was tested with the inputs in the examples, all gave the |
|
| 9 |
#' expected returned value, so there should be no issues. |
|
| 10 |
#' |
|
| 11 |
#' @param x vector of dates in character, in `yyyy-mm-dd` format |
|
| 12 |
#' @return A vector. |
|
| 13 |
#' |
|
| 14 |
#' @export |
|
| 15 |
#' @examplesIf require(lubridate) |
|
| 16 |
#' require(lubridate) |
|
| 17 |
#' |
|
| 18 |
#' # expected to return "2019" |
|
| 19 |
#' format_date("2019")
|
|
| 20 |
#' |
|
| 21 |
#' # expected to return "20APR2019" |
|
| 22 |
#' format_date("2019-04-20")
|
|
| 23 |
#' |
|
| 24 |
#' # expected to return "" |
|
| 25 |
#' format_date("")
|
|
| 26 |
#' |
|
| 27 |
#' # expected to return "18JUN2019" |
|
| 28 |
#' format_date("2019-06-18T10:32")
|
|
| 29 |
#' |
|
| 30 |
#' # expected to return "APR2019" |
|
| 31 |
#' format_date("2019-04")
|
|
| 32 |
#' |
|
| 33 |
format_date <- function(x) {
|
|
| 34 | 5x |
x_form <- case_when( |
| 35 | 5x |
nchar(x) > 10 ~ toupper(format(lubridate::date(lubridate::ymd_hms(x, truncated = 3, quiet = TRUE)), "%d%b%Y")), |
| 36 | 5x |
nchar(x) == 10 ~ toupper(format(lubridate::ymd(x, quiet = TRUE), "%d%b%Y")), |
| 37 | 5x |
nchar(x) == 7 ~ substr(toupper(format(lubridate::ymd(x, truncated = 2, quiet = TRUE), "%d%b%Y")), 3, 9), |
| 38 | 5x |
nchar(x) == 4 ~ x, |
| 39 | 5x |
is.na(x) ~ "", |
| 40 | 5x |
TRUE ~ "" |
| 41 |
) |
|
| 42 | ||
| 43 | 5x |
x_form |
| 44 |
} |
| 1 |
#' Null report |
|
| 2 |
#' |
|
| 3 |
#' @author Thomas Neitmann (`neitmant`) |
|
| 4 |
#' |
|
| 5 |
#' @details |
|
| 6 |
#' This will create a null report similar as STREAM does. You can use |
|
| 7 |
#' it inside output functions as shown in the example below. |
|
| 8 |
#' @return An empty `rtables` object |
|
| 9 |
#' @examplesIf require(filters) |
|
| 10 |
#' library(dplyr) |
|
| 11 |
#' library(filters) |
|
| 12 |
#' data <- list( |
|
| 13 |
#' adsl = eg_adsl, |
|
| 14 |
#' adae = eg_adae %>% mutate(AREL = "") |
|
| 15 |
#' ) |
|
| 16 |
#' |
|
| 17 |
#' null_report() |
|
| 18 |
#' |
|
| 19 |
#' ## An example how to use the `null_report()` inside an output function |
|
| 20 |
#' t_ae <- function(datasets) {
|
|
| 21 |
#' trt <- "ACTARM" |
|
| 22 |
#' anl <- semi_join( |
|
| 23 |
#' datasets$adae, |
|
| 24 |
#' datasets$adsl, |
|
| 25 |
#' by = c("STUDYID", "USUBJID")
|
|
| 26 |
#' ) |
|
| 27 |
#' |
|
| 28 |
#' return(null_report()) |
|
| 29 |
#' } |
|
| 30 |
#' |
|
| 31 |
#' data %>% |
|
| 32 |
#' filters::apply_filter("SER_SE") %>%
|
|
| 33 |
#' t_ae() |
|
| 34 |
#' |
|
| 35 |
#' @export |
|
| 36 |
#' |
|
| 37 |
null_report <- function() {
|
|
| 38 | 4x |
rtable( |
| 39 | 4x |
header = " ", |
| 40 | 4x |
rrow("", "Null Report: No observations met the reporting criteria for inclusion in this output.")
|
| 41 |
) |
|
| 42 |
} |
| 1 |
#' Demographic table with gtsummary |
|
| 2 |
#' |
|
| 3 |
#' @param adsl ADSL data set, dataframe |
|
| 4 |
#' @param arm Arm variable, character, "`TRT01P" by default. |
|
| 5 |
#' @param vars Characters of variables |
|
| 6 |
#' @return gtsummary object |
|
| 7 |
#' @inherit gen_notes note |
|
| 8 |
#' @export |
|
| 9 |
#' @examples |
|
| 10 |
#' library(dplyr) |
|
| 11 |
#' adsl <- eg_adsl |
|
| 12 |
#' out1 <- gt_t_dm_slide(adsl, "TRT01P", c("SEX", "AGE", "RACE", "ETHNIC", "COUNTRY"))
|
|
| 13 |
#' print(out1) |
|
| 14 |
#' generate_slides(out1, paste0(tempdir(), "/dm.pptx")) |
|
| 15 |
#' |
|
| 16 |
gt_t_dm_slide <- function(adsl, |
|
| 17 |
arm = "TRT01P", |
|
| 18 |
vars = c("AGE", "SEX", "RACE")) {
|
|
| 19 | 1x |
adsl |> |
| 20 | 1x |
select(all_of(c(vars, arm))) |> |
| 21 | 1x |
tbl_summary(by = all_of(arm)) |> |
| 22 | 1x |
modify_caption(caption = "Demographic slide") # Set default title |
| 23 |
} |
| 1 |
#' autoslider_error class |
|
| 2 |
#' @details this function is used to create autoslider_error object. |
|
| 3 |
#' this function is for internal use only to create the autoslider_error object. |
|
| 4 |
#' It enable us for further functionalities, like providing help on easy debugging, |
|
| 5 |
#' e.g. if the error is inside the user function, provide the call and let the user |
|
| 6 |
#' run the code outside the pipeline. |
|
| 7 |
#' @param x character scaler |
|
| 8 |
#' @param spec spec should be a list containing "program" and "suffix" |
|
| 9 |
#' @param step step is a character indicating in which step the pipeline encounter error |
|
| 10 |
#' @return autoslider_error object |
|
| 11 |
#' @export |
|
| 12 |
autoslider_error <- function(x, spec, step) {
|
|
| 13 | 1x |
assert_is_character_scalar(x) |
| 14 | 1x |
structure( |
| 15 | 1x |
.Data = x, |
| 16 | 1x |
step = step, |
| 17 | 1x |
spec = spec, |
| 18 | 1x |
class = "autoslider_error" |
| 19 |
) |
|
| 20 |
} |