nlmixr2plot events for loggers

rxode2 provides a small event bus: packages that finish a top-level piece of work announce it with rxode2::rxEventEmit(), and any package can listen with rxode2::rxEventListen(). The main consumer is nlmixr2log (in development), which records fits, simulations and their results, but you can write your own listener. See ?rxode2::rxEventListen for the full API.

Two rules of the bus matter here:

This vignette describes what nlmixr2plot itself emits.

What nlmixr2plot emits

nlmixr2plot emits a single event, fitResult, when one of these functions returns successfully:

Function kind fit data
plot() of a fit (plot.nlmixr2FitData()) "plot" the fit NULL
vpcPlot() "vpcPlot" the fit (see below) NULL
plot() of an augPred() object (plot.nlmixr2AugPred()) "augPredPlot" NULL the augPred() data

The fitResult payload fields are:

Field Contents
fit The fit the plots belong to, or NULL for plot(augPred(...)).
result The value the function returns: the ggtibble::gglist of plots from plot(fit) or plot(augPred(...)), or the ggplot from vpcPlot() (the vpc database with vpcdb = TRUE).
kind "plot", "vpcPlot" or "augPredPlot".
call The call, normalized by rxode2 so its head is the kind (e.g. plot(fit), vpcPlot(fit, n = 5) or augPredPlot(ap)) and large inlined values are replaced by markers.
data For "augPredPlot" only, the augPred() data that was plotted; otherwise NULL.

Details:

What is not emitted

Everything that happens inside these functions is silent:

Other events you may see around plotting come from other packages: nlmixr2est emits fitComplete when nlmixr2() finishes a fit and solveComplete from augPred(), simulate(), predict() and vpcSim() called at the top level. Those require an nlmixr2est with event hooks; nlmixr2plot does not emit them.

Listening to the events

A listener is a function function(event, ...); the payload fields arrive as named arguments. Here is a small listener that records what it sees. First fit a small model:

library(nlmixr2plot)
library(nlmixr2est)
#> Loading required package: nlmixr2data

one.cmt <- function() {
  ini({
    tka <- log(1.57); tcl <- log(2.72); tv <- log(31.5)
    eta.ka ~ 0.6; eta.cl ~ 0.3; eta.v ~ 0.1
    add.sd <- 0.7
  })
  model({
    ka <- exp(tka + eta.ka)
    cl <- exp(tcl + eta.cl)
    v <- exp(tv + eta.v)
    linCmt() ~ add(add.sd)
  })
}

fit <- suppressMessages(
  nlmixr2(one.cmt, nlmixr2data::theo_sd, est = "posthoc",
          control = list(print = 0))
)

Then register a listener and make some plots:

seen <- list()
rxode2::rxEventListen("demo", function(event, ...) {
  p <- list(...)
  seen[[length(seen) + 1L]] <<- data.frame(
    event = event,
    kind = if (is.null(p$kind)) NA_character_ else p$kind,
    hasFit = !is.null(p$fit),
    resultClass = class(p$result)[1],
    call = paste(deparse(p$call), collapse = " ")
  )
})

p <- plot(fit)
v <- suppressMessages(suppressWarnings(vpcPlot(fit, n = 50)))
ap <- suppressMessages(augPred(fit))
a <- plot(ap)

rxode2::rxEventUnlisten("demo")
do.call(rbind, seen)
#>       event        kind hasFit     resultClass                 call
#> 1 fitResult        plot   TRUE          gglist            plot(fit)
#> 2 fitResult     vpcPlot   TRUE ggplot2::ggplot vpcPlot(fit, n = 50)
#> 3 fitResult augPredPlot  FALSE          gglist      augPredPlot(ap)

plot(fit) and vpcPlot() each report one fitResult, and the simulation inside vpcPlot() stays silent. plot(ap) reports a fitResult with no fit. With an nlmixr2est that has event hooks, the top-level augPred() call also reports a solveComplete (emitted by nlmixr2est).

After rxEventUnlisten(), plotting reports nothing:

n <- length(seen)
p <- plot(fit)
length(seen) == n
#> [1] TRUE

Full logging

For a complete record of fits, simulations, updates and the plots made from them, use nlmixr2log (in development), which listens to all of these events (including fitResult from nlmixr2plot) and stores the plots with the run they belong to.