library(ggplot2)
library(mrgsolve)
library(minqa)
library(dplyr)
library(magrittr)
options(mrgsolve.soloc="build")
This tutorial illustrates how to do MAP Bayes estimation with mrgsolve
.
The setup was adapted from an existing project, where only a single individual was considered. With some additional R
coding, it could be expanded to consider multiple individuals in a single run.
1 About
This document shows how to simulate some data and then re-estimate the MAP Bayes estimates. For clarity, just the optimization piece has been included in a separate doc map_bayes_example.html.
2 One compartment model, keep it simple for now
The model specification code below is for a one-compartment model, where
mrgsolve
will calculate the amount inCENT
from closed-form equationsFor now,
$OMEGA
and$SIGMA
are filled with zeros; we’ll update it laterThe control stream is set up so that we can either simulate the etas or pass them in.
ETA(1)
andETA(2)
are the etas thatmrgsolve
will draw from$OMEGA
.ETA1
andETA2
are fixed and known at the time of time of the simulation. The optimizer will search for values ofETA1
andETA2
that optimize the objective function. Note thatETA1
andETA2
must be in the parameter list for this to workWe do a trick where
CL=TVCL*exp(ETA1+ETA(1));
The assumption is that eitherETA1
(simulating) is zero orETA(1)
is zero (estimating)We table out
ETA(1)
andETA(2)
so we can know the true (simulated) values (but not both zero and not both non-zero)DV
is output as a function ofEPS(1)
; this will be zero until we add in values for$SIGMA
. But when we’re estimating, we need to make sure thatEPS(1)
is zero; the prediction shouldn’t have any randomness in it (just the individual prediction based on known etas)
<- '
code $SET request=""
$PARAM TVCL=1.5, TVVC=23.4, ETA1=0, ETA2=0
$CMT CENT
$PKMODEL ncmt=1
$OMEGA 0 0
$SIGMA 0
$MAIN
double CL = TVCL*exp(ETA1 + ETA(1));
double V = TVVC*exp(ETA2 + ETA(2));
$TABLE
capture DV = (CENT/V)*(1+EPS(1));
capture ET1 = ETA(1);
capture ET2 = ETA(2);
'
<- mcode_cache("map", code) mod
3 First, simulate some data
$OMEGA
and $SIGMA
;
- The result may look better or worse depending on what we choose here
- These will be used to both simulate and fit the data
- The
cmat
call makes a2x2
matrix where the off-diagonal is a correlation (?cmat
).
<- cmat(0.23,-0.78, 0.62)
omega <- solve(omega)
omega.inv <- matrix(0.0032) sigma
Just a single dose to CENT
with an events object
<- ev(amt=750,cmt=1) dose
Take these times for concentration observations
<- c(0.5,12,24) sampl
Simulate
- Here, we’re populating
$OMEGA
and$SIGMA
so that the simulated data will be random - It is important to
carry.out
all of the items that we will need in the estimation data set (doses, evid, etc) - Using
end=-1
withadd=sampl
makes sure that we only get observation records at the times listed insampl
set.seed(1012)
<-
sim %>%
mod ev(dose) %>%
omat(omega) %>%
smat(sigma) %>%
carry_out(amt,evid,cmt) %>%
mrgsim(end=-1, add=sampl)
sim
Model: map
Dim: 4 x 8
Time: 0 to 24
ID: 1
ID time evid amt cmt DV ET1 ET2
1: 1 0.0 1 750 1 41.067 0.5196 -0.2728
2: 1 0.5 0 0 0 42.749 0.5196 -0.2728
3: 1 12.0 0 0 0 6.932 0.5196 -0.2728
4: 1 24.0 0 0 0 1.375 0.5196 -0.2728
4 Create input for optimization
- Using the simulated data as the starting point here
- Set
DV
toNA
for the dosing record
<- mutate(sim, DV = ifelse(evid==1, NA_real_, DV)) sim
Create a data set to use in the optimization
- We need to drop
ET1
andET2
since they are in the parameter list
<- sim %>% select(-ET1, -ET2)
data
data
# A tibble: 4 × 6
ID time evid amt cmt DV
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 0 1 750 1 NA
2 1 0.5 0 0 0 42.7
3 1 12 0 0 0 6.93
4 1 24 0 0 0 1.37
5 Optimize
This function takes in a set of proposed \(\eta\)s along with the observed data vector, the data set and a model object and returns the value of the EBE objective function
When we do the estimation, the fixed effects and random effect variances are fixed.
The estimates are the \(\eta\) for clearance and volume
Arguments:
eta
the current values from the optimizerycol
the observed data column named
the data setm
the model objectdvcol
the predicted data column namepred
ifTRUE
, just return predicted values
5.1 What is this function doing?
- get the matrix for residual error
- Make sure
eta
is a list - Make sure
eta
is properly named (i.e.ETA1
andETA2
) - Copy
eta
into a matrix that is one row - Update the model object (
m
) with the current values ofETA1
andETA2
- Simulate from data set
d
and save output toout
object - If we are just requesting predictions (
if(pred)
) return the simulated data - The final lines calculate the EBE objective function; see this paper for reference
- Notice that the function returns a single value (a number); the optimizer will minimize this value
<- function(eta,d,ycol,m,dvcol,pred=FALSE) {
mapbayes
<- as.numeric(sigma)
sig2 <- as.list(eta)
eta names(eta) <- names(init)
<- eta %>% unlist %>% matrix(nrow=1)
eta_m <- param(m,eta)
m <- m %>% zero_re() %>% mrgsim(data=d,output="df")
out if(pred) return(out)
# http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3339294/
<- out[[dvcol]]^2*sig2
sig2j <- log(sig2j) + (1/sig2j)*(d[[ycol]] - out[[dvcol]])^2
sqwres <- diag(eta_m %*% omega.inv %*% t(eta_m))
nOn return(sum(sqwres,na.rm=TRUE) + nOn)
}
5.2 Initial estimate
- Note again that we are optimizing the etas here
<- c(ETA1=-0.3, ETA2=0.2) init
Fit the data
newuoa
is from theminqa
package- Other optimizers (via
optim
) could probably also be used
Arguments to newuoa
- First: the initial estimates
- Second: the function to optimize
- The other argument are passed to
mapbayes
<- nloptr::newuoa(init,mapbayes,ycol="DV",m=mod,d=data,dvcol="DV") fit
Here are the final estimates
$par fit
[1] 0.4995400 -0.3274858
Here are the simulated values
slice(sim,1) %>% select(ET1, ET2)
# A tibble: 1 × 2
ET1 ET2
<dbl> <dbl>
1 0.520 -0.273
6 Look at the result
A data set and model to get predictions; this will give us a smooth prediction line
<- data %>% filter(evid==1)
pdata <- mod %>% update(end=24, delta=0.1) pmod
Predicted line based on final estimates
<- mapbayes(fit$par,ycol="DV",pdata,pmod,dvcol="DV",pred=TRUE) %>% filter(time > 0)
pred head(pred)
ID time DV ET1 ET2
1 1 0.1 43.82331 0 0
2 1 0.2 43.18567 0 0
3 1 0.3 42.55731 0 0
4 1 0.4 41.93809 0 0
5 1 0.5 41.32789 0 0
6 1 0.6 40.72656 0 0
Predicted line based on initial estimates
<- mapbayes(init,ycol="DV",pdata,pmod,dvcol="DV",pred=TRUE) %>% filter(time > 0)
initial head(initial)
ID time DV ET1 ET2
1 1 0.1 26.13954 0 0
2 1 0.2 26.03811 0 0
3 1 0.3 25.93707 0 0
4 1 0.4 25.83642 0 0
5 1 0.5 25.73616 0 0
6 1 0.6 25.63629 0 0
Plot
ggplot() +
geom_line(data=pred, aes(time,DV),col="firebrick", lwd=1) +
geom_line(data=initial,aes(time,DV), lty=2, col="darkgreen", lwd=1) +
geom_point(data=data %>% filter(evid==0), aes(time,DV), col="darkslateblue",size=3) +
theme_bw()