This shows you how to take a dataset, fit a model, and apply tdmore to investigate predictive performance and precision dosing.
set.seed(0) library(ggplot2) library(dplyr) library(tidyr) library(tdmore)
We will use a well-known example dataset: pheno
.
pheno %>% filter(ID %in% c(1,2))
We will fit a 1-compartment model to this dataset using nlmixr.
library(nlmixr) library(tidyverse) modelCode <- function() { ini({ LTHETA2 <- log(50) LTHETA3 <- log(0.1) #variances ETA2 ~ 1 ETA3 ~ 1 EPS1 <- 0.1 }) model({ LWT = log(WT / 70) V=exp(LTHETA2+LWT+ETA2) CL=exp(LTHETA3+LWT*0.75+ETA3) d/dt(centr) = -CL/V*centr CONC=centr/V CONC ~ add(EPS1) }) } pheno_nlmixr <- pheno %>% nlmixr(modelCode, data=., est="focei")
An nlmixr model already contains both the structural model and the description of inter-individual variability and residual variability.
m1 <- tdmore(pheno_nlmixr) summary(m1)
Alternatively, you can specify your model using RxODE
or algebraic
equations. In these cases, you have to specify the inter-individual variability and residual variability manually.
Now that we have a model, we can perform a posthoc fit on retrospective data. We will first convert the dataset to tdmore format.
regimen <- pheno %>% filter(EVID==1) %>% select(ID, TIME, AMT) observed <- pheno %>% filter(EVID==0 & MDV == 0) %>% transmute(ID, TIME, CONC=DV) covariates <- pheno %>% select(ID, TIME, WT) db <- dataTibble(object=m1, regimen, observed, covariates) #We will only perform the evaluation for the first 5 subjects, for performance reasons db <- db[1:5, ]
And then perform a posthoc fit.
posthocPrediction <- posthoc(db) print(posthocPrediction)
You can plot a single fit from this easily using autoplot
.
autoplot(posthocPrediction$fit[[1]], se.fit=FALSE)
Let us now explore how well we can predict future concentrations. Proseval creates a tibble with a fit per concentration.
prosevalResults <- proseval(db) head(prosevalResults)
We can use this data to plot how well we can predict the future for this population. The OBS
variable describes how many previous concentrations are included in the fit.
prosevalResults %>% unnest(cols=c(observed, ipred), names_sep=".") %>% group_by(ID, OBS) %>% mutate(INCLUDED = row_number() <= OBS) %>% ggplot(aes(x=ipred.TIME, y=ipred.CONC - observed.CONC)) + geom_smooth(se=F) + geom_hline(yintercept=0) + geom_hline(yintercept=c(-1.96, 1.96)*m1$res_var[[1]]$sigma(), linetype=2) + geom_point(aes(color=INCLUDED)) + scale_x_continuous(breaks=seq(0, 999, by=24)) + facet_wrap(~OBS, labeller=label_both)
The concentration data early post-treatment is insufficient to accurately predict the future time course.
We can now simulate precision dosing using this model.
## We need to add some metadata to the model to easily optimize m2 <- m1 %>% metadata(formulation("Default", "mg", dosing_interval=24)) %>% metadata(target(min=30, max=30)) ## The modified simulation database: ## keep ID, fit, and covariates ## the fit is used as the "truth" to predict new concentrations ## the new regimen assumes a default 24h-based administration simulationDb <- posthocPrediction %>% ungroup() %>% select(ID, fit, covariates) %>% mutate(regimen=list(data.frame(TIME=seq(0, 140, by=24), AMT=NA, FORM="Default")), object=list(m2)) ## Adjustment option 1: measure at each trough measureAtTrough <- function(fit, regimen, truth) { if(nrow(fit$observed) == 0) { rec <- findDoses(fit) list(nextTime=regimen$TIME[2]-0.01, regimen=rec$regimen) } else { now <- max(fit$observed$TIME) + 3 regimen$FIX <- !(regimen$TIME > now) rec <- findDoses(fit, regimen=regimen) nextTime <- regimen$TIME[match(FALSE, regimen$FIX)] #next trough list(nextTime=nextTime-0.01, regimen=rec$regimen) } } ## Adjustment option 2: measure 8h before trough, so you can still adapt measure8hBeforeTrough <- function(fit, regimen, truth) { if(nrow(fit$observed) == 0) { rec <- findDoses(fit) list(nextTime=regimen$TIME[2]-8, regimen=rec$regimen) } else { now <- max(fit$observed$TIME) + 3 regimen$FIX <- !(regimen$TIME > now) rec <- findDoses(fit, regimen=regimen) nextTime <- regimen$TIME[match(FALSE, regimen$FIX)+1] #next trough list(nextTime=nextTime-8, regimen=rec$regimen) } } ## Adjustment option 3: rich profile at the beginning richProfileAtBeginning <- function(fit, regimen, truth) { richProfile <- c(1, 3, 4, 6, 8, 10) if(nrow(fit$observed) == 0) { rec <- findDoses(fit) return( list(nextTime=richProfile[1], regimen=rec$regimen) ) } nextTime <- richProfile[nrow(fit$observed) + 1] regimen$FIX <- FALSE regimen$FIX[1] <- TRUE #loading dose cannot be changed if(nrow(fit$observed) == length(richProfile)) { rec <- findDoses(fit, regimen=regimen) regimen <- rec$regimen } list(nextTime=nextTime, regimen=regimen) } simulationResult <- doseSimulation(simulationDb, optimize=measureAtTrough) %>% mutate(method="A. At Trough") simulationResult2 <- doseSimulation(simulationDb, optimize=measure8hBeforeTrough) %>% mutate(method="B. 8h before trough") simulationResult3 <- doseSimulation(simulationDb, optimize=richProfileAtBeginning) %>% mutate(method="C. Rich profile") fullResult <- bind_rows(simulationResult, simulationResult2, simulationResult3) %>% group_by(ID, method) %>% filter(row_number() == n()) %>% #only keep the last regimen mutate(ipred = purrr::map2(fit, next_regimen, ~predict(.x, regimen=.y, newdata=0:144))) head(fullResult)
The doseSimulation
function has performed an iterative estimate - adapt routine. It uses nextTime
to know when to simulate the next concentration sample, re-estimate the individual fit, and allow dose adaptation.
The final result is a dataset detailing every dose adaptation step. The final regimen can be found at the last line for each subject. We use this regimen to predict concentration from 0 to 150 hours. This can be used to show in silico dosing performance.
ggplot(fullResult, aes(x=TIME, y=CONC)) + geom_line(data=. %>% unnest(cols=ipred), aes(group=interaction(method,ID), color=method)) + geom_point(data=. %>% unnest(cols=observed)) + geom_hline(yintercept=30, linetype=2) + geom_hline(yintercept=30+c(1.96, -1.96)*m1$res_var[[1]]$add, linetype=2, alpha=0.3) + theme(legend.position="none") + facet_wrap(~method)
The dots in the above figure show simulated concentration samples, used to refine the individual fit. The lines are the resulting concentrations from the "true" fit.
Conclusion? For our simple example, simulations indicate measuring 8h before trough (strategy B) is the superior approach, as it reaches the target sooner with similar accuracy to measuring at trough (strategy A). Note that the high additive residual error of r m1$res_var[[1]]$add
limits the information obtained from each concentration sample.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.