nlmixr2 and tidyvpc

By Matthew Fidler in nlmixr2

February 27, 2026

VPCs – a new method for VPCs in nlmixr2

I am excited to announce a new VPC method available in nlmixr2tidyvpc.

This method can be built on-top of previous code and provides slightly different visualizations than the vpc package.

Below is an example that shows the two different methods:

library(nlmixr2)
## ── Attaching packages ───────────────────────────────────────────────────────── nlmixr2 5.0.0 ──
## ✔ lotri         1.0.2      ✔ monolix2rx    0.0.6 
## ✔ nlmixr2data   2.0.9      ✔ nlmixr2lib    0.3.2 
## ✔ nlmixr2est    5.0.2      ✔ nlmixr2rpt    0.2.2 
## ✔ nlmixr2extra  5.0.0      ✔ nonmem2rx     0.1.9 
## ✔ nlmixr2plot   5.0.1      ✔ posologyr     1.2.8 
## ✔ rxode2        5.0.1      ✔ shinyMixR     0.5.3 
## ✔ babelmixr2    0.1.11     ✔ xpose.nlmixr2 0.4.1 
## ✔ ggPMX         1.3.2
## ── Optional Packages Loaded/Ignored ─────────────────────────────────────────── nlmixr2 5.0.0 ──
## ✔ babelmixr2        ✔ nonmem2rx    
## ✔ ggPMX             ✔ posologyr    
## ✔ monolix2rx        ✔ shinyMixR    
## ✔ nlmixr2lib        ✔ xpose.nlmixr2
## ✔ nlmixr2rpt
## ── Conflicts ───────────────────────────────────────────────────────────── nlmixr2conflicts() ──
## ✖ rxode2::boxCox()     masks nlmixr2est::boxCox()
## ✖ rxode2::yeoJohnson() masks nlmixr2est::yeoJohnson()
library(patchwork)


pheno <- function() {
  ini({
    tcl <- log(0.008) # typical value of clearance
    tv <-  log(0.6)   # typical value of volume
    ## var(eta.cl)
    eta.cl + eta.v ~ c(1,
                       0.01, 1) ## cov(eta.cl, eta.v), var(eta.v)
                      # interindividual variability on clearance and volume
    add.err <- 0.1    # residual variability
  })
  model({
    cl <- exp(tcl + eta.cl) # individual value of clearance
    v <- exp(tv + eta.v)    # individual value of volume
    ke <- cl / v            # elimination rate constant
    d/dt(A1) = - ke * A1    # model differential equation
    cp = A1 / v             # concentration in plasma
    cp ~ add(add.err)       # define error model
  })
}

fit <- nlmixr(pheno, pheno_sd, "saem",
              control=list(print=0),
              table=list(cwres=TRUE, npde=TRUE))
## ℹ parameter labels from comments are typically ignored in non-interactive mode
## ℹ Need to run with the source intact to parse comments
## → loading into symengine environment...
## → pruning branches (`if`/`else`) of saem model...
## ✔ done
## → finding duplicate expressions in saem model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## ✔ done
## ℹ calculate uninformed etas
## ℹ done
## Calculating covariance matrix
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → loading into symengine environment...
## → pruning branches (`if`/`else`) of saem model...
## ✔ done
## → finding duplicate expressions in saem predOnly model 0...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in saem predOnly model 1...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in saem predOnly model 2...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## ✔ done
## → Calculating residuals/tables
## → loading into symengine environment...
## → pruning branches (`if`/`else`) of full model...
## ✔ done
## → calculate jacobian
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate sensitivities
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate ∂(f)/∂(η)
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate ∂(R²)/∂(η)
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in inner model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in inner model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00 
## 
## [====|====|====|====|====|====|====|====|====|====] 0:00:00 
## 
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in EBE model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in EBE model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → compiling inner model...
## ✔ done
## → finding duplicate expressions in FD model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in FD model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → compiling EBE model...
## ✔ done
## → compiling events FD model...
## ✔ done
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## ✔ done
## → loading into symengine environment...
## → pruning branches (`if`/`else`) of full model...
## ✔ done
## → calculate jacobian
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate sensitivities
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate ∂(f)/∂(η)
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → calculate ∂(R²)/∂(η)
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in inner model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in inner model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00 
## 
## [====|====|====|====|====|====|====|====|====|====] 0:00:00 
## 
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → finding duplicate expressions in EBE model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in EBE model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → compiling inner model...
## ✔ done
## → finding duplicate expressions in FD model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → optimizing duplicate expressions in FD model...
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## → compiling EBE model...
## ✔ done
## → compiling events FD model...
## ✔ done
# Using the VPC package, a regular VPC
p1a <- vpcPlot(fit, show=list(obs_dv=TRUE))
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## Warning in filter_dv(obs, verbose): No software packages matched for filtering values, not filtering.
##  Object class: other, data.frame
##  Available filters: phoenix, nonmem
## Warning in filter_dv(sim, verbose): No software packages matched for filtering values, not filtering.
##  Object class: other, nlmixr2vpcSim, data.frame
##  Available filters: phoenix, nonmem
## Registered S3 method overwritten by 'classInt':
##   method               from 
##   print.classIntervals ggPMX
## A prediction-corrected VPC
p2a <- vpcPlot(fit, pred_corr = TRUE, show=list(obs_dv=TRUE))
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## Warning in filter_dv(obs, verbose): No software packages matched for filtering values, not filtering.
##  Object class: other, data.frame
##  Available filters: phoenix, nonmem
## Warning in filter_dv(obs, verbose): No software packages matched for filtering values, not filtering.
##  Object class: other, nlmixr2vpcSim, data.frame
##  Available filters: phoenix, nonmem
# using the tidyvpc package with method="tidyvpc"
# Using the VPC package, a regular VPC
p1b <- vpcPlot(fit, method="tidyvpc")
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
## A prediction-corrected VPC
p2b <- vpcPlot(fit, pred_corr = TRUE, method="tidyvpc")
## [====|====|====|====|====|====|====|====|====|====] 0:00:00
(p1a * p1b) / (p2a * p2b)

On the left is the vpc package and on the right is the tidyvpc package.

What inspired adding tidyvpc package now?

The addition of tidyvpc was driven by a CRAN request to fix nlmixr2plot. These errors started when the vpc package was updated. For people experiencing similar errors, using the new tidyvpc method may be helpful.

Still, I have submitted a vpc bug-fix which also fixes all the issues we had and will hopefully be integrated in the next CRAN release of vpc.

If you want to try out the bug-fix I submitted before it is released use:

remotes::install_github("mattfidler/vpc")
Posted on:
February 27, 2026
Length:
5 minute read, 985 words
Categories:
nlmixr2
See Also: