knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.align='center' )
library(dplyr) library(xpose) library(xpose.xtras)
A model diagnostic is rarely just a diagnostic once it leaves an R session. It becomes a figure in a report, and figures in reports want consistent titles, an obvious "this is a draft" stamp while a model is still in flux, and a predictable place to land on disk. Doing all of that by hand, plot-by-plot and model-by-model, gets old fast.
xpose.xtras adds a small, composable toolkit for this last mile:
apply_default_labs() / set_default_labs() fill in (or overwrite) plot titles, subtitles, captions and tags.add_watermark() / set_default_watermark() stamp a plot with draft/preliminary/confidential-style text.ggsave_xp() saves a plot, applying the above first, with a swappable save backend.plot.xpose_data() / set_default_plots() generate a whole batch of diagnostic plots from one xpdb in a single call.All four read from the same kind of layered defaults, resolved in the same order every time: a session-wide R option, then a per-model (xpdb) default, then whatever is passed directly to the function call. set_xtras_options() and get_xtras_option() round this out by giving the whole xpose.xtras.* option family one place to be set and inspected.
Once a default is actually configured, it applies itself. print.xpose_plot() and ggsave_xp() both pick up a configured default_labs/default_watermark automatically, with nothing else to call. That behavior is controlled by the xpose.xtras.auto_apply option, TRUE unless changed; it's a no-op until something is configured, so nothing changes for existing code until a default is actually set. The functions below are still the way to configure those defaults in the first place, apply them explicitly on demand, or reach an xpdb-level default (which, unlike the option, print() has no way to pick up on its own; see the note under apply_default_labs()). The last section, [Automatic application], covers the auto-apply behavior itself.
apply_default_labs() takes a ggplot/xpose_plot object and fills in any of title/subtitle/caption/tag that aren't already set, using (in increasing precedence) the xpose.xtras.default_labs option, an xpdb-level default, and anything passed directly.
The simplest use is a session-wide option, handy for something that should apply to every plot in a script or report, like a draft caption:
options(xpose.xtras.default_labs = list(caption = "DRAFT: do not distribute")) p <- dv_vs_ipred(xpdb_ex_pk, quiet = TRUE) p_labelled <- apply_default_labs(p) p_labelled
Because dv_vs_ipred() already sets its own caption, that one wasn't touched. apply_default_labs() only fills in labels that are still empty. Passing overwrite = TRUE replaces existing labels too, and the value can use the same @keyword placeholders xpose::parse_title() understands elsewhere in the package:
apply_default_labs(p, caption = "Run @run, @nobs observations", overwrite = TRUE) options(xpose.xtras.default_labs = NULL)
A single global caption is a blunt instrument once more than one model is in play. set_default_labs() attaches label defaults to a specific xpdb instead, and those take precedence over the option. This is useful when different models in the same report need different framing:
xpdb_draft <- xpdb_x %>% set_default_labs(caption = "Model @run, interim, subject to change") dv_vs_ipred(xpdb_draft, quiet = TRUE) %>% apply_default_labs(xpdb = xpdb_draft, overwrite = TRUE)
apply_default_labs() can't reach back into the xpdb a plot was built from on its own. xpose's plotting functions only carry a reduced summary forward onto the plot object, not the full xpdb, so the xpdb argument has to be supplied explicitly whenever the xpdb-level tier (or @keyword resolution) should be used. Whatever is passed directly to apply_default_labs() still wins over both tiers:
dv_vs_ipred(xpdb_draft, quiet = TRUE) %>% apply_default_labs(caption = "Final for submission", xpdb = xpdb_draft, overwrite = TRUE)
add_watermark() overlays large, semi-transparent, rotated text across a plot, the kind of stamp that makes it obvious at a glance that a figure isn't final yet.
p %>% add_watermark()
label, colour, alpha, size, angle and fontface are all adjustable:
p %>% add_watermark(label = "PRELIMINARY", colour = "firebrick", alpha = 0.15, angle = 20)
The watermark is added as an ordinary ggplot2 layer, so it survives faceting and pagination: one watermark per panel, automatically:
dv_vs_ipred(xpdb_ex_pk, quiet = TRUE, facets = "SEX") %>% add_watermark(label = "DRAFT")
Because the watermark is drawn at a fixed rotation, it can visually merge into a plot that already has a strong diagonal of its own, like the line of unity on a dv_vs_pred() plot. The default 30 degree angle sits close to that line and gets lost among the points around it:
pred_plot <- dv_vs_pred(xpdb_ex_pk, quiet = TRUE) pred_plot %>% add_watermark(alpha = 0.6)
Crossing the other way keeps the watermark clearly legible instead, without competing with the plot's own diagonal. This is exactly the kind of case angle (and alpha/size/colour) exist for:
pred_plot %>% add_watermark(alpha = 0.6, angle = -45)
Like the label functions, watermark settings resolve from the xpose.xtras.default_watermark option, then an xpdb-level default set with set_default_watermark(), then whatever is passed directly. A whole project can default to, say, a red "CONFIDENTIAL" stamp without repeating those arguments at every call site:
options(xpose.xtras.default_watermark = list(label = "CONFIDENTIAL", colour = "firebrick")) p %>% add_watermark() options(xpose.xtras.default_watermark = NULL)
plot()Building a standard set of diagnostics for a model usually means calling several plotting functions one after another, then doing it all again for the next model. plot.xpose_data() (an S3 method for the base plot() generic) runs a whole list of plot-generating calls in one go and returns them as a flat, named list.
Called with no plots argument, it runs this package's built-in default battery: dv_vs_ipred, dv_vs_pred, res_vs_idv, res_vs_pred, eta_distrib, eta_grid, eta_vs_cov_grid, and ind_plots_sample.
default_plots <- plot(xpdb_x, quiet = TRUE) names(default_plots) default_plots$dv_vs_ipred
A custom plots list can mix bare functions and one-sided formulas in the ~ fn(.x) idiom used elsewhere in this package (see focus_function()). The formula form lets extra arguments be baked straight into the call, which is also how the same underlying plot function can appear more than once with different options:
custom_plots <- plot( xpdb_x, plots = list( xpose::dv_vs_ipred, ~ xpose::res_vs_idv(.x, res = "CWRES"), ~ xpose::res_vs_idv(.x, res = "IWRES") ), quiet = TRUE ) names(custom_plots)
If a single entry itself returns a list of plots, that list is flattened into the overall output rather than kept nested, so the result is always a flat list regardless of what any one entry produces.
A failing plot, by default, aborts the whole call immediately (showing the original error as its cause) without returning anything -- useful when a batch of plots feeds directly into a report and a silent gap would be worse than stopping early. Passing force = TRUE instead turns a failure into a warning and skips just that entry, so the rest of plots still gets a chance to run:
flaky_plots <- plot( xpdb_x, plots = list( xpose::dv_vs_ipred, ~ stop("simulated failure"), xpose::eta_distrib ), force = TRUE, quiet = FALSE ) names(flaky_plots)
Like default_labs/default_watermark, a project-wide plots spec can be set once via the xpose.xtras.default_plots session option (see set_xtras_options()), or attached to a specific xpdb with set_default_plots(), which takes precedence over the option. Unlike those two, though, this default is resolved as a whole rather than merged entry by entry -- a plot spec's entries aren't addressable by a stable key, since the same function can legitimately appear more than once:
xpdb_custom <- set_default_plots(xpdb_x, list(~ xpose::dv_vs_ipred(.x), ~ xpose::eta_distrib(.x))) names(plot(xpdb_custom, quiet = TRUE))
ggsave_xp()ggsave_xp() is a drop-in, ggplot2::ggsave()-compatible way to write a plot to disk: same plot/filename/path/width/height arguments, but it applies apply_default_labs()/add_watermark() first (apply_labs/apply_watermark, more on those in [Automatic application]), and path/width/height fall back to the xpose.xtras.save_dir/save_width/save_height options when not supplied.
out_dir <- tempdir() saved_path <- ggsave_xp(p, filename = "dv_vs_ipred.png", path = out_dir, width = 6, height = 4) basename(saved_path)
That's a real file on disk. Here's what actually got written:
# knitr/pkgdown can only reliably embed images under knitr's own fig.path, # not an arbitrary absolute path like the tempdir() used above to # demonstrate an arbitrary save location preview_path <- knitr::fig_path("png") dir.create(dirname(preview_path), recursive = TRUE, showWarnings = FALSE) file.copy(saved_path, preview_path, overwrite = TRUE) knitr::include_graphics(preview_path)
filename/path can also use @keyword placeholders, expanded the same way xpose::xpose_save() does it:
run_path <- ggsave_xp(dv_vs_ipred(xpdb_ex_pk, quiet = TRUE), filename = "@run_@plotfun.png", path = out_dir) basename(run_path)
What actually does the saving is itself swappable, via save_fun. The default is ggplot2::ggsave(), but anything sharing its signature works, including third-party wrappers like reportifyr::ggsave_with_metadata(). As a self-contained illustration, so this vignette doesn't need to depend on a third-party package just to demonstrate the idea, here's a save_fun that writes a plain-text note alongside the image:
save_with_note <- function(plot, filename, path = NULL, width, height, note = "", ...) { saved <- ggplot2::ggsave(filename = filename, plot = plot, path = path, width = width, height = height, ...) writeLines(note, sub("\\.[^.]+$", ".txt", saved)) saved } annotated_path <- ggsave_xp( p, filename = "dv_vs_ipred_annotated.png", path = out_dir, save_fun = save_with_note, note = "Generated for the Q3 interim analysis." ) # the .png was written by ggplot2::ggsave() as usual; the .txt is new readLines(sub("\\.png$", ".txt", annotated_path))
Any function accepting plot/filename/path/width/height (plus whatever else it needs via ...) can be dropped in this way. There's nothing xpose.xtras-specific about save_fun itself.
Everything above can be called explicitly, but it doesn't have to be. Once a default_labs/default_watermark is actually configured, at the option level, as in the examples so far, print.xpose_plot() and ggsave_xp() apply it on their own:
options( xpose.xtras.default_labs = list(caption = "DRAFT: do not distribute"), xpose.xtras.default_watermark = list(label = "DRAFT") ) dv_vs_ipred(xpdb_ex_pk, quiet = TRUE)
No apply_default_labs(), no add_watermark(), just an ordinary dv_vs_ipred() call, printed the ordinary way. This is exactly what happens when a plot auto-prints at the console or in a knitted report. It's controlled by the xpose.xtras.auto_apply option, TRUE unless changed. Note that it's the configured default that's applied automatically, not an unconditional one: with default_watermark left unset, auto_apply = TRUE would not stamp a "DRAFT" on every plot on its own. It's purely "apply what was already configured, without asking again."
To turn this off everywhere at once:
options(xpose.xtras.auto_apply = FALSE) dv_vs_ipred(xpdb_ex_pk, quiet = TRUE)
options( xpose.xtras.auto_apply = NULL, xpose.xtras.default_labs = NULL, xpose.xtras.default_watermark = NULL )
Or, to opt out for one ggsave_xp() call without touching the option, pass apply_labs = FALSE/apply_watermark = FALSE directly to that call.
Either way, this only ever reaches the session-wide option tier. print() has no way to receive the xpdb a plot was built from, so an xpdb-level default (set via set_default_labs()/set_default_watermark()) still needs apply_default_labs()/add_watermark(), or ggsave_xp()'s xpdb argument, called explicitly. Auto-apply covers the common "one default for the whole session" case, not the per-model one.
Setting several of the options above one at a time is easy to get wrong: a typo in an option name just silently does nothing, since options() doesn't validate names. set_xtras_options() is a small, validated wrapper for the whole xpose.xtras.* family (prefix added automatically), good for pinning a project's defaults once at the top of a script:
set_xtras_options( default_labs = list(caption = "DRAFT"), default_watermark = list(label = "DRAFT"), save_dir = tempdir(), save_width = 6, save_height = 4 ) # labels and watermark are auto-applied, and save_dir/save_width/save_height # supply filename's path/width/height: one call, nothing else configured saved_together <- ggsave_xp(dv_vs_ipred(xpdb_ex_pk, quiet = TRUE), filename = "tied_together.png") basename(saved_together)
preview_path <- knitr::fig_path("png") dir.create(dirname(preview_path), recursive = TRUE, showWarnings = FALSE) file.copy(saved_together, preview_path, overwrite = TRUE) knitr::include_graphics(preview_path)
The same family also covers a session-wide default theme, gg_theme/xp_theme, picked up automatically the first time an xpose_data object is converted with as_xpdb_x(). A project look can be set once instead of calling xpose::update_themes() on every model:
set_xtras_options(gg_theme = theme_bw) xpdb_ex_pk %>% as_xpdb_x() %>% dv_vs_ipred(quiet = TRUE)
get_xtras_option() reports, for a given option and (optionally) a specific xpdb, what's currently set at each tier and which one would win:
xpdb_labelled <- xpdb_x %>% set_default_labs(caption = "Model-specific caption") get_xtras_option("default_labs", xpdb_labelled)
set_xtras_options( default_labs = NULL, default_watermark = NULL, save_dir = NULL, save_width = NULL, save_height = NULL, gg_theme = NULL )
The full list of recognized options, what each one does, and which ones have an xpdb-level counterpart, is documented on ?set_xtras_options.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.