Patch Pharmpy's nlmixr execution backend
patch_pharmpy_nlmixr_results.RdApplies Python monkey-patches to pharmpy.tools.external.nlmixr.run, the
module every nlmixr candidate fit dispatched by a Pharmpy tool
(bootstrap, modelsearch, covsearch, iivsearch, amd) goes through.
Three upstream bugs make it fail for essentially every real dataset, so
those tools are unusable against nlmixr-format models without this patch
(InsightRX/pharmr.extra#121):
Details
rdata["thetas"].loc[get_thetas(model).names]—Parameters$namesis a tuple in Pharmpy >= 2.0, andpandasreads a tuple passed to.locas a multi-axis indexer, so this raisesIndexingError: Too many indexers. (In Pharmpy 1.8namesis a list and the line happens to work.)predictions.set_index(model.dataset[model.dataset["DV"] != 0].index)— nlmixr2 returns one prediction row per observation record, but this indexes the dataset by "DV is non-zero". Any dataset with an observation whoseDVis exactly 0 (BLQ imputed to 0, a baseline 0 sample, ...) therefore has fewer index labels than prediction rows and pandas raisesValueError: Length mismatch.execute_model()writes the candidate's dataset withwrite_dataset()/write_csv(), which names the file after the datainfo path, but the R script it generates reads<model name>.csv. Whenever the model carries a datainfo path — which everymodelsearchcandidate does, since they are derived from the input model — the two names differ and the R run dies withcannot open file '.../<model name>.csv', leaving no results for the tool to rank.
The patch fixes (1) by wrapping the get_thetas that run.py imported so
names is always a list, (2) by handing the parser a model whose DV
column is 1 on observation records and 0 elsewhere — so the upstream
DV != 0 mask selects exactly the observation rows, with the dataset's own
index labels preserved (observation records are identified from EVID /
MDV; masks are tried in turn and the first one the parser accepts wins,
and if none work the unpatched call is made so the original Pharmpy error
surfaces) — and (3) by wrapping the dataset writer so that, when it is
handed a directory, it writes <model name>.csv: the name the generated R
script actually reads, keeping the caller's force so a leftover file in a
reused run folder still raises.
Because upstream only accepts a mask whose row count matches the number of
prediction rows, a mask that parses matched on count alone. If a different
mask selects the same number of (different) rows, the choice is ambiguous
and a RuntimeWarning is emitted rather than returning silently misaligned
predictions.
The patched parse_modelfit_results is installed both on
pharmpy.tools.external.nlmixr.run and on the pharmpy.tools.external.nlmixr
package alias — the latter is the binding
pharmpy.tools.read_modelfit_results() resolves, so without it reading an
nlmixr fit outside execute_model() stays broken. It also tolerates the
strict= keyword that alias passes but run.py does not accept.
Idempotent, and a no-op on a Pharmpy release that has fixed these bugs.
Called automatically by call_pharmpy_tool() for nlmixr-format models.