Overview

This document contains plots for safety data as well as the R code that generates these graphs. These plots are not strictly exploratory plots as data from a PopPK model are used to generate some of the plots.

Setup

library(ggplot2)
library(dplyr)
library(tidyr)
library(gridExtra)
library(zoo)
library(xgxr)

#flag for labeling figures as draft
status = "DRAFT"

## ggplot settings
xgx_theme_set()

Load Dataset

The plots presented here are based on merged safety data with a popPK model generated parameters data (download dataset), as well as a dataset containing model-generated AUCs (download dataset). Data specifications can be accessed on Datasets and Rmarkdown template to generate this page can be found on Rmarkdown-Template.

auc <- read.csv("../Data/AUC_Safety.csv")
ae.data <- read.csv("../Data/AE_xgx.csv")
ae.data$DOSE_label <- paste(ae.data$Dose,"mg")
ae.data = ae.data %>%
  arrange(DOSE_label) %>%
  mutate(DOSE_label_low2high      = factor(DOSE_label, levels = unique(DOSE_label)),
         DOSE_label_high2low      = factor(DOSE_label, levels = rev(unique(DOSE_label))))

#ensure dataset has all the necessary columns
ae.data = ae.data %>%
  mutate(SUBJID      = SUBJID,           # ID   column
         DAY         = DAY,              # Day of the AE
         time        = time,             # time of the AE in (h)
         Dose        = Dose,             # DOSE column here (numeric value)
         AUC         = AUC,              # Cumulative AUC at AE day
         Cmax        = Cmax,             # Cmax at AE day
         Cmin        = Cmin,             # Cmin at AE day\
         AUCDAY1     = AUCDAY1,          # AUC at day 1
         AUCAVE      = AUCAVE,           #  Average AUC up to the AE day
         AETOXGRS    = AETOXGRS,         #  AE grades (Character)
         AETOXGRDN   = AETOXGRDN,        #  AE grades (numenric)
         AE          = AE,               #  Binary YES/NO AE
         )

auc = auc %>%
  mutate(SUBJID       = SUBJID,           # Subject ID
         time_hr      =time_hr,         #   Simulation time in (h)
         AUC_popPK    = AUC_popPK,      # Cumulative AUC from popPK model
         Cmax_popPK   = Cmax_popPK,     #   Cmax from popPK
         Cmin_popPK   = Cmin_popPK      #   Cmin from popPK
         )

# add binary 0 and 1 colun to the dataset
ae.data$AE_binary <- as.numeric(as.character( plyr::mapvalues(ae.data$AE,
                                                               c('Yes','No'), 
                                                               c(1, 0)) ))

# AUC dataset for additional plots
ae.data = ae.data %>%
  mutate(AUC_bins = cut(AUCAVE, quantile(AUCAVE, na.rm=TRUE), na.rm=TRUE, include.lowest = TRUE)) %>%
  group_by(AUC_bins) %>%
  mutate(AUC_midpoints = median(AUCAVE))

# Function to add number of subjects in each group
n_fun <- function(x){
  return(data.frame(y = median(x)*0.01, label = paste0("n = ",length(x))))
}

#units and labels
time_units_dataset = "hours"
time_units_plot    = "days"
trtact_label       = "Dose"
dose_label         = "Dose (mg)"
time_label         = "Time(Days)"
timemonth_label    = "Time (months)"
conc_label         = "Concentration (ng/ml)"
trough_label       = "Trough Concentration (ng/mL)"
conc_units         = "ng/ml"
auctau_label       = "AUCtau (ng.h/ml)"
ae_label           = "AE"
aeprob_label       = "Probability of AE (%)"
sld_label          = "Percent Change in\nSum of Longest Diameters"

#directories for saving individual graphs
dirs = list(
parent_dir= tempdir(),
rscript_dir  = "./",
rscript_name = "Example.R",
results_dir  = "./",
filename_prefix   = "",
filename     = "Example.png")

Exposure-safety plots

These plots are looking at the AUC (or Cmax and Ctrough) for patients with the AE and for those who don’t have the AE at each dose group. The number of the patients at each dose level is also included in the plots. With these type of plots we are looking to see if there is a correlation (or absence a correlation) between exposure and AE.

AUC vs Dose group by AE

gg <- ggplot(data= ae.data,aes(x=factor(Dose), y=AUCAVE, fill=factor(AE))) 
gg <- gg + geom_boxplot(data=ae.data,aes(x=factor(Dose),y=AUCAVE, fill=factor(AE)),outlier.shape = NA)
gg <- gg + geom_point(color="black",binaxis='y', stackdir='center',dotsize=0.5,
                   position=position_jitterdodge(0.27))
gg <- gg + guides(color=guide_legend(""))
gg <- gg + stat_summary(data=ae.data, fun.data = n_fun, geom = "text",
                     fun.y=mean, position = position_dodge(width = 0.75)) 
gg <- gg + scale_fill_discrete(name="AE occurrence:")
gg <- gg + ylab("")
gg <- gg + labs(y=auctau_label,x=dose_label)
gg <- gg + xgx_annotate_status(status)
gg

Explore AE vs AUC

Plot binary AE occurrence against AUC or Cmax.

gg <- ggplot(data = ae.data, aes(y=AUCAVE,x=AE))
gg <- gg + geom_jitter(data = ae.data, 
                       aes(color = DOSE_label_high2low), shape=19,  width = 0.1, height = 0, alpha = 0.5)
gg <- gg + geom_boxplot(width = 0.5, fill = NA, outlier.shape=NA) 
gg <- gg + guides(color=guide_legend(""),fill=guide_legend(""))
gg <- gg + coord_flip() 
gg <- gg + labs(y=auctau_label,x=ae_label)
gg <- gg + xgx_annotate_status(status)
gg

Probability of AE for each dose group

Plot binary AE occurrence against dose. Using summary statistics can be helpful, e.g. Mean +/- SE, or median, 5th & 95th percentiles. For binary response data, plot the percent responders along with binomial confidence intervals.

Here are some questions to ask yourself when looking at Dose-safety plots: Do you see any relationship? Does AE increase (decrease) with increasing dose?

gg1 <- ggplot(data = ae.data, aes(x=Dose,y=AE_binary))
gg1 <- gg1 + geom_smooth( method = "glm",method.args=list(family=binomial(link = logit)), color = "black")
gg1 <- gg1 + xgx_stat_ci(conf_level = 0.95, distribution = "binomial", geom = c("point"), shape = 0, size = 4)
gg1 <- gg1 + xgx_stat_ci(conf_level = 0.95, distribution = "binomial", geom = c("errorbar"), size = 0.5)
gg1 <- gg1 + guides(color=guide_legend(""),fill=guide_legend(""))
gg1 <- gg1 + scale_y_continuous(labels=scales::percent) 
gg1 <- gg1 + labs(y=aeprob_label,x=dose_label)
gg1 <- gg1 + xgx_annotate_status(status)

## Same plot but on a log scale
gg2 <- gg1 + xgx_scale_x_log10(breaks=unique(ae.data$Dose)) 

#put linear and log scale plots side-by-side:
grid.arrange(gg1, gg2, ncol=2)

Probability of AE by AUC

Plot AE against exposure. Include a logistic regression for binary data to help determine the shape of the exposuresafety relationship. Summary information such as mean and 95% confidence intervals by quartiles of exposure can also be plotted. Exposure and Cmax metric that you use in these pltots could be either be raw concentrations, or NCA or model-derived exposure metrics (e.g. Cmin, Cmax, AUC), and may depend on the level of data that you have available.

gg <- ggplot(data = ae.data, aes(x=AUCAVE,y=AE_binary))
gg <- gg + geom_jitter(aes( color = DOSE_label_high2low), width = 0, height = 0.05, alpha = 0.5) 
gg <- gg + geom_smooth( method = "glm",method.args=list(family=binomial(link = logit)), color = "black")
gg <- gg + xgx_stat_ci(mapping = aes(x = AUC_midpoints, y = AE_binary), 
                       conf_level = 0.95, distribution = "binomial", geom = c("point"), shape = 0, size = 4)
gg <- gg + xgx_stat_ci(mapping = aes(x = AUC_midpoints, y = AE_binary), 
                       conf_level = 0.95, distribution = "binomial", geom = c("errorbar"), size = 0.5)
gg <- gg + guides(color=guide_legend(""),fill=guide_legend(""))
gg <- gg + scale_y_continuous(breaks=c(0,1)) + coord_cartesian(ylim=c(-0.2,1.2))
gg <- gg + labs(x=auctau_label,y=ae_label)
gg <- gg + xgx_annotate_status(status)
gg

Boxplots of Exposure over time, with AE highlighted

The plot below shows time vs AUC from a pop PK model at each day. The colored dots corresponding to adverse events. It is not strictly an exploratory plot. But once you have a PopPK model, it is simple to generate this plot. Just plot the time vs the predicted AUC and color the time points red on days that an adverse event occurred.

data_to_plot <- ae.data %>% subset(!is.na(AETOXGRDN),)
gg <- ggplot()
gg <- gg + geom_jitter(data=auc %>% subset(AUC_day<30,), 
                       aes(x=AUC_day, y=AUC_popPK/AUC_day, group=AUC_day),
                       color="grey", alpha = 0.5, 
                       position=position_jitter(width=.1, height=0))
gg <- gg + geom_boxplot(data=auc %>% subset(AUC_day<30,), 
                        aes(x=AUC_day, y=AUC_popPK/AUC_day, group=AUC_day),
                        outlier.shape = NA, fill = NA)
gg <- gg + geom_point(data=data_to_plot %>% subset(DAY<30,), 
                      aes(x=DAY, y=AUC/DAY, color=factor(AETOXGRS)),size=2)
gg <- gg + scale_color_manual(breaks = c("GR1", "GR2", "GR3"),
                        values=c(rgb(1,0.5,0.5), rgb(0.75,0.25,0.25), rgb(0.5,0,0)))
gg <- gg + guides(color=guide_legend(""),fill=guide_legend(""))
gg <- gg + scale_x_continuous(breaks = seq(0, 30, by = 3))
gg <- gg + xgx_annotate_status(status)
gg <- gg + labs(x=time_label,y=auctau_label)
gg

Oncology individual plots of percent change from baseline, including dosing history, labeled by “Overall Response” and AE grade

These plots allow one to look for subtle trends in the individual trajectories with respect to the dosing history, safety events and efficacy as percent change of tunor size from baseline. The plots below make use of the following oncology datasets: RECIST and nmpk dataset (download here) and dose record dataset (download here)

# Read oncology efficacy data from the oncology efficacy 
# page and combine them with safety data in this page

safety_data <- read.csv("../Data/AE_xgx.csv")
efficacy_data <- read.csv("../Data/Oncology_Efficacy_Data.csv")
dose_record <- read.csv("../Data/Oncology_Efficacy_Dose.csv")

efficacy_data$DOSE_label <- paste(efficacy_data$DOSE_ABC,"mg")
efficacy_data$DOSE_label <- factor(efficacy_data$DOSE_label,levels = c(paste(unique(efficacy_data$DOSE_ABC),"mg")))

efficacy_data.monotherapy = efficacy_data %>% filter(COMB=="Single")
efficacy_data.combo = efficacy_data %>% filter(COMB=="Combo")


# Dose record data preparation for making step function plot
# in order to show any dose reduction during the study
# the idea is that at each time point, you have both the current dose and the previous dose
# the dplyr::lag command implements this
data_areaStep <- bind_rows(old = dose_record,
                           new = dose_record %>% 
                 group_by(IDSHORT) %>% 
                 mutate(DOSE = lag(DOSE)),
                        .id  = "source") %>%
                 arrange(IDSHORT, TIME, source) %>%
  ungroup() %>%
  mutate(DOSE = ifelse(lag(IDSHORT)!=IDSHORT, NA, DOSE), 
          TIME = TIME/24) #convert to days

data_areaStep.monotherapy = filter(data_areaStep,COMB=="Single")

# calculate average dose intensity up to the first assessment:
# "TIME==57"" is the first assessment time in this dataset
first.assess.time = 57
dose_record <- dose_record %>%
  group_by(IDSHORT) %>%
  mutate(ave_dose_intensity = mean(DOSE[TIME/24 < first.assess.time]))

dose_intensity <- dose_record[c("IDSHORT","COMB","ave_dose_intensity")]
dose_intensity <- subset(dose_intensity, !duplicated(IDSHORT))


# This part is optional to label "OR" in the plot
# "OR" can be substituted with other information, such as non-target, new target lesions
#  make the OR label for the plot


safety_label <- safety_data %>%
  select(SUBJID, DAY, AETOXGRS, Dose)

colnames(safety_label)[2] <- "TIME"
colnames(safety_label)[4] <- "DOSE_ABC"
safety_label$AETOXGRS <- as.character(safety_label$AETOXGRS)
safety_label <- safety_label[!safety_label$AETOXGRS =="",]

efficacy_AE_label <- efficacy_data %>%
  select(SUBJID, TIME, psld, DOSE_ABC)
efficacy_AE_label <- merge(safety_label,efficacy_AE_label, by = c("SUBJID", "TIME","DOSE_ABC"),
                            all.x=T, all.y=T)

subj <- efficacy_AE_label  %>% 
  subset(!is.na(psld)) %>%
  group_by(SUBJID) %>%
  mutate(CountNonNa = length(psld))

subj <- c(unique(subset(subj, CountNonNa>1, "SUBJID")))

efficacy_AE_label <- efficacy_AE_label  %>% 
  subset(SUBJID%in%subj$SUBJID)%>%
  group_by(SUBJID) %>%
  mutate(ValueInterp = na.approx(psld,TIME, na.rm=FALSE))

efficacy_AE_label <- efficacy_AE_label[!is.na(efficacy_AE_label$AETOXGRS),]
efficacy_AE_label <- efficacy_AE_label[!is.na(efficacy_AE_label$ValueInterp),]
efficacy_AE_label <- subset( efficacy_AE_label, select = -psld )
colnames(efficacy_AE_label)[5] <- "psld"
colnames(efficacy_AE_label)[1] <- "IDSHORT"



efficacy_data.label <- efficacy_data %>%
  group_by(SUBJID) %>%
  mutate(label_psld = as.numeric(ifelse(TIME==TIME_OR , psld,""))) %>%
  filter(!(is.na(label_psld) | label_psld==""))

dose.shift = 50
dose.scale = 1.2
data_areaStep.monotherapy = data_areaStep.monotherapy %>%
  mutate(DOSE.shift = DOSE/dose.scale+dose.shift)

dose.unique = c(0,unique(efficacy_data.monotherapy$DOSE_ABC))


gg <- ggplot(data = efficacy_data.monotherapy)
gg <- gg + geom_point(mapping = aes(y= psld, x= TIME))
gg <- gg + geom_text(data= efficacy_data.label,aes(y= label_psld, x= TIME_OR, label=OR), vjust=-.5)
gg <- gg + geom_hline(aes(yintercept = 0),size=0.1, colour="black")
gg <- gg + geom_line(mapping = aes(y= psld, x= TIME))
gg <- gg + geom_ribbon(data= data_areaStep.monotherapy,
                       aes( ymin = 50, ymax = DOSE.shift , x= TIME),
                       fill="palegreen2", color = "black", alpha=0.5 )
gg <- gg + geom_text(data= efficacy_AE_label,
                     aes(y= psld, x= TIME, label=AETOXGRS), colour="red",fontface=2,
                     size=5, show.legend = F, hjust=-0.05, vjust=2)
gg <- gg + geom_vline(data= efficacy_AE_label,
                      aes(xintercept= TIME), 
                      size=1, linetype="dashed", colour="red")
gg <- gg + facet_wrap(~IDSHORT, ncol=6)
gg <- gg + scale_y_continuous(
  sec.axis = sec_axis(~(.-dose.shift)*dose.scale, name = "Dose(mg)", breaks = dose.unique))
gg <- gg + labs(y = sld_label, x= timemonth_label)
gg <- gg + xgx_scale_x_time_units(units_dataset = "day", units_plot ="month")
gg <- gg + theme(text = element_text(size=15))
gg <- gg + xgx_annotate_status(status)
gg

R Session Info

sessionInfo()
## R version 4.1.0 (2021-05-18)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Red Hat Enterprise Linux Server 7.9 (Maipo)
## 
## Matrix products: default
## BLAS/LAPACK: /CHBS/apps/EB/software/imkl/2019.1.144-gompi-2019a/compilers_and_libraries_2019.1.144/linux/mkl/lib/intel64_lin/libmkl_gf_lp64.so
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8   
##  [6] LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] xgxr_1.1.1    zoo_1.8-11    gridExtra_2.3 tidyr_1.2.1   dplyr_1.0.10  ggplot2_3.3.6
## 
## loaded via a namespace (and not attached):
##  [1] nlme_3.1-160        bitops_1.0-7        RColorBrewer_1.1-3  Deriv_4.1.3         tools_4.1.0         backports_1.4.1     bslib_0.4.0        
##  [8] utf8_1.2.2          R6_2.5.1            rpart_4.1.16        mgcv_1.8-41         Hmisc_4.7-0         DBI_1.1.3           colorspace_2.0-3   
## [15] nnet_7.3-17         withr_2.5.0         tidyselect_1.2.0    Exact_2.1           compiler_4.1.0      cli_3.4.1           htmlTable_2.2.1    
## [22] binom_1.1-1         expm_0.999-6        labeling_0.4.2      sass_0.4.2          scales_1.2.1        checkmate_2.1.0     mvtnorm_1.1-3      
## [29] readr_2.1.3         proxy_0.4-26        stringr_1.4.1       digest_0.6.30       foreign_0.8-82      rmarkdown_2.17      base64enc_0.1-3    
## [36] jpeg_0.1-9          pkgconfig_2.0.3     htmltools_0.5.3     fastmap_1.1.0       highr_0.9           htmlwidgets_1.5.4   rlang_1.0.6        
## [43] rstudioapi_0.14     jquerylib_0.1.4     generics_0.1.3      farver_2.1.1        jsonlite_1.8.3      RCurl_1.98-1.4      magrittr_2.0.3     
## [50] Formula_1.2-4       interp_1.1-2        Matrix_1.5-1        Rcpp_1.0.9          DescTools_0.99.42   munsell_0.5.0       fansi_1.0.3        
## [57] lifecycle_1.0.3     stringi_1.7.8       yaml_2.3.6          MASS_7.3-58.1       rootSolve_1.8.2.2   plyr_1.8.7          grid_4.1.0         
## [64] lmom_2.8            deldir_1.0-6        lattice_0.20-45     splines_4.1.0       pander_0.6.4        hms_1.1.2           knitr_1.40         
## [71] pillar_1.8.1        boot_1.3-28         gld_2.6.2           codetools_0.2-18    glue_1.6.2          evaluate_0.17       latticeExtra_0.6-30
## [78] data.table_1.14.2   png_0.1-7           vctrs_0.5.0         tzdb_0.3.0          gtable_0.3.1        purrr_0.3.5         assertthat_0.2.1   
## [85] cachem_1.0.6        xfun_0.34           e1071_1.7-8         class_7.3-19        survival_3.4-0      minpack.lm_1.2-1    tibble_3.1.8       
## [92] cluster_2.1.3       ellipsis_0.3.2