--- title: "Creating Kaplan-Meier Survival Plots with tflmetaR and gridify" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Creating Kaplan-Meier Survival Plots with tflmetaR and gridify} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE ) ``` ## Introduction This vignette demonstrates how to create publication-quality Kaplan-Meier survival plots using the `survminer` package for visualization, `gridify` for professional annotation layouts, and `tflmetaR` for centralized metadata management. The workflow covers: - Loading and preparing ADTTE (Time-to-Event) data from CDISC-compliant datasets - Fitting survival models and generating Kaplan-Meier curves - Combining survival plots with risk tables - Annotating figures with headers, titles, and footnotes using `gridify` - Integrating `tflmetaR` to pull annotations from external metadata files --- ## Loading Required Packages We begin by loading all necessary packages for survival analysis, data manipulation, and figure annotation. ```{r load-packages, message=FALSE, warning=FALSE} library(tflmetaR) library(gridify) library(haven) library(dplyr) library(survival) library(survminer) ``` --- ## Loading and Preparing ADTTE Data The Analysis Dataset for Time-to-Event (ADTTE) follows CDISC ADaM standards. Here we use a synthetic oncology dataset generated by the CDISC Dataset Generator, containing 3 treatment arms and 500 subjects. ```{r load-data} # get addtte data ---- # synthetic adtte for oncology therapeutic area from CDISC adtte <- read_xpt(system.file("extdata", "adam_adtte.xpt", package = "tflmetaR")) ``` ### Data Transformation We filter the data for Progression-Free Survival (PFS) analysis and prepare it for survival modeling. ```{r prepare-data} tte <- adtte |> rename(avalc = AVALU) |> rename_with(tolower) |> filter(paramcd == "PFS") |> arrange(desc(trtp)) |> relocate(usubjid, trtp, cnsr, evntdesc, avalc, aval, trta) |> select(usubjid, trtp, cnsr, evntdesc, avalc, aval, trta) tte$trtp <- factor(tte$trtp) ``` --- ## Fitting the Survival Model We create a survival object and fit a Kaplan-Meier model stratified by treatment group (`trtp`). ```{r fit-survival-model} # run survival modal ---- surv_object <- Surv(time = tte$aval, event = tte$cnsr) fit1 <- survfit(surv_object ~ trtp, data = tte) ``` --- ## Creating the Survival Plot The `survminer` package provides `ggsurvplot()` for creating publication-ready Kaplan-Meier curves with integrated risk tables, p-values, and median survival lines. ```{r create-survplot} # survial plot and table ---- ggsurv <- ggsurvplot(fit1, data = tte, risk.table = TRUE, size = 1, # line size pval = TRUE, pval.size = 4, pval.method = TRUE, pval.method.size = 3, fontsize = 3, surv.median.line = "hv", tables.theme = theme_survminer() + theme( axis.text.x = element_text(size = 9, color = "gray30"), axis.text.y = element_text(size = 9, color = "gray30"), plot.title = element_text(size = 9), element_text(size = 9, color = "red") ) ) ``` --- ## Customizing the Risk Table We modify the risk table labels to display treatment group names in a more readable format. ```{r customize-risk-table} # risk table ----- t <- ggsurv$table + scale_y_discrete(label = c("Treatment 3", "Treatment 2", "Treatment 1")) p1 <- ggsurvplot(fit1, data = tte, risk.table = TRUE) ## can not add to the gridify directly ``` --- ## Combining Plot and Risk Table Using `ggpubr::ggarrange()`, we combine the survival plot and risk table into a single figure with proper alignment and proportions. ```{r combine-plot-table} # ggrance the survival plot and risk table ---- p1 <- ggarrange( ggsurv$plot + labs(x = "", y = "Survival Probability") + scale_color_discrete() + theme( axis.title.x = element_text(vjust = 0, size = 9), axis.title.y = element_text(vjust = -3, size = 9), # y axis label axis.text.y = element_text(size = 9, color = "gray30"), # tick values axis.text.x = element_text(size = 9, color = "gray30"), legend.title = element_blank() ), t + labs(y = "") + theme( axis.title.x = element_text(vjust = 0, size = 9), axis.text.x = element_text(size = 9, color = "gray30") ), heights = c(2, 1.0), ncol = 1, nrow = 2, align = "v" ) ``` --- ## Annotating with gridify The `gridify` package provides a framework for adding professional annotations to figures, including headers, titles, footnotes, and page numbers. The `pharma_layout_base()` function creates a layout compliant with pharmaceutical industry standards. ### Basic Gridify Example This example demonstrates manual annotation using `gridify::set_cell()` to populate each annotation field. ```{r gridify-basic} ## --- use gridify to annotate the figure --- fig <- gridify( p1, layout = pharma_layout_base( margin = grid::unit(c(1, 1, 1.23, 1), "inches"), global_gpar = grid::gpar(fontfamily = "Courier") ) ) |> set_cell("header_left_1", "UCB") |> set_cell("header_left_2", "Drug X / Unspecified") |> set_cell("header_left_3", "STUDY001") |> set_cell("header_right_1", "CONFIDENTIAL") |> set_cell("header_right_2", "Final") |> set_cell("header_right_3", paste0("Data Cut-Off Date")) |> set_cell("output_num", "Figure F01") |> set_cell("title_1", "The Kaplan-Meier Curves of Progression-Free Survival Among Treatment Groups") |> set_cell("title_2", "Population: Safety Set") |> set_cell("title_3", "") |> set_cell( "note", paste0( "The synthetic oncology ADTTE was generated by CDISC Dataset Generator, ", "with 3 treatment arms and 500 subjects.\n", "Note: The Kaplan-Meier estimate of survival probability at a given time ", "is the product of these conditional probabilities up until that given time." ), mchar = 132 ) |> set_cell("footer_left", "Program: f_surv_gridify.R, Source(s): ADTTE") |> set_cell("footer_right", paste0("Page ", 1, " of ", 1)) ``` ```{r km-plot, dev='png', dpi=300, fig.width=10, fig.height=8, out.width='95%'} print(fig) ``` --- ## Integrating tflmetaR for Metadata Management The `tflmetaR` package enables **separation of metadata and code** by storing titles, footnotes, and headers in external spreadsheets. This approach: - Centralizes all annotations in a single location to support audit and traceability - Reduces code complexity and maintenance burden - Supports regulatory compliance workflows ### Loading Metadata Files We read the titles and header information from an Excel spreadsheet. ```{r tflmetar-load-meta} ## --- tflmetaR related code ---- # read headers, titles, and footnotes excel spreadsheet file_name <- system.file("extdata", "st_titles.xls", package = "tflmetaR") title_file <- tflmetaR::read_tfile(filename = file_name, sheetname = "Sheet1") header_file <- tflmetaR::read_tfile(filename = file_name, sheetname = "Headr", validate = FALSE) fig_number <- "Figure F01" ``` ### Extracting Annotations Using `tflmetaR` accessor functions, we retrieve titles, footnotes, and header content for the specific figure. ```{r tflmetar-extract} ulheader <- tflmetaR::get_ulheader(header_file)[1, ] urheader <- tflmetaR::get_urheader(header_file)[1, ] titles <- tflmetaR::get_title(title_file, tnumber = fig_number) footnotes <- tflmetaR::get_footnote(title_file, tnumber = fig_number, add_footr_tstamp = FALSE) pgmname <- tflmetaR::get_pgmname(title_file, tnumber = fig_number) source <- tflmetaR::get_source(title_file, tnumber = fig_number) # convert footnote list to string footnote_str <- paste(unlist(footnotes), collapse = "\n") ``` --- ## Creating the Final Annotated Figure Now we combine `gridify` with `tflmetaR`-extracted metadata to create the final publication-ready figure. This approach ensures that any updates to titles or footnotes in the metadata spreadsheet are automatically reflected in the output. ```{r final-figure} ## --- re-draw graph with pulled titles and footnotes ---- fig2 <- gridify( p1, layout = pharma_layout_base( margin = grid::unit(c(0.5, 1, 0.25, 1), "inches"), global_gpar = grid::gpar(fontfamily = "Courier") ) ) |> set_cell("header_left_1", ulheader[[1]]) |> set_cell("header_left_2", ulheader[[2]]) |> set_cell("header_left_3", ulheader[[3]]) |> set_cell("header_right_1", urheader[[1]]) |> set_cell("header_right_2", urheader[[2]]) |> set_cell("header_right_3", urheader[[3]]) |> set_cell("output_num", titles[[1]]) |> set_cell("title_1", "") |> set_cell("title_2", titles[[2]]) |> set_cell("title_3", titles[[3]]) |> set_cell("note", footnote_str, mch = 120) |> set_cell("footer_left", paste0( "Program: ", pgmname, ", ", "Source(s): ", source )) |> set_cell("footer_right", sprintf("Page %d of %d", 1, 1)) ``` You can now preview the annotated figure or export the result to a PDF file using `gridify::export_to(fig2, "f_surv_tflmetar.pdf")`. ```{r km-plot-tflmetaR, dev='png', dpi=300, fig.width=10, fig.height=8, out.width='95%'} print(fig2) ``` --- ## Summary This vignette demonstrated a complete workflow for creating annotated Kaplan-Meier survival plots: 1. **Data preparation**: Loading and transforming ADTTE data following CDISC standards 2. **Survival analysis**: Fitting Kaplan-Meier models using the `survival` package 3. **Visualization**: Creating publication-quality plots with `survminer` 4. **Annotation**: Adding professional headers, titles, and footnotes using `gridify` 5. **Metadata integration**: Leveraging `tflmetaR` to externalize annotations for better maintainability By combining these tools, organizations can produce regulatory-compliant figures while maintaining a clear separation between analysis code and presentation metadata. --- ## Related Resources ### Key Packages Used | Package | Purpose | |---------|---------| | `survival` | Survival analysis and Kaplan-Meier estimation | | `survminer` | Publication-ready survival plots | | `gridify` | Professional figure annotation layouts | | `tflmetaR` | Centralized metadata management | ### tflmetaR Core Functions | Function | Description | |----------|-------------| | `read_tfile()` | Read metadata from Excel or CSV | | `get_title()` | Retrieve titles and subtitles | | `get_footnote()` | Retrieve footnotes | | `get_ulheader()` | Retrieve upper-left header content | | `get_urheader()` | Retrieve upper-right header content | ---