| Title: | Aggregate Data Modelling |
|---|---|
| Description: | Fit pharmacokinetic/pharmacodynamic (PK/PD) models to aggregate-level data (mean vector and covariance matrix per study) rather than individual-level data, for meta-analysis across studies. Integrates with the 'nlmixr2'/'rxode2' ecosystem via four estimation methods: a First-Order ('FO') analytical estimator, a Monte Carlo (MC) estimator, a Gauss-Hermite quadrature ('GH') estimator, and an Iterative Reweighting Monte Carlo ('IRMC') estimator. Methods are based on Välitalo (2021) <doi:10.1007/s10928-021-09760-1>; software described in van de Beek et al. (2025) <doi:10.1007/s10928-025-10011-w>. |
| Authors: | H. van de Beek [aut, cre], P.A.J. Välitalo [aut], L.B. Zwep [aut], J.G.C. van Hasselt [aut] |
| Maintainer: | H. van de Beek <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.3.0 |
| Built: | 2026-07-17 15:09:41 UTC |
| Source: | https://github.com/leidenpharmacology/admixr2 |
Creates a control object for nlmixr2(est = "adfo"). The FO estimator
linearises model predictions at : it is faster than the MC
estimator but less accurate for models with large IIV or strongly
non-linear individual predictions.
adfoControl( studies = list(), grad = c("none", "analytical", "fd", "cfd"), algorithm = NULL, maxeval = 500L, ftol_rel = .Machine$double.eps^(1/2), print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad_h = 1e-04, grad_bounds = 5, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), covMethod = c("r", "none"), n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )adfoControl( studies = list(), grad = c("none", "analytical", "fd", "cfd"), algorithm = NULL, maxeval = 500L, ftol_rel = .Machine$double.eps^(1/2), print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad_h = 1e-04, grad_bounds = 5, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), covMethod = c("r", "none"), n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )
studies |
Named list of study specifications (same format as
|
grad |
Gradient mode. |
algorithm |
nloptr algorithm, or |
maxeval |
Maximum function evaluations (default 500). |
ftol_rel |
Relative tolerance (default |
print |
Print-frequency for live progress (0 = silent). |
seed |
Random seed (used for restarts). |
cores |
OpenMP threads for |
nDisplayProgress |
Passed to |
grad_h |
Finite-difference step for unpaired struct theta gradient and FD Jacobian. |
grad_bounds |
Box-constraint half-width when using gradients. |
cov_h |
Inner FD step for the gradient-based Hessian (only used when
|
cov_h_outer |
Outer step scale for NLL-FD Hessian. |
covMethod |
|
n_restarts |
Number of optimizer restarts (1 = no multi-start). |
restart_sd |
Standard deviation for random perturbations of initial struct thetas at each restart (> 1). |
workers |
Number of parallel workers (mirai daemons) for multi-restart
(default 1 = sequential). Requires the |
rxControl |
|
calcTables, compress, ci, sigdig, sigdigTable, optExpression, sumProd, literalFix
|
Passed to |
addProp |
How combined additive+proportional error is parameterised in
the nlmixr2 output tables: |
returnAdmr |
If |
... |
Unused arguments (trigger an error). |
An adfoControl object (a named list).
rxode2::rxSolve() estimates free RAM on every call. When the memuse
package is not installed its fallback ends up shelling out to vm_stat, a
macOS-only command, so on Windows and Linux every solve spawns a process that
can only fail. Because the FO estimator issues many small solves, this
overhead is measurable (roughly 17% of an FO gradient). Installing memuse
makes the fallback unreachable:
install.packages("memuse")
# Inspect defaults ctl <- adfoControl() ctl$grad ctl$maxeval # Analytical gradient, more evaluations ctl2 <- adfoControl(grad = "analytical", maxeval = 1000L) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) print(fit)# Inspect defaults ctl <- adfoControl() ctl$grad ctl$maxeval # Analytical gradient, more evaluations ctl2 <- adfoControl(grad = "analytical", maxeval = 1000L) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) print(fit)
Creates a control object for nlmixr2(est = "adgh"). The GH estimator
integrates model predictions against the random-effects prior
using a deterministic tensor-product
Gauss-Hermite quadrature grid. It is unbiased at any IIV magnitude (unlike
FO), noise-free (unlike MC), and much faster than MC for models with up to
~4 etas.
adghControl( studies = list(), n_nodes = 5L, grad = c("analytical", "fd", "cfd", "none"), algorithm = "NLOPT_LN_BOBYQA", maxeval = 500L, ftol_rel = .Machine$double.eps^(1/2), print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad_h = 1e-04, grad_bounds = 5, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/4), covMethod = c("r", "none"), n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )adghControl( studies = list(), n_nodes = 5L, grad = c("analytical", "fd", "cfd", "none"), algorithm = "NLOPT_LN_BOBYQA", maxeval = 500L, ftol_rel = .Machine$double.eps^(1/2), print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad_h = 1e-04, grad_bounds = 5, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/4), covMethod = c("r", "none"), n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )
studies |
Named list of study specifications (same format as
|
n_nodes |
Number of quadrature nodes per eta dimension (default 5).
Total nodes = |
grad |
Gradient mode. |
algorithm |
nloptr algorithm. Automatically coerced to
|
maxeval |
Maximum function evaluations (default 500). |
ftol_rel |
Relative tolerance (default |
print |
Print-frequency for live progress (0 = silent). |
seed |
Random seed (used for restarts). |
cores |
OpenMP threads for |
nDisplayProgress |
Passed to |
grad_h |
Finite-difference step for unpaired struct theta gradient and FD Jacobian fallback. |
grad_bounds |
Box-constraint half-width when using gradients. |
cov_h |
Inner FD step for the gradient-based Hessian (only used when
|
cov_h_outer |
Outer step scale for numerical Hessian. Default
|
covMethod |
|
n_restarts |
Number of optimizer restarts (1 = no multi-start). |
restart_sd |
SD of random perturbations of initial struct thetas at each restart. |
workers |
Number of parallel workers (mirai daemons) for multi-restart
(default 1 = sequential). Requires the |
rxControl |
|
calcTables, compress, ci, sigdig, sigdigTable, optExpression, sumProd, literalFix
|
Passed to |
addProp |
How combined additive+proportional error is parameterised in
the nlmixr2 output tables: |
returnAdmr |
If |
... |
Unused arguments (trigger an error). |
An adghControl object (a named list).
admControl(), adfoControl(), adirmcControl()
ctl <- adghControl() ctl$n_nodes ctl$grad # More nodes for large IIV, analytical gradient ctl2 <- adghControl(n_nodes = 7L, grad = "analytical", maxeval = 300L) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adgh", control = adghControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))) ) )ctl <- adghControl() ctl$n_nodes ctl$grad # More nodes for large IIV, analytical gradient ctl2 <- adghControl(n_nodes = 7L, grad = "analytical", maxeval = 300L) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adgh", control = adghControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))) ) )
Constructs a control object for est = "adirmc", the Iterative Reweighting
Monte Carlo estimator.
adirmcControl( studies = list(), n_sim = 2500L, outer_iter = 50L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), algorithm = NULL, maxeval = 5000L, ftol_rel = .Machine$double.eps, print = 1L, omega_expansion = 1, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad = c("analytical", "none", "fd"), kappa_method = c("exact", "linearized", "linearized_gh"), kappa_n_nodes = 5L, grad_h = 1e-04, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), phases = c(2, 1, 0.5, 0.01), convcrit = 1e-05, max_worse = 5L, covMethod = c("r", "none"), cov_n_sim = 10000L, n_restarts = 1L, restart_sd = 0.2, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )adirmcControl( studies = list(), n_sim = 2500L, outer_iter = 50L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), algorithm = NULL, maxeval = 5000L, ftol_rel = .Machine$double.eps, print = 1L, omega_expansion = 1, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad = c("analytical", "none", "fd"), kappa_method = c("exact", "linearized", "linearized_gh"), kappa_n_nodes = 5L, grad_h = 1e-04, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), phases = c(2, 1, 0.5, 0.01), convcrit = 1e-05, max_worse = 5L, covMethod = c("r", "none"), cov_n_sim = 10000L, n_restarts = 1L, restart_sd = 0.2, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )
studies |
Named list of study specifications. Each element is a list with:
Multi-compartment (multiple observed outputs). To fit several observed
compartments simultaneously (e.g. plasma and brain/CSF), give the study an
Long format (one row per endpoint/time). As an alternative to the
# independent blocks: per-row variances; optional per-endpoint `n` column
# and per-endpoint `ev` (a list of event tables keyed by endpoint)
list(n = 60L, ev = ev,
data = data.frame(DVID = c("cp", "cp", "cCSF"), TIME = c(1, 2, 2),
E = c(9.1, 7.4, 2.2), V = c(1.2, 0.9, 0.1)))
# joint (same subjects): ONE stacked covariance whose rows/cols align with
# the rows of `data` -- no `cross` blocks to assemble by hand
list(n = 60L, ev = ev, data = data.frame(DVID = ..., TIME = ..., E = ...),
V = V_joint)
A study-level |
n_sim |
Number of Monte Carlo samples per NLL evaluation. |
outer_iter |
Maximum inner optimiser iterations per phase. |
sampling |
Sampling method for eta draws: |
algorithm |
nloptr algorithm string, or |
maxeval |
Maximum number of optimizer function evaluations. |
ftol_rel |
Relative function-value tolerance for convergence. |
print |
Print progress every this many evaluations (0 = silent). |
omega_expansion |
Inflate proposal Omega by this factor (>= 1). |
seed |
Random seed for reproducibility. |
cores |
Number of OpenMP threads for |
nDisplayProgress |
Passed to |
grad |
Gradient mode for the inner optimiser: |
kappa_method |
Kappa correction method for models with non-mu-referenced
struct thetas: |
kappa_n_nodes |
Number of GH nodes per eta dimension for
|
grad_h |
Step size for finite-difference gradient evaluation during
optimization (used by |
cov_h |
Inner FD step for the gradient-based Hessian (only used when
|
cov_h_outer |
Outer step scale for the numerical Hessian. The actual step
for parameter |
phases |
Numeric vector of box-constraint half-widths, one per phase. Phases progressively tighten the search region. |
convcrit |
Convergence criterion: phase ends when |
max_worse |
Stop a phase after this many consecutive worsening iterations. |
covMethod |
Covariance method: |
cov_n_sim |
Number of MC samples for the covariance (Hessian) step.
More samples reduce MC noise in NLL evaluations. The NLL-based Hessian
( |
n_restarts |
Number of optimization restarts. Runs in parallel when
|
restart_sd |
Standard deviation of structural theta perturbations for restart initialisation. |
workers |
Number of parallel workers for multi-restart. |
rxControl |
|
calcTables, compress, ci, sigdig, sigdigTable, optExpression, sumProd, literalFix
|
Passed to |
addProp |
How combined additive+proportional error is parameterised in
the nlmixr2 output tables: |
returnAdmr |
If |
... |
Additional arguments (none allowed; triggers an error). |
Multi-compartment fits (a study observations list with several observed
outputs) are not supported by adirmc; use est = "admc", "adfo", or
"adgh" for those. Single-output studies are fit as usual.
An object of class adirmcControl.
# Inspect defaults ctl <- adirmcControl() ctl$phases ctl$omega_expansion # Tighter phases, more restarts ctl2 <- adirmcControl( n_sim = 1000L, omega_expansion = 1.5, phases = c(2, 1, 0.5, 0.01), n_restarts = 3L ) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- diag(diag(cov.wt(dv_mat, method = "ML")$cov)) pk_model <- function() { ini({ tcl <- log(5); tv1 <- log(12); tv2 <- log(25) tq <- log(12); tka <- log(1.2) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v1 ~ 0.09; eta.v2 ~ 0.09 eta.q ~ 0.09; eta.ka ~ 0.09 }) model({ cl <- exp(tcl + eta.cl); v1 <- exp(tv1 + eta.v1) v2 <- exp(tv2 + eta.v2); q <- exp(tq + eta.q) ka <- exp(tka + eta.ka) d/dt(depot) <- -ka * depot d/dt(central) <- ka * depot - (cl/v1 + q/v1) * central + (q/v2) * peripheral d/dt(peripheral) <- (q/v1) * central - (q/v2) * peripheral cp <- central / v1 cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adirmc", control = adirmcControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), n_sim = 500L ) ) print(fit)# Inspect defaults ctl <- adirmcControl() ctl$phases ctl$omega_expansion # Tighter phases, more restarts ctl2 <- adirmcControl( n_sim = 1000L, omega_expansion = 1.5, phases = c(2, 1, 0.5, 0.01), n_restarts = 3L ) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- diag(diag(cov.wt(dv_mat, method = "ML")$cov)) pk_model <- function() { ini({ tcl <- log(5); tv1 <- log(12); tv2 <- log(25) tq <- log(12); tka <- log(1.2) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v1 ~ 0.09; eta.v2 ~ 0.09 eta.q ~ 0.09; eta.ka ~ 0.09 }) model({ cl <- exp(tcl + eta.cl); v1 <- exp(tv1 + eta.v1) v2 <- exp(tv2 + eta.v2); q <- exp(tq + eta.q) ka <- exp(tka + eta.ka) d/dt(depot) <- -ka * depot d/dt(central) <- ka * depot - (cl/v1 + q/v1) * central + (q/v2) * peripheral d/dt(peripheral) <- (q/v1) * central - (q/v2) * peripheral cp <- central / v1 cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adirmc", control = adirmcControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), n_sim = 500L ) ) print(fit)
Constructs a control object for est = "admc", the Monte Carlo aggregate
data modelling estimator.
admControl( studies = list(), n_sim = 5000L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), algorithm = NULL, maxeval = 500L, ftol_rel = .Machine$double.eps^2, print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad = c("sens", "fd", "cfd", "none"), grad_h = 1e-04, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), grad_bounds = 5, covMethod = c("r", "none"), cov_n_sim = 10000L, n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )admControl( studies = list(), n_sim = 5000L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), algorithm = NULL, maxeval = 500L, ftol_rel = .Machine$double.eps^2, print = 10L, seed = 12345L, cores = rxode2::rxCores(), nDisplayProgress = .Machine$integer.max, grad = c("sens", "fd", "cfd", "none"), grad_h = 1e-04, cov_h = 0.001, cov_h_outer = .Machine$double.eps^(1/5), grad_bounds = 5, covMethod = c("r", "none"), cov_n_sim = 10000L, n_restarts = 1L, restart_sd = 0.5, workers = 1L, rxControl = NULL, calcTables = FALSE, compress = TRUE, ci = 0.95, sigdig = 4, sigdigTable = NULL, addProp = c("combined2", "combined1"), optExpression = TRUE, sumProd = FALSE, literalFix = TRUE, returnAdmr = FALSE, ... )
studies |
Named list of study specifications. Each element is a list with:
Multi-compartment (multiple observed outputs). To fit several observed
compartments simultaneously (e.g. plasma and brain/CSF), give the study an
Long format (one row per endpoint/time). As an alternative to the
# independent blocks: per-row variances; optional per-endpoint `n` column
# and per-endpoint `ev` (a list of event tables keyed by endpoint)
list(n = 60L, ev = ev,
data = data.frame(DVID = c("cp", "cp", "cCSF"), TIME = c(1, 2, 2),
E = c(9.1, 7.4, 2.2), V = c(1.2, 0.9, 0.1)))
# joint (same subjects): ONE stacked covariance whose rows/cols align with
# the rows of `data` -- no `cross` blocks to assemble by hand
list(n = 60L, ev = ev, data = data.frame(DVID = ..., TIME = ..., E = ...),
V = V_joint)
A study-level |
n_sim |
Number of Monte Carlo samples per NLL evaluation. |
sampling |
Sampling method for eta draws: |
algorithm |
nloptr algorithm string, or |
maxeval |
Maximum number of optimizer function evaluations. |
ftol_rel |
Relative function-value tolerance for convergence. |
print |
Print progress every this many evaluations (0 = silent). |
seed |
Random seed for reproducibility. |
cores |
Number of OpenMP threads for |
nDisplayProgress |
Passed to |
grad |
Gradient mode: |
grad_h |
Step size for finite-difference gradient evaluation during
optimization (used by |
cov_h |
Inner FD step for the gradient-based Hessian (only used when
|
cov_h_outer |
Outer step scale for the numerical Hessian. The actual step
for parameter |
grad_bounds |
Box-constraint half-width when using gradients. |
covMethod |
Covariance method: |
cov_n_sim |
Number of MC samples for the covariance (Hessian) step.
More samples reduce MC noise in NLL evaluations. The NLL-based Hessian
( |
n_restarts |
Number of optimization restarts. Runs in parallel when
|
restart_sd |
Standard deviation of structural theta perturbations for restart initialisation. |
workers |
Number of parallel workers for multi-restart. |
rxControl |
|
calcTables, compress, ci, sigdig, sigdigTable, optExpression, sumProd, literalFix
|
Passed to |
addProp |
How combined additive+proportional error is parameterised in
the nlmixr2 output tables: |
returnAdmr |
If |
... |
Additional arguments (none allowed; triggers an error). |
An object of class admControl.
# Minimal control object -- inspect defaults ctl <- admControl() ctl$n_sim ctl$algorithm # Override key settings without fitting ctl2 <- admControl( n_sim = 2000L, maxeval = 300L, grad = "fd", seed = 42L ) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv1 <- log(12); tv2 <- log(25) tq <- log(12); tka <- log(1.2) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v1 ~ 0.09; eta.v2 ~ 0.09 eta.q ~ 0.09; eta.ka ~ 0.09 }) model({ cl <- exp(tcl + eta.cl); v1 <- exp(tv1 + eta.v1) v2 <- exp(tv2 + eta.v2); q <- exp(tq + eta.q) ka <- exp(tka + eta.ka) d/dt(depot) <- -ka * depot d/dt(central) <- ka * depot - (cl/v1 + q/v1) * central + (q/v2) * peripheral d/dt(peripheral) <- (q/v1) * central - (q/v2) * peripheral cp <- central / v1 cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "admc", control = admControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), n_sim = 1000L, maxeval = 200L ) ) print(fit)# Minimal control object -- inspect defaults ctl <- admControl() ctl$n_sim ctl$algorithm # Override key settings without fitting ctl2 <- admControl( n_sim = 2000L, maxeval = 300L, grad = "fd", seed = 42L ) library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv1 <- log(12); tv2 <- log(25) tq <- log(12); tka <- log(1.2) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v1 ~ 0.09; eta.v2 ~ 0.09 eta.q ~ 0.09; eta.ka ~ 0.09 }) model({ cl <- exp(tcl + eta.cl); v1 <- exp(tv1 + eta.v1) v2 <- exp(tv2 + eta.v2); q <- exp(tq + eta.q) ka <- exp(tka + eta.ka) d/dt(depot) <- -ka * depot d/dt(central) <- ka * depot - (cl/v1 + q/v1) * central + (q/v2) * peripheral d/dt(peripheral) <- (q/v1) * central - (q/v2) * peripheral cp <- central / v1 cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "admc", control = admControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), n_sim = 1000L, maxeval = 200L ) ) print(fit)
Returns a minimal NONMEM-style data frame that satisfies nlmixr2's data
argument requirement. The single observation row carries a non-NA
placeholder DV (1); the dose row keeps DV = NA. The placeholder is
purely for dispatch and output construction and never enters the reported
objective – each estimator overwrites fit$env$objective (and
OBJF/logLik/AIC/BIC) with its own aggregate -2LL. A non-NA observation is
required because nlmixr2's post-fit output construction
(nlmixr2CreateOutputFromUi) solves the model over this frame, and rxode2's
event-table translation rejects a dataset with no non-NA observation rows
("no rows in event table or input data"), the same reason the multi-endpoint
frame below uses a placeholder DV.
admData(outputs = NULL)admData(outputs = NULL)
outputs |
Optional character vector of observed output (endpoint) names
for a multi-compartment model with several prediction lines (e.g.
|
A data frame with columns ID, TIME, DV, AMT, EVID, CMT
(single-endpoint), plus a DVID endpoint column when outputs is given.
admData() admData(c("cp", "cCSF"))admData() admData(c("cp", "cCSF"))
Stops any worker processes (mirai daemons) started by a parallel-restart fit
(admControl(workers = N)). Workers are stopped automatically after the
restart phase completes, so this function is only needed if a fit was
interrupted before cleanup could run.
admStopWorkers()admStopWorkers()
NULL, invisibly.
# Safe to call at any time; no-op if no workers are running admStopWorkers()# Safe to call at any time; no-op if no workers are running admStopWorkers()
Generates population mean vectors (E) and covariance matrices
(V) for each study by integrating over the IIV distribution – either by
Monte Carlo (the default) or by a deterministic First-Order expansion
(method = "fo", see datagenControl()). Each study may specify its own PK/PD model (as would be the
case when digitising data from several published studies, each fit with a
different structural model). True parameter values are taken from the
ini() block of each study's model. Each element of the returned list
is ready to supply directly to admControl(studies = ...).
datagen(studies, model = NULL, control = datagenControl())datagen(studies, model = NULL, control = datagenControl())
studies |
A named list of study specifications. Each element is a list with:
|
model |
Optional default model function used for any study that does not
supply its own |
control |
A |
With control = datagenControl(method = "mc") (the default) population
moments are computed via the same Monte Carlo engine as est = "admc":
where and are the model and initial estimates
from the ini() block of study , the sample covariance uses the
ML denominator n_sim, and is diagonal with entries
determined by that study model's residual error type (additive, proportional,
or log-normal).
With method = "fo" the moments are instead the deterministic First-Order
expansion used by est = "adfo":
with the Jacobian obtained from the sensitivity model (or finite
differences if that is unavailable). This is the natural choice for design
evaluation and optimal design: the moments are fast and reproducible, and
because the data-generating and data-analytic models coincide, the FO Hessian
of the log-likelihood (the expected information matrix) is evaluated at the
true maximum rather than at a point that is not an MLE of the generated data.
Note est = "adfo" always adds to its predicted covariance, so
for a consistent FIM keep the residual error in the generating model; omit it
only when residual-free (IIV-only) moments are genuinely what you want.
With method = "gh" the moments are computed by deterministic
Gauss-Hermite quadrature over the random-effects prior :
where are the Cholesky-scaled tensor-product GH nodes and
weights. Unlike FO this is unbiased at any IIV magnitude; unlike MC the result
is noise-free and exactly reproducible. Matching the moments of est = "adgh"
makes method = "gh" the natural choice for optimal design with that estimator.
Models are compiled and cached on first use (keyed by model expression digest), so repeated calls or multiple studies sharing the same model incur only a single compilation.
A named list with one element per study. Each element contains:
EPopulation mean vector at times.
VPopulation covariance matrix
(length(times) x length(times); ML denominator n_sim for
method = "mc", the analytical FO covariance for method = "fo",
or the GH weighted covariance for method = "gh").
The diagonal carries the model's residual-error variance; to generate
residual-free (IIV-only) moments, omit the error term from the model.
nSample size (NA_integer_ if not supplied).
timesObservation times.
evDosing event table.
samplesRaw n_sim x length(times) prediction matrix
(only when control$return_samples = TRUE).
datagenControl(), admControl()
library(rxode2) pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } study_data <- datagen( studies = list( study1 = list(times = c(1, 2, 4, 8, 12, 24), ev = rxode2::et(amt = 100), n = 200L) ), model = pk_model, control = datagenControl(n_sim = 2000L) ) # E and V plug directly into admControl(studies = ...) round(study_data$study1$E, 2)library(rxode2) pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } study_data <- datagen( studies = list( study1 = list(times = c(1, 2, 4, 8, 12, 24), ev = rxode2::et(amt = 100), n = 200L) ), model = pk_model, control = datagenControl(n_sim = 2000L) ) # E and V plug directly into admControl(studies = ...) round(study_data$study1$E, 2)
datagen()
Control parameters for datagen()
datagenControl( method = c("mc", "fo", "gh"), n_sim = 5000L, n_nodes = 5L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), seed = 12345L, cores = 1L, return_samples = FALSE )datagenControl( method = c("mc", "fo", "gh"), n_sim = 5000L, n_nodes = 5L, sampling = c("sobol", "halton", "torus", "lhs", "rnorm"), seed = 12345L, cores = 1L, return_samples = FALSE )
method |
Moment approximation used to generate |
n_sim |
Number of Monte Carlo samples used to approximate population
moments. Ignored when |
n_nodes |
Number of Gauss-Hermite nodes per eta dimension for
|
sampling |
Quasi-random sampling method: |
seed |
Integer seed. Applied before stochastic methods
( |
cores |
Number of |
return_samples |
Include the raw |
A list of class "datagenControl".
ctrl <- datagenControl(n_sim = 2000L) ctrl$sampling # "sobol" # Deterministic FO moments for design evaluation: datagenControl(method = "fo")$method # "fo" # GH quadrature moments (unbiased, noise-free): datagenControl(method = "gh", n_nodes = 5L)$n_nodesctrl <- datagenControl(n_sim = 2000L) ctrl$sampling # "sobol" # Deterministic FO moments for design evaluation: datagenControl(method = "fo")$method # "fo" # GH quadrature moments (unbiased, noise-free): datagenControl(method = "gh", n_nodes = 5L)$n_nodes
A simulated pharmacokinetic dataset for the fictional drug examplomycin,
intended as a worked example for aggregate data modelling with admixr2.
The dataset contains 500 subjects, each with 9 observation time points,
generated from a two-compartment model with first-order absorption.
examplomycinexamplomycin
A data frame with 5000 rows and 6 columns:
ID: Subject identifier (integer, 1–500).
TIME: Time after dose (hours).
DV: Observed plasma concentration (mg/L).
AMT: Dose amount (mg); 100 for dosing records, 0 otherwise.
EVID: Event type (101 = dose, 0 = observation).
CMT: Compartment (1 = depot, 2 = central).
True population parameters:
| Parameter | Value |
| CL (L/hr) | 5 |
| V1 (L) | 10 |
| V2 (L) | 30 |
| Q (L/hr) | 10 |
| ka (1/hr) | 1 |
| IIV (all, SD on log scale) | 0.3 |
| Proportional error (SD) | 0.2 |
Single oral dose of 100 mg; sampling at 0.1, 0.25, 0.5, 1, 2, 3, 5, 8, and 12 hours post-dose.
Generated from a two-compartment PK model using rxode2::rxSolve().
See vignette("admixr2") for a full modelling example.
data("examplomycin") head(examplomycin) # Compute aggregate statistics obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) E <- sapply(times, function(t) mean(obs$DV[obs$TIME == t])) round(E, 3)data("examplomycin") head(examplomycin) # Compute aggregate statistics obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) E <- sapply(times, function(t) mean(obs$DV[obs$TIME == t])) round(E, 3)
Called automatically by nlmixr2(model, admData(), est = "adfo", control = adfoControl(...)). Not typically called directly.
## S3 method for class 'adfo' nlmixr2Est(env, ...)## S3 method for class 'adfo' nlmixr2Est(env, ...)
env |
nlmixr2 environment containing |
... |
Unused. |
An admFit nlmixr2 fit object.
Called automatically by nlmixr2(model, admData(), est = "adgh", control = adghControl(...)). Not typically called directly.
## S3 method for class 'adgh' nlmixr2Est(env, ...)## S3 method for class 'adgh' nlmixr2Est(env, ...)
env |
nlmixr2 environment containing |
... |
Unused. |
An admFit nlmixr2 fit object.
Called automatically by
nlmixr2(model, admData(), est = "adirmc", control = adirmcControl(...)).
Not typically called directly.
## S3 method for class 'adirmc' nlmixr2Est(env, ...)## S3 method for class 'adirmc' nlmixr2Est(env, ...)
env |
nlmixr2 environment containing |
... |
Unused. |
An admFit nlmixr2 fit object.
Called automatically by nlmixr2(model, admData(), est = "admc", control = admControl(...)).
Not typically called directly.
## S3 method for class 'admc' nlmixr2Est(env, ...)## S3 method for class 'admc' nlmixr2Est(env, ...)
env |
nlmixr2 environment containing |
... |
Unused. |
An admFit nlmixr2 fit object.
Generates up to four diagnostic panels:
## S3 method for class 'admFit' plot(x, which = c("mean", "cov", "nll", "par"), n_sim = NULL, seed = 1L, ...)## S3 method for class 'admFit' plot(x, which = c("mean", "cov", "nll", "par"), n_sim = NULL, seed = 1L, ...)
x |
An |
which |
Character vector selecting which panel types to produce.
Any subset of |
n_sim |
Number of MC samples for the final prediction. Defaults to the
value used during fitting. Only used when |
seed |
Random seed for reproducibility. |
... |
Unused. |
"mean" – Observed vs predicted mean per study (2x2 grid). Upper row:
observed and predicted mean lines with +/-1 SD ribbon on a shared y scale
(black throughout). Lower row: raw residual lollipop with +/-2 SE band and
standardised residual z-scores with +/-1.96 reference lines.
"cov" – Observed vs predicted (co)variance heatmaps per study (2x2
grid). Upper row shares a common colour scale (blue-white-red). Lower row
uses distinct diverging scales: residual (red-white-green) and
standardised residual (gold-white-purple). Significance stars overlaid on
the standardised residual panel.
"nll" – NLL trace per restart over optimizer evaluations. Restarts
coloured with the Okabe-Ito palette.
"par" – Parameter trace per restart on the natural scale (struct thetas
back-transformed, sigma as SD, omega diagonal as variance labelled
V(eta.x)). Facets ordered as in the model ini() block. Restarts
coloured with the Okabe-Ito palette.
A named list of ggplot2 objects, invisibly. Prints each selected
top-level panel. For the "mean" and "cov" panels the returned list also
contains each sub-panel individually so a single panel (or a few) can be
extracted in code without reprinting the whole grid. Elements can be pulled
out by name – plot(fit, which = "mean")$mean_study1_pred or
plot(fit, which = "cov")$cov_study1_std_resid – or by position, with the
combined 2x2 grid stored first per study
(plot(fit, which = "mean")[[1]] is the full grid, [1] the length-1
named sub-list). The sub-panel keys are <type>_<study>_obs, _pred,
_resid, and _std_resid; the combined grid stays under <type>_<study>.
The extra sub-panel keys are not printed on their own.
Every admixr2 fit also carries the observed and predicted aggregate data in
fit$env$aggData, a named list with one entry per study. Each entry holds the
observation times, the study n, and two moment sets – obs (from the
data) and pred (predicted at the fitted parameters) – each a list with the
mean vector E and the (co)variance matrix V:
fit$env$aggData$study1$obs$E # observed mean vector fit$env$aggData$study1$obs$V # observed covariance matrix fit$env$aggData$study1$pred$E # predicted mean vector fit$env$aggData$study1$pred$V # predicted covariance matrix
The predicted moments are computed by one MC simulation at the fitted
parameters using the fit's own n_sim and a fixed seed, so they match the
default plot(fit) mean/cov panels. The slot is absent only when the fit
cannot be simulated (no simulation model available).
traceplot()
admixr2 fits also plug into the nlmixr2 traceplot() generic. During fitting
the parameter iteration history of the best restart is stored on the fit in
the standard parHistData slot (natural scale), so traceplot(fit) produces
the familiar per-parameter, free-y facetted trace used elsewhere in the
nlmixr2 ecosystem. There is no burn-in marker (admixr2 records optimizer
evaluations, not SAEM iterations), and only the best restart is shown – the
per-restart overlay and the NLL trace remain available via
plot(fit, which = c("par", "nll")). The trace stores only improving
evaluations (steps that lowered the best NLL), so the iter axis indexes
those improvement steps rather than raw optimizer iterations.
library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) plot(fit)library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) plot(fit)
Delegates to print.nlmixr2FitCore for the standard nlmixr2 coloured
output. admFit class is kept on the object during the call so that
head.admFit intercepts any head(fit) calls that arise in the paged-
output path (R Markdown / notebooks), preventing the
[.data.frame(.subset2(env, integer)) crash that occurs when an
environment-backed fit is subscripted like a plain list.
## S3 method for class 'admFit' print(x, ...)## S3 method for class 'admFit' print(x, ...)
x |
An |
... |
Passed to |
x, invisibly.
library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) print(fit)library(rxode2) library(nlmixr2) data("examplomycin") obs <- examplomycin[examplomycin$EVID == 0, ] obs <- obs[order(obs$ID, obs$TIME), ] times <- sort(unique(obs$TIME)) ids <- unique(obs$ID) dv_mat <- do.call(rbind, lapply(ids, function(i) { sub <- obs[obs$ID == i, ]; sub$DV[order(sub$TIME)] })) E <- colMeans(dv_mat) V <- cov.wt(dv_mat, method = "ML")$cov pk_model <- function() { ini({ tcl <- log(5); tv <- log(30) prop.sd <- c(0, 0.2) eta.cl ~ 0.09; eta.v ~ 0.04 }) model({ cl <- exp(tcl + eta.cl) v <- exp(tv + eta.v) d/dt(central) <- -(cl/v) * central cp <- central / v cp ~ prop(prop.sd) }) } fit <- nlmixr2( pk_model, admData(), est = "adfo", control = adfoControl( studies = list(study1 = list(E = E, V = V, n = length(ids), times = times, ev = et(amt = 100))), maxeval = 100L ) ) print(fit)