Simple Pharmacokinetics with Jags

[This article was first published on Wiekvoet, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In this post I want to analyze a first order pharmocokinetcs problem: the data of study problem 9, chapter 3 of Rowland and Tozer (Clinical pharmacokinetics and pharmacodynamics, 4th edition) with Jags. It is a surprising simple set of data, but still there is enough to play around with.

Data, model and analysis

The data is simple enough. One subject’s concentration of cocaine (μg/L) in plasma as function of time, after a starting dose of 33 mg cocaine hydrochlorine. The model is simple enough, C=C0*exp(-k*t). A number of additional parameters are to be calculated, half-live (t1/2), Clearance (CL) and distribution volume (V). Additional information needed; molecular weight of cocaine 303 g/mol, cocaine hydrochloride 330 g/mol.
ch3sp9 <- data.frame(
    time=c(0.16,0.5,1,1.5,2,2.5,3),
    conc=c(170,122,74,45,28,17,10))

Bayesian model

This model has as feature that the (non-informative) priors are directly on the parameters which are in PK analysis important; distribution volume and clearance.
library(R2jags)
datain <- as.list(ch3sp9)
datain$n <- nrow(ch3sp9)
datain$dose <- 33*1000*303/340 # in ug cocaine
model1 <- function() {
  for (i in 1:n) {
    pred[i] <- c0*(exp(-k*time[i]))
    conc[i] ~ dnorm(pred[i],tau)
  }
  tau <- 1/pow(sigma,2)
  sigma ~ dunif(0,100)
  c0 <- dose/V
  V ~ dlnorm(2,.01)
  k <- CL/V
  CL ~ dlnorm(1,.01)
  AUC <- dose/CL
  t0_5 <- log(2)/k
}

parameters <- c('k','AUC','CL','V','t0_5')
inits <- function() 
  list(V=rlnorm(1,1,.1),CL=rlnorm(1,1,.1),sigma=rlnorm(1,1,.1))
jagsfit <- jags(datain, model=model1, 
    inits=inits, parameters=parameters,progress.bar=”gui”,
    n.chains=4,n.iter=14000,n.burnin=5000,n.thin=2)
jagsfit
Inference for Bugs model at “…”, 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
AUC      201.756   0.766 200.258 201.369 201.749 202.143 203.233 1.001 10000
CL       145.767   0.553 144.705 145.485 145.769 146.045 146.855 1.001 10000
V        147.568   0.496 146.607 147.320 147.569 147.819 148.515 1.002 18000
k          0.988   0.005   0.978   0.985   0.988   0.991   0.998 1.001 13000
t0_5       0.702   0.004   0.694   0.700   0.702   0.704   0.709 1.001 13000
deviance   6.769   4.166   2.236   3.801   5.682   8.557  17.538 1.001  6300

For each parameter, n.eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor (at convergence, Rhat=1).

DIC info (using the rule, pD = var(deviance)/2)
pD = 8.7 and DIC = 15.4

DIC is an estimate of expected predictive error (lower deviance is better).
The expected answers according to the book are:
AUC=0.200 mg-hr/L, CL 147 L/hr, V=147 L, t1/2=0.7 hr. The numbers are slightly off compared to the book, but the expectation is that students use graph paper and a ruler.

Alternative Baysian model

This is more or less the same model, however log(conc) rather than conc is modelled.
datain2 <- as.list(ch3sp9)
datain2$conc <- log(datain2$conc)
datain2$n <- nrow(ch3sp9)
datain2$dose <- 33*1000*303/340 # in ug cocaine
model2 <- function() {
  for (i in 1:n) {
    pred[i] <- c0-k*time[i]
    conc[i] ~ dnorm(pred[i],tau)
  }
  tau <- 1/pow(sigma,2)
  sigma ~ dunif(0,100)
  c0 <- log(dose/V)
  V ~ dlnorm(2,.01)
  k <- CL/V
  CL ~ dlnorm(1,.01)
  AUC <- dose/CL
  t0_5 <- log(2)/k
}

parameters <- c('k','AUC','CL','V','t0_5')
inits <- function() 
  list(V=rlnorm(1,1,.1),CL=rlnorm(1,1,.1),sigma=rlnorm(1,1,.1))
jagsfit2 <- jags(datain2, model=model2, 
    inits=inits, parameters=parameters,progress.bar=”gui”,
    n.chains=4,n.iter=14000,n.burnin=5000,n.thin=2)
jagsfit2
Inference for Bugs model at “…93c1bb35a79.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
AUC      201.817   1.857 198.159 200.825 201.806 202.812 205.446 1.002  2700
CL       145.732   1.343 143.147 145.005 145.728 146.440 148.410 1.002  2700
V        146.904   2.248 142.588 145.702 146.882 148.049 151.428 1.002  2200
k          0.992   0.009   0.975   0.988   0.992   0.997   1.009 1.002  3200
t0_5       0.699   0.006   0.687   0.695   0.699   0.702   0.711 1.002  3200
deviance -37.541   3.922 -42.015 -40.424 -38.553 -35.772 -27.287 1.002  1900

For each parameter, n.eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor (at convergence, Rhat=1).

DIC info (using the rule, pD = var(deviance)/2)
pD = 7.7 and DIC = -29.9
DIC is an estimate of expected predictive error (lower deviance is better).
The results are fairly close to the first calculation, although the standard deviations are a bit larger. The question which is the better obviously depends on the expectations or knowledge on measurement error.  The book states the data is adapted from a paper (Chow et al., 1985, link to pdf), but that did not help me for this question.

Looking at residuals

I find classic regression to be a bit more convenient to help me with the structure of the error.
l1 <- lm(log(conc) ~ time,data=ch3sp9)
l1
Call:
lm(formula = log(conc) ~ time, data = ch3sp9)

Coefficients:
(Intercept)         time  
     5.2994      -0.9922  
n1 <- nls(conc~c0*(exp(-k*time)),
    data=ch3sp9,
    start=c(k=0.1,c0=120))
n1

Nonlinear regression model
  model: conc ~ c0 * (exp(-k * time))
   data: ch3sp9
       k       c0 
  0.9878 199.2918 
 residual sum-of-squares: 0.5369

Number of iterations to convergence: 5 
Achieved convergence tolerance: 1.809e-07
par(mfrow=c(2,2))
plot(l1)

par(mfrow=c(1,1))
plot(x=predict(n1),y=resid(n1),main=’nonlinear’)
To me the non-linear model seems to have somewhat better behaving residuals.

Using a package

The package PKfit does offer these calculations. It is also completely menu operated, hence no code in this blog part. It does offer the possibility to specify error as shown here.
<< Weighting Schemes >> 
1: equal weight
2: 1/Cp
3: 1/Cp^2
The results are in line with before. 
               << Subject 1 >>


<< PK parameters obtained from Nelder-Mead Simplex algorithm >>

  Parameter       Value
1       kel   0.9883067
2        Vd 147.4714404

<< Residual sum-of-square (RSS) and final PK parameters with nls >>

  0:    0.31284706: 0.988307  147.471
  1:    0.31284252: 0.988292  147.474
  2:    0.31284252: 0.988292  147.474

<< Output >>

  Time Observed Calculated Wt. Residuals    AUC    AUMC
1 0.16      170  170.19433    -0.1943293     NA      NA
2 0.50      122  121.53930     0.4607031  49.64  14.994
3 1.00       74   74.24673    -0.2467286  98.64  48.744
4 1.50       45   45.29682    -0.2968236 128.39  84.119
5 2.00       28   27.64910     0.3508985 146.64 114.994
6 2.50       17   16.87096     0.1290408 157.89 139.619
7 3.00       10   10.29481    -0.2948107 164.64 157.744

<< AUC (0 to infinity) computed by trapezoidal rule >>

[1] 174.7585

<< AUMC (0 to infinity) computed by trapezoidal rule >>

[1] 198.3377


<< Akaike's Information Criterion (AIC) >>
[1] 8.961411

<< Log likelihood >>
‘log Lik.’ -1.480706 (df=3)

<< Schwarz's Bayesian Criterion (SBC/BIC) >>
[1] 8.799142


Formula: conc ~ modfun1(time, kel, Vd)

Parameters:
     Estimate Std. Error t value Pr(>|t|)    
kel 9.883e-01  3.518e-03   280.9 1.09e-11 ***
Vd  1.475e+02  3.231e-01   456.4 9.58e-13 ***
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ‘ 1

Residual standard error: 0.3537 on 5 degrees of freedom

Algorithm “port”, convergence message: both X-convergence and relative convergence (5)

Conclusion: distribution of parameters

In hindsight the first Bayesian model is the one I prefer. Since I wanted to have some idea about the distribution of the parameters I have added this plot.
par(mfrow=c(3,2))
lapply(1:length(jagsfit$BUGSoutput$sims.list),
    function(x) plot(density(jagsfit$BUGSoutput$sims.list[[x]]),
main=names(jagsfit$BUGSoutput$sims.list)[x]))


To leave a comment for the author, please follow the link and comment on their blog: Wiekvoet.

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.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)