Truong et al. (2025): Full Human Gestational IVIVE

Kimberly T.Truong, John F. Wambaugh, Dustin F. Kapraun, Sarah E. Davidson-Fritz, Stephanie Eytcheson, Richard Judson, Katie Paul-Friedman

2025-04-19

from “Interpretation of thyroid-relevant bioactivity data for comparison to in vivo exposures: A prioritization approach for putative chemical inhibitors of in vitro deiodinase activity”

Kimberly T Truong, John F Wambaugh, Dustin F Kapraun, Sarah E Davidson-Fritz, Stephanie Eytcheson, Richard S Judson, and Katie Paul Friedman

Toxicological Sciences, 2025

Overview

In this vignette, we show how to use the full human gestational model from httk to perform in vitro-in vivo extrapolation (IVIVE) on a relevant in vitro bioactivity dataset. Briefly, this demonstrates how to take thyroid-related in vitro bioactivity data from ToxCast, perform IVIVE using the 3-compartment steady state model and the full human pregnancy model within httk, and compare it with the latest exposure estimates from ExpoCast as a method of prioritization. This is detailed in Truong et al. 2025 (in review).

Truong et al. 2025 describes a prioritization workflow for thyroid-related bioactivity data related to deiodinase inhibition by applying the full human gestational model to compute administered equivalent doses (AEDs) and bioactivity-to-exposure ratios (BERs) by integrating SEEM3 ExpoCast predictions. See Figure 1 for an overview of the workflow.

This vignette reproduces Figures 9 and 10 from Truong et al. 2025.

HTTK Version

This vignette was created with httk v2.6.0 which includes new model functionality (see Simulate full-term human pregnancy model).

rm(list = ls())

eval = execute.vignette

Several chunks of code in this vignette have “eval = execute.vignette” and we set execute.vignette to be FALSE, by default. We do this to include all the code that was necessary to produce the figures but was not run when the vignette was built since some of these chunks take a considerable amount of time and the checks on CRAN limit how long we can spend building the package. You can still reproduce the end figures knitting the .Rmd file, since all the necessary and minimal data objects have been stored in httk. If you want to rerun the whole workflow on your own, you must rerun all code from top to bottom, either by changing execute.vignette to TRUE or clicking on the “green arrow” in RStudio.

execute.vignette <- FALSE

Data Import

We need to load in the processed in vitro bioactivity data. This is stored within httk and can be accessed by httk::thyroid.ac50s. From the R command prompt, ?thyroid.ac50s for more details.

ac50.dt <- httk::thyroid.ac50s

Load libraries

library(httk)
library(data.table)
library(dplyr)
library(openxlsx)
library(ggpubr)
library(cowplot)
library(ggrepel)
library(latex2exp)
library(viridis)
library(scales)
library(ggstar)

IVIVE using 3-compartment steady state model

Examine Chemicals

Only 50 chemicals have experimental toxicokinetic (TK) parameters sufficient for httk.

# how many chems had experimental TK params?
human.data.pre <- subset(get_cheminfo(info = 'all', species = 'Human', 
                                      model = '3compartmentss'), 
                         DTXSID %in% ac50.dt$dsstox_substance_id)  

nrow(human.data.pre)

In silico predictions of TK parameters

We need to supplement these with in silico predictions of TK parameters.

# predict Clint/fup values for missing chems 
# this loads new chem.physical_and_invitro.data table into the global env 
load_dawson2021() # most data for ToxCast chems
load_sipes2017() # most data for pharma compounds 
load_pradeep2020() # best ML method for in silico prediction 

Examine missing chemicals

human.httk.dt <- subset(ac50.dt, dsstox_substance_id %in% get_cheminfo(info = 'DTXSID', 
                                                                       species = 'Human', 
                                                                       model = '3compartmentss', 
                                                                       physchem.exclude = FALSE,  
                                                                       median.only = TRUE))

missing.dtxsids <- setdiff(ac50.dt$dsstox_substance_id, human.httk.dt$dsstox_substance_id)

View(ac50.dt[dsstox_substance_id %in% missing.dtxsids])

Predicting AEDs based on Css50 (in plasma)

There are two ways to compute an oral AED in httk:
1. compute a steady-state plasma concentration via calc_mc_css and then: \[ AED = \frac{AC_{50}}{CSS_{50}} \] 2. use calc_mc_oral_equiv passing the AC50 to the conc argument and specifying which.quantile = c(0.5).


for (i in 1:nrow(human.httk.dt)) {
  
  message('Calculating for chemical: ', human.httk.dt$chnm[i], '\n')
  set.seed(123)
  out <- calc_mc_css(dtxsid = human.httk.dt$dsstox_substance_id[i], 
                     species = 'Human', 
                     daily.dose = 1, 
                     which.quantile = c(0.5, 0.95), 
                     model = '3compartmentss', 
                     output.units = 'uM', 
                     parameterize.args.list=list(physchem.exclude=FALSE), 
                     suppress.messages = TRUE)
  
  human.httk.dt$mc.css50[i] <- out["50%"]

  set.seed(123)
  oralequiv.out <- calc_mc_oral_equiv(conc = human.httk.dt$min_ac50[i],
                                      dtxsid = human.httk.dt$dsstox_substance_id[i], 
                                      species = 'Human',
                                      which.quantile = c(0.5, 0.95),
                                      input.units = 'uM',
                                      output.units = 'mgpkgpday',
                                      model = '3compartmentss',
                                      restrictive.clearance = TRUE,
                                      parameterize.args.list=list(physchem.exclude=FALSE),
                                      suppress.messages = TRUE)
  
   human.httk.dt$oral_equiv50[i] <- oralequiv.out["50%"]
}                                                                

human.httk.dt[, calc.aed50 := min_ac50/mc.css50]
human.httk.dt[, fold_diff50 := log10(oral_equiv50/calc.aed50)]

Load ExpoCast predictions (SEEM3)


# let's use the aed values from the division 
human.httk.dt2 <- human.httk.dt[, c('dsstox_substance_id', 'chnm', 
                                    'DIO1', 'DIO2', 'DIO3', 'IYD', 'min_ac50', 
                                    'mc.css50', 'calc.aed50')]

SEEM <- httk::truong25.seem3

# integrate SEEM3 predictions with merge
human.httk.dt2.seem3 <- merge.data.table(human.httk.dt2, 
                                         SEEM[, c("dsstox_substance_id", "seem3", "seem3.u95")], 
                                         by = c("dsstox_substance_id"), 
                                         all.x = TRUE)

human.httk.dt2.seem3[, plasmaBER50 := calc.aed50/seem3.u95]
human.httk.dt2.seem3[, log.pBER50 := log10(plasmaBER50)]

ivive.moe.tb <- human.httk.dt2.seem3
setnames(ivive.moe.tb, "dsstox_substance_id", "dtxsid")

IVIVE using the Full Human Gestational Model

Compute half-lives

for (i in 1:nrow(ivive.moe.tb)) {
  ivive.moe.tb$half.life[i] <- calc_half_life(dtxsid = ivive.moe.tb$dtxsid[i], # t1/2 in hours 
                                              physchem.exclude = FALSE) 
  ivive.moe.tb$half.life.days[i] <- ivive.moe.tb$half.life[i]/24 # in days
}

Simulate full-term human pregnancy model


# tissues where DIO enzymes live 
impacted_tissues <- c('Cliver', 'Cthyroid', 'Cfliver', 'Cfbrain', 'Cfthyroid', 
                      'Cplacenta', 'Cconceptus', 'Cplasma')
targets <- c('DIO1', 'DIO2', 'DIO3', 'IYD')

# max conc in every tissue for every chemical
Cmax.tissues <- data.frame(matrix(NA, ncol = length(impacted_tissues) + 1, 
                                  nrow = nrow(ivive.moe.tb), 
                                  dimnames = list(NULL, c('dtxsid', impacted_tissues))))

for(i in 1:nrow(ivive.moe.tb)) {
  
  sol.out <- solve_full_pregnancy(dtxsid = ivive.moe.tb$dtxsid[i],
                            daily.dose = 1, 
                            doses.per.day = 1,
                            time.course = seq(0, 40*7, 1/24), # view solution by hour
                            track.vars = impacted_tissues, 
                            physchem.exclude = FALSE)

  
  # get max of every column re: compt 
  Cmax.tissues[i, impacted_tissues] <- apply(sol.out[, impacted_tissues], 2, max, na.rm = T)
  Cmax.tissues[i, 'dtxsid'] <- ivive.moe.tb$dtxsid[i]
  
}

Calculate AEDs by Target and Tissue

tissue.aeds <- list()

# tissues specific for each target 
tissue_list <- list(DIO1 = c('Cliver', 'Cfliver', 'Cthyroid', 'Cfthyroid', 'Cconceptus'), 
                    DIO2 = c('Cthyroid', 'Cfthyroid', 'Cfbrain', 'Cconceptus'), 
                    DIO3 = c('Cthyroid', 'Cfthyroid', 'Cfbrain', 'Cplacenta', 'Cconceptus'), 
                    IYD = c('Cthyroid', 'Cfthyroid', 'Cconceptus'))

for (i in names(tissue_list)) {
  active.chem.ind <- which(!is.na(ivive.moe.tb[, ..i]))
  n.chems <- length(active.chem.ind)
  
  empty.mat <- matrix(NA, nrow = n.chems, ncol = length(tissue_list[[i]]) + 1)
  df.target <- data.frame(empty.mat)
  colnames(df.target) <- c('dtxsid', tissue_list[[i]])
 
  df.target[, 2: (length(tissue_list[[i]])+1) ] <- mapply(function(x,y) x/y, ivive.moe.tb[active.chem.ind, ..i], Cmax.tissues[active.chem.ind, tissue_list[[i]]])
  
  df.target$dtxsid <- ivive.moe.tb$dtxsid[active.chem.ind]
  tissue.aeds[[i]] <- df.target
}

Figure 9: Priority Chemicals based on maternal-fetal BERs

impacted_tissues <- c('Cliver', 'Cthyroid', 'Cfliver', 'Cfbrain', 'Cfthyroid', 
                      'Cplacenta', 'Cconceptus', 'Cplasma')

get_ber_data <- function(name, df) {
  
  df$exposure <- ivive.moe.tb$seem3.u95[match(df$dtxsid, ivive.moe.tb$dtxsid)]
  df.m <- df %>% select(-exposure) %>%
    reshape2::melt(id.vars = c('dtxsid'), 
                   variable.name = 'compt', value.name = 'aed')
  df.m$exposure <- ivive.moe.tb$seem3.u95[match(df.m$dtxsid, ivive.moe.tb$dtxsid)]
  df.m$ber = df.m$aed/df.m$exposure
  df.m2 <- df.m %>% 
    select(-exposure) %>%
    reshape2::melt(id.vars = c('dtxsid', 'compt', 'ber'))
  
  # add melted ExpoCast data as rows for each unique chem 
  expocast.dat <- df %>% select(dtxsid, exposure) %>% 
    reshape2::melt(id.vars = c('dtxsid'))
  
  df.m2 <- bind_rows(df.m2, expocast.dat)
  df.m2$value <- log10(df.m2$value)
  df.m2$ber <- log10(df.m2$ber)
  df.m2$chnm <- ivive.moe.tb$chnm[match(df.m2$dtxsid, ivive.moe.tb$dtxsid)]
  df.m2$enzyme <- name
  return(df.m2)
}

new.list <- Map(get_ber_data, names(tissue.aeds), tissue.aeds)
ggdata = bind_rows(new.list) 
setDT(ggdata)

# order chemicals within each enzyme group by the most sensitive tissue/lifestage BER
ggdata[, min_ber := min(ber, na.rm = TRUE), keyby = .(enzyme, dtxsid)]
ggdata <- ggdata[order(min_ber), .SD, keyby = .(enzyme)]

# exposure values are dependent on dtxsid not httk compt 
ggdata[variable == 'exposure', compt := 'exposure']

# make a deep copy or else it will be modified by reference 
tissue.bers <- copy(ggdata)

# round all numerical values including BERs to 2 sigfigs 
num.cols <- c("value", "ber", "min_ber")
tissue.bers[, c(num.cols) := lapply(.SD, signif, digits = 2), .SDcols = num.cols]
min_val <- tissue.bers[min_ber <= 3, min(value), by = enzyme][, min(V1)]
max_val <- tissue.bers[min_ber <= 3, max(value), by = enzyme][, max(V1)]
ylims <- c(min_val, max_val)

# generate a set of 3 viridis colors for lifestage 
# yellow represents neither maternal or fetal, i.e., conceptus
my_viridis_colors <- viridis(n = 3)

listgg <- list()
for(k in names(tissue.aeds)) {
  
  aeds <- tissue.bers[enzyme == k & ber <= 3]
  chems <- tissue.bers[enzyme == k & ber <= 3, dtxsid]
  exposures <- tissue.bers[enzyme == k & variable == "exposure" & dtxsid %in% chems]
  
  listgg[[k]] <- ggplot(data = rbind(aeds,exposures), 
                        mapping = aes(x = reorder(chnm, -min_ber), 
                                      y = value)) +
    geom_star(aes(starshape = compt, fill = compt, color = compt), 
              alpha = 0.65, size = 1.25, # default: 1.5
              starstroke = 0.25, # default: 0.5
              show.legend = T) +
    scale_y_continuous(breaks = seq(ylims[1], ylims[2], 1),
                       limits = ylims) +
    scale_starshape_manual(name = "Compartment corresponding to AED \n or Exposure", 
                       labels = c("maternal liver", "fetal liver", 
                                  "maternal thyroid", "fetal thyroid", 
                                  "conceptus", 
                                  "fetal brain", 
                                  "placenta", "exposure"),
                       values = c('Cfbrain' = 15,
                                  'exposure' = 28, 
                                  'Cliver' = 11,
                                  'Cfliver' = 11, 
                                  'Cplacenta' = 13, 
                                  'Cthyroid' = 1,
                                  'Cfthyroid' = 1, 
                                  'Cconceptus' = 23), 
                       drop = F) + # this makes sure all levels of a factor are displayed even if unused 
    scale_fill_manual(name = "Compartment corresponding to AED \n or Exposure", 
                       labels = c("maternal liver", "fetal liver", 
                                  "maternal thyroid", "fetal thyroid", 
                                  "conceptus", 
                                  "fetal brain", 
                                  "placenta", "exposure"),
                       values = c('Cfbrain' = my_viridis_colors[2],
                                  'exposure' = 'dimgrey', 
                                  'Cliver' = my_viridis_colors[1],
                                  'Cfliver' = my_viridis_colors[2], 
                                  'Cplacenta' = '#990066', 
                                  'Cthyroid' = my_viridis_colors[1],
                                  'Cfthyroid' = my_viridis_colors[2], 
                                  'Cconceptus' = '#73D055FF'), 
                       drop = F) +
     scale_color_manual(name = "Compartment corresponding to AED \n or Exposure",
                       labels = c("maternal liver", "fetal liver",
                                  "maternal thyroid", "fetal thyroid",
                                  "conceptus",
                                  "fetal brain",
                                  "placenta", "exposure"),
                       values = c('Cfbrain' = my_viridis_colors[2],
                                  'exposure' = 'dimgrey',
                                  'Cliver' = my_viridis_colors[1],
                                  'Cfliver' = my_viridis_colors[2],
                                  'Cplacenta' = '#990066',
                                  'Cthyroid' = my_viridis_colors[1],
                                  'Cfthyroid' = my_viridis_colors[2],
                                  'Cconceptus' = '#73D055FF'),
                       drop = F) +
    labs(x = 'Chemical', y = 'Oral AED or Exposure (log10 mg/kg-bw/day)', title = k) +
    theme_bw() +
    theme(legend.position = "none", 
          plot.title = element_text(size = 8, face = "bold"), 
          plot.margin = unit(c(0.1,0.1,0.1,0.1), "cm"),
          axis.title = element_text(size = 6), 
          axis.text.y = element_text(size = 5), 
          axis.text.x = element_text(size = 6),
          legend.text = element_text(size = 6), 
          legend.title = element_text(size = 6, hjust = 0.5))+
    coord_flip()
}

# remove extra axis labels 
listgg$IYD <- listgg$IYD + labs(x = '', y = '')
listgg$DIO3 <- listgg$DIO3 + 
  labs(x = '') 
listgg$DIO1 <- listgg$DIO1 + labs(y = '')

# arrange DIO1 and DIO2 with equal sizes since they have ~20 chems each (21 vs 24)
pcol1 <- plot_grid(listgg$DIO1, listgg$DIO2, 
                  align = "hv", nrow = 2, 
                  rel_heights = c(1,1.1))

# make DIO3 3x taller than IYD (44 chems vs 4)
pcol2 <- plot_grid(listgg$IYD, listgg$DIO3, 
                   align = "hv", nrow = 2, 
                   rel_heights = c(1,3))

# combine two columns of plots
prow <- plot_grid(pcol1, pcol2, 
                    align = "h", ncol = 2, 
                    rel_widths = c(1,1))

# extract legend from one of the plots
grobs <- ggplotGrob(
  listgg$DIO2 + 
  guides(
    starshape = guide_legend(ncol = 2, byrow = F, 
                             keyheight = unit(0.25, "cm")), 
    fill = guide_legend(ncol = 2, byrow = F, 
                        keyheight = unit(0.25, "cm")), 
    color = guide_legend(ncol = 2, byrow = F, 
                         keyheight = unit(0.25, "cm")),
  ) +
  theme(legend.position = "bottom")
  )$grobs
legend2 <- grobs[[which(sapply(grobs, function(x) x$name) == "guide-box")]]

# add the legend underneath the plots; make it 10% of the height of the combined plots
fig9 <- plot_grid(prow, legend2, 
          nrow = 2, rel_heights = c(0.88,0.12)) +
   theme(plot.background = element_rect(fill = "white", color = "white"))

# ggsave(plot = fig9,
#        units = "mm",
#        dpi = 300,
#        width = 190, height = 150,
#        device = "png",
#        filename = "./figures/preg_prioritization_by_BER.png")

Comparing IVIVE models based on BER prioritization

Table 8

targets <- c('DIO1', 'DIO2', 'DIO3', 'IYD')

# DATA WRANGLING
tissue.bers[min_ber == ber, most_sensitive_tissue := compt]

# table matching order of chemicals in enzyme subplots with all calculated BERs
# every enzyme x chem x min BER constitutes a row 
minber.by.chem <- tissue.bers[!is.na(most_sensitive_tissue), 
                       .(chnm, min_ber, most_sensitive_tissue), by = .(enzyme, dtxsid)]

# check if there are multiple most-sensitive tissues per chem in each enzyme group 
minber.by.chem[, if(.N > 1) .SD, by = .(enzyme, dtxsid)][, length(unique(dtxsid))] #70 chems

# collapse multiple most-sensitive tissues per chem in each enzyme group
min.ber.reduced <- minber.by.chem[, most_sensitive_compts := paste(most_sensitive_tissue, collapse = ", "), by = .(enzyme, dtxsid)][, .(chnm, min_ber, most_sensitive_compts), by = .(enzyme, dtxsid)]
min.ber.reduced <- unique(min.ber.reduced)

# min BER matrix
min.ber.mat <- data.table::dcast(min.ber.reduced[, 1:4], 
                     dtxsid + chnm ~ enzyme, value.var = c('min_ber'))
min.ber.mat[, min_across_enzyme := pmin(DIO1, DIO2, DIO3, IYD, na.rm = T)]
min.ber.mat <- min.ber.mat[order(min_across_enzyme)]

# find the target corresponding to the min_across_enzyme
min.ber.mat[, enzyme := mapply(function(x) targets[x], apply(min.ber.mat[, targets, with = FALSE], 1, which.min))]

# merge data on most sensitive tissues reflecting min BER 
new.ranking.tissues <- merge.data.table(min.ber.mat, 
                                 minber.by.chem[, .(dtxsid, chnm, enzyme, min_ber, most_sensitive_tissue)], 
                                 by = c('dtxsid', 'chnm', 'enzyme'), 
                                 all.x = TRUE)
setnames(new.ranking.tissues, old = 'enzyme', new = 'target_min')

# round all BERs to 2 sigfigs for easy table viewing 
ranked.by.plasma <- ivive.moe.tb[, lmoe50 := signif(log.pBER50, digits = 2)][order(lmoe50)]

final.new.ranking <- new.ranking.tissues[, -c("target_min", "min_ber")]
final.new.ranking <- final.new.ranking[order(min_across_enzyme)]

# comparing tissue BERs from full preg model with plasma BERs from 3compss model 
plasma.tissue.bers <- merge.data.table(ranked.by.plasma[, c('dtxsid', 'chnm', 'lmoe50')], 
                           final.new.ranking[, -c('chnm')], 
                           by = c('dtxsid'))

setnames(plasma.tissue.bers, old = colnames(plasma.tissue.bers)[3:8], 
         new = c('plasma_BER50', 'DIO1_BER', 'DIO2_BER', 'DIO3_BER', 'IYD_BER', 'min_BER'))

plasma.tissue.bers <- plasma.tissue.bers[order(min_BER)]

Figure 10: Understanding and Interpreting Maternal-Fetal BERs

# merge min BER with plasma BER for each chem with unique row for each chem
# ignoring multiple sensitive tissues
ber.comp <- merge.data.table(ranked.by.plasma[, c('dtxsid', 'chnm', 'lmoe50')], 
                             min.ber.mat[, -c("chnm")], 
                             by = c("dtxsid"))

setnames(ber.comp, old = colnames(ber.comp)[3:8], 
         new = c('plasma_BER50', 'DIO1_BER', 'DIO2_BER', 'DIO3_BER', 'IYD_BER', 'min_BER'))

max_ber <- max(ber.comp[, c('plasma_BER50', 'min_BER')], na.rm = T)
min_ber <- min(ber.comp[, c('plasma_BER50', 'min_BER')], na.rm = T)
ber.lims <- c(floor(min_ber), ceiling(max_ber))

# chems with labels 
chem.labels <- ber.comp[min_BER < 0][order(min_BER)][1:6, chnm]

# theme for the next 2 figures 
my_theme <- theme_bw() +
  theme(axis.title = element_text(size = 8, face = "bold"), 
        axis.text = element_text(size = 7))
  
preg.vs.nonpreg.pp <- ggplot(data = ber.comp, aes(x = plasma_BER50, y = min_BER)) +
  geom_point(alpha = 0.75, size = 1, 
             show.legend = T) +
  geom_abline(slope = 1, linewidth = 0.5, linetype = 'solid', color = "#666666") +
  geom_abline(aes(slope = 1, intercept = 1), linetype = 'longdash', color = 'grey', linewidth = 0.5) + # default linesize = 1
  geom_abline(aes(slope = 1, intercept = -1), linetype = 'longdash', color = 'grey', linewidth = 0.5) +
  geom_label_repel(aes(label = chnm),
                   data = ber.comp[chnm %in% chem.labels],
                   max.overlaps = Inf, 
                   force = 50, 
                   size = 2, 
                   label.padding = 0.1, 
                   label.size = 0.1, 
                   label.r = 0.08, 
                   segment.size = 0.2, 
                   min.segment.length = 0) +
  my_theme +
  scale_x_continuous(breaks = seq(ber.lims[1], ber.lims[2], 1), 
                     limits = ber.lims) +
  scale_y_continuous(breaks = seq(ber.lims[1], ber.lims[2], 1), 
                     limits = ber.lims) +
  labs(x = 'plasma BER using 3compss model', y = 'min BER across DIO/IYD targets and tissues')

# how many chems to the right of y = x?
ber.comp[plasma_BER50 > min_BER, .N] #89 chems 

# plot SEEM3 uncertainty
ivive.moe.tb$uncertainty = log10(ivive.moe.tb$seem3.u95/ivive.moe.tb$seem3)
ber.comp$uncertainty <- ivive.moe.tb$uncertainty[match(ber.comp$dtxsid, ivive.moe.tb$dtxsid)]

min.min.ber <- min(ber.comp$min_BER)
max.min.ber <- max(ber.comp$min_BER)
min.ber.lims <- c(floor(min.min.ber), ceiling(max.min.ber))
max.uncertainty <- max(ber.comp$uncertainty)

seem3pp <- ggplot(data = ber.comp, 
       mapping = aes(x = min_BER, 
                     y = uncertainty)) +
  geom_point(size = 1, alpha = 0.8) +
  geom_label_repel(aes(label = chnm),
                   data = subset(ber.comp, chnm %in% chem.labels), 
                   max.overlaps = Inf, 
                   force = 75,
                   size = 2, 
                   label.padding = 0.1, 
                   label.size = 0.1, 
                   label.r = 0.08, 
                   segment.size = 0.2, 
                   min.segment.length = 0) +
  my_theme +
  scale_x_continuous(breaks = seq(min.ber.lims[1], min.ber.lims[2], 1), 
                     limits = min.ber.lims) +
  scale_y_continuous(breaks = seq(0, ceiling(max.uncertainty), 1), 
                     limits = c(0, ceiling(max.uncertainty))) +
  labs(x = 'min BER by Chemical', y = TeX(r'($log_{10}ExpoCast_{u95} - log_{10}ExpoCast_{med}$)'))

median(ber.comp$min_BER)
#> [1] 2.9

fig10 <- plot_grid(preg.vs.nonpreg.pp, seem3pp, 
          labels = "AUTO", label_size = 10)

# ggsave(plot = fig10,
#        units = "mm",
#        dpi = 300,
#        width = 190, height = 100,
#        device = "png",
#        filename = "./figures/BER_uncertainty.png")

Code used to create data distributed with vignette

thyroid.ac50s <- ac50.dt
truong25.seem3 <- SEEM

save(thyroid.ac50s, truong25.seem3,
     file="./httk/Truong2025Vignette.RData")