The model-diagram test of a long product no longer checks how long
modelGraph() takes, and is skipped on CRAN. The timing check failed at
random on loaded CRAN machines (48 s against a 30 s limit).
plot() no longer fails on a fit with more than one variance level. A model
with between-occasion variability reports omega as a list holding one matrix
per level rather than as a single matrix, and the between-subject-variability
guard tested nrow(omega) > 0, which is NA for a list, so plot() stopped
with "missing value where TRUE/FALSE needed" before drawing anything. The
guard now asks whether the fit reports any between-subject eta, which is what
the BSV plots need and does not depend on the number of variance levels.
The between-subject-variability plots now take their etas from the
subject-level omega block (dropping any fixed at zero) rather than from
every column ranef() returns, so a mixture fit no longer gets a QQ plot of
its mixnum subpopulation labels or a spurious correlation panel, and an
occasion-only fit is correctly treated as having no BSV.
plot() of a fit, vpcPlot() and plot() of an augPred() object
now emit one fitResult event on the rxode2 event bus (when rxode2
has one) with the plots they made, so loggers such as nlmixr2log can
store them with the fit. Simulations done inside vpcPlot() emit
nothing. Without a listener nothing changes.
modelDiagram(..., engine = "ggplot2", labels = TRUE) now draws the
arrow labels on a white background, so the arrow no longer runs
through the label text.
plot() of an augPred() object no longer titles a single-endpoint
figure with the last endpoint of an earlier multiple-endpoint plot.
The figures from plot() of a fit, traceplot() and plot() of an
augPred() object no longer carry the fit, the full plotting data or the
other figures in their environments, so saving them (with saveRDS() or
as a 'targets' target) is far smaller. Before, each figure's aes(),
facet and layer environments held the frame it was built in, and with it
the whole fit or every figure built before it (the trace plot and the
individual plots could each serialize to hundreds of MB). Each figure now
stores its data once, in $data; the plots look the same.
The VPC figures from vpcPlot(), vpcPlotTad(), vpcCens() and
vpcCensTad() no longer carry the VPC simulation, and with method = "tidyvpc" no longer carry the fit either. 'vpc' built its figure in
about a dozen frames that each held the whole simulation, and the tidyvpc
figure kept the frame of vpcPlot() itself, so saving a VPC of a
1500-observation fit with 50 simulations wrote 97 MB ('vpc') or up to
34 MB ('tidyvpc'). The 'vpc' figure now takes about 3 MB, and neither
grows with the number of simulations. The figures look the same.
The engine = "ggplot2" diagram from modelDiagram() no longer keeps its
layer data and a second copy of itself in its environments, and adds its
scales last; together about 1 MB (up to 3.4 MB for large models) less for
each saved diagram. The diagrams look the same.
The bootplot figure that plot() includes for a fit with bootstrap
results no longer carries the frame of plot(), with the plotting data and
every other figure: for theo_sd with 3 bootstrap samples it went from
152 MB to 6 MB saved. It still carries the fit, which
nlmixr2extra::bootplot() keeps in its own frame.
Added automatic model diagrams (#10). modelGraph() parses a model's
differential equations (from a model function, rxode2 model/UI or an
nlmixr2 fit) into a graph of compartments and flows (mass transfer,
eliminations, inputs and non-mass-transfer interactions like effect
compartments or PD stimulation/inhibition), detecting dosing compartments
from the dosing records. modelDiagram() lays it out with dosing and
absorption compartments above the central compartment, peripheral
compartments to the left, eliminations/metabolites below and PD models to
the right, and draws it with the "DiagrammeR" (Graphviz), "ggplot2" or
"dot" (DOT source) engine. Dosing properties (lag(), f(), rate(),
dur()) are shown as annotations on their compartment, and delay() terms
are understood. The diagrams were checked against all 3043 models of
'nlmixr2lib', including large QSP/PBPK models. plot() of an rxode2
user interface (rxUi) object or compiled rxode2 model draws its
diagram, and the new "Automatic model diagrams" vignette describes the
feature.
Fixed vpcPlot() with method = "tidyvpc" when the observed data has
observation records with a missing DV. Those records were dropped from
the observed data but kept in the simulation, so a stratified (e.g.
multiple-endpoint) VPC errored and a single-endpoint VPC paired simulated
values with the wrong observations. The matching simulated records are now
dropped too (#74).
plot() of a multiple-endpoint fit no longer creates empty
"Endpoint: " groups for state compartments without observations (like
depot or central); only observed endpoints are plotted. plot() of an
augPred() object likewise skips endpoints without data (#44).
vpcPlot()/vpcPlotTad() of a multiple-endpoint fit whose data codes the
endpoints with cmt no longer shows an extra NA panel (or a single NA
panel with pred_corr = TRUE); the simulated compartment labels are now
matched to the observed ones by name (#44).
The censored VPC (vpcCens(), or vpcPlot() with cens = TRUE and the
vpc backend) of a multiple-endpoint fit now stratifies by its endpoints
only, matching the simulated endpoints to the observed ones; an endpoint
whose observations are all missing no longer becomes an NA panel (#44).
plot() of a censored multiple-endpoint fit no longer errors in
geom_cens() ("argument must be coercible to non-negative integer") for an
endpoint without censored observations (#44).
The censored VPC (vpcCens(), vpcCensTad(), or vpcPlot() with
cens = TRUE and the vpc backend) now honours the data argument; it
previously always used the fitted data, whatever data was supplied (#55).
The observed data now comes from the original (or supplied) dataset instead of
the fit, so censored records are identified from the CENS column rather than
from the fit's imputed DV (which, for censMethod = "cdf", could land above
the limit and be counted as uncensored). Records without an observation are
dropped, as in the fit.
The VPC observed data now drops records with MDV = 1 even when an EVID
column is also present (previously only EVID was checked when both existed).
plot() of an augPred() object now accepts a base-R style log argument
(log = "y", "x" or "xy") to draw the individual plots on log-scaled
axes; non-positive values, which cannot be shown on a log axis, are dropped
(#32).
vpcPlot(), vpcPlotTad(), vpcCens() and vpcCensTad() now use a
supplied nlmixr2est::vpcSim() simulation instead of discarding it and
re-simulating with the default n = 300. Supplying n alongside a
simulation warns that it is ignored, and pred_corr = TRUE errors unless the
simulation was created with pred = TRUE (#57). The observed-data
prediction correction for a supplied simulation is computed from that
simulation's own fit rather than from whichever vpcSim(pred = TRUE) ran
last.
Fixed the VPC simulation ignoring the data argument of vpcPlot(),
vpcPlotTad(), vpcCens() and vpcCensTad(); the simulation always used
the fit's dataset, so it did not match the observed data built from data
(and tidyvpc warned that xsim did not match the observed x-values). The
simulation now uses data when supplied. A supplied
nlmixr2est::vpcSim() simulation was made from its own dataset, so passing
data alongside one now warns that it only replaces the observed data
(#68). Because the simulation now follows data, an endpoint without
observations in data is no longer simulated either, so it drops out of a
multiple-endpoint VPC instead of being shown as a simulated-only panel.
For a model with a non-normal endpoint, data now needs a numeric CMT
column (the model's compartment numbers), since the compartments would
otherwise be taken from the fitted data by row number, or read from a
factor's level order, and would not match data.
Fixed the prediction-corrected VPC (pred_corr = TRUE) ignoring the data
argument; the observed data was rebuilt from the fit's dataset instead of the
supplied data (#62).
Fixed stratified censored VPCs (vpcCens()/vpcCensTad(), or vpcPlot()
with cens = TRUE) failing with "The following specified stratification
columns were NOT found in observation data"; the observed data now comes
from the original (or supplied) dataset, which keeps the stratification
columns that the fit table drops (#56, #55).
Fixed the confidence-band width (and its legend label) for vpcPlot()/
vpcPlotTad() with the tidyvpc backend; ci = c(lower, upper) was passed
to tidyvpc::vpcstats() as conf.level = ci[2] instead of the actual
interval width ci[2] - ci[1], so the default ci = c(0.05, 0.95) (a 90% CI)
was drawn and labeled as a 95% CI.
Fixed vpcPlotTad()/vpcCensTad() (and any idv taken from the fit) with a
user-supplied data that is a subset or reordering of the fitted data; tad
was merged from the fit by row number, so it silently became NA (or was
paired with the wrong row). Each supplied row is now matched by content to
the fitted row it came from, and a warning is given for observations that
are not in the fitted data (#60).
vpcCensTad()
(and vpcCens()/vpcPlot()/vpcPlotTad() with cens = TRUE and a non-time
idv); the censored VPC now passes the sim/obs column mappings to
vpc::vpc_cens() explicitly instead of relying on column guessing
(nlmixr2#390).dv is now mapped to the
sim column instead of being copied into a new dv column, so vpc groups
the simulated data by replicate as it does for the uncensored VPC.quantile() "missing values and NaNs not allowed" error in
prediction-corrected vpcPlot()/vpcPlotTad() on censored (LLOQ/ULOQ) fits
with the vpc backend; censored records are now dropped before the
pred-corrected VPC (which is shown for non-censored data only), matching the
vpc package's stated behavior (nlmixr2#390).vpcPlot()/vpcPlotTad() with
method = "tidyvpc", cens = TRUE, which previously errored that the observed
data had no cens column; the censoring column is now retained when the
observed dataset is rebuilt for pred-correction.plot() on a fit with between-subject variability (BSV) now adds a nested
"bsv" section (inside each data/compartment group) with QQ plots for each
BSV parameter, BSV-BSV correlation plots (when more than one BSV parameter is
present) and, via the new covariate argument, BSV-by-covariate plots
(box-and-whisker for categorical or low-cardinality covariates, scatter plus a
linear trend with confidence interval for continuous ones). Covariate values
are taken from each subject's first record, so they are assumed time-invariant
(#51).plot() (for fits and augPred objects) are now
gglist objects from the ggtibble package instead of the internal
nlmixr2PlotList class. They still print/plot all figures at once and support
broadcasting ggplot2 additions (e.g. plot(fit) + ggplot2::theme_bw()), and
the top-level object remains a named, nested collection
(plot(fit)[["Endpoint: ..."]]). The collection can also be converted to a
ggtibble for reporting with ggtibble::as_ggtibble().augPred plots are now paginated with
ggforce::facet_wrap_paginate(), removing the hand-written 16-IDs-per-page
chunking. Requires ggtibble (>= 1.0.3.9000) and ggforce; the minimum R
version is now 4.3.plot() on a fit (and on an augPred object) is now about 3x faster. The
paginated individual plots were expanded one-per-page with
ggtibble::as_gglist(), which deep-copies the plot through
serialize()/unserialize(); because those plots are built inside functions
that hold the whole fit, each copy walked the entire fit object. The pages
are now produced by re-adding the paginated facet for each page instead.rxode2::.linCmtSensB() test guards. That internal
was removed from rxode2 in 2025, so the try()/skip_if_not()
checks always fell through and never skipped. linCmt() gradients are
now always available, so the guards are unnecessary and the tests run
unconditionally.tidyvpc packageOps method (removing + method) to work
with ggplot2 version 4 (#39)+vpcPlot() where input data frame for models did not
stratify all the time when the columns were upper case.plot() now returns a named list of lists so that users can more easily
choose which plots to include, if all plots are not desired. Or the user
could use those names as the basis of figure captions (fix #8).
Models without eta values (between subject variability) now have more consistent plotting to models with eta values (fix #18).
The package was updated to align with changes in ggplot2.
The package was updated to skip tests with "focei" and linCmt()
Added fixes for upcoming 'ggplot2' 3.4 release
Added changes for the generalized log-likelihood supported by 'nlmixr2est'
Added a NEWS.md file to track changes to the package.