Automatic slide generation with autoslider.core
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
We are excited to announce that the
The normal process of creating clinical study slides is that a statistician manually types in numbers from outputs and a separate statistician then double checks the typed-in numbers. This process is time consuming, resource intensive, and error prone. This package lets users create slides with study-specific outputs in an automated and reproducible way – without the need to copy-paste. It reduces the amount of work and the required time when creating slides, and reduces the risk of errors from manually typing or copying numbers from the output to slides. It also helps users to avoid unnecessary stress when creating large amounts of slide decks in a short time window. It is particularly helpful for slides that need to be continuously created with updated data, such as slides needed for dose-escalation meetings.
The autoslider
development work started as a Roche internal initiative. As it grew over the years, it was well received and we decided to open-source this package while at the same time not causing any downstream breaking changes to our internal users. Therefore, the autoslider
package became two layers, the outer layer exposed to internal users remains as autoslider
. It now mostly contains Roche-specific formats, layouts and designs. The inner layer, aka autoslider
as a framework, and would like to invite users to build their own autoslider.*
packages. You can find t0he developer guidelines at the package homepage.
The value proposition of the
- functions to create standard outputs with (some) inbuilt customization capability
- mostly built on code from the tlg catalog
- you can create your own functions as well
- back-end machinery to manage the workflow. This includes:
- creating outputs from a specification file
- filtering data according to a specification file
- decorating outputs (think: footnotes & titles)
- adding these outputs to a PowerPoint slide deck, including pagination.
In this blog post, we will guide you through the
Requirements
To follow along with this tutorial, you need to have the
library(autoslider.core)
Registered S3 method overwritten by 'tern': method from tidy.glm broom
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats': filter, lag
The following objects are masked from 'package:base': intersect, setdiff, setequal, union
Workflow
To get started, the folder structure for your project could look something like this:
Copy code ├── programs │ ├── run_script.R │ ├── R │ │ ├── helping_functions.R │ │ ├── output_functions.R ├── outputs ├── specs.yml ├── filters.yml
The run_script.R
file. This script guides through the workflow without needing the files in R/
. However, custom output-creating functions can be stored in the R/
folder.
Specifications (specs.yml
)
This file contains the specifications of all outputs you want to create. Each output includes details such as the program name, footnotes & titles, paper orientation and font size, suffix, and additional arguments.
The following example specs.yml
content would create two outputs based off of two template functions, t_ds_slide() and t_dm_slide():
- program: t_ds_slide titles: Patient Disposition ({filter_titles("adsl")}) footnotes: 't_ds footnotes' paper: L6 suffix: ITT - program: t_dm_slide titles: Patient Demographics and Baseline Characteristics footnotes: 't_dm_slide footnote' paper: L6 suffix: ITT args: arm: "TRT01A" vars: ["SEX", "AGE", "RACE", "ETHNIC", "COUNTRY"]
Filters (filters.yml)
In the filters.yml file, the names of the filters used across outputs are specified. Each filter includes a name, title, filtering condition, target dataset, and type.
The following example filters.yml
file specifies four commonly used filters:
ITT: title: Intent to Treat Population condition: ITTFL =='Y' target: adsl type: slref SAS: title: Secondary Analysis Set condition: SASFL == 'Y' target: adsl type: slref SE: title: Safety Evaluable Population condition: SAFFL=='Y' target: adsl type: slref SER: title: Serious Adverse Events condition: AESER == 'Y' target: adae type: anl
Functions
An overview of all
Backend Machinery
A typical workflow involves defining paths to the YAML files, loading the filters, reading the data, creating the outputs based on the specifications, and decorating the outputs with titles and footnotes. Example code for setting up the workflow:
# define path to the yml files spec_file <- "spec.yml" filters <- "filters.yml"
library("dplyr") # load all filters filters::load_filters(filters, overwrite = TRUE) # read data data <- list( "adsl" = eg_adsl %>% mutate( FASFL = SAFFL, # add FASFL for illustrative purpose for t_pop_slide # DISTRTFL is needed for t_ds_slide but is missing in example data DISTRTFL = sample(c("Y", "N"), size = length(TRT01A), replace = TRUE, prob = c(.1, .9)) ) %>% preprocess_t_ds(), # this preproccessing is required by one of the autoslider.core functions "adae" = eg_adae, "adtte" = eg_adtte, "adrs" = eg_adrs, "adlb" = eg_adlb ) # create outputs based on the specs and the functions outputs <- spec_file %>% read_spec() %>% filter_spec(., program %in% c("t_ds_slide", "t_dm_slide")) %>% generate_outputs(datasets = data) %>% decorate_outputs(version_label = NULL)
✔ 2/3 outputs matched the filter condition `program %in% c("t_ds_slide", "t_dm_slide")`. ❯ Running program `t_ds_slide` with suffix 'ITT'.
Filter 'ITT' matched target ADSL.
400/400 records matched the filter condition `ITTFL == 'Y'`.
❯ Running program `t_dm_slide` with suffix 'ITT'.
Filter 'ITT' matched target ADSL. 400/400 records matched the filter condition `ITTFL == 'Y'`.
Example of saving outputs to a slide:
# Output to slides with template and color theme outputs %>% generate_slides( outfile = tempfile(fileext = ".pptx"), template = file.path(system.file(package = "autoslider.core"), "/theme/basic.pptx"), table_format = autoslider_format )
[1] " Patient Disposition (Intent to Treat Population)" [1] " Patient Disposition (Intent to Treat Population) (cont.)"
[1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)"
Writing Custom Functions
For study-specific outputs not covered by data.frame
objects (for listings).
Example custom function:
lbt06 <- function(datasets) { adsl <- datasets$adsl %>% tern::df_explicit_na() adlb <- datasets$adlb %>% tern::df_explicit_na() adlb_f <- adlb %>% dplyr::filter(ABLFL != "Y") %>% dplyr::filter(!(AVISIT %in% c("SCREENING", "BASELINE"))) %>% dplyr::mutate(AVISIT = droplevels(AVISIT)) %>% formatters::var_relabel(AVISIT = "Visit") adlb_f_crp <- adlb_f %>% dplyr::filter(PARAMCD == "CRP") split_fun <- rtables::drop_split_levels lyt <- rtables::basic_table(show_colcounts = TRUE) %>% rtables::split_cols_by("ARM") %>% rtables::split_rows_by("AVISIT", split_fun = split_fun, label_pos = "topleft", split_label = formatters::obj_label(adlb_f_crp$AVISIT)) %>% tern::count_abnormal_by_baseline("ANRIND", abnormal = c(Low = "LOW", High = "HIGH"), .indent_mods = 4L) %>% tern::append_varlabels(adlb_f_crp, "ANRIND", indent = 1L) %>% rtables::append_topleft(" Baseline Status") result <- rtables::build_table(lyt = lyt, df = adlb_f_crp, alt_counts_df = adsl) %>% rtables::trim_rows() result }
Testing the custom function:
lbt06(data)
Visit Analysis Reference Range Indicator A: Drug X B: Placebo C: Combination Baseline Status (N=134) (N=134) (N=132) ——————————————————————————————————————————————————————————————————————————————————————— WEEK 1 DAY 8 Low Not low 16/119 (13.4%) 22/113 (19.5%) 24/112 (21.4%) Low 2/15 (13.3%) 2/21 (9.5%) 7/20 (35%) Total 18/134 (13.4%) 24/134 (17.9%) 31/132 (23.5%) High Not high 21/114 (18.4%) 20/112 (17.9%) 17/115 (14.8%) High 2/20 (10%) 4/22 (18.2%) 3/17 (17.6%) Total 23/134 (17.2%) 24/134 (17.9%) 20/132 (15.2%) WEEK 2 DAY 15 Low Not low 26/119 (21.8%) 20/113 (17.7%) 12/112 (10.7%) Low 2/15 (13.3%) 3/21 (14.3%) 4/20 (20%) Total 28/134 (20.9%) 23/134 (17.2%) 16/132 (12.1%) High Not high 15/114 (13.2%) 17/112 (15.2%) 15/115 (13%) High 2/20 (10%) 4/22 (18.2%) 4/17 (23.5%) Total 17/134 (12.7%) 21/134 (15.7%) 19/132 (14.4%) WEEK 3 DAY 22 Low Not low 15/119 (12.6%) 21/113 (18.6%) 18/112 (16.1%) Low 0/15 3/21 (14.3%) 0/20 Total 15/134 (11.2%) 24/134 (17.9%) 18/132 (13.6%) High Not high 22/114 (19.3%) 18/112 (16.1%) 17/115 (14.8%) High 2/20 (10%) 5/22 (22.7%) 1/17 (5.9%) Total 24/134 (17.9%) 23/134 (17.2%) 18/132 (13.6%) WEEK 4 DAY 29 Low Not low 30/119 (25.2%) 13/113 (11.5%) 16/112 (14.3%) Low 3/15 (20%) 2/21 (9.5%) 5/20 (25%) Total 33/134 (24.6%) 15/134 (11.2%) 21/132 (15.9%) High Not high 17/114 (14.9%) 11/112 (9.8%) 16/115 (13.9%) High 2/20 (10%) 6/22 (27.3%) 3/17 (17.6%) Total 19/134 (14.2%) 17/134 (12.7%) 19/132 (14.4%) WEEK 5 DAY 36 Low Not low 17/119 (14.3%) 19/113 (16.8%) 16/112 (14.3%) Low 2/15 (13.3%) 3/21 (14.3%) 5/20 (25%) Total 19/134 (14.2%) 22/134 (16.4%) 21/132 (15.9%) High Not high 19/114 (16.7%) 17/112 (15.2%) 11/115 (9.6%) High 4/20 (20%) 6/22 (27.3%) 2/17 (11.8%) Total 23/134 (17.2%) 23/134 (17.2%) 13/132 (9.8%)
To use the custom function within the
source("programs/output_functions.R")
With the correct specs.yml
and filters.yml
, integrate the custom function into the general workflow:
filters <- "filters.yml" spec_file <- "specs.yml" filters::load_filters(filters, overwrite = TRUE) outputs <- spec_file %>% read_spec() %>% generate_outputs(data) %>% decorate_outputs() outputs$lbt06_ITT_LBCRP_LBNOBAS
outputs <- spec_file %>% read_spec() %>% generate_outputs(data) %>% decorate_outputs()
❯ Running program `t_ds_slide` with suffix 'ITT'.
Filter 'ITT' matched target ADSL.
400/400 records matched the filter condition `ITTFL == 'Y'`.
❯ Running program `t_dm_slide` with suffix 'ITT'.
Filter 'ITT' matched target ADSL. 400/400 records matched the filter condition `ITTFL == 'Y'`.
❯ Running program `lbt06` with suffix 'ITT_LBCRP_LBNOBAS'.
Filter 'ITT' matched target ADSL. 400/400 records matched the filter condition `ITTFL == 'Y'`.
Filters 'LBCRP', 'LBNOBAS' matched target ADLB.
2000/8400 records matched the filter condition `PARAMCD == 'CRP' & (ABLFL != 'Y' & !(AVISIT %in% c('SCREENING', 'BASELINE')))`.
outputs$lbt06_ITT_LBCRP_LBNOBAS
An object of class "dVTableTree" Slot "tbl":
Patient Disposition (Intent to Treat Population) ——————————————————————————————————————————————————————————————————————————————————————— Visit Analysis Reference Range Indicator A: Drug X B: Placebo C: Combination Baseline Status (N=134) (N=134) (N=132) ——————————————————————————————————————————————————————————————————————————————————————— WEEK 1 DAY 8 Low Not low 16/119 (13.4%) 22/113 (19.5%) 24/112 (21.4%) Low 2/15 (13.3%) 2/21 (9.5%) 7/20 (35%) Total 18/134 (13.4%) 24/134 (17.9%) 31/132 (23.5%) High Not high 21/114 (18.4%) 20/112 (17.9%) 17/115 (14.8%) High 2/20 (10%) 4/22 (18.2%) 3/17 (17.6%) Total 23/134 (17.2%) 24/134 (17.9%) 20/132 (15.2%) WEEK 2 DAY 15 Low Not low 26/119 (21.8%) 20/113 (17.7%) 12/112 (10.7%) Low 2/15 (13.3%) 3/21 (14.3%) 4/20 (20%) Total 28/134 (20.9%) 23/134 (17.2%) 16/132 (12.1%) High Not high 15/114 (13.2%) 17/112 (15.2%) 15/115 (13%) High 2/20 (10%) 4/22 (18.2%) 4/17 (23.5%) Total 17/134 (12.7%) 21/134 (15.7%) 19/132 (14.4%) WEEK 3 DAY 22 Low Not low 15/119 (12.6%) 21/113 (18.6%) 18/112 (16.1%) Low 0/15 3/21 (14.3%) 0/20 Total 15/134 (11.2%) 24/134 (17.9%) 18/132 (13.6%) High Not high 22/114 (19.3%) 18/112 (16.1%) 17/115 (14.8%) High 2/20 (10%) 5/22 (22.7%) 1/17 (5.9%) Total 24/134 (17.9%) 23/134 (17.2%) 18/132 (13.6%) WEEK 4 DAY 29 Low Not low 30/119 (25.2%) 13/113 (11.5%) 16/112 (14.3%) Low 3/15 (20%) 2/21 (9.5%) 5/20 (25%) Total 33/134 (24.6%) 15/134 (11.2%) 21/132 (15.9%) High Not high 17/114 (14.9%) 11/112 (9.8%) 16/115 (13.9%) High 2/20 (10%) 6/22 (27.3%) 3/17 (17.6%) Total 19/134 (14.2%) 17/134 (12.7%) 19/132 (14.4%) WEEK 5 DAY 36 Low Not low 17/119 (14.3%) 19/113 (16.8%) 16/112 (14.3%) Low 2/15 (13.3%) 3/21 (14.3%) 5/20 (25%) Total 19/134 (14.2%) 22/134 (16.4%) 21/132 (15.9%) High Not high 19/114 (16.7%) 17/112 (15.2%) 11/115 (9.6%) High 4/20 (20%) 6/22 (27.3%) 2/17 (11.8%) Total 23/134 (17.2%) 23/134 (17.2%) 13/132 (9.8%) ——————————————————————————————————————————————————————————————————————————————————————— t_ds footnotes Confidential and for internal use only GitHub repository: NA Git hash: d90795d2a97abda810205d7fd0f0fc335200e9fc Slot "titles": Patient Disposition (Intent to Treat Population) Slot "footnotes": [1] "t_ds footnotes" [2] "Confidential and for internal use only" Slot "paper": [1] "L6" Slot "width": [1] 36 14 14 14
Generate the slides:
filepath <- tempfile(fileext = ".pptx") generate_slides(outputs, outfile = filepath)
[1] " Patient Disposition (Intent to Treat Population)" [1] " Patient Disposition (Intent to Treat Population) (cont.)"
[1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)" [1] " Patient Demographics and Baseline Characteristics, Intent to Treat Population (cont.)"
[1] " Patient Disposition (Intent to Treat Population)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)" [1] " Patient Disposition (Intent to Treat Population) (cont.)"
We hope this guide helps you get started with
Last updated
2025-03-17 09:30:58.01543
Details
Reuse
Citation
@online{p._thoma,_joe_zhu2025, author = {P. Thoma, Joe Zhu, Stefan}, title = {Automatic Slide Generation with Autoslider.core}, date = {2025-03-14}, url = {https://pharmaverse.github.io/blog/posts/2025-03-14_automatic_s.../automatic_slide_generation_with_{autoslide_r}.html}, langid = {en} }
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.