Skip to contents

nlmixr2 fits are fully supported in the current release, including many convenience functions to ensure consumed nlmixr2 results are fully compatible with most all available features of xpose and xpose.xtras.

Basics

While the xpose.xtras package already contains a few nlmixr2 examples, let’s start fresh with a new fit. The model below is from the nlmixr2 documentation; to start using the resulting xpose_data directly, refer to ?nlmixr2_warfarin.

pk.turnover.emax3 <- function() {
  ini({
    tktr <- log(1)
    tka <- log(1)
    tcl <- log(0.1)
    tv <- log(10)
    ##
    eta.ktr ~ 1
    eta.ka ~ 1
    eta.cl ~ 2
    eta.v ~ 1
    prop.err <- 0.1
    pkadd.err <- 0.1
    ##
    temax <- logit(0.8)
    tec50 <- log(0.5)
    tkout <- log(0.05)
    te0 <- log(100)
    ##
    eta.emax ~ .5
    eta.ec50  ~ .5
    eta.kout ~ .5
    eta.e0 ~ .5
    ##
    pdadd.err <- 10
  })
  model({
    ktr <- exp(tktr + eta.ktr)
    ka <- exp(tka + eta.ka)
    cl <- exp(tcl + eta.cl)
    v <- exp(tv + eta.v)
    emax = expit(temax+eta.emax)
    ec50 =  exp(tec50 + eta.ec50)
    kout = exp(tkout + eta.kout)
    e0 = exp(te0 + eta.e0)
    ##
    DCP = center/v
    PD=1-emax*DCP/(ec50+DCP)
    ##
    effect(0) = e0
    kin = e0*kout
    ##
    d/dt(depot) = -ktr * depot
    d/dt(gut) =  ktr * depot -ka * gut
    d/dt(center) =  ka * gut - cl / v * center
    d/dt(effect) = kin*PD -kout*effect
    ##
    cp = center / v
    cp ~ prop(prop.err) + add(pkadd.err)
    effect ~ add(pdadd.err) | pca
  })
}
fit.TOS <- nlmixr2(pk.turnover.emax3, warfarin, "focei", control=list(print=0),
                  table=list(cwres=TRUE, npde=TRUE))
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> calculating covariance matrix
#> [====|====|====|====|====|====|====|====|====|====] 0:01:12 
#> done

Now that we have a fit, we have a few options to consume the results into xpose. We could use xpose.nlmixr2::xpose_data_nlmixr2 and then convert to an xp_xtras object from there.

xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
  as_xp_xtras()
#> 
#> ── ~ xp_xtras object 
#> Model description: not implemented
#> fit.TOS overview: 
#>  - Software: nlmixr2 4.1.0 
#>  - Attached files (memory usage 553.8 Kb): 
#>    + obs tabs: $prob no.1: nlmixr2 
#>    + sim tabs: <none> 
#>    + output files: <none> 
#>    + special: <none> 
#>    + fit: <none>
#>  - gg_theme: :: xpose theme_readable 
#>  - xp_theme: xp_xtra_theme new_x$xp_theme 
#>  - Options: dir = NULL, quiet = TRUE, manual_import = NULL, cvtype = exact

It is helpful though, for traceability and for some convenience functions for the fit object to be attached to the xpose_data object.

xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
  as_xp_xtras() %>%
  attach_nlmixr2(fit.TOS)
#> 
#> ── ~ xp_xtras object 
#> Model description: not implemented
#> fit.TOS overview: 
#>  - Software: nlmixr2 4.1.0 
#>  - Attached files (memory usage 725.7 Kb): 
#>    + obs tabs: $prob no.1: nlmixr2 
#>    + sim tabs: <none> 
#>    + output files: <none> 
#>    + special: <none> 
#>    + fit: attached as (this)$fit
#>  - gg_theme: :: xpose theme_readable 
#>  - xp_theme: xp_xtra_theme new_x$xp_theme 
#>  - Options: dir = NULL, quiet = TRUE, manual_import = NULL, cvtype = exact

One such convenience function is to backfill some of the "na" properties pulled from the fit results (at time of writing, these are condition number and significant digits). It’s acknowledged that these backfills are opinionated, hence it is optional.

xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
  as_xp_xtras() %>%
  attach_nlmixr2(fit.TOS) %>%
  backfill_nlmixr2_props() %>%
  {print(get_prop(., "condn")); .} %>%
  get_prop("nsig")
#> [1] "32318.0467755017"
#> [1] "3"

A more useful application of the attached fit result is a method to make get_prm() work. Note, xpose::prm_table() does not work despite this new feature because it uses the original get_prm() defined within xpose namespace. Since prm_table() is a quick check convenience function to print in place, get_prm() was considered a better function to get working.

try({
xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
    as_xp_xtras() %>% xpose::get_prm()
})
#> Error : No `files` slot could be found in this xpdb.
xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
  as_xp_xtras() %>%
  attach_nlmixr2(fit.TOS) %>%
  get_prm() %>%
  # Remove some columns for readability
  dplyr::select(-c(fixed,diagonal,label))
#> # A tibble: 20 × 9
#>    type  name         value      se      rse     m     n      cv     shk
#>    <chr> <chr>      <num:3> <num:3>  <num:3> <int> <int> <num:3> <num:3>
#>  1 the   tktr         0.104  2.20   21.1         1    NA    NA      NA  
#>  2 the   tka          0.302  2.18    7.23        2    NA    NA      NA  
#>  3 the   tcl         -2.04   0.109   0.0536      3    NA    NA      NA  
#>  4 the   tv           2.06   0.0916  0.0444      4    NA    NA      NA  
#>  5 the   prop.err     0.148 NA      NA           5    NA    NA      NA  
#>  6 the   pkadd.err    0.172 NA      NA           6    NA    NA      NA  
#>  7 the   temax        4.75   6.20    1.30        7    NA    NA      NA  
#>  8 the   tec50        0.157  0.229   1.46        8    NA    NA      NA  
#>  9 the   tkout       -2.93   0.128   0.0436      9    NA    NA      NA  
#> 10 the   te0          4.57   0.0399  0.00874    10    NA    NA      NA  
#> 11 the   pdadd.err    3.76  NA      NA          11    NA    NA      NA  
#> 12 ome   eta.ktr      0.840 NA      NA           1     1   101.     62.3
#> 13 ome   eta.ka       0.944 NA      NA           2     2   120.     60.6
#> 14 ome   eta.cl       0.268 NA      NA           3     3    27.3    -0.1
#> 15 ome   eta.v        0.221 NA      NA           4     4    22.4    10.3
#> 16 ome   eta.emax     0.590 NA      NA           5     5    64.5    95.1
#> 17 ome   eta.ec50     0.453 NA      NA           6     6    47.7     5.2
#> 18 ome   eta.kout     0.153 NA      NA           7     7    15.4    32.3
#> 19 ome   eta.e0       0.103 NA      NA           8     8    10.3    39.6
#> 20 sig   sigma(1,1)   1     NA      NA           1     1    NA      10.7

To build on this, because nlmixr2 coerces users to use mu-referencing and captures parameter associations automatically, another useful backfill includes mapping these parameter associations with add_prm_association(), by way of nlmixr2_prm_associations(). In this model, temax is logit transformed, so to keep assumptions valid in the CV% calculation it needs to be back-transformed (as indicated in a cli message).

xpose.nlmixr2::xpose_data_nlmixr2(fit.TOS) %>%
  as_xp_xtras() %>%
  attach_nlmixr2(fit.TOS) %>%
  nlmixr2_prm_associations() %>%
  mutate_prm(temax ~ plogis) %>%
  get_prm() %>%
  # Remove some columns for readability
  dplyr::select(-c(fixed,diagonal,label))
#> # A tibble: 20 × 9
#>    type  name         value      se      rse     m     n      cv     shk
#>    <chr> <chr>      <num:3> <num:3>  <num:3> <int> <int> <num:3> <num:3>
#>  1 the   tktr         0.104  2.20   21.1         1    NA  NA        NA  
#>  2 the   tka          0.302  2.18    7.23        2    NA  NA        NA  
#>  3 the   tcl         -2.04   0.109   0.0536      3    NA  NA        NA  
#>  4 the   tv           2.06   0.0916  0.0444      4    NA  NA        NA  
#>  5 the   prop.err     0.148 NA      NA           5    NA  NA        NA  
#>  6 the   pkadd.err    0.172 NA      NA           6    NA  NA        NA  
#>  7 the   temax        0.991  0.362   0.365       7    NA  NA        NA  
#>  8 the   tec50        0.157  0.229   1.46        8    NA  NA        NA  
#>  9 the   tkout       -2.93   0.128   0.0436      9    NA  NA        NA  
#> 10 the   te0          4.57   0.0399  0.00874    10    NA  NA        NA  
#> 11 the   pdadd.err    3.76  NA      NA          11    NA  NA        NA  
#> 12 ome   eta.ktr      0.840 NA      NA           1     1 101.       62.3
#> 13 ome   eta.ka       0.944 NA      NA           2     2 120.       60.6
#> 14 ome   eta.cl       0.268 NA      NA           3     3  27.3      -0.1
#> 15 ome   eta.v        0.221 NA      NA           4     4  22.4      10.3
#> 16 ome   eta.emax     0.590 NA      NA           5     5   0.647    95.1
#> 17 ome   eta.ec50     0.453 NA      NA           6     6  47.7       5.2
#> 18 ome   eta.kout     0.153 NA      NA           7     7  15.4      32.3
#> 19 ome   eta.e0       0.103 NA      NA           8     8  10.3      39.6
#> 20 sig   sigma(1,1)   1     NA      NA           1     1  NA        10.7
#> # Parameter table includes the following associations: tktr~log(eta.ktr),
#> tka~log(eta.ka), tcl~log(eta.cl), tv~log(eta.v), temax~logit(eta.emax),
#> tec50~log(eta.ec50), tkout~log(eta.kout), and te0~log(eta.e0)

That’s a lot of boilerplate to pipe through, so a convenience function has been developed to cover all of that. Note the parameter associations are mapped automatically, but we could skip that step if we weren’t planning to use get_prm() at all by setting .skip_assoc=TRUE.

nlmixr2_warfarin <- nlmixr2_as_xtra(fit.TOS)

General Usage

We covered most of the usage unique to this package in the Basics section, but this section is to illustrate that common functions will continue to work. At time of writing, some missing properties are present and should be added with a PR to xpose.nlmixr2 (these are not backfill candidates since they don’t exist in the summary element, violating how set_prop() was designed to work).

list_vars(nlmixr2_warfarin)
#> List of available variables for problem no. 1
#>  - Subject identifier (id)               : ID
#>  - Dependent variable (dv)               : DV
#>  - Independent variable (idv)            : TIME
#>  - DV identifier (dvid)                  : DVID [0]
#>  - Dose amount (amt)                     : AMT
#>  - Event identifier (evid)               : EVID
#>  - Model typical predictions (pred)      : CPRED
#>  - Model individual predictions (ipred)  : IPRED
#>  - Model parameter (param)               : KA, CL, V
#>  - Eta (eta)                             : eta.ktr, eta.ka, eta.cl, eta.v, eta.emax, eta.ec50, eta.kout, eta.e0
#>  - Residuals (res)                       : NPDE, RES, WRES, IRES, IWRES, CRES, CWRES
#>  - Categorical covariates (catcov)       : SEX [0]
#>  - Continuous covariates (contcov)       : WT, AGE
#>  - Not attributed (na)                   : NLMIXRLLIKOBS, CMT, EPRED, ERES, NPD, PDE, PD, PRED, ETA.KTR, ETA.KA, ETA.CL, ETA.V, ETA.EMAX, ETA.EC50, ETA.KOUT, ETA.E0, DEPOT, GUT, CENTER, EFFECT, KTR, EMAX, EC50, KOUT, E0, DCP, PD.1, KIN, TAD, DOSENUM

dv_vs_ipred(nlmixr2_warfarin, facet="DVID")
#> Using data from $prob no.1
#> Filtering data by EVID == 0
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'


dv_vs_pred(nlmixr2_warfarin, facet="DVID")
#> Using data from $prob no.1
#> Filtering data by EVID == 0
#> `geom_smooth()` using formula = 'y ~ x'`geom_smooth()` using formula = 'y ~ x'


eta_vs_catcov(nlmixr2_warfarin, etavar = eta.cl)
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Tidying data by ID, TIME, AMT, DV, DVID ... and 53 more variables
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.

eta_vs_contcov(nlmixr2_warfarin, etavar = eta.cl)
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Tidying data by ID, TIME, AMT, DV, DVID ... and 52 more variables
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'


eta_vs_cov_grid(nlmixr2_warfarin, etavar = c(eta.cl,eta.v,eta.ka), quiet=TRUE)
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.


eta_vs_cov_grid(nlmixr2_warfarin, etavar = c(eta.kout,eta.e0,eta.emax), quiet=TRUE)
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.

Sets

A simple change to the warfarin PK model is to use the same parameters for ka and ktr. In the PD model, we could also fix emax to 1 (and drop the interindividual variability). An analyst would explore these separately and then maybe combine them, as we have done below.

fit.TOS.kaktr <- fit.TOS %>%
  model({
    ka <- ktr
  }) %>%
  nlmixr2(warfarin, "focei",
    control = list(print = 0),
    table = list(cwres = TRUE, npde = TRUE)
  )
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> calculating covariance matrix
#> [====|====|====|====|====|====|====|====|====|====] 0:00:43 
#> done

fit.TOS.emax1 <- fit.TOS %>%
  model({
    emax = 1
  }) %>%
  nlmixr2(warfarin, "focei",
    control = list(print = 0),
    table = list(cwres = TRUE, npde = TRUE)
  )
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> calculating covariance matrix
#> [====|====|====|====|====|====|====|====|====|====] 0:00:46 
#> done

fit.TOS.simple <- fit.TOS %>%
  model({
    ka <- ktr
    emax = 1
  }) %>%
  nlmixr2(warfarin, "focei",
    control = list(print = 0),
    table = list(cwres = TRUE, npde = TRUE)
  )
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00 
#> 
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> [====|====|====|====|====|====|====|====|====|====] 0:00:00
#> calculating covariance matrix
#> [====|====|====|====|====|====|====|====|====|====] 0:00:28 
#> done

These explorations make up a set which can be examined with an xpose_set:

# Since these models were created with piping, need to update their labels
warf_ka <- nlmixr2_as_xtra(fit.TOS.kaktr) %>%
  set_prop(run="fit.TOS.kaktr") %>%
  # Just to remove github action path
  set_option(
    dir = paste0("~")
  ) %>%
  set_prop(
    dir = paste0("~")
  )
warf_emax <- nlmixr2_as_xtra(fit.TOS.emax1) %>%
  set_prop(run="fit.TOS.emax1") %>%
  set_option(
    dir = paste0("~")
  ) %>%
  set_prop(
    dir = paste0("~")
  )
warf_simple <- nlmixr2_as_xtra(fit.TOS.simple) %>%
  set_prop(run="fit.TOS.simple") %>%
  set_option(
    dir = paste0("~")
  ) %>%
  set_prop(
    dir = paste0("~")
  )

warfarin_set <- xpose_set(
  nlmixr2_warfarin, warf_ka, warf_emax, warf_simple,
  .relationships = c(
    warf_ka~nlmixr2_warfarin,
    warf_emax~nlmixr2_warfarin,
    warf_simple ~ warf_ka + warf_emax
  )
) %>%
  # Add iOFVs
  focus_qapply(backfill_iofv)

warfarin_set
#> 
#> ── xpose_set object ────────────────────────────────────────────────────────────
#> • Number of models: 4
#> • Model labels: nlmixr2_warfarin, warf_ka, warf_emax, and warf_simple
#> • Number of relationships: 4
#> • Focused xpdb objects: none
#> • Exposed properties: none
#> • Base model: none

warfarin_set$warf_simple
#> 
#> ── Part of an xpose_set, with label: warf_simple ───────────────────────────────
#> • Parent(s): warf_ka and warf_emax
#> • In focus?: no
#> • Base model?: no
#> 
#> ── xpdb object (accessible with {xpose_set}$warf_simple$xpdb): 
#> 
#> ── ~ xp_xtras object 
#> Model description: not implemented
#> fit.TOS.simple overview: 
#>  - Software: nlmixr2 4.1.0 
#>  - Attached files (memory usage 693.4 Kb): 
#>    + obs tabs: $prob no.1 (modified): na, nlmixr2 
#>    + sim tabs: <none> 
#>    + output files: <none> 
#>    + special: <none> 
#>    + fit: attached as (this)$fit
#>  - gg_theme: :: xpose theme_readable 
#>  - xp_theme: xp_xtra_theme new_x$xp_theme 
#>  - Options: dir = ~, quiet = TRUE, manual_import = NULL, cvtype = exact

warfarin_set %>%
  expose_property(ofv) %>%
  expose_property(condn) %>%
  reshape_set() %>%
  # Remove some columns for readability
  dplyr::select(-c(parent,base,focus))
#> # A tibble: 4 × 4
#>   xpdb         label            ..ofv  ..condn
#>   <named list> <chr>            <dbl>    <dbl>
#> 1 <xp_xtras>   nlmixr2_warfarin 1400. 3395179.
#> 2 <xp_xtras>   warf_ka          1338.   21266.
#> 3 <xp_xtras>   warf_emax        1351.     464.
#> 4 <xp_xtras>   warf_simple      1338.     269.

warfarin_set %>%
  dofv_vs_id(nlmixr2_warfarin, warf_simple, .inorder = TRUE, df=1)
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.


warfarin_set %>%
  # This comparison should be more interesting
  focus_qapply(set_var_types, param=c(KA,EC50), na=c(CL, V)) %>%
  prm_waterfall(nlmixr2_warfarin, warf_simple)
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Tidying data by ID, TIME, AMT, DV, DVID ... and 55 more variables
#> Warning: nind is not part of the available keywords. Check ?template_titles for
#> a full list.


warfarin_set %>%
  dv_vs_ipred_modavg(warf_emax, warf_ka)
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'

Derived parameter explorations

The suggestion to have nlmixr2 installed enables rxode2 utility functions to be leveraged for diagnostics (and probably many other unrealized benefits, possibly using nonmem2rx). One of those features is exploring the derived parameters for a model.

Now any model (it does not have to be fit with nlmixr2) can have derived parameters calculated and checked for signs of misspecification. The derive_prm() family of functions can be used to generate a table of derived parameters or to set derived parameters as param type variables.

nlmixr2_m3 %>%
  backfill_derived() %>%
  list_vars()
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> List of available variables for problem no. 1
#>  - Subject identifier (id)               : ID
#>  - Dependent variable (dv)               : DV
#>  - Independent variable (idv)            : TIME
#>  - Dose amount (amt)                     : AMT
#>  - Event identifier (evid)               : EVID
#>  - Model typical predictions (pred)      : CPRED
#>  - Model individual predictions (ipred)  : IPRED
#>  - Model parameter (param)               : KA, CL, V, VC, KEL, VSS, T12ALPHA, ALPHA, A, FRACA
#>  - Eta (eta)                             : eta.ka, eta.cl, eta.v
#>  - Residuals (res)                       : RES, WRES, IRES, IWRES, CRES, CWRES
#>  - Continuous covariates (contcov)       : WT
#>  - Not attributed (na)                   : CMT, CENS, LLOQ, NLMIXRLLIKOBS, PRED, LOWERLIM, UPPERLIM, ETA.KA, ETA.CL, ETA.V, DEPOT, CENT, BLQLIKE, TAD, DOSENUM

derive_prm(nlmixr2_m3) %>%
  dplyr::select(ID,KA,CL,VSS:(dplyr::last_col())) %>%
  head()
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#>   ID        KA       CL      VSS  T12ALPHA      ALPHA          A FRACA
#> 1  1 1.7251120 1.729018 28.98376 11.619316 0.05965473 0.03450208     1
#> 2 10 0.7766272 1.877865 26.98758  9.961507 0.06958256 0.03705408     1
#> 3 11 3.1875227 3.793889 35.58941  6.502218 0.10660165 0.02809825     1
#> 4 12 0.9618226 2.418444 26.25119  7.523821 0.09212701 0.03809351     1
#> 5  2 1.8883358 3.263475 31.49839  6.690114 0.10360769 0.03174766     1
#> 6  3 2.2065158 2.938127 32.87793  7.756385 0.08936472 0.03041554     1


# If param has no vars, .prm should be set
pheno_base %>%
  backfill_derived(
    .prm = c(CL,V)
  ) %>%
  list_vars()
#> Warning: There was 1 warning in `dplyr::mutate()`.
#>  In argument: `type = stringr::str_replace(.$type, "\\d$", "")`.
#> Caused by warning:
#> ! Unknown or uninitialised column: `type`.
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> List of available variables for problem no. 1
#>  - Subject identifier (id)               : ID
#>  - Dependent variable (dv)               : DV
#>  - Independent variable (idv)            : TIME
#>  - Dose amount (amt)                     : AMT
#>  - Event identifier (evid)               : EVID
#>  - Missing dependent variable (mdv)      : MDV
#>  - Model typical predictions (pred)      : PRED
#>  - Model individual predictions (ipred)  : IPRED
#>  - Model parameter (param)               : CL, V, VC, KEL, VSS, T12ALPHA, ALPHA, A, FRACA
#>  - Eta (eta)                             : ETA1, ETA2
#>  - Residuals (res)                       : IWRES, CWRES, NPDE, RES, WRES
#>  - Categorical covariates (catcov)       : APGR ('Apgar score') [10]
#>  - Continuous covariates (contcov)       : WT ('Weight', kg)
#>  - Not attributed (na)                   : IRES, CRES

These derived parameters can be fed into diagnose_constants() to perform some quick checks for common issues. In this case, we have both VC (derived) and V (fitted) representing the same quantity, so to avoid catching both in the volume check (which per the documentation should only be one volume) the vol_pattern has been updated.

nlmixr2_m3 %>%
  backfill_derived() %>%
  diagnose_constants(vol_pattern = "^V$")
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#>  Checking for absorption flip-flop (first-order absorption slower than derived rate constants)...
#>  No parameter sets are suggestive of flip-flop.
#>  Checking for negative microconstants or volume...
#>  No parameter sets have negative microconstants or volumes.

nlmixr2_m3 %>%
  backfill_derived() %>%
  diagnose_constants(
    vol_pattern = "^V$",
    df_units = list(KA = "1/hr", ALPHA = "1/hr"),
    checks = list(neg_microvol = FALSE)
  )
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#>  Checking for absorption flip-flop (first-order absorption slower than derived rate constants)...
#>  No parameter sets are suggestive of flip-flop.
#>  Checking that compared units match...
#>  All relevant units seem to match.

# Using df form
derive_prm(nlmixr2_m3) %>%
  diagnose_constants(df = ., vol_pattern = "^V$")
#> Using data from $prob no.1
#> Removing duplicated rows based on: ID
#>  Checking for absorption flip-flop (first-order absorption slower than derived rate constants)...
#>  No parameter sets are suggestive of flip-flop.
#>  Checking for negative microconstants or volume...
#>  No parameter sets have negative microconstants or volumes.

Session info

sessionInfo()
#> R version 4.5.1 (2025-06-13)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.3 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#>  [1] xpose.xtras_0.1.0   xpose.nlmixr2_0.4.1 xpose_0.4.20       
#>  [4] ggplot2_3.5.2       rxode2_4.1.0        nlmixr2plot_3.0.3  
#>  [7] nlmixr2extra_3.0.2  nlmixr2est_4.1.0    nlmixr2data_2.0.9  
#> [10] lotri_1.0.2         nlmixr2_4.0.1      
#> 
#> loaded via a namespace (and not attached):
#>  [1] tidyselect_1.2.1      dplyr_1.1.4           farver_2.1.2         
#>  [4] S7_0.2.0              fastmap_1.2.0         lazyeval_0.2.2       
#>  [7] GGally_2.4.0          tweenr_2.0.3          rex_1.2.1            
#> [10] stringfish_0.17.0     digest_0.6.37         lifecycle_1.0.4      
#> [13] magrittr_2.0.3        dparser_1.3.1-13      compiler_4.5.1       
#> [16] rlang_1.1.6           sass_0.4.10           tools_4.5.1          
#> [19] utf8_1.2.6            yaml_2.3.10           data.table_1.17.8    
#> [22] symengine_0.2.10      knitr_1.50            lbfgsb3c_2024-3.5    
#> [25] labeling_0.4.3        htmlwidgets_1.6.4     pmxcv_0.0.2          
#> [28] RColorBrewer_1.1-3    withr_3.0.2           purrr_1.1.0          
#> [31] sys_3.4.3             desc_1.4.3            grid_4.5.1           
#> [34] polyclip_1.10-7       scales_1.4.0          MASS_7.3-65          
#> [37] cli_3.6.5             rmarkdown_2.29        crayon_1.5.3         
#> [40] ragg_1.5.0            generics_0.1.4        RcppParallel_5.1.11-1
#> [43] rstudioapi_0.17.1     tzdb_0.5.0            cachem_1.1.0         
#> [46] ggforce_0.5.0         RApiSerialize_0.1.4   stringr_1.5.2        
#> [49] splines_4.5.1         vctrs_0.6.5           Matrix_1.7-3         
#> [52] jsonlite_2.0.0        PreciseSums_0.7       hms_1.1.3            
#> [55] systemfonts_1.2.3     tidyr_1.3.1           jquerylib_0.1.4      
#> [58] rxode2ll_2.0.13       glue_1.8.0            pkgdown_2.1.3        
#> [61] ggstats_0.10.0        codetools_0.2-20      stringi_1.8.7        
#> [64] gtable_0.3.6          tibble_3.3.0          pillar_1.11.0        
#> [67] htmltools_0.5.8.1     R6_2.6.1              textshaping_1.0.3    
#> [70] conflicted_1.2.0      evaluate_1.0.5        lattice_0.22-7       
#> [73] readr_2.1.5           backports_1.5.0       vpc_1.2.2            
#> [76] qs_0.27.3             memoise_2.0.1         n1qn1_6.0.1-12       
#> [79] bslib_0.9.0           Rcpp_1.1.0            nlme_3.1-168         
#> [82] checkmate_2.3.3       mgcv_1.9-3            xfun_0.53            
#> [85] fs_1.6.6              forcats_1.0.0         pkgconfig_2.0.3