Stan Model of the Week: PK Calculation of IV and Oral Dosing
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
We’re going to be starting a Stan “model of the P” (for some time period P) column, so I thought I’d kick things off with one of my own. I’ve been following the Wingvoet blog, the author of which is identified only by the Blogger handle Wingfeet; a couple of days ago this lovely post came out:
Wingfeet’s post implemented an answer to question 6 from chapter 6 of problem from Rowland and Tozer’s 2010 book, Clinical Pharmacokinetics and Pharmacodynamics, Fourth edition, Lippincott, Williams & Wilkins.
So in the grand tradition of using this blog to procrastinate, I thought I’d take a break from C++ coding and paper writing and translate Wingfeet’s model to Stan. First, let me repeat Wingfeet’s model in JAGS, exactly as posted:
tau <- 1/pow(sigma,2)
sigma ~ dunif(0,100)
# IV part
kIV ~dnorm(.4,1)
cIV ~ dlnorm(1,.01)
for (i in 1:nIV) {
predIV[i] <- c0*exp(-k*timeIV[i]) +cIV*exp(-kIV*timeIV[i])
concIV[i] ~ dnorm(predIV[i],tau)
}
c0 <- doseIV/V
V ~ dlnorm(2,.01)
k <- CL/V
CL ~ dlnorm(1,.01)
AUCIV <- doseIV/CL+cIV/kIV
# oral part
for (i in 1:nO) {
predO[i] <- c0star*(exp(-k*timeO[i])-exp(-ka*timeO[i]))
concO[i] ~ dnorm(predO[i],tau)
}
c0star <- doseO*(ka/(ka-k))*F/V
AUCO <- c0star/k
F ~ dunif(0,1)
ka ~dnorm(.4,1)
ta0_5 <- log(2) /ka
t0_5 <- log(2)/k
I still find JAGS models pretty hard to follow on their own, but Wingfeet provided the data, and the call to R2Jags, so I could work backwards from there. Here's what I came up with as a translation to Stan.
data {
int nIV;
int nOral;
real doseIV;
real doseOral;
vector[nIV] timeIV;
vector[nIV] concIV;
vector[nOral] timeOral;
vector[nOral] concOral;
}
parameters {
real CL;
real V;
real F;
real ka;
real kIV;
real cIV;
real sigma;
}
transformed parameters {
real k;
real c0;
real AUCIV;
real c0star;
real AUCOral;
real ta0_5;
real t0_5;
vector[nIV] predIV;
vector[nOral] predOral;
k <- CL / V;
c0 <- doseIV / V;
AUCIV <- doseIV / CL + cIV / kIV;
c0star <- doseOral * (ka / (ka - k)) * F / V;
AUCOral <- c0star / k;
ta0_5 <- log(2) / ka;
t0_5 <- log(2) / k;
predIV <- c0 * exp(-k * timeIV) + cIV * exp(-kIV * timeIV);
predOral <- c0star * (exp(-k * timeOral) - exp(-ka * timeOral));
}
model {
// IV component
kIV ~ normal(0.4, 1);
cIV ~ normal(1,10);
V ~ normal(2,10);
CL ~ normal(1,10);
concIV ~ normal(predIV, sigma);
// oral component
ka ~ normal(0.4,1);
concOral ~ normal(predOral, sigma);
}
Do I ever appreciate the emacs mode for Stan! The Stan model is a direct translation of Wingfeet's JAGS model, with exactly the same priors (Stan uses a sd parameterization of the normal, whereas JAGS follows bugs in using inverse variance). I moved all the deterministic nodes in the JAGS model to the transformed parameters block in the Stan model so we could inspect them in the output. I also vectorized the prediction calculations, which seems much cleaner than using loops, but opinions may vary on this point.
It was critical to put the lower-bounds on all the concentration and volume parameters in Stan, as well as upper and lower bounds on the noise term (which had a broad uniform distribution in the JAGS model).
Here's the data in Stan's dump format, which I dropped in a file named data.R:
nIV <- 12 nOral <- 11 doseIV <- 500 doseOral <- 500 timeIV <- c(0.33, 0.5, 0.67, 1.5, 2, 4, 6, 10, 16, 24, 32, 48) concIV <- c(14.7, 12.6, 11, 9, 8.2, 7.9, 6.6, 6.2, 4.6, 3.2, 2.3, 1.2) timeOral <- c(0.5, 1, 1.5, 2, 4, 6, 10, 16, 24, 32, 48) concOral <- c(2.4, 3.8, 4.2, 4.6, 8.1, 5.8, 5.1, 4.1, 3, 2.3, 1.3)
Here's the code to run it in RStan, with number of warmup iterations and sampling iterations matching Wingfeet's call to the R2Jags package.
library('rstan');
source('data.R');
fit <- stan('pk_iv_oral.stan',
data=c("nIV","nOral","doseIV","doseOral",
"timeIV","concIV","timeOral","concOral"),
chains=4, warmup=5000, iter=14000);
It took about 10s to compile the model and 8s to run 56K iterations in the release version of RStan 2.2.0 (on my several years old Macbook Pro with 2.3GHz Intel Core i7). This uses the default diffuse initializations drawn uniformly on (-2,2) on the unconstrained scale corresponding to roughly (0.1, 10) on the positive-constrained scale
The model converged almost instantly and mixes very well, so this number of iterations is rather an overkill for Stan.
Inference for Stan model: pk_iv_oral.
4 chains, each with iter=14000; warmup=5000; thin=1;
post-warmup draws per chain=9000, total post-warmup draws=36000.
mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat
CL 2.41 0.00 0.19 2.07 2.28 2.40 2.53 2.81 16552 1
V 53.42 0.02 2.31 48.73 51.95 53.45 54.93 57.90 15682 1
F 0.86 0.00 0.05 0.75 0.82 0.86 0.89 0.96 14672 1
ka 0.62 0.00 0.10 0.46 0.56 0.62 0.68 0.84 15872 1
kIV 2.28 0.00 0.49 1.36 1.94 2.26 2.60 3.30 13662 1
cIV 10.99 0.02 2.53 6.47 9.27 10.84 12.53 16.48 12323 1
sigma 0.59 0.00 0.12 0.41 0.50 0.57 0.65 0.87 11027 1
k 0.05 0.00 0.00 0.04 0.04 0.04 0.05 0.05 15355 1
c0 9.38 0.00 0.41 8.64 9.10 9.35 9.62 10.26 15374 1
AUCIV 213.50 0.13 16.30 182.75 202.70 213.04 223.79 246.72 16616 1
c0star 8.69 0.01 0.68 7.49 8.23 8.64 9.08 10.18 14116 1
AUCOral 192.83 0.10 14.23 164.46 183.62 192.85 201.99 220.96 22358 1
ta0_5 1.14 0.00 0.17 0.83 1.02 1.12 1.24 1.51 15611 1
t0_5 15.46 0.01 1.49 12.63 14.48 15.43 16.41 18.50 15824 1
predIV[1] 14.33 0.00 0.52 13.25 14.00 14.34 14.68 15.32 18211 1
predIV[2] 12.63 0.00 0.33 11.98 12.41 12.63 12.84 13.28 36000 1
predIV[3] 11.46 0.00 0.34 10.82 11.23 11.45 11.68 12.17 21663 1
predIV[4] 9.17 0.00 0.33 8.60 8.94 9.14 9.36 9.90 13319 1
predIV[5] 8.72 0.00 0.31 8.17 8.51 8.69 8.90 9.40 14229 1
predIV[6] 7.83 0.00 0.27 7.31 7.64 7.82 8.00 8.40 16367 1
predIV[7] 7.15 0.00 0.24 6.69 6.98 7.14 7.30 7.65 17242 1
predIV[8] 5.96 0.00 0.23 5.52 5.82 5.96 6.11 6.41 17905 1
predIV[9] 4.55 0.00 0.25 4.04 4.39 4.55 4.71 5.03 17120 1
predIV[10] 3.17 0.00 0.27 2.63 3.00 3.18 3.35 3.69 16452 1
predIV[11] 2.22 0.00 0.26 1.70 2.05 2.22 2.39 2.72 16205 1
predIV[12] 1.09 0.00 0.20 0.71 0.95 1.08 1.21 1.49 16090 1
predOral[1] 2.11 0.00 0.21 1.73 1.97 2.10 2.24 2.55 21720 1
predOral[2] 3.60 0.00 0.29 3.04 3.41 3.59 3.78 4.20 24051 1
predOral[3] 4.65 0.00 0.31 4.04 4.45 4.64 4.84 5.27 28282 1
predOral[4] 5.37 0.00 0.30 4.77 5.18 5.37 5.56 5.96 32197 1
predOral[5] 6.46 0.00 0.28 5.91 6.28 6.46 6.64 7.01 26222 1
predOral[6] 6.37 0.00 0.31 5.74 6.17 6.37 6.57 6.98 19048 1
predOral[7] 5.49 0.00 0.31 4.89 5.29 5.49 5.69 6.10 18489 1
predOral[8] 4.21 0.00 0.26 3.68 4.04 4.21 4.37 4.70 21074 1
predOral[9] 2.93 0.00 0.23 2.45 2.79 2.94 3.09 3.38 21675 1
predOral[10] 2.05 0.00 0.22 1.60 1.91 2.05 2.19 2.47 20102 1
predOral[11] 1.00 0.00 0.17 0.68 0.89 1.00 1.11 1.34 18362 1
lp__ -9.22 0.03 2.31 -14.78 -10.46 -8.82 -7.52 -5.94 7951 1
Samples were drawn using NUTS(diag_e) at Mon Mar 10 14:49:24 2014.
Here's the fit that Wingfeet got from JAGS:
Inference for Bugs model at "C:/...BEnL/model1b6027171a7a.txt", fit using jags,
4 chains, each with 14000 iterations (first 5000 discarded), n.thin = 2
n.sims = 18000 iterations saved
mu.vect sd.vect 2.5% 25% 50% 75% 97.5% Rhat n.eff
AUCIV 219.959 16.975 189.743 208.636 218.846 230.210 256.836 1.002 2800
AUCO 200.468 15.102 171.908 190.523 199.987 209.928 231.722 1.001 7500
CL 2.346 0.182 1.995 2.226 2.344 2.461 2.712 1.002 2900
F 0.876 0.052 0.773 0.841 0.876 0.911 0.976 1.002 3200
V 56.463 2.752 51.610 54.583 56.280 58.112 62.465 1.002 3100
c0 8.876 0.426 8.005 8.604 8.884 9.160 9.688 1.002 3100
c0star 8.314 0.615 7.119 7.911 8.304 8.704 9.574 1.002 2000
cIV 11.229 2.485 7.029 9.511 11.003 12.654 16.804 1.003 1100
k 0.042 0.004 0.034 0.039 0.042 0.044 0.050 1.002 2200
kIV 2.064 0.510 1.132 1.704 2.041 2.396 3.126 1.004 800
ka 0.659 0.105 0.489 0.589 0.646 0.715 0.903 1.002 3200
predIV[1] 14.343 0.532 13.240 14.009 14.355 14.690 15.378 1.002 2500
predIV[2] 12.637 0.331 11.990 12.422 12.632 12.851 13.298 1.001 12000
predIV[3] 11.435 0.357 10.755 11.196 11.427 11.667 12.147 1.002 1600
predIV[4] 8.923 0.326 8.333 8.709 8.902 9.116 9.638 1.002 2900
predIV[5] 8.410 0.298 7.845 8.213 8.401 8.594 9.021 1.001 14000
predIV[6] 7.522 0.286 6.943 7.340 7.525 7.713 8.064 1.001 5900
predIV[7] 6.910 0.255 6.385 6.749 6.915 7.077 7.399 1.001 6200
predIV[8] 5.848 0.222 5.406 5.704 5.849 5.993 6.285 1.001 7800
predIV[9] 4.557 0.231 4.098 4.409 4.555 4.707 5.012 1.001 4500
predIV[10] 3.270 0.252 2.781 3.107 3.267 3.433 3.773 1.002 3100
predIV[11] 2.349 0.251 1.867 2.183 2.343 2.509 2.865 1.002 2700
predIV[12] 1.217 0.207 0.839 1.076 1.204 1.343 1.662 1.002 2500
predO[1] 2.138 0.214 1.750 1.995 2.124 2.263 2.599 1.001 8700
predO[2] 3.626 0.293 3.070 3.432 3.616 3.807 4.234 1.001 12000
predO[3] 4.653 0.306 4.050 4.452 4.652 4.847 5.264 1.001 16000
predO[4] 5.351 0.294 4.754 5.161 5.354 5.541 5.930 1.001 15000
predO[5] 6.375 0.277 5.801 6.202 6.383 6.562 6.894 1.002 3200
predO[6] 6.274 0.308 5.629 6.079 6.286 6.485 6.842 1.002 2800
predO[7] 5.455 0.292 4.852 5.270 5.461 5.648 6.018 1.002 3900
predO[8] 4.262 0.245 3.764 4.106 4.265 4.423 4.736 1.001 8600
predO[9] 3.057 0.227 2.605 2.910 3.058 3.207 3.504 1.001 8200
predO[10] 2.195 0.218 1.773 2.050 2.192 2.335 2.638 1.001 5200
predO[11] 1.135 0.180 0.805 1.013 1.127 1.247 1.519 1.002 3500
t0_5 16.798 1.713 13.830 15.655 16.652 17.770 20.617 1.002 2200
ta0_5 1.077 0.164 0.768 0.969 1.072 1.177 1.419 1.002 3200
deviance 38.082 5.042 30.832 34.357 37.207 40.918 50.060 1.003 1200
It's evident Stan did a way better job of mixing given the effective sample sizes. Wingfeet reports that the "correct" answers (presumably given in the book) were the following, here reported with the 95% posterior intervals produced by Stan and JAGs (we've been meaning to reduce RStan's posterior intervals to 90% to match CmdStan's, but it hasn't happened yet):
Parameter Correct Stan 95% JAGS 95% --------- ----------- ------------ ------------ t0_5 16.7 hr (12.6, 18.5) (13.8, 20.6) AUCiv 217 mg-hr/L ( 183, 247) ( 190, 256) AUCoral 191 mg-hr/L ( 164, 221) ( 172, 232) CL 2.3 L/hr ( 2.1, 2.8) ( 2.0, 2.7) V 55 L (48.8, 57.9) (51.6, 62.5) F 0.88 (0.75, 0.96) (0.77, 0.98)
I'm not sure what's up with the rather different posterior intervals from JAGS and Stan, as both samplers had enough effective samples to estimate these reasonably well (for instance, JAGS had 2200 effective samples and Stan 15,800 for t0_5). The width of the intervals is similar, so I don't think it was an issue of one system or the other not getting into the tails of the distributions. Of course, these are fairly wide posteriors (as noted by Wingfeet) and the "correct" answers are within the 95% posterior intervals for both systems.
The only "suspicious" parameter is F, which was pinned very near its upper bound of 1.0 in Stan for many samples and has a very wide and skewed posterior; here's a histogram:
The post Stan Model of the Week: PK Calculation of IV and Oral Dosing appeared first on Statistical Modeling, Causal Inference, and Social Science.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
