nlmixr2save 0.2.1
CRAN release: 2026-09-25
saveFit()no longer zips and deletes the files of another fit whose name extends its own with-<suffix>(#10). It picked a fit’s files out of the target directory by the name pattern<file>-*, so with the loose files ofsaveFit(fit2, "fit-alt", zip=FALSE)in the directory,saveFit(fit)put them infit.zipand then deleted them. The same match also swept in the files an earlierzip=FALSEsave of the same name left behind, so an item the new fit lacks (e.g.origDatawithdata=FALSE) came back from the old fit on load.saveFit()now writes every file into a private temporary directory and zips (or, withzip=FALSE, copies) only those, so other files already in the target directory are never read, zipped or removed. A save still replaces the fit saved earlier under the same name: azip=TRUEsave removes that fit’s loose loader<file>.R, and azip=FALSEsave removes its<file>.zip, whichloadFit()would otherwise load instead – each only when it really is a saved fit, so an unrelated script or archive of that name is kept. A target directory that is an existing file is now an error rather than being written over.A
:=cache under anlmixr2save.prefixno longer overwrites (and, on loading, can no longer lose) an unrelated<name>.zipin the cache directory while writing or reading<prefix><name>.zip, and a prefix that names a directory (e.g."run1/") now creates it instead of silently failing to write the cache.loadFit()now loads a fit given as a path, with or without the.zip(or.R) extension:loadFit("path/to/fit.zip")andloadFit("path/to/fit")both work from any working directory. It used to append.zipto whatever it was given, so a path ending in.ziplooked forfit.zip.zipand failed with “cannot find fit file” even though the file existed. It also extracted the archive into the working directory rather than beside the.zip, so a fit in another directory was never found, and a same-named file already in the working directory was overwritten and then deleted. The archive is now extracted to a temporary directory instead.loadFit()also loads a fit that an earlier version saved under a path –saveFit(fit, "models/fit"),saveFit(fit, "/home/me/models/fit")orsaveFit(fit, "~/models/fit")– from anywhere, including on another user’s machine. Those versions wrote the path into the loader script inside the archive: it named the fit after the path and read every file from it, so loading elsewhere failed with “cannot open file” or “Permission denied” for the original location. A~path also garbled the names the items were restored under. The archive is now extracted flat, and a loader tied to a path is regenerated from the files in it; the restored fit matches one saved without a path. A.ziprenamed after saving loads too.loadFit(myfit)with a bare symbol loadsmyfit.zipagain when no objectmyfitexists; it looked for a file named after the “object not found” error instead.saveFit(fit, "path_model/fit")now writes the files insidepath_model/under the bare namefit, creating the directory if needed. The files, and the loader’s references to them, used to be namedpath_model/fit-..., so the archive stored apath_model/folder: unzipping it (including byloadFit(), which unzipped into the working directory) recreatedpath_model/wherever that happened, and the loader only worked from the directory it was saved from.New vignette, “Keeping fits in a models directory”, on sending
:=caches to a directory withnlmixr2save.dir(and naming them withnlmixr2save.prefix), setting that for a whole project, saving and loading by path, and committing the directory to version control.loadFit()no longer rebuilds a fit’s models while loading. A saved fit stores its compiled model lists (foceiModel,saemModel) and itsuiasrxode2::rxode2()calls, and all of them were rebuilt on load – for a large model, several long C compilations that looked like a hang, plus a parse of the whole model for theui. Each is now built only when something first uses it: estimates, tables,fixef(),summary()and the like need none of them;print()andaugPred()build theui, and re-estimation or residual recalculation compiles the model list. Re-saving a loaded fit (e.g.nlmixr2saveShare()) writes the model lists back without compiling them. Fits saved under a path by an earlier version get this too, since their loader is regenerated.loadFit()(and therefore:=) no longer depends on the installed lotri to read a cache. A fit’s matrices (cov,omega,R,phiC, …) are stored aslotri({...})blocks with one row per statement;loadFit()now reads that form itself and passes anything else to lotri. Some development versions of lotri rejected the named rowssaveFit()writes (“matrix expression should be ‘name ~ c(lower-tri)’”), which is what broke the:=example on the package website, and a cache could not be loaded at all without nlmixr2est attached, since the scripts calllotri()unqualified. Existing caches benefit without being re-saved.loadFit()now brings a restorediniDf0in line with the installed rxode2’siniDf. It takes the columns and types from the fit’s ownui, which the installed rxode2 rebuilds on load, rather than from a version check. A cache written before rxode2 addedpriorgains the column (asNA), and an all-NAprior– which reads back from the.csvas logical – is character again, as rxode2 keeps it. The reloaded fit’siniDf0now matches the original.
nlmixr2save 0.2.0
CRAN release: 2026-08-04
fit := nlmixr2(...)no longer fails when data.table is attached after nlmixr2save (e.g.library(nlmixr2); library(data.table)). data.table’s exported:=masked nlmixr2save’s and errored with “Check that is.data.table(DT) == TRUE”. nlmixr2save now re-attaches itself in front of data.table when data.table is attached; data.table’sDT[, a := b]is unaffected (#8).saveFit()now restores everyiniDf0column with the type it had when saved, rather than a fixed list of columns. rxode2’s newer characterpriorcolumn is allNAfor fits without priors, whichread.csv()read back as logical, so a loaded fit’siniDf0no longer matched the original.loadFit()(and therefore:=) now restores the fit table’sIDcolumn as a factor. The table round-trips through a plain.csv, soIDcame back as an integer while a live fit carries a factor; anything joining the fit table to something derived from the fit then hit a type mismatch, sincenlme::augPred()keeps itsida factor.ggPMX::pmx_nlmixr()on a cached fit failed outright with “Incompatible join types: x.ID (factor) and i.ID (integer)”. The repair happens on load, so caches written by earlier versions are fixed too.saveFit()now records theparHistData$typefactor levels for fits that storeparHistDatacompressed (the nlmixr2est default). It read the levels straight out of the fit environment, where a compressed fit keeps a raw vector rather than a data frame, so the levels were silently not recorded andloadFit()fell back to a hardcoded level list. nlmixr2est has since added types that list predates (“Analytic Gradient (relaxed)” and friends), and those came back asNA.loadFit()now repairs aparHistData$typethat the cache’s own restore script dropped toNA. Those levels are applied by the script stored inside the cache, so a cache written before nlmixr2est added a type has no level for it and coerces it toNA– and re-saving cannot recover it, because by then the string is already gone.loadFit()reads the column back from the-parHistData.csvin the cache and appends whatever the script was missing, so existing caches are repaired in place.Saving or loading a fit no longer disturbs another cache whose base name it is a suffix of. The list of files belonging to a fit was matched with an unanchored pattern, so a cache named
fitalso matchedmyfit-env.R– zipping another cache’s files into its own archive and then deleting them from disk. This is reachable whenever unzipped files are lying around, whichsaveFit(zip=FALSE)leaves by design. The names are now matched literally rather than as a regexp, so a base name holding a metacharacter (my.fitis an ordinary R name, and as a pattern its.also matchedmy_fit) is matched exactly.saveFit(fit, zip=FALSE)now actually leaves the fit unzipped for a fit table (anlmixr2FitData). The method wrote the fit.csvand then called the core method with a hardcodedzip=TRUE, so the argument was silently ignored for every fit that carries data.
nlmixr2save 0.2.0
CRAN release: 2026-08-04
nlmixrDataSimplify()gainedestandcontrolarguments and no longer drops the covariate columns thatest="vae"searches for. The VAE covariate search picks its covariates out of the data instead of out of the model, so the candidate columns (as identified bynlmixr2est::vaeCovariates(), matched to thevaeControl()search options) are now kept in the simplified data and included in the hash. When the installednlmixr2estdoes not exportvaeCovariates()the search is skipped rather than erroring.-
You can now export a fit without the original data so a fitted model can be shared when the subject-level data cannot:
-
saveFit(fit, data = FALSE)(oroptions(nlmixr2save.data = FALSE)) writes a fit whoseorigDatais omitted from the zip. - New
nlmixr2saveShare()strips an existing saved fit (a live object or a.zipbase name; honorsnlmixr2save.dir/nlmixr2save.prefix) into a shareable sibling zip:fit-noData.zip(data removed) or, withnoFit = TRUE,fit-noData-noFit.zip(data and the returned prediction/residual table removed, keeping the model, estimates, eta table and parameter history). The original fit is left unchanged.
See
vignette("sharing-fits"), which also documents the side effects (e.g. VPC, residual re-derivation, and re-fitting need the original data). -
saveFit()no longer stores the redundantmodelelement (the loader always rebuilds it fromui). This removes a spurious “could not determine how to save object of class call for item model” warning when re-saving a previously loaded fit (including vianlmixr2saveShare()).-
Saved fits now record the
nlmixr2estandrxode2versions they were produced with (including each package’s commit sha when it was installed from a remote such as GitHub). When a fit is later loaded and the installednlmixr2estorrxode2differs:-
loadFit()warns that the fit was run with a different package version (controllable with the newcheckVersionargument). - the
:=caching operator, in an interactive session, asks whether to rerun the fit with the currently installed packages; non-interactively it loads the cached fit and warns. (Trusted-cache mode,nlmixr2save.check = FALSE, is left untouched so committed caches stay stable across versions.)
Fits saved by older
nlmixr2saveversions (which carry no version metadata) continue to load without any warning. The whole check can be turned off withoptions(nlmixr2save.checkVersion = FALSE)(or per call vialoadFit(..., checkVersion = FALSE)); it isTRUEby default. Seevignette("version-tracking"). -
The
parHistData$typefactor levels are now recorded from the fit at save time and restored on load, so the factor round-trips correctly regardless of whichnlmixr2estversion produced it (the level set has grown over versions, e.g."Analytic Gradient"). A hardcoded fallback covers fits saved before this was recorded.-
The
:=caching operator gains threeoptions()(mirroringnlmixr2save.quiet):-
nlmixr2save.prefix(default""): prepended to the assigned variable name to form the cache file, e.g. withoptions(nlmixr2save.prefix = "modelPiping-"),fit := nlmixr2(...)caches tomodelPiping-fit.zip. -
nlmixr2save.dir(default"."): the directory the cache files live in. -
nlmixr2save.check(defaultTRUE): whenTRUE,:=keeps verifying the cache against the current model/data/arguments (historical behavior); whenFALSE,:=simply loads the cache file if it exists (a fit as.zip, a simulation/other value as.rds) and otherwise runs and saves it – the cache is trusted and regenerated only when missing. This keeps a committed cache stable across nlmixr2/rxode2 versions.
-
New
nlmixr2saveInvalidate(): deletes every:=cache entry under the activenlmixr2save.prefix/nlmixr2save.dir, so cached fits/simulations are re-run on the next render whennlmixr2save.checkisFALSE.The saved-fit loader script now quotes its variable name, so a
nlmixr2save.prefixcontaining non-syntactic characters (e.g.-) round-trips correctly.Support both
$parFixedDfstructures produced by nlmixr2est: the current one (named “Estimate”/“SE” columns) and the upcoming$parFixedrefactor (nlmixr2est#645, unnamed columns). The structure of the fit is detected when it is saved and recorded in the restore script, so fits round-trip exactly with either version (nlmixr2saveParFixedDf()gained anamedargument; existing saved zip files continue to load unchanged).Additional all-
NAnumeric columns of$parFixedDf(for example “CI Lower”/“CI Upper” when there is no covariance step) are now coerced back to numeric when a saved fit is loaded, instead of being left as logical columns from the CSV round-trip.
nlmixr2save 0.1.0
CRAN release: 2026-06-17
Create zip files that are (mostly) R independent to save nlmixr2 fit items
Create a cached assignment operator
:=to save nlmixr2 fits automatically; if used, it will load a cached fit if function arguments are the same (a sort of disk memoization of the outermost function)Initial CRAN submission.
