| Title: | A Unified Toolkit for Visualising Statistical Models and Data |
|---|---|
| Description: | A cohesive, publication-ready toolkit of plots that span the whole analysis workflow with one consistent look. It covers exploratory data analysis (distributions, categorical summaries, bivariate plots, scatter-plot matrices, correlation heatmaps, missing-data maps, outliers, estimation statistics and descriptive tables); multivariate analysis, clustering with diagnostics and Kaplan-Meier survival curves; time series (trends, autocorrelation, decomposition, seasonality and forecasting); model estimates and inference (forest plots, model comparison, frequentist and Bayesian estimates, predicted values, interactions, random effects and optimiser checks); diagnostics and classification (residual panels, binned residuals, influence, quantile-quantile, receiver operating characteristic (ROC) curves, calibration, threshold tuning and confusion matrices); uncertainty and power; and reporting helpers (a shared theme, colourblind-aware palettes, plot composition and saving). Every plotting function returns a 'ggplot2' object (or a 'patchwork' object for composite panels), heavier modelling back-ends are optional, and the package ships with reproducibly simulated datasets so that every example and vignette runs without further setup. |
| Authors: | Pablo Bernabeu [aut, cre] (ORCID: <https://orcid.org/0000-0003-1083-2460>) |
| Maintainer: | Pablo Bernabeu <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.3.0 |
| Built: | 2026-10-15 20:37:06 UTC |
| Source: | https://github.com/pablobernabeu/depictr |
Plots the autocorrelation (or partial autocorrelation) function of a series, with the approximate significance bounds, as a clean lollipop chart.
acf_plot( x, lag_max = NULL, type = c("correlation", "partial"), conf_level = 0.95, title = NULL, x_lab = "Lag", y_lab = NULL )acf_plot( x, lag_max = NULL, type = c("correlation", "partial"), conf_level = 0.95, title = NULL, x_lab = "Lag", y_lab = NULL )
x |
A numeric vector or |
lag_max |
Maximum lag (passed to |
type |
|
conf_level |
Confidence level for the significance bounds; a single number strictly between 0 and 1. |
title, x_lab, y_lab
|
Title and axis labels. |
A missing value inside the series is kept in its place, and the
correlations are computed from the pairs of observations that are both
present (na.action = na.pass), with a warning. Dropping it would join the
observations on either side of the gap as if they were neighbours. The
Python package refuses such a series and asks for the gap to be dropped or
imputed first.
A ggplot2::ggplot object.
acf_plot(AirPassengers) acf_plot(AirPassengers, type = "partial")acf_plot(AirPassengers) acf_plot(AirPassengers, type = "partial")
A thin, friendly wrapper around patchwork::wrap_plots() that adds the
common finishing touches: collecting duplicate legends into one, an overall
title and subtitle, and automatic panel tags (A, B, C, ...).
arrange_plots( ..., ncol = NULL, nrow = NULL, guides = c("collect", "keep"), title = NULL, subtitle = NULL, tag_levels = "A" )arrange_plots( ..., ncol = NULL, nrow = NULL, guides = c("collect", "keep"), title = NULL, subtitle = NULL, tag_levels = "A" )
... |
Plots to combine, or a single list of plots. |
ncol, nrow
|
Layout dimensions (passed to patchwork). |
guides |
How to treat legends: |
title, subtitle
|
Overall title and subtitle. |
tag_levels |
Panel tag style, e.g. |
A 'patchwork' object.
p1 <- explore_distribution(crop_yield, yield) p2 <- scatter_trend(crop_yield, fertiliser, yield) arrange_plots(p1, p2, ncol = 2, title = "Crop yield", tag_levels = "A")p1 <- explore_distribution(crop_yield, yield) p2 <- scatter_trend(crop_yield, fertiliser, yield) arrange_plots(p1, p2, ncol = 2, title = "Crop yield", tag_levels = "A")
For a fitted glm (especially binary or count models), a plot of raw
residuals against fitted values is hard to read because the residuals take
only a few values. Following Gelman and Hill (2007), this plot instead splits
the data into equal-count bins of fitted values and plots, for each bin, the
mean residual against the mean fitted value, inside a grey band of
standard errors of that mean. Under a well-specified model, the
binned residuals scatter around zero, and about 95% of them should fall
within the band. Systematic departures (a trend, or many points outside the
band) indicate a misspecified mean structure.
binned_residual_plot( model, bins = NULL, type = c("response", "pearson"), point_colour = depictr_brand(), band_colour = depictr_accent(), title = NULL, band = c("model", "empirical") )binned_residual_plot( model, bins = NULL, type = c("response", "pearson"), point_colour = depictr_brand(), band_colour = depictr_accent(), title = NULL, band = c("model", "empirical") )
model |
A fitted |
bins |
Number of bins. The default follows Gelman and Hill's rule of
thumb of roughly |
type |
Residual type used for the bin means: |
point_colour, band_colour
|
Colours for the bin points and the
|
title |
Plot title. |
band |
Where the standard errors come from. The default, |
By default (band = "model"), the standard error is the one the fitted
model implies. The model gives the response the variance
, where is the family's variance
function, the prior weight and the dispersion. The
dispersion is 1 for the binomial and Poisson families,
summary(model)$dispersion for the others and for an
lm. Taking this as the variance of the response residual
, the band gives the mean of the residuals in
bin the standard error
. A Pearson
residual, , then has
variance , so its bin mean has standard error
. The fit absorbs a little of each observation's
variance, so a residual varies slightly less than this and the band errs on
the wide side. Observations with zero prior weight take no part in the fit
and are left out of the bins.
band = "empirical" draws the band of depictr 0.3.0 and earlier, the one
arm::binnedplot() draws: twice the standard deviation of the residuals in
the bin, divided by . It collapses wherever the outcomes in
a bin are all alike. The residuals in a logistic model's bin without events
all lie just below zero, so its band nearly vanishes and the bin is flagged.
Where events are rare, such bins are what the model expects. The example
below, a correctly specified model, has five of its seventeen bins flagged
by that band and none by the model's.
A ggplot2::ggplot object. The per-bin summary (mean fitted value,
mean residual, number of observations n, the band's half-width se2
and an outside flag) is attached as attr(plot, "bins").
Gelman A, Hill J (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press, Cambridge, UK. ISBN 978-0-521-68689-1. doi:10.1017/CBO9780511790942.
gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) binned_residual_plot(gfit) # The band of depictr 0.3.0, which flags the bins that hold no events: binned_residual_plot(gfit, band = "empirical")gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) binned_residual_plot(gfit) # The band of depictr 0.3.0, which flags the bins that hold no events: binned_residual_plot(gfit, band = "empirical")
Assesses how well predicted probabilities match observed frequencies. The scores are split into bins; for each bin the mean predicted probability is plotted against the observed event rate, with the diagonal marking perfect calibration. A Wilson binomial confidence interval is drawn on each bin's observed proportion so that bins backed by few observations are not over-interpreted. Pass a named list of models / (actual, score) pairs to overlay several colour-coded calibration curves with a legend.
calibration_plot( x, score = NULL, bins = 10, colour = depictr_brand(), conf_level = 0.95, title = NULL )calibration_plot( x, score = NULL, bins = 10, colour = depictr_brand(), conf_level = 0.95, title = NULL )
x |
A binomial |
score |
When |
bins |
Number of (equal-count) bins. |
colour |
Point/line colour for the single-model case. Defaults to the depictr brand blue. Ignored when several models are overlaid. |
conf_level |
Confidence level for the per-bin Wilson interval on the
observed proportion, a single number strictly between 0 and 1. Use |
title |
Plot title. |
A ggplot2::ggplot object.
# Calibration is judged against the base rate, so the example uses the rare # clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) calibration_plot(gfit, bins = 6)# Calibration is judged against the base rate, so the example uses the rare # clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) calibration_plot(gfit, bins = 6)
Checks a figure as it will be submitted, rather than the palette it was built
from. palette_safety() can promise that the eight colours depictr ships
stay apart under colour-vision deficiency, but it knows nothing about the
figure in front of you: how many of those colours it uses, what you replaced
them with, how small the text will be once the figure is squeezed into a
journal column, or whether the only thing separating two groups is their
colour. check_figure() reads the built plot and answers those
questions with numbers.
check_figure( plot, width_cm = 17.78, render_width_cm = 17.78, min_delta_e = 5, min_text_pt = 6, metric = c("ciede2000", "cie76"), max_text_pt = NULL )check_figure( plot, width_cm = 17.78, render_width_cm = 17.78, min_delta_e = 5, min_text_pt = 6, metric = c("ciede2000", "cie76"), max_text_pt = NULL )
plot |
A plot, as returned by any depictr plotting function, including
one extended afterwards with |
width_cm |
The width, in centimetres, that the figure will occupy in the
finished document. Defaults to 17.78 cm, the seven inches |
render_width_cm |
The width, in centimetres, that the figure is drawn at. Defaults to the same 17.78 cm. The ratio of the two widths is the factor every point size is multiplied by. |
min_delta_e |
The smallest acceptable colour difference, on the scale
of |
min_text_pt |
The smallest acceptable printed text size, in points, for the text around the panel and the text inside it alike. Defaults to 6, below the floor most journals set (see "Auditing at the printed size"). |
metric |
The colour difference the colour separability checks measure:
|
max_text_pt |
The largest acceptable printed text size, in points, or
|
Every row carries the value it measured next to the threshold it was measured
against, so a verdict can be argued with rather than merely accepted. A check
passes when the measured value is at least the threshold. The one exception
is text_size_max, a ceiling added when max_text_pt is given, which
passes when the measured value is at most its threshold. A check that has
nothing to measure, such as colour separability on a figure that encodes
nothing by colour, reports NA and a verdict of "not applicable" instead
of a free pass.
A data frame with one row per check and columns check, measured,
threshold, verdict ("pass", "fail" or "not applicable") and
detail, a short note naming what produced the measurement. There are
ten rows, and an eleventh, text_size_max, when max_text_pt is given.
For a composite, the rows of each panel in turn, after a leading integer
column panel.
Colours and text are measured as drawn. A translucent colour is composited over the background it is drawn on with the alpha it is drawn with, so it is measured as the paler colour the reader sees. As ggplot2 draws them, bars, tiles, areas, densities, ribbons, polygons, violins, boxplots, crossbars and smooths apply a layer's alpha to their fill and leave the outline opaque. Lines, paths, segments, error bars and reference lines apply it to their colour, and points apply it to both. Any other geom is taken to apply it to both, the cautious assumption. A colour that carries its own alpha keeps it when the layer sets none, as ggplot2 draws it. A colour drawn at several alphas within one layer is measured at the smallest. Where marks overlap one another, the colour that shows depends on what lies beneath it, and that is not measured.
colour_separability is the smallest colour difference between two of the
figure's encoding colours, by the CIEDE2000 formula (Sharma, Wu & Dalal,
2005) unless metric asks for the older CIE76. The three
colour_separability_* rows repeat that measurement after simulating each
dichromacy at full severity with simulate_cvd(). Encoding colours are the
distinct colour and fill values that tell groups apart. A layer contributes
its colours when it maps colour or fill to a discrete scale, even if it
draws a single colour, or when it draws two or more. Groups drawn one per
layer are therefore audited as they would be in one layer. A colour set as a
constant, as in geom_point(colour = "red"), is left out, even when another
layer sets a different one, and so is a lone colour passed through
ggplot2::scale_colour_identity(). The audit cannot tell such a colour from
decoration, so a colour meant to tell groups apart should be mapped, with
ggplot2::scale_colour_manual() to fix its values. The outline colours,
across every layer, are compared among themselves, and so are the fill
colours, and the row reports the closest pair from either set. An outline is
therefore never compared with its own fill, nor a group with itself as
another layer draws it.
A continuous colour or fill scale, such as that of a heatmap, is a smooth
ramp rather than a set of codes, and none of the colour rows audits it. When
nothing else in the figure encodes by colour, each of them abstains with a
detail that says so. Whether a ramp reads in order, to a reader with a
colour-vision deficiency and in greyscale as well, depends on how evenly its
lightness changes, which the audit does not measure. Prefer a perceptually
ordered map, such as viridis or depictr's sequential and diverging ramps,
over a rainbow (Crameri et al., 2020).
greyscale_separability is the smallest difference in CIE lightness between
those same colours, which is what survives printing in black and white. It
is the absolute difference in lightness, whichever metric the colour rows
use.
text_size is the smallest point size of the text the theme draws around
the panel: axis labels and titles, legends, facet strips, titles and
captions. It is scaled by width_cm / render_width_cm, so a figure drawn
seven inches wide and printed in an 8.9 cm column has every point size
halved. Text drawn inside the panel is measured by annotation_text_size.
text_contrast is the WCAG 2.2 contrast ratio of the text that falls
furthest short of its own threshold. Each text is measured against the
background it is drawn on: a facet strip's fill for strip text, the legend
box's fill for legend text, and the plot background for the rest. A
translucent fill is measured as the colour it shows over what lies beneath
it, and so is text drawn with an alpha. Text that is large at its printed
size, 18 pt or 14 pt in bold, is held to 3:1, and other text to 4.5:1, as
success criterion 1.4.3 asks. The row's threshold is the one that applies
to the text it reports, and its detail names that text's colour,
background, printed size and weight.
geometry_contrast is the smallest WCAG 2.2 contrast ratio of any encoded
group's drawn colour against the panel background, or against the plot
background where the panel paints none. Where a layer maps its outline
colour to the same groups as its fill, each group counts the better of the
two, since WCAG lets a border carry a graphical object's contrast. For a
translucent colour, the detail names the colour, its alpha and the colour
it is drawn as.
redundant_encoding counts how many of shape and line type also vary
across the layers that draw the encoding colours. They are gathered as the
colours are. A layer contributes the values it maps to a discrete scale, or
the two or more it draws, and a shape or line type set as a constant is left
out. Zero means the distinction between groups is carried by colour alone,
which is the single most common way an otherwise careful figure becomes
unreadable.
annotation_text_size is the smallest point size of the text drawn inside
the panel, scaled as text_size is and held to the same min_text_pt. That
text comes from layers such as ggplot2::geom_text() and
ggplot2::geom_label(), from annotations, and from depictr's own cell values
and labels. Its contrast is not measured, since it sits on the marks rather
than on one background. A label that a layer builds only when the figure is
drawn, as ggrepel builds its repelled labels, is not yet in the built plot,
so it is not measured either.
text_size_max is reported only when max_text_pt is given, as the last
row. It is the largest point size of all the text measured, around the
panel and inside it, scaled as text_size is. It is the one row that
passes at or below its threshold.
The eight-colour qualitative palette clears the colour-separability checks
comfortably and fails greyscale_separability: its orange (#e69f00) and
sky blue (#56b4e9) differ by only 0.79 in lightness, so they print as the
same grey. The colourblind-safety guarantee depictr makes is about hue
confusion, and it was never a claim about greyscale. A figure that may be
printed in black and white should use fewer groups, or a sequential palette,
or add a redundant shape or line type; the four leading colours of the
palette are also not safe in greyscale, since the bluish green and the
vermillion differ by 3.55. The threshold has been left where it is rather
than moved to let the package's own defaults through.
The palette falls short on contrast as well. Four of its eight colours are
below the 3:1 against white that WCAG asks of a graphical object: the orange
at 2.25:1, the sky blue at 2.31:1, the grey at 2.85:1 and the yellow at
1.32:1. The orange is the second colour, so a default figure on white whose
colour tells two or more groups apart fails geometry_contrast, and drawing
its marks translucent lowers the ratio further. Opaque marks, the vermillion
(#d55e00, at 3.87:1) in the orange's place and a redundant shape clear
that row and redundant_encoding, as the first example below shows. The
contrast-first order of depictr_palette() puts the vermillion second and
the reddish purple third, and scale_colour_depictr() takes it as
order = "contrast". Up to three groups then pass every colour row, and
four keep 3:1 against white.
Text is drawn in points, so the size it prints at depends on the two widths.
A figure drawn at the size it will be printed, as save_plot() draws one
given width = 8.9, units = "cm", needs no scaling. Pass the saved width as
both width_cm and render_width_cm, or leave both at their defaults.
Given alone, width_cm audits the figure as though it had been drawn seven
inches wide and then shrunk to fit. save_plot() with check = TRUE audits
a figure at the width it saves it at, given as both.
The default min_text_pt of 6 is lenient. As their guidelines stood on 30
September 2026, PLOS asked for text of 8 to 12 pt and APA Style for 8 to 14
pt. Elsevier asked for 7 pt as a rule of thumb, and Nature for 5 to 7 pt,
with panel labels at 8 pt. Pass the floor of the journal the figure is for
as min_text_pt, and its ceiling, where it sets one, as max_text_pt.
Nature's 7 pt applies to all text but the panel labels. The tags of a
composite, where patchwork puts them, are not measured, but a panel label
drawn as a panel's title or annotation is measured with the rest. The four
pages are listed under References.
A composite, such as arrange_plots(), residual_diagnostics_plot() and
model_report() return, is audited panel by panel. Each panel has its own
scales, theme and text, so each is audited as it would be on its own. The
reports are stacked under a leading integer column, panel, which only the
report of a composite has. The widths are those of the whole composite. A
panel's text keeps its point size whatever share of the width the panel
takes, so the text of every panel scales by the same ratio.
Panels are numbered depth-first through any composite nested inside, in the order the composite holds them. That is the order patchwork tags them in, and the reading order of a grid filled row by row. A spacer or a guide area draws no figure of its own and takes no number. A wrapped element, such as a grob or a table, cannot be read, so it keeps its number and is left out of the report with a warning.
A panel's legend is measured with the panel, as the panel would draw it on
its own, even when guides = "collect" gathers it into one legend for the
composite. What the composite draws around its panels is not measured: the
title, subtitle and caption of patchwork::plot_annotation() and the panel
tags.
APA Style. Figures. https://apastyle.apa.org/style-grammar-guidelines/tables-figures/figures
Crameri F, Shephard GE, Heron PJ (2020). “The misuse of colour in science communication.” Nature Communications, 11(1), 5444. doi:10.1038/s41467-020-19160-7.
Elsevier. Artwork sizing. https://www.elsevier.com/about/policies-and-standards/author/artwork-and-media-instructions/artwork-sizing
Machado GM, Oliveira MM, Fernandes LAF (2009). “A physiologically-based model for simulation of color vision deficiency.” IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. doi:10.1109/TVCG.2009.113.
Nature. Guide to preparing final artwork. https://www.nature.com/documents/nature-final-artwork.pdf
PLOS ONE. Figures. https://journals.plos.org/plosone/s/figures
Sharma G, Wu W, Dalal EN (2005). “The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations.” Color Research & Application, 30(1), 21–30. doi:10.1002/col.20070.
World Wide Web Consortium (2023). “Web Content Accessibility Guidelines (WCAG) 2.2.” W3C Recommendation, https://www.w3.org/TR/WCAG22/. Accessed 2026-08-18.
palette_safety() for the palette in the abstract, and
palette_preview() to look at it.
library(ggplot2) # A figure that clears every check it can be held to: two well-separated # colours, a redundant shape, and text left at the size it was drawn. It # draws no text inside the panel, so that row is not applicable. good <- ggplot(crop_yield, aes(rainfall, yield, colour = treatment, shape = treatment)) + geom_point() + scale_colour_manual(values = c("#005b96", "#d55e00")) + theme_depictr() check_figure(good) # The same figure destined for an 8.9 cm journal column, where the text is # half the size it looks on screen. check_figure(good, width_cm = 8.9) # Saved at 8.9 cm in the first place, with save_plot(width = 8.9, # units = "cm"), it is drawn at the size it prints, so both widths are 8.9. check_figure(good, width_cm = 8.9, render_width_cm = 8.9) # Nature allowed no text above 7 pt but the panel labels. The axis titles # here are 11 pt, so the ceiling row fails. subset(check_figure(good, max_text_pt = 7), check == "text_size_max") # Each panel of a composite is audited, and a column says which panel each # row describes. At base size 5, the second panel's text is too small. small <- good + theme_depictr(base_size = 5) both <- check_figure(arrange_plots(good, small)) both[both$verdict == "fail", c("panel", "check", "measured")]library(ggplot2) # A figure that clears every check it can be held to: two well-separated # colours, a redundant shape, and text left at the size it was drawn. It # draws no text inside the panel, so that row is not applicable. good <- ggplot(crop_yield, aes(rainfall, yield, colour = treatment, shape = treatment)) + geom_point() + scale_colour_manual(values = c("#005b96", "#d55e00")) + theme_depictr() check_figure(good) # The same figure destined for an 8.9 cm journal column, where the text is # half the size it looks on screen. check_figure(good, width_cm = 8.9) # Saved at 8.9 cm in the first place, with save_plot(width = 8.9, # units = "cm"), it is drawn at the size it prints, so both widths are 8.9. check_figure(good, width_cm = 8.9, render_width_cm = 8.9) # Nature allowed no text above 7 pt but the panel labels. The axis titles # here are 11 pt, so the ceiling row fails. subset(check_figure(good, max_text_pt = 7), check == "text_size_max") # Each panel of a composite is audited, and a column says which panel each # row describes. At base size 5, the second panel's text is too small. small <- good + theme_depictr(base_size = 5) both <- check_figure(arrange_plots(good, small)) both[both$verdict == "fail", c("panel", "check", "measured")]
A reproducibly simulated randomised clinical trial, for time-to-event
(survival) and imbalanced-classification examples. The two arms have a real
survival difference: the treatment arm has a lower hazard, so the
Kaplan-Meier curves separate clearly and the log-rank test is highly
significant, and - because its event times are longer - the treatment arm is
more often right-censored by the 36-month study end. age and biomarker
also affect the hazard. The data feed survival_plot() (pass time,
event and arm, or a survival::survfit object).
clinical_trialclinical_trial
A data frame with 300 rows and 7 variables:
Patient identifier.
Treatment arm (factor): placebo or treatment.
Age in years at enrolment.
Standardised baseline laboratory value.
Follow-up time, in months.
Event indicator: 1 event observed, 0 right-censored.
Rare adverse-event indicator: 1 yes, 0 no (about 10% positive).
adverse_event is a deliberately rare binary safety outcome (about a 10%
base rate) that is predictable from arm, age and biomarker. It gives
the precision-recall (pr_curve_plot()), cumulative-gains (gain_plot()),
lift (lift_plot()) and calibration (calibration_plot()) demos an
imbalanced target, where those charts are most informative.
The data are synthetic, generated by data-raw/generate_datasets.R with a
fixed seed; they do not describe any real individuals.
Simulated; see data-raw/generate_datasets.R.
Runs k-means clustering on the numeric columns of a data frame (or uses a supplied cluster assignment) and plots the observations on the first two principal components, coloured by cluster, with optional convex hulls and labelled centroids. Projecting onto principal components means the plot works for any number of variables.
cluster_plot( data, cols = NULL, k = 3, clusters = NULL, scale = TRUE, suggest_k = NULL, k_range = NULL, hulls = TRUE, label_centers = TRUE, point_alpha = 0.8, palette = NULL, title = NULL, seed = NULL, nstart = 10, iter.max = 10 )cluster_plot( data, cols = NULL, k = 3, clusters = NULL, scale = TRUE, suggest_k = NULL, k_range = NULL, hulls = TRUE, label_centers = TRUE, point_alpha = 0.8, palette = NULL, title = NULL, seed = NULL, nstart = 10, iter.max = 10 )
data |
A data frame. |
cols |
Numeric columns to cluster on (default: all numeric). |
k |
Number of clusters for k-means (ignored if |
clusters |
Optional vector of cluster assignments (e.g. from
|
scale |
Whether to scale variables to unit variance before clustering
and the PCA. Columns with (near-)zero variance cannot be scaled and are
dropped with a message, as in |
suggest_k |
Optionally choose |
k_range |
Candidate values of |
hulls |
Whether to draw a shaded convex hull around each cluster. |
label_centers |
Whether to label the cluster centroids. |
point_alpha |
Point transparency. |
palette |
Colours for the clusters; defaults to |
title |
Plot title. |
seed |
Optional integer seed for reproducible k-means (ignored when
|
nstart |
Number of random starts for |
iter.max |
Maximum iterations for |
A ggplot2::ggplot object. When suggest_k is used, the chosen k
and the full diagnostic are attached as the attribute "k_diagnostic".
k_diagnostic() and silhouette_plot() for cluster-quality
diagnostics.
cluster_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), k = 3, seed = 1) # Let a silhouette diagnostic choose k: cluster_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), suggest_k = TRUE, k_range = 2:6, seed = 1)cluster_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), k = 3, seed = 1) # Let a silhouette diagnostic choose k: cluster_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), suggest_k = TRUE, k_range = 2:6, seed = 1)
Draws a horizontal point-and-interval ("forest") plot of model estimates.
The input can be a fitted model (anything tidy_estimates() understands) or
a data frame of pre-computed estimates.
coefficient_plot( x, conf_level = 0.95, intercept = FALSE, order = c("none", "ascending", "descending"), labels = NULL, interaction = c("times", "asterisk", "colon", "space"), point_colour = depictr_brand(), reference_colour = depictr_reference(), reference_line = if (exponentiate) 1 else 0, point_size = 2.2, line_size = 0.7, facet = FALSE, scales = c("fixed", "free"), standardise = FALSE, sd_multiple = 1, exponentiate = FALSE, title = NULL, subtitle = NULL, x_lab = NULL )coefficient_plot( x, conf_level = 0.95, intercept = FALSE, order = c("none", "ascending", "descending"), labels = NULL, interaction = c("times", "asterisk", "colon", "space"), point_colour = depictr_brand(), reference_colour = depictr_reference(), reference_line = if (exponentiate) 1 else 0, point_size = 2.2, line_size = 0.7, facet = FALSE, scales = c("fixed", "free"), standardise = FALSE, sd_multiple = 1, exponentiate = FALSE, title = NULL, subtitle = NULL, x_lab = NULL )
x |
A fitted model or a tidy data frame of estimates. |
conf_level |
Confidence/credible level, passed to |
intercept |
Whether to keep the intercept term. Defaults to |
order |
Order of the terms. |
labels |
Optional display labels for the terms. An unnamed vector gives
one label per term in model order (the order |
interaction |
Passed to |
point_colour, reference_colour
|
Colours for the estimates and the reference line. |
reference_line |
Position of a vertical reference line. Defaults to 0,
or to 1 when |
point_size, line_size
|
Size of the points and interval lines. |
facet |
Whether to give each term its own panel with a free x-axis,
laid out one per row. This removes the squish that occurs when terms live
on very different scales (for example a large intercept alongside small
slopes). Defaults to |
scales |
Either |
standardise |
Whether to scale each coefficient by the spread of its
inputs, following Gelman (2008). A continuous input (numeric, with more
than two distinct values) is scaled by |
sd_multiple |
How many standard deviations of a continuous input one
unit of the standardised scale spans when |
exponentiate |
Whether to draw the exponentiated estimates and
intervals on a log10 axis, which shows the coefficients of a logistic
regression as odds ratios and those of a Poisson or negative binomial
model as rate ratios. It applies after |
title, subtitle, x_lab
|
Plot title, subtitle and x-axis label. |
A ggplot2::ggplot object.
Gelman A (2008). “Scaling regression inputs by dividing by two standard deviations.” Statistics in Medicine, 27(15), 2865–2873. doi:10.1002/sim.3107.
fit <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) coefficient_plot(fit) # Order terms and add a title coefficient_plot(fit, order = "descending", title = "Drivers of crop yield") # When an intercept or large term squishes the rest, give each term its own # free-scaled panel: coefficient_plot(fit, intercept = TRUE, facet = TRUE) # Or scale the continuous slopes per SD of their predictors, leaving the # treatment contrast in the outcome's units: coefficient_plot(fit, standardise = TRUE) # Odds ratios from a logistic regression, on a log axis with the line at 1: gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) coefficient_plot(gfit, exponentiate = TRUE)fit <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) coefficient_plot(fit) # Order terms and add a title coefficient_plot(fit, order = "descending", title = "Drivers of crop yield") # When an intercept or large term squishes the rest, give each term its own # free-scaled panel: coefficient_plot(fit, intercept = TRUE, facet = TRUE) # Or scale the continuous slopes per SD of their predictors, leaving the # treatment contrast in the outcome's units: coefficient_plot(fit, standardise = TRUE) # Odds ratios from a logistic regression, on a log axis with the line at 1: gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) coefficient_plot(gfit, exponentiate = TRUE)
Overlays the estimates from two or more models (or tidy estimate tables) on a
single forest plot, with one colour per source. It is the general engine
behind frequentist_bayesian_plot() (frequentist against Bayesian), and
serves equally well for comparing nested models, several optimisers, or
estimates before and after a transformation.
compare_models( ..., names = NULL, conf_level = 0.95, intercept = FALSE, order = c("none", "ascending", "descending"), labels = NULL, interaction = c("times", "asterisk", "colon", "space"), dodge_width = 0.6, reference_line = if (exponentiate) 1 else 0, palette = NULL, point_size = 2.2, line_size = 0.7, facet = FALSE, scales = c("fixed", "free"), exponentiate = FALSE, legend_title = "Source", legend_ncol = 1, title = NULL, subtitle = NULL, x_lab = NULL )compare_models( ..., names = NULL, conf_level = 0.95, intercept = FALSE, order = c("none", "ascending", "descending"), labels = NULL, interaction = c("times", "asterisk", "colon", "space"), dodge_width = 0.6, reference_line = if (exponentiate) 1 else 0, palette = NULL, point_size = 2.2, line_size = 0.7, facet = FALSE, scales = c("fixed", "free"), exponentiate = FALSE, legend_title = "Source", legend_ncol = 1, title = NULL, subtitle = NULL, x_lab = NULL )
... |
Two or more fitted models and/or tidy data frames of estimates.
Name the arguments to label the sources (e.g.
|
names |
Optional character vector of source labels, overriding the
names of |
conf_level |
Confidence/credible level for models, a single number strictly between 0 and 1. |
intercept |
Whether to keep the intercept term. Defaults to |
order |
Order of the terms, by their average estimate across sources.
|
labels |
Optional display labels. An unnamed vector gives one label per
term, whatever |
interaction |
Passed to |
dodge_width |
Vertical spacing between sources sharing a term. |
reference_line |
Position of a vertical reference line. Defaults to 0,
or to 1 when |
palette |
Colours for the sources; defaults to |
point_size, line_size
|
Point and interval-line sizes. |
facet |
Whether to give each term its own panel with a free x-axis,
laid out one per row, so that terms on very different scales (for example a
large intercept alongside small slopes) stay legible. The source overlay
and its single legend are preserved within the faceted layout. Defaults to
|
scales |
Either |
exponentiate |
Whether to draw every source's exponentiated estimates
and intervals on a log10 axis, as odds or rate ratios. Each model is
exponentiated by |
legend_title, legend_ncol
|
Legend title and number of columns. |
title, subtitle, x_lab
|
Title, subtitle and x-axis label. |
A ggplot2::ggplot object.
m1 <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) m2 <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield[crop_yield$treatment == "standard", ]) compare_models(`All fields` = m1, `Standard only` = m2, title = "Estimates by subset") # Keep the intercept legible alongside the slopes: compare_models(`All fields` = m1, `Standard only` = m2, intercept = TRUE, facet = TRUE) # Odds ratios from two logistic regressions: g1 <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) g2 <- glm(accuracy ~ word_frequency + condition + modality, data = lexical_decision, family = binomial) compare_models(Reduced = g1, Full = g2, exponentiate = TRUE)m1 <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) m2 <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield[crop_yield$treatment == "standard", ]) compare_models(`All fields` = m1, `Standard only` = m2, title = "Estimates by subset") # Keep the intercept legible alongside the slopes: compare_models(`All fields` = m1, `Standard only` = m2, intercept = TRUE, facet = TRUE) # Odds ratios from two logistic regressions: g1 <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) g2 <- glm(accuracy ~ word_frequency + condition + modality, data = lexical_decision, family = binomial) compare_models(Reduced = g1, Full = g2, exponentiate = TRUE)
Cross-tabulates predicted against actual classes and displays the counts as a
heatmap. The input can be a fitted binomial glm (with a probability
threshold) or a pair of vectors of actual and predicted classes. Each count
is drawn in black or white, whichever has the higher contrast against its
cell.
confusion_matrix_plot( x, predicted = NULL, threshold = 0.5, normalise = c("none", "row", "col"), title = NULL )confusion_matrix_plot( x, predicted = NULL, threshold = 0.5, normalise = c("none", "row", "col"), title = NULL )
x |
A binomial |
predicted |
When |
threshold |
When |
normalise |
One of |
title |
Plot title. |
A ggplot2::ggplot object. The threshold actually used is stored in
attr(plot, "threshold").
gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) confusion_matrix_plot(gfit, threshold = 0.5) # Reuse the Youden-optimal operating point. confusion_matrix_plot(gfit, threshold = "youden")gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) confusion_matrix_plot(gfit, threshold = 0.5) # Reuse the Youden-optimal operating point. confusion_matrix_plot(gfit, threshold = "youden")
Computes the pairwise correlations between the numeric columns of a data frame and displays them as a colour-coded heatmap, optionally annotated with the correlation values.
correlation_heatmap( data, cols = NULL, method = "pearson", use = "pairwise.complete.obs", show_values = TRUE, digits = 2, palette = depictr_palette(5, "diverging")[c(1, 3, 5)], reorder = FALSE, title = NULL )correlation_heatmap( data, cols = NULL, method = "pearson", use = "pairwise.complete.obs", show_values = TRUE, digits = 2, palette = depictr_palette(5, "diverging")[c(1, 3, 5)], reorder = FALSE, title = NULL )
data |
A data frame. |
cols |
Optional character vector of columns to include, each numeric or
logical. Any other column, such as a factor or a date, is an error that
names it. If |
method |
Correlation method: |
use |
Missing-value handling passed to |
show_values |
Whether to annotate each cell with its correlation, drawn in black or white, whichever has the higher contrast against the cell. |
digits |
Number of decimal places for the annotations. |
palette |
Length-3 vector of colours for the lowest, mid (zero) and
highest correlations. Defaults to the endpoints and midpoint of the
colourblind-aware |
reorder |
Whether to reorder the variables by hierarchical clustering of
the correlation matrix (using |
title |
Plot title. |
Columns with (near-)zero variance cannot be correlated and are
dropped automatically with an informative message, so the raw
"the standard deviation is zero" warning from stats::cor() is not
surfaced. If, after dropping them, any cells still come out NA (e.g. two
variables that never co-occur under "pairwise.complete.obs"), those cells
are rendered in grey and labelled n/a rather than left blank.
A ggplot2::ggplot object.
correlation_heatmap(wellbeing_survey) correlation_heatmap(crop_yield, method = "spearman", show_values = FALSE) # Cluster correlated variables together: correlation_heatmap(wellbeing_survey, reorder = TRUE)correlation_heatmap(wellbeing_survey) correlation_heatmap(crop_yield, method = "spearman", show_values = FALSE) # Cluster correlated variables together: correlation_heatmap(wellbeing_survey, reorder = TRUE)
A reproducibly simulated agronomy field trial relating crop yield to
rainfall, fertiliser, soil pH and a management treatment. Used for the
regression, coefficient-plot, scatter-trend and interaction examples. The
data-generating process contains a genuine fertiliser-by-treatment
interaction: fertiliser raises yield far more under the enhanced treatment
than under standard, so interaction_plot(lm(yield ~ fertiliser * treatment + ..., crop_yield), "fertiliser", "treatment") shows real,
diverging slopes rather than noise. The main effects (rainfall, fertiliser,
soil pH and treatment) are retained.
crop_yieldcrop_yield
A data frame with 200 rows and 6 variables:
Field identifier.
Management treatment (factor): standard or enhanced.
Seasonal rainfall, in millimetres.
Fertiliser applied, in kilograms per hectare.
Soil pH.
Crop yield, in tonnes per hectare.
The data are synthetic, generated by data-raw/generate_datasets.R with a
fixed seed.
Simulated; see data-raw/generate_datasets.R.
Decomposes a seasonal time series into trend, seasonal and remainder components and shows them, with the original series, as stacked panels.
decompose_plot( x, frequency = NULL, method = c("stl", "classical"), robust = FALSE, s_window = "periodic", confidence = FALSE, level = 0.95, title = NULL )decompose_plot( x, frequency = NULL, method = c("stl", "classical"), robust = FALSE, s_window = "periodic", confidence = FALSE, level = 0.95, title = NULL )
x |
A |
frequency |
Number of observations per period (e.g. 12 for monthly
data); taken from |
method |
|
robust |
For |
s_window |
For |
confidence |
Whether to draw a confidence ribbon around the trend
component. The band is a normal-approximation interval based on the
remainder's standard deviation (see Details). |
level |
Coverage of the trend confidence ribbon (a single number strictly between 0 and 1). |
title |
Plot title. |
The trend confidence ribbon is a pragmatic normal-approximation band:
trend +/- z * sd(remainder), where z = qnorm((1 + level) / 2) and the
remainder standard deviation is computed on the non-missing remainder values.
It conveys the scale of the unexplained variation around the smoothed trend
rather than a formal sampling-distribution interval.
A 'patchwork' object (printable like a ggplot2::ggplot).
Cleveland RB, Cleveland WS, McRae JE, Terpenning I (1990). “STL: A seasonal-trend decomposition procedure based on loess.” Journal of Official Statistics, 6(1), 3–73.
ts_forecast(), seasonal_plot()
decompose_plot(AirPassengers) decompose_plot(AirPassengers, method = "classical") # Robust STL with a confidence ribbon on the trend decompose_plot(AirPassengers, robust = TRUE, confidence = TRUE) # A seasonal component free to grow with the level of the series decompose_plot(AirPassengers, s_window = 13)decompose_plot(AirPassengers) decompose_plot(AirPassengers, method = "classical") # Robust STL with a confidence ribbon on the trend decompose_plot(AirPassengers, robust = TRUE, confidence = TRUE) # A seasonal component free to grow with the level of the series decompose_plot(AirPassengers, s_window = 13)
Hierarchical-clustering dendrogram, drawn with ggplot2 so it shares the
package theme. Accepts a data frame (the distance matrix and clustering are
computed for you), a stats::dist object, or an stats::hclust object.
Optionally cut the tree into k clusters, colouring the leaf labels and
drawing the cut height.
dendrogram_plot( x, cols = NULL, distance = "euclidean", method = "complete", scale = TRUE, k = NULL, horizontal = FALSE, labels = NULL, palette = NULL, title = NULL )dendrogram_plot( x, cols = NULL, distance = "euclidean", method = "complete", scale = TRUE, k = NULL, horizontal = FALSE, labels = NULL, palette = NULL, title = NULL )
x |
A data frame, a |
cols |
When |
distance |
Distance measure passed to |
method |
Linkage method passed to |
scale |
Whether to scale variables before computing distances (data frame input). |
k |
Optional number of clusters to highlight (between 1 and the number
of leaves). The cut-height line is drawn only when |
horizontal |
Whether to draw the tree horizontally. |
labels |
Whether to print the leaf labels. |
palette |
Colours for the |
title |
Plot title. |
A ggplot2::ggplot object.
# Cluster the survey regions by their wellbeing averages d <- aggregate(cbind(stress, sleep_hours, life_satisfaction) ~ region, data = wellbeing_survey, FUN = mean) rownames(d) <- d$region dendrogram_plot(d[-1], k = 2)# Cluster the survey regions by their wellbeing averages d <- aggregate(cbind(stress, sleep_hours, life_satisfaction) ~ region, data = wellbeing_survey, FUN = mean) rownames(d) <- d$region dendrogram_plot(d[-1], k = 2)
depictr reads a small set of global options so that you can configure the
shared look of every plot once, at the top of a script or in your
.Rprofile, instead of passing the same arguments to each function. The
options are honoured by theme_depictr() (base size, base family and the
brand colour used for titles), by depictr_palette() and the
scale_colour_depictr() family (an optional custom qualitative palette), and
by the colour accessors depictr_brand(), depictr_accent() and
depictr_reference().
depictr_options( base_size, base_family, brand, accent, reference, palette, na_value )depictr_options( base_size, base_family, brand, accent, reference, palette, na_value )
base_size |
Base font size in points for |
base_family |
Base font family for |
brand |
The depictr brand colour, used for plot titles and single-series
geoms. It coincides with the default palette's first colour but does not
alter a palette; use |
accent |
A secondary highlight colour. Returned by |
reference |
The colour used for reference / annotation lines. Returned
by |
palette |
An optional custom qualitative palette: a character vector of
hex colours used by |
na_value |
Colour used for |
Called with no arguments, depictr_options() returns the currently resolved
values (option if set, otherwise package default). Called with named
arguments it sets the matching options(depictr.<name> = ) entries and
returns the previous resolved values invisibly, so the pattern
old <- depictr_options(...); on.exit(do.call(depictr_options, old)) restores
them. Pass NULL for an argument to clear that option and fall back to the
package default.
A named list of the resolved option values. When setting, the previous values are returned invisibly.
# Inspect the current settings depictr_options() # Set a larger base size and a different accent, then restore old <- depictr_options(base_size = 14, accent = "#e69f00") theme_depictr() # now uses base_size 14 do.call(depictr_options, old) # Use a custom qualitative palette everywhere old <- depictr_options(palette = c("#1b9e77", "#d95f02", "#7570b3")) depictr_palette(3) do.call(depictr_options, old) # Or the built-in palette in its contrast-first order old <- depictr_options(palette = depictr_palette(order = "contrast")) depictr_palette(3) do.call(depictr_options, old)# Inspect the current settings depictr_options() # Set a larger base size and a different accent, then restore old <- depictr_options(base_size = 14, accent = "#e69f00") theme_depictr() # now uses base_size 14 do.call(depictr_options, old) # Use a custom qualitative palette everywhere old <- depictr_options(palette = c("#1b9e77", "#d95f02", "#7570b3")) depictr_palette(3) do.call(depictr_options, old) # Or the built-in palette in its contrast-first order old <- depictr_options(palette = depictr_palette(order = "contrast")) depictr_palette(3) do.call(depictr_options, old)
Colourblind-aware palettes shared by every depictr plot. The qualitative palette is based on the Okabe-Ito set (Okabe & Ito, 2008), a widely recommended categorical palette that stays distinguishable under the common forms of colour-vision deficiency, with the depictr brand blue leading. The sequential and diverging palettes are perceptually ordered single-hue and red-blue ramps.
depictr_palette( n = NULL, type = c("qualitative", "sequential", "diverging"), order = c("default", "contrast") )depictr_palette( n = NULL, type = c("qualitative", "sequential", "diverging"), order = c("default", "contrast") )
n |
Number of colours to return. If |
type |
Palette type: |
order |
The order of the built-in qualitative palette: |
The guarantee belongs to the eight Okabe-Ito colours themselves, not to any number of colours: past eight the qualitative palette has to interpolate between them, and the interpolated colours sit close enough together to fail the package's own colour-distance check. That case warns rather than handing back colours under a guarantee it cannot keep.
The qualitative palette can be overridden globally with
options(depictr.palette = ) (see depictr_options()); when set, that
custom palette replaces the built-in Okabe-Ito set and is interpolated when
more colours are requested than it provides. The sequential and diverging
ramps are unaffected.
A character vector of hex colour codes.
In the default order the second colour is the orange (#e69f00), at 2.25:1
against white. Any figure on white whose colour tells two or more groups
apart therefore fails the geometry_contrast row of check_figure(). No
more than three of the eight colours pass every colour row of that audit
together.
order = "contrast" puts those three first: the brand blue, the vermillion
(#d55e00) and the reddish purple (#cc79a7), then the bluish green
(#009e73) and the other four in their default order. Up to three groups
then pass every colour row, contrast against white included. They stay at
least 14.01 apart in CIEDE2000 under every vision type, 6.86 apart in
lightness and 3.06:1 against white. Four groups keep 3:1 against white, but
the bluish green and the vermillion are 3.31 apart in lightness, which fails
greyscale_separability. Asking for more than four colours warns, since the
fifth, the orange, falls below 3:1.
Under this order, the second group is drawn in the vermillion. That is also
the accent (depictr_options()) that depictr uses for single highlights,
such as the flagged points of outlier_plot() and the missing cells of
missingness_map(). The default order is unchanged.
The order applies to the built-in palette alone. A palette set with
depictr_options(palette = ) is the user's own, so it is returned in the
order it was set, with a warning. That is also how to make the order the
default for every later plot:
depictr_options(palette = depictr_palette(order = "contrast")). The
palette is then the user's own, so asking for more than four colours no
longer warns. scale_colour_depictr() and scale_fill_depictr() take
order for a single plot.
Okabe M, Ito K (2008). “Color Universal Design (CUD): How to make figures and presentations that are friendly to colorblind people.” https://jfly.uni-koeln.de/color/. Accessed 2026-06-14.
World Wide Web Consortium (2023). “Web Content Accessibility Guidelines (WCAG) 2.2.” W3C Recommendation, https://www.w3.org/TR/WCAG22/. Accessed 2026-08-18.
depictr_palette(3) depictr_palette(3, order = "contrast") depictr_palette(7, type = "sequential") scales::show_col(depictr_palette())depictr_palette(3) depictr_palette(3, order = "contrast") depictr_palette(7, type = "sequential") scales::show_col(depictr_palette())
Compares a numeric value between exactly two groups across a set of categories: for each category the two group values are drawn as points joined by a connecting segment, so the size and direction of the gap is read at a glance. It is a clearer alternative to paired or grouped bars for before/after, two-condition or two-period comparisons.
dumbbell_plot( data, category, value, group, sort = c("gap", "value", "none"), point_size = 3, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = NULL, y_lab = NULL )dumbbell_plot( data, category, value, group, sort = c("gap", "value", "none"), point_size = 3, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
category |
The categorical axis (string or unquoted name); one row of the plot per level. |
value |
The numeric value to compare (string or unquoted name). |
group |
The two-level grouping variable whose levels are the two ends of each dumbbell (string or unquoted name). |
sort |
How to order the categories up the axis: |
point_size |
Size of the end points. |
palette |
Length-2 colours for the two groups; defaults to
|
legend_inside |
When |
title, x_lab, y_lab
|
Plot title and axis labels. |
When a category has several rows per group the values are averaged. Rows with a missing category, value or group are dropped.
A ggplot2::ggplot object.
wb <- wellbeing_survey wb$age_group <- ifelse(wb$age < median(wb$age), "younger", "older") dumbbell_plot(wb, region, life_satisfaction, age_group)wb <- wellbeing_survey wb$age_group <- ifelse(wb$age < median(wb$age), "younger", "older") dumbbell_plot(wb, region, life_satisfaction, age_group)
Draws the empirical CDF of a numeric variable (the proportion of observations at or below each value), optionally split by a grouping variable. Unlike a histogram it needs no bin-width choice and makes quantiles, medians and group shifts easy to read directly off the curve.
ecdf_plot( data, x, group = NULL, reference_quantiles = NULL, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = NULL, y_lab = "Cumulative proportion" )ecdf_plot( data, x, group = NULL, reference_quantiles = NULL, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = NULL, y_lab = "Cumulative proportion" )
data |
A data frame. |
x |
The numeric variable (string or unquoted name). |
group |
Optional grouping variable (string or unquoted name) mapped to colour, giving one ECDF per group. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
reference_quantiles |
Optional numeric vector of probabilities in
[0, 1] to mark with light horizontal guides (e.g. |
palette |
Colours for the groups; defaults to |
legend_inside |
When |
title, x_lab, y_lab
|
Plot title and axis labels. |
A ggplot2::ggplot object.
ecdf_plot(lexical_decision, RT) ecdf_plot(lexical_decision, RT, group = condition, reference_quantiles = c(0.25, 0.5, 0.75))ecdf_plot(lexical_decision, RT) ecdf_plot(lexical_decision, RT, group = condition, reference_quantiles = c(0.25, 0.5, 0.75))
Shows the values a model predicts as one focal predictor varies, holding the other predictors at typical values (the mean for numeric predictors, the most frequent level for factors). A confidence band (numeric predictor) or confidence intervals (factor predictor) convey uncertainty.
effects_plot( model, predictor, conf_level = 0.95, n = 100, rug = TRUE, colour = depictr_brand(), title = NULL, x_lab = NULL, y_lab = NULL )effects_plot( model, predictor, conf_level = 0.95, n = 100, rug = TRUE, colour = depictr_brand(), title = NULL, x_lab = NULL, y_lab = NULL )
model |
A fitted model ( |
predictor |
Name of the focal predictor (string): a variable in the
model's formula, or a model-frame name such as |
conf_level |
Confidence level for the interval, a single number strictly between 0 and 1. |
n |
Number of points across the range of a numeric predictor. |
rug |
Whether to add a rug of the observed predictor values (numeric predictors). |
colour |
Colour for the line/points and band. Defaults to the depictr brand blue. |
title, x_lab, y_lab
|
Title and axis labels. |
The prediction grid holds the variables the formula names, on their own
scale, and stats::predict() re-applies each transformation to it. A model
with terms such as log(rainfall), poly(fertiliser, 2), splines::ns(),
scale(), factor() or I() is therefore predicted as fitted, and the
x-axis shows the raw variable. Typical values and the range of the focal
predictor are taken over the rows the model was fitted to. A numeric
variable that enters only through factor() is held at its most frequent
value. A variable that enters only through a transformation is recovered
from the data named in the model's call, so such a model fitted to
workspace vectors without data = is refused. A term computed from
statistics of its rows, such as I(x - mean(x)), is refused too, since
predict() would compute it afresh over the grid. Centre such a variable in
the data, or use scale(), which predict() re-applies as fitted.
An offset, whether an offset() term or the offset argument, is held at
its mean over the rows the model was fitted to, on the link scale. For a
Poisson rate model with offset(log(exposure)), the line is the expected
count at the geometric-mean exposure. The offset stays at its mean when the
focal predictor enters it too, as in log(exposure) + offset(log(exposure)),
so the line then shows the predictor's effect beyond the offset. The y-axis
label ends in "at the mean offset".
Predictions and standard errors come from stats::predict(); glm
predictions are formed on the link scale and back-transformed, so a binomial
model shows predicted probabilities. The band uses the multiplier of the
model's own tests, as tidy_estimates() does. An lm, and a glm whose
dispersion is estimated, get t on the residual degrees of freedom, so a
Gaussian glm draws the band of the same lm. Binomial and Poisson fits
and mixed models get z. Mixed models fitted with
lme4::lmer()/lme4::glmer() are supported too: predictions use only the
fixed effects (as re.form = NA does) and standard errors come from the
fixed-effect design matrix and vcov(). Works with lm, glm and
merMod; other model classes are attempted on a best-effort basis.
A ggplot2::ggplot object.
fit <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) effects_plot(fit, "fertiliser") effects_plot(fit, "treatment") gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) effects_plot(gfit, "word_frequency") # predicted probabilityfit <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) effects_plot(fit, "fertiliser") effects_plot(fit, "treatment") gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) effects_plot(gfit, "word_frequency") # predicted probability
An estimation plot puts the effect size and its uncertainty at the centre of the comparison, rather than a p-value. The upper panel shows each group's raw data (jittered) with its mean and confidence interval. The lower panel shows the difference between each other group's mean and a reference group's mean, with a bootstrap confidence interval. Each difference is drawn in its group's column, beneath that group's data. The two panels are stacked with 'patchwork', and each has its own outcome axis.
estimation_plot( data, y, group, reference = NULL, conf_level = 0.95, n_boot = 5000, effsize = c("hedges_g", "cohens_d", "none"), show_points = TRUE, point_alpha = 0.25, palette = NULL, title = NULL, y_lab = NULL, heights = c(2, 1.4) )estimation_plot( data, y, group, reference = NULL, conf_level = 0.95, n_boot = 5000, effsize = c("hedges_g", "cohens_d", "none"), show_points = TRUE, point_alpha = 0.25, palette = NULL, title = NULL, y_lab = NULL, heights = c(2, 1.4) )
data |
A data frame. |
y |
The numeric outcome (string or unquoted name). |
group |
The grouping variable (string or unquoted name). |
reference |
The reference (control) group that the others are compared
with; defaults to the first level of |
conf_level |
Confidence level for both the group intervals (t-based) and the bootstrap difference intervals, a single number strictly between 0 and 1. |
n_boot |
Number of bootstrap resamples for the difference intervals. |
effsize |
Standardised effect size annotated beside each difference:
|
show_points |
Whether to draw the raw data behind the group means. |
point_alpha |
Transparency of the raw points. |
palette |
Colours for the groups; defaults to |
title, y_lab
|
Title and outcome-axis label. |
heights |
Relative heights of the upper (raw data) and lower
(difference) panels, passed to |
This stacked layout is the Cumming estimation plot (Cumming, 2012; Ho et al., 2019), with two groups as with more. Each difference is annotated with a standardised effect size: Cohen's d or, by default, the small-sample corrected Hedges' g (Hedges, 1981). The Gardner-Altman plot (Gardner & Altman, 1986) is a different layout, which sets a single difference beside the two groups on a floating axis whose zero is level with the reference group's mean.
The lower-panel interval is a non-parametric bootstrap of the mean
difference: the two groups are resampled with replacement n_boot times and
the requested percentile interval is read off the resampled differences. This
makes no normality assumption about the sampling distribution of the
difference. The bootstrap uses base R only; set a seed beforehand for
reproducibility.
A group with fewer than two observations has no estimable spread, so its confidence interval is omitted (the mean is still drawn) and a difference involving it is shown as a point without a bootstrap interval; a warning is issued in both cases.
A 'patchwork' object (printable like a ggplot2::ggplot). The
computed differences and their bootstrap intervals are attached as the
"differences" attribute (a data frame).
Cumming, G. (2012). Understanding the new statistics: Effect sizes, confidence intervals, and meta-analysis. Routledge.
Gardner, M. J., & Altman, D. G. (1986). Confidence intervals rather than P values: Estimation rather than hypothesis testing. BMJ, 292(6522), 746-750. doi:10.1136/bmj.292.6522.746
Hedges, L. V. (1981). Distribution theory for Glass's estimator of effect size and related estimators. Journal of Educational Statistics, 6(2), 107-128. doi:10.3102/10769986006002107
Ho, J., Tumkaya, T., Aryal, S., Choi, H., & Claridge-Chang, A. (2019). Moving beyond P values: Data analysis with estimation graphics. Nature Methods, 16(7), 565-566. doi:10.1038/s41592-019-0470-3
set.seed(1) # n_boot is kept small here for speed; use the default for real work. estimation_plot(lexical_decision, RT, condition, n_boot = 1000) estimation_plot(crop_yield, yield, treatment) # More than two groups: differences vs a chosen reference estimation_plot(wellbeing_survey, life_satisfaction, region, reference = "North")set.seed(1) # n_boot is kept small here for speed; use the default for real work. estimation_plot(lexical_decision, RT, condition, n_boot = 1000) estimation_plot(crop_yield, yield, treatment) # More than two groups: differences vs a chosen reference estimation_plot(wellbeing_survey, life_satisfaction, region, reference = "North")
Chooses an appropriate plot for the relationship between two variables according to their types. Two numeric variables are shown as a scatter plot with a fitted trend; a numeric and a categorical variable as box plots of the numeric variable by level; and two categorical variables as a filled bar chart of proportions.
explore_bivariate( data, x, y, method = "lm", palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )explore_bivariate( data, x, y, method = "lm", palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
x, y
|
The two variables (string or unquoted name). |
method |
Smoothing method for the numeric-numeric case (passed to
|
palette |
Colours used when a fill/colour is needed; defaults to
|
title, x_lab, y_lab
|
Title and axis labels (default to variable names). |
Dates, date-times and durations (Date, POSIXct and difftime columns)
count as numeric. Against a numeric variable, they give a scatter plot with
a trend, drawn by scatter_trend(), and against a categorical one box plots
of their values by level. A duration is drawn as a number in its own units,
named in the default axis title, as in stay (days).
A ggplot2::ggplot object.
explore_bivariate(crop_yield, fertiliser, yield) # numeric ~ numeric explore_bivariate(lexical_decision, condition, RT) # categorical ~ numeric explore_bivariate(wellbeing_survey, region, education) # categorical ~ categoricalexplore_bivariate(crop_yield, fertiliser, yield) # numeric ~ numeric explore_bivariate(lexical_decision, condition, RT) # categorical ~ numeric explore_bivariate(wellbeing_survey, region, education) # categorical ~ categorical
Counts (or proportions) of the levels of a categorical variable, optionally split by a grouping variable.
explore_categorical( data, x, group = NULL, proportion = FALSE, position = c("dodge", "stack", "fill"), sort = TRUE, horizontal = FALSE, palette = NULL, title = NULL, x_lab = NULL )explore_categorical( data, x, group = NULL, proportion = FALSE, position = c("dodge", "stack", "fill"), sort = TRUE, horizontal = FALSE, palette = NULL, title = NULL, x_lab = NULL )
data |
A data frame. |
x |
The categorical variable (string or unquoted name). Numeric columns are accepted only when they have at most 20 distinct values. |
group |
Optional grouping variable mapped to fill, with the groups in the order of its levels. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
proportion |
Whether to show proportions instead of counts. When
|
position |
Bar position when |
sort |
Whether to order the bars from most to least frequent. |
horizontal |
Whether to draw horizontal bars, which helps when there are many levels or long labels. |
palette |
Colours for the groups; defaults to |
title, x_lab
|
Plot title and category-axis label (defaults to the variable name). |
A continuous numeric column (more than 20 distinct values) is rejected with
an error, since coercing it to a factor would draw one bar per value; use
explore_distribution() for such variables instead.
A ggplot2::ggplot object.
explore_categorical(wellbeing_survey, region) explore_categorical(wellbeing_survey, education, group = region, proportion = TRUE, position = "dodge")explore_categorical(wellbeing_survey, region) explore_categorical(wellbeing_survey, education, group = region, proportion = TRUE, position = "dodge")
Draws a histogram, a density curve, or both, optionally split by a grouping variable. A quick first look at a continuous variable.
explore_distribution( data, x, group = NULL, type = c("histogram", "density", "both"), bins = 30, alpha = 0.6, position = NULL, palette = NULL, facet = FALSE, legend_inside = FALSE, title = NULL, x_lab = NULL )explore_distribution( data, x, group = NULL, type = c("histogram", "density", "both"), bins = 30, alpha = 0.6, position = NULL, palette = NULL, facet = FALSE, legend_inside = FALSE, title = NULL, x_lab = NULL )
data |
A data frame. |
x |
The continuous variable to display. Either a string or an unquoted column name. |
group |
Optional grouping variable (string or unquoted name) mapped to colour/fill. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
type |
One of |
bins |
Number of histogram bins. |
alpha |
Fill transparency (useful when groups overlap). |
position |
Histogram position adjustment, e.g. |
palette |
Colours for the groups; defaults to |
facet |
When a |
legend_inside |
When |
title, x_lab
|
Plot title and x-axis label (defaults to the variable name). |
A ggplot2::ggplot object.
explore_distribution(lexical_decision, RT) explore_distribution(lexical_decision, RT, group = condition, type = "density") # One panel per group keeps many groups legible: explore_distribution(wellbeing_survey, life_satisfaction, group = region, type = "both", facet = TRUE)explore_distribution(lexical_decision, RT) explore_distribution(lexical_decision, RT, group = condition, type = "density") # One panel per group keeps many groups legible: explore_distribution(wellbeing_survey, life_satisfaction, group = region, type = "both", facet = TRUE)
A matrix of pairwise plots for a set of numeric variables: scatter plots below the diagonal, densities on the diagonal and correlation coefficients above the diagonal. Optionally colour everything by a grouping variable. Built with 'patchwork', so it shares the package theme and palette.
explore_pairs( data, cols = NULL, group = NULL, max_cols = 8, point_alpha = 0.5, method = c("pearson", "spearman", "kendall"), palette = NULL, title = NULL )explore_pairs( data, cols = NULL, group = NULL, max_cols = 8, point_alpha = 0.5, method = c("pearson", "spearman", "kendall"), palette = NULL, title = NULL )
data |
A data frame. |
cols |
Numeric columns to include, which cannot include |
group |
Optional grouping variable mapped to colour, with each level in the same colour in every panel. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
max_cols |
Safety cap on the number of variables (a k-by-k matrix grows quickly). |
point_alpha |
Point transparency in the scatter panels. |
method |
Correlation method for the upper-triangle coefficients, passed
to |
palette |
Colours for the groups; defaults to |
title |
Overall title for the matrix. |
A 'patchwork' object (printable like a ggplot2::ggplot).
explore_pairs(crop_yield, cols = c("rainfall", "fertiliser", "yield")) explore_pairs(crop_yield, cols = c("rainfall", "fertiliser", "yield"), group = treatment, method = "spearman")explore_pairs(crop_yield, cols = c("rainfall", "fertiliser", "yield")) explore_pairs(crop_yield, cols = c("rainfall", "fertiliser", "yield"), group = treatment, method = "spearman")
Cleans up the term names produced by modelling functions so that they read
well on a plot: the intercept is renamed, an optional b_/bs_ Bayesian
prefix is stripped, interaction colons are converted to a chosen symbol, and
underscores are shown as spaces (e.g. word_frequency becomes
"word frequency"). The b_/bs_ prefix is stripped from each component
of an interaction (e.g. b_x:b_y), not just the leading term. Missing values
(NA) are kept as NA rather than being rendered as the literal text
"NA".
format_terms( x, interaction = c("times", "asterisk", "colon", "space"), strip_prefix = TRUE, tidy_intercept = TRUE, wrap = NULL )format_terms( x, interaction = c("times", "asterisk", "colon", "space"), strip_prefix = TRUE, tidy_intercept = TRUE, wrap = NULL )
x |
Character vector of term names. |
interaction |
How to render interaction colons: |
strip_prefix |
Whether to remove a leading |
tidy_intercept |
Whether to replace |
wrap |
Optional integer width at which to wrap long labels onto new
lines (see |
A character vector the same length as x, with NA preserved.
format_terms(c("(Intercept)", "b_conditionB", "freq:condition")) format_terms("region:education:age", interaction = "asterisk") format_terms(c("word_frequency", "b_sleep_hours")) format_terms(c("b_x:b_y", NA))format_terms(c("(Intercept)", "b_conditionB", "freq:condition")) format_terms("region:education:age", interaction = "asterisk") format_terms(c("word_frequency", "b_sleep_hours")) format_terms(c("b_x:b_y", NA))
Presents the estimates from a frequentist model and a Bayesian model on one
plot, in the first two colours of the colourblind-safe depictr_palette():
brand blue for the frequentist estimates and orange for the Bayesian ones,
whether the Bayesian side is drawn from draws or from summaries.
frequentist_bayesian_plot( frequentist, bayesian, conf_level = 0.95, labels = NULL, interaction = c("times", "asterisk", "colon", "space"), intercept = TRUE, facet = TRUE, scales = c("free", "fixed"), note_frequentist_no_prior = FALSE, vertical_line_at_x = 0, title = NULL, subtitle = NULL, x_lab = "Estimate", caption = NULL, ... )frequentist_bayesian_plot( frequentist, bayesian, conf_level = 0.95, labels = NULL, interaction = c("times", "asterisk", "colon", "space"), intercept = TRUE, facet = TRUE, scales = c("free", "fixed"), note_frequentist_no_prior = FALSE, vertical_line_at_x = 0, title = NULL, subtitle = NULL, x_lab = "Estimate", caption = NULL, ... )
frequentist |
A frequentist model (e.g. from |
bayesian |
A Bayesian model, a |
conf_level |
Level of the intervals the function computes: the
confidence intervals of a frequentist model and, when |
labels, interaction, intercept
|
See |
facet, scales
|
Layout controls. Because a Bayesian model almost always
carries a large intercept alongside small slopes, the comparison defaults
to a faceted, free-scaled layout ( |
note_frequentist_no_prior |
If |
vertical_line_at_x |
Position of the vertical reference line ( |
title, subtitle, x_lab
|
Title, subtitle and x-axis label. |
caption |
Plot caption. The default ( |
... |
Further arguments passed to |
This is the modernised successor to the original frequentist_bayesian_plot()
gist, which built on brms::mcmc_plot() to show the full Bayesian posterior
with the frequentist estimate overlaid. That namesake behaviour is restored
here: when bayesian carries posterior draws (a brms/rstanarm fit, a
posterior draws object, a draws matrix, or a long/wide draws data frame),
the full posterior distribution is drawn per term (a 'ggdist' half-eye)
and the frequentist point and confidence interval is overlaid at the same
position. When bayesian is only a table of posterior summaries, the
function shows the familiar two-source forest plot via compare_models().
It reads tidy columns (term, estimate, conf.low, conf.high), the
Estimate, Q2.5 and Q97.5 of the matrix brms::fixef() returns, and the
l-95% CI and u-95% CI of summary(fit)$fixed.
Terms are aligned by their canonical display label, so the brms-style b_
prefix is reconciled automatically against the frequentist term names. Draws
are read as in posterior_plot(), so a draws object holding b_ parameters
keeps only those, and two terms that would share a label are an error. With
draws, only the terms both sides share are drawn, and a message names any
dropped, such as a sigma in the posterior. The forest plot of summaries
draws every term of either input.
conf_level is the level of every interval the function computes. For a
frequentist model, that is its confidence interval. For posterior draws, it
is the outer of the equal-tailed credible intervals drawn beneath each
half-eye, so the two sources are compared at one level. The inner interval
is 66%, drawn only when conf_level exceeds 0.66 so that it stays the
narrower of the two. A table of estimates keeps the interval limits it
holds, at whatever level they were computed. By default, a caption names the
level of each source's intervals or says that they are as supplied.
A ggplot2::ggplot object.
# Summary path: a tidy "Bayesian" summary as a data frame. The summary here # stands in for a regularised posterior, so a weakly informative prior pulls # each estimate toward zero and narrows its interval; a real posterior would # come from the model fit rather than from arithmetic on the frequentist one. freq <- lm(life_satisfaction ~ stress + sleep_hours + exercise_days, data = wellbeing_survey) bayes <- tidy_estimates(freq) bayes$estimate <- bayes$estimate * 0.9 half <- (bayes$conf.high - bayes$conf.low) / 2 * 0.8 bayes$conf.low <- bayes$estimate - half bayes$conf.high <- bayes$estimate + half frequentist_bayesian_plot(freq, bayes, title = "Frequentist vs. Bayesian estimates") # Distribution path: simulated posterior draws (one column per term) drawn as # full posteriors with the frequentist point + CI overlaid. set.seed(1) co <- coef(freq) draws <- as.data.frame(lapply(co, function(m) rnorm(400, m, abs(m) * 0.1 + 0.05))) names(draws) <- names(co) frequentist_bayesian_plot(freq, draws, title = "Posterior with frequentist overlay")# Summary path: a tidy "Bayesian" summary as a data frame. The summary here # stands in for a regularised posterior, so a weakly informative prior pulls # each estimate toward zero and narrows its interval; a real posterior would # come from the model fit rather than from arithmetic on the frequentist one. freq <- lm(life_satisfaction ~ stress + sleep_hours + exercise_days, data = wellbeing_survey) bayes <- tidy_estimates(freq) bayes$estimate <- bayes$estimate * 0.9 half <- (bayes$conf.high - bayes$conf.low) / 2 * 0.8 bayes$conf.low <- bayes$estimate - half bayes$conf.high <- bayes$estimate + half frequentist_bayesian_plot(freq, bayes, title = "Frequentist vs. Bayesian estimates") # Distribution path: simulated posterior draws (one column per term) drawn as # full posteriors with the frequentist point + CI overlaid. set.seed(1) co <- coef(freq) draws <- as.data.frame(lapply(co, function(m) rnorm(400, m, abs(m) * 0.1 + 0.05))) names(draws) <- names(co) frequentist_bayesian_plot(freq, draws, title = "Posterior with frequentist overlay")
Shows how many of the positive cases are captured as a growing share of the population is targeted in order of predicted score. It is the customary chart for judging a classifier's value for ranking and targeting, as in marketing, triage and fraud detection. The diagonal marks the no-model baseline, and a second reference line marks a perfect model. Pass a named list of models / (actual, score) pairs to overlay several colour-coded gains curves with a legend. The perfect-model line is then omitted, since it depends on the prevalence of the outcome and the overlaid models need not share one.
gain_plot( x, score = NULL, colour = depictr_brand(), legend_inside = FALSE, title = NULL )gain_plot( x, score = NULL, colour = depictr_brand(), legend_inside = FALSE, title = NULL )
x |
A binomial |
score |
When |
colour |
Curve colour for the single-model case. Defaults to the depictr brand blue. Ignored when several models are overlaid. |
legend_inside |
When |
title |
Plot title. |
A ggplot2::ggplot object.
# Targeting only pays off when the positive class is scarce, so the example # uses the rare clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) gain_plot(gfit) # Compare two models. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) gain_plot(list(Full = gfit, Reduced = reduced))# Targeting only pays off when the positive class is scarce, so the example # uses the rare clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) gain_plot(gfit) # Compare two models. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) gain_plot(list(Full = gfit, Reduced = reduced))
An estimation-style plot of a numeric outcome across the levels of a grouping variable: each group's mean with a confidence interval, over a backdrop of the raw (jittered) data. By showing both the estimate and its uncertainty, it conveys whether the groups differ more faithfully than a bar chart does.
group_comparison_plot( data, y, group, conf_level = 0.95, show_points = TRUE, point_alpha = 0.25, differences = FALSE, reference = NULL, n_boot = 5000, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )group_comparison_plot( data, y, group, conf_level = 0.95, show_points = TRUE, point_alpha = 0.25, differences = FALSE, reference = NULL, n_boot = 5000, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
y |
The numeric outcome (string or unquoted name). |
group |
The grouping variable (string or unquoted name). A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
conf_level |
Confidence level for the intervals (t-based), a single number strictly between 0 and 1. |
show_points |
Whether to draw the raw data behind the means. |
point_alpha |
Transparency of the raw points. |
differences |
If |
reference |
Reference group for the difference panel when
|
n_boot |
Number of bootstrap resamples for the difference intervals when
|
palette |
Colours for the groups; defaults to |
title, x_lab, y_lab
|
Title and axis labels. |
A group with a single observation has no degrees of freedom for a t-based interval, so only its mean is drawn (no interval) and a warning is issued.
A ggplot2::ggplot object, or a 'patchwork' object when
differences = TRUE.
group_comparison_plot(lexical_decision, RT, condition) group_comparison_plot(crop_yield, yield, treatment) # Append the pairwise mean-difference panel (an estimation plot): set.seed(1) group_comparison_plot(crop_yield, yield, treatment, differences = TRUE)group_comparison_plot(lexical_decision, RT, condition) group_comparison_plot(crop_yield, yield, treatment) # Append the pairwise mean-difference panel (an estimation plot): set.seed(1) group_comparison_plot(crop_yield, yield, treatment, differences = TRUE)
A bubble plot of leverage against the studentised residuals, with bubble area proportional to Cook's distance. It summarises in a single picture which observations most influence a fitted model. Reference lines mark large residuals and high-leverage points, and the most influential observations are labelled.
influence_plot(model, n_label = 3, colour = depictr_brand(), title = NULL)influence_plot(model, n_label = 3, colour = depictr_brand(), title = NULL)
model |
A fitted |
n_label |
Number of most-influential points (by Cook's distance) to label. |
colour |
Bubble colour. Defaults to the depictr brand blue. |
title |
Plot title. |
A ggplot2::ggplot object.
Cook RD (1977). “Detection of influential observation in linear regression.” Technometrics, 19(1), 15–18. doi:10.1080/00401706.1977.10489493.
fit <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) influence_plot(fit)fit <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) influence_plot(fit)
Shows how the predicted relationship between a focal predictor and the response changes across the levels (or representative values) of a second, moderating predictor. Other predictors are held at typical values.
interaction_plot( model, predictor, moderator, moderator_values = NULL, conf_level = 0.95, n = 80, band = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )interaction_plot( model, predictor, moderator, moderator_values = NULL, conf_level = 0.95, n = 80, band = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
model |
A fitted model ( |
predictor |
Name of the focal predictor on the x-axis (string), given
as in |
moderator |
Name of the moderating predictor, mapped to colour
(string), given in the same way as |
moderator_values |
For a numeric moderator, the values to show, on the variable's own scale. Defaults to the 10th, 50th and 90th percentiles in the estimation sample. |
conf_level |
Confidence level for the bands/intervals, a single number strictly between 0 and 1. |
n |
Number of points across the range of a numeric focal predictor. |
band |
Whether to draw confidence bands (numeric focal predictor). |
palette |
Colours for the moderator; defaults to |
title, x_lab, y_lab
|
Title and axis labels. |
The grid is built as in effects_plot(). It holds the raw variables, so
transformed terms are predicted as fitted. Typical values come from the
estimation sample, and an offset is held at its mean on the link scale.
A ggplot2::ggplot object.
fit <- lm(yield ~ fertiliser * treatment + rainfall, data = crop_yield) interaction_plot(fit, "fertiliser", "treatment")fit <- lm(yield ~ fertiliser * treatment + rainfall, data = crop_yield) interaction_plot(fit, "fertiliser", "treatment")
Computes a cluster-quality diagnostic across a range of k and draws the
diagnostic curve, with the suggested k highlighted. Three criteria are
available: the average silhouette width (maximised), the total within-cluster
sum of squares "elbow" (the point of maximum curvature), and the gap statistic
(the smallest k whose gap is within one standard error of the next, using
the Tibshirani et al. heuristic).
k_diagnostic( data, k_range = NULL, method = c("silhouette", "wss", "gap"), cols = NULL, scale = TRUE, nstart = 10, B = 50, title = NULL )k_diagnostic( data, k_range = NULL, method = c("silhouette", "wss", "gap"), cols = NULL, scale = TRUE, nstart = 10, B = 50, title = NULL )
data |
A data frame or numeric matrix. |
k_range |
Integer vector of candidate cluster counts to evaluate. The
default, |
method |
Diagnostic: |
cols |
When |
scale |
Whether to scale variables to unit variance first. Columns with
(near-)zero variance cannot be scaled and are dropped with a message, as in
|
nstart |
Number of random starts for |
B |
Number of reference bootstrap samples for the gap statistic. |
title |
Plot title. |
Only the gap statistic can conclude that the data hold no clusters, by
suggesting k = 1, and only when the search starts at 1, as it does by
default. The elbow is the candidate farthest from the straight line joining
the first and last points of the curve, so it is never the first or last
candidate. A range therefore has to extend one step beyond any k the elbow
should be able to choose.
A ggplot2::ggplot object showing the diagnostic value against k,
with the suggested k marked and named in the subtitle. The underlying
computation is attached as attributes: attr(p, "k_table") (a data frame
with one row per k), attr(p, "suggested") (the chosen k) and
attr(p, "method").
Rousseeuw, P. J. (1987). Silhouettes: A graphical aid to the interpretation and validation of cluster analysis. Journal of Computational and Applied Mathematics, 20, 53-65. doi:10.1016/0377-0427(87)90125-7
Tibshirani, R., Walther, G., & Hastie, T. (2001). Estimating the number of clusters in a data set via the gap statistic. Journal of the Royal Statistical Society: Series B, 63(2), 411-423. doi:10.1111/1467-9868.00293
p <- k_diagnostic(crop_yield, k_range = 2:6, cols = c("rainfall", "fertiliser", "soil_ph", "yield")) attr(p, "suggested")p <- k_diagnostic(crop_yield, k_range = 2:6, cols = c("rainfall", "fertiliser", "soil_ph", "yield")) attr(p, "suggested")
A reproducibly simulated psycholinguistic lexical-decision experiment, in
which participants judge whether letter strings are real words. The design
is fully crossed and counterbalanced: 24 participants each respond to all 40
items once (960 trials). condition (related/unrelated priming) and
modality (visual/auditory) are both within-participant and within-item,
assigned by a Latin-square list rotation so that, across participants, every
item appears equally often in each condition and in each modality. Neither
factor is therefore collinear with the item random intercept, which makes
this a clean fit for crossed-random-effects models such as
lmer(RT ~ condition + modality + word_frequency + (1 | participant) + (1 | item)), and hence for optimizer_fixef_plot(), coefficient_plot()
and the distribution plots.
lexical_decisionlexical_decision
A data frame with 960 rows and 7 variables:
Participant identifier (factor, 24 levels).
Item identifier (factor, 40 levels).
Priming condition (factor): related or unrelated.
Presentation modality (factor): visual or auditory.
Item-level word frequency on the Zipf scale.
Reaction time, in milliseconds.
Response accuracy: 1 correct, 0 incorrect.
RT is generated log-normally from participant and item random intercepts
plus the fixed effects (unrelated priming about 35 ms slower; auditory a
touch slower; more frequent words faster), with a 250 ms floor; RT analyses
should use correct trials (accuracy == 1). accuracy is generated from
the exogenous predictors (word_frequency, condition, modality) and
random effects only - it is not derived from RT - so classification demos
built on it are not circular.
The data are synthetic, generated by data-raw/generate_datasets.R with a
fixed seed; they do not describe any real individuals.
Simulated; see data-raw/generate_datasets.R.
Shows how many times more positive cases a classifier captures, at each depth of the score-ordered population, than random targeting would. A lift of 3 at the top 10% means that decile contains three times the baseline rate of positives. The horizontal line at 1 is the no-model baseline. Pass a named list of models / (actual, score) pairs to overlay several colour-coded lift curves with a legend.
lift_plot( x, score = NULL, colour = depictr_brand(), legend_inside = FALSE, title = NULL )lift_plot( x, score = NULL, colour = depictr_brand(), legend_inside = FALSE, title = NULL )
x |
A binomial |
score |
When |
colour |
Curve colour for the single-model case. Defaults to the depictr brand blue. Ignored when several models are overlaid. |
legend_inside |
When |
title |
Plot title. |
A ggplot2::ggplot object.
# Lift is measured against the base rate, so the example uses the rare # clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) lift_plot(gfit) # Compare two models. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) lift_plot(list(Full = gfit, Reduced = reduced))# Lift is measured against the base rate, so the example uses the rare # clinical-trial adverse event (about 10% positive). gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) lift_plot(gfit) # Compare two models. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) lift_plot(list(Full = gfit, Reduced = reduced))
Draws a tile map of the data frame with one column per variable and one row per observation, shading the cells that are missing. The variables are ordered by their proportion of missing values, and that proportion is shown in the axis labels, making it easy to spot variables and patterns that need attention before modelling.
missingness_map( data, cols = NULL, sort = TRUE, show_pct = TRUE, colours = c("grey85", depictr_accent()), legend_inside = FALSE, title = NULL )missingness_map( data, cols = NULL, sort = TRUE, show_pct = TRUE, colours = c("grey85", depictr_accent()), legend_inside = FALSE, title = NULL )
data |
A data frame. |
cols |
Optional character vector of columns to include (default: all). |
sort |
Whether to order variables by their proportion of missing values. |
show_pct |
Whether to append the percentage missing to each variable label. |
colours |
Length-2 vector: colours for present and missing cells.
Defaults to a muted grey for present cells and the colourblind-safe
|
legend_inside |
When |
title |
Plot title. |
A ggplot2::ggplot object.
missingness_map(wellbeing_survey)missingness_map(wellbeing_survey)
Collects the usual model-comparison statistics into one tidy data frame, one row per model. These comprise the number of observations, the degrees of freedom, AIC, BIC, the log-likelihood, an R-squared and the root-mean-square error.
model_fit_table(..., digits = 3)model_fit_table(..., digits = 3)
... |
One or more fitted models. Name the arguments to label the rows. |
digits |
Number of decimal places to round to. |
The R-squared depends on the kind of model. An lm reports the ordinary
coefficient of determination. Every glm, including quasi-likelihood
families and negative binomial fits from MASS::glm.nb(), reports the
deviance R-squared, . Here is the residual deviance
of the model and the deviance of the null model, which
stats::glm() fits to the same rows with the same prior weights and
offset. The null model has an intercept only if the model has one. Both
deviances are stored with the fit, so nothing is refitted. The value is the
share of the null deviance that the predictors explain, the
Kullback-Leibler R-squared of Cameron and Windmeijer (1997). It equals the
ordinary R-squared for a Gaussian model with the identity link and
McFadden's (1974) pseudo-R-squared for binary logistic regression. For
Poisson, grouped binomial, Gamma and other models, the two measures differ.
Mixed models (merMod) report an R-squared of NA, since no single
definition is standard for them.
A linear mixed model fitted with lme4::lmer() by restricted maximum
likelihood (REML, its default) is refitted by maximum likelihood with
lme4::refitML(). Its AIC, BIC and log-likelihood come from the refit, and
a message says so. A REML likelihood is the likelihood of the residuals left
once the fixed effects are removed. Two models with different fixed effects
therefore have REML likelihoods of different data, and their criteria cannot
be compared. The anova() method for lme4 fits refits them by maximum
likelihood for the same reason. If a refit fails, a warning says so, and
that model's df, AIC, BIC and logLik are NA. The other columns
describe the model as supplied.
A data frame with one row per model and columns model, n, df,
AIC, BIC, logLik, R2 and RMSE.
Cameron AC, Windmeijer FAG (1997). “An R-squared measure of goodness of fit for some common nonlinear regression models.” Journal of Econometrics, 77(2), 329–342. doi:10.1016/S0304-4076(96)01818-0.
McFadden D (1974). “Conditional logit analysis of qualitative choice behavior.” In Zarembka P (ed.), Frontiers in econometrics, 105–142. Academic Press, New York, NY.
m1 <- lm(yield ~ rainfall, data = crop_yield) m2 <- lm(yield ~ rainfall + fertiliser, data = crop_yield) m3 <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) model_fit_table(simple = m1, medium = m2, full = m3)m1 <- lm(yield ~ rainfall, data = crop_yield) m2 <- lm(yield ~ rainfall + fertiliser, data = crop_yield) m3 <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) model_fit_table(simple = m1, medium = m2, full = m3)
Assembles a compact, consistent overview of a fitted model in a single
figure: the coefficient estimates, the predicted effect of a focal predictor,
a residual panel and a normal Q-Q plot, with a subtitle of key fit
statistics. It is a convenience wrapper that composes several depictr
plots with arrange_plots(), and serves well for a rapid model review or a
report appendix.
model_report( model, predictor = NULL, standardise = TRUE, title = NULL, subtitle = NULL, glm_panels = TRUE, seed = NULL )model_report( model, predictor = NULL, standardise = TRUE, title = NULL, subtitle = NULL, glm_panels = TRUE, seed = NULL )
model |
A fitted |
predictor |
Focal predictor for the effect panel, given as in
|
standardise |
Whether the coefficient panel scales each continuous
predictor's coefficient by that predictor's standard deviation, leaving
factor and binary contrasts in the outcome's units, as described for
|
title |
Overall title. |
subtitle |
Overall subtitle. If |
glm_panels |
For a |
seed |
Optional integer seed for the randomisation of discrete
quantile residuals, for a reproducible Q-Q panel of a binomial or Poisson
|
For an lm, the residual panel plots the residuals against the fitted
values, and the Q-Q panel is qq_plot() of the standardised residuals. A
glm gets the panels residual_diagnostics_plot() draws for it
(glm_panels = TRUE). The residual panel is a binned_residual_plot()
(Gelman & Hill, 2007). The Q-Q panel plots randomised quantile residuals
(Dunn & Smyth, 1996), which are standard normal under a correctly specified
model whatever the response family, so the band of qq_plot() applies to
them. The standardised deviance residuals of a binary or count model are
not expected to be normal, so even a correctly specified model can put many
of them outside that band (about a third, in a simulated logistic model with
400 observations). The quantile residuals of a binomial or Poisson model are
randomised, and even under a correct model a run of them occasionally
leaves the pointwise band. A departure worth acting on persists when the
panel is drawn again with another seed. For a family with no quantile
residual here, such as a quasi-likelihood family, the Q-Q panel plots the
deviance residuals and its axis says so.
A 'patchwork' object (printable like a ggplot2::ggplot).
Gelman A, Hill J (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press, Cambridge, UK. ISBN 978-0-521-68689-1. doi:10.1017/CBO9780511790942.
Dunn PK, Smyth GK (1996). “Randomized quantile residuals.” Journal of Computational and Graphical Statistics, 5(3), 236–244. doi:10.1080/10618600.1996.10474708.
fit <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) model_report(fit, title = "Crop-yield model") # A logistic model gets binned and quantile-residual panels, and a seed # fixes the randomisation of its quantile residuals: gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) model_report(gfit, seed = 1)fit <- lm(yield ~ rainfall + fertiliser + soil_ph + treatment, data = crop_yield) model_report(fit, title = "Crop-yield model") # A logistic model gets binned and quantile-residual panels, and a seed # fixes the randomisation of its quantile residuals: gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) model_report(gfit, seed = 1)
A reproducibly simulated seasonal monthly time series in tidy long form, for
the time-series examples. It holds two product lines (indoor and
outdoor), each with six years of monthly observations carrying a linear
trend, a clear twelve-month seasonal cycle (with different seasonal peaks per
line) and noise. The two-series shape exercises the multi-group
timeseries_plot() path on first-party data; extracting one series as a
monthly ts (frequency 12) lets decompose_plot() and acf_plot() reveal
the trend and seasonality.
monthly_salesmonthly_sales
A data frame with 144 rows and 3 variables:
First day of the month (Date), from January 2018.
Product line (factor): indoor or outdoor.
Monthly sales (units).
The data are synthetic, generated by data-raw/generate_datasets.R with a
fixed seed.
Simulated; see data-raw/generate_datasets.R.
Visualises how the fixed-effect estimates of a mixed model vary across the
optimisers tried by lme4::allFit(). It offers a quick check that a model
has settled on a stable solution: tight clusters of points indicate
agreement between optimisers, whereas scatter signals a fragile fit.
optimizer_fixef_plot( x, intercept = TRUE, select_terms = NULL, interaction = c("times", "asterisk", "colon", "space"), labels = NULL, number_optimizers = TRUE, free_y = TRUE, ncol = NULL, point_size = 2, palette = NULL, y_lab = "Fixed effect", title = NULL )optimizer_fixef_plot( x, intercept = TRUE, select_terms = NULL, interaction = c("times", "asterisk", "colon", "space"), labels = NULL, number_optimizers = TRUE, free_y = TRUE, ncol = NULL, point_size = 2, palette = NULL, y_lab = "Fixed effect", title = NULL )
x |
Either the object returned by |
intercept |
Whether to keep the intercept panel. Defaults to |
select_terms |
Optional character vector of terms to display (the
intercept is always kept when |
interaction |
Passed to |
labels |
Optional named character vector renaming the term panels, e.g.
|
number_optimizers |
Whether to prefix each optimiser name with a number, so that the legend doubles as an index. |
free_y |
Whether to give each panel its own y-axis range. This is advisable, because the intercept and the slopes usually occupy very different scales. |
ncol |
Number of facet columns. If |
point_size |
Point size. |
palette |
Colours for the optimisers; defaults to |
y_lab, title
|
Axis label and plot title. |
Each fixed effect occupies its own panel, with its own y-axis. The function also accepts a plain data frame, so it can be used without 'lme4'.
A ggplot2::ggplot object.
# Without lme4, build the input data frame directly: set.seed(1) df <- expand.grid( optimizer = c("bobyqa", "Nelder_Mead", "nlminbwrap"), term = c("(Intercept)", "rainfall", "fertiliser") ) df$value <- c(5, 5.01, 4.99, 0.3, 0.31, 0.29, -0.2, -0.18, -0.21) optimizer_fixef_plot(df) if (requireNamespace("lme4", quietly = TRUE)) { m <- lme4::lmer(life_satisfaction ~ stress + (1 | region), data = wellbeing_survey) af <- lme4::allFit(m) optimizer_fixef_plot(af) }# Without lme4, build the input data frame directly: set.seed(1) df <- expand.grid( optimizer = c("bobyqa", "Nelder_Mead", "nlminbwrap"), term = c("(Intercept)", "rainfall", "fertiliser") ) df$value <- c(5, 5.01, 4.99, 0.3, 0.31, 0.29, -0.2, -0.18, -0.21) optimizer_fixef_plot(df) if (requireNamespace("lme4", quietly = TRUE)) { m <- lme4::lmer(life_satisfaction ~ stress + (1 | region), data = wellbeing_survey) af <- lme4::allFit(m) optimizer_fixef_plot(af) }
Shows the distribution of a numeric variable (optionally by group) as a box and/or violin plot, with points beyond the 1.5 * IQR fences highlighted so that outliers are easy to spot before modelling.
outlier_plot( data, y, group = NULL, type = c("box", "violin", "both"), flag = TRUE, outlier_colour = depictr_accent(), palette = NULL, title = NULL, y_lab = NULL )outlier_plot( data, y, group = NULL, type = c("box", "violin", "both"), flag = TRUE, outlier_colour = depictr_accent(), palette = NULL, title = NULL, y_lab = NULL )
data |
A data frame. |
y |
The numeric variable (string or unquoted name). |
group |
Optional grouping variable on the x-axis. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
type |
One of |
flag |
Whether to highlight outliers (points beyond 1.5 * IQR). |
outlier_colour |
Colour for highlighted outliers. |
palette |
Colours for the groups; defaults to |
title, y_lab
|
Plot title and value-axis label. |
A ggplot2::ggplot object.
outlier_plot(crop_yield, yield) outlier_plot(lexical_decision, RT, group = condition, type = "both")outlier_plot(crop_yield, yield) outlier_plot(lexical_decision, RT, group = condition, type = "both")
Displays the colours returned by depictr_palette() as labelled swatches.
It is useful when choosing how many groups to show, selecting a palette type,
or documenting a figure. Each swatch's hex label is drawn in black or white,
whichever has the higher WCAG contrast against that tile, so every label
reaches the 4.5:1 that WCAG 2.2 asks of text.
palette_preview( n = 8, type = c("qualitative", "sequential", "diverging", "all"), cvd = c("none", "deutan", "protan", "tritan") )palette_preview( n = 8, type = c("qualitative", "sequential", "diverging", "all"), cvd = c("none", "deutan", "protan", "tritan") )
n |
Number of colours to preview. |
type |
Palette type to preview: |
cvd |
Colour-vision-deficiency simulation for the tiles: |
Set cvd to render the swatches as they would appear under a colour-vision
deficiency, using the Machado et al. (2009) simulation. The tiles are
recoloured to the simulated appearance while the labels keep the original
hex code, so you can see whether two groups would still be told apart by a
colourblind reader.
A ggplot2::ggplot object.
Machado GM, Oliveira MM, Fernandes LAF (2009). “A physiologically-based model for simulation of color vision deficiency.” IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. doi:10.1109/TVCG.2009.113.
palette_preview() palette_preview(7, type = "sequential") palette_preview(type = "all") palette_preview(cvd = "deutan")palette_preview() palette_preview(7, type = "sequential") palette_preview(type = "all") palette_preview(cvd = "deutan")
For normal vision and each deficiency at full severity, the palette's colours
are converted to CIELAB and the smallest colour difference between any two
of them is found. The difference is CIEDE2000 (Sharma, Wu & Dalal, 2005)
unless metric asks for the older measure. The lower this minimum, the
more likely two categories are to be confused. A palette counts as safe when
the minimum across all four conditions is at least threshold.
palette_safety(colours = NULL, threshold = 5, metric = c("ciede2000", "cie76"))palette_safety(colours = NULL, threshold = 5, metric = c("ciede2000", "cie76"))
colours |
The palette to test. Defaults to the depictr qualitative palette. Needs at least two colours: a pairwise distance over fewer than two has no value, rather than an infinitely safe one. |
threshold |
The smallest acceptable colour difference, on the scale of
|
metric |
The colour difference to measure: |
Earlier versions measured the Euclidean distance in CIELAB, known as CIE76,
which overstates the difference between saturated colours: the blues
#005b96 and #0060a8 are 8.20 apart by CIE76 and 2.69 by CIEDE2000.
ggplot2's default palette of five colours passed the check under CIE76,
although under deuteranopia its sky blue and its pink come within 2.74 of
each other in CIEDE2000.
The default threshold of 5 is calibrated against the reference
colourblind-safe palette: the Okabe-Ito set's tightest pair (reddish purple
against grey) sits at 6.43 under full deuteranopia, so the cut must lie
below that to pass the recommended palette, while still flagging colours
that become near-identical under a deficiency. The difference includes
lightness, which survives colour-vision deficiency, so two colours that share
a hue but differ in lightness are correctly treated as distinguishable. Full
severity is the worst case; most colour-vision deficiency is milder.
This looks at a palette in the abstract. To audit a finished figure, which
uses only as many colours as it has groups and has text and a background
besides, see check_figure().
A list with min_delta_e (the worst case across conditions),
by_condition (a named numeric vector, one minimum colour difference for
normal vision and for each deficiency), worst_condition and worst_pair
(the closest colours and where they were closest), safe and
threshold.
Okabe M, Ito K (2008). “Color Universal Design (CUD): How to make figures and presentations that are friendly to colorblind people.” https://jfly.uni-koeln.de/color/. Accessed 2026-06-14.
Machado GM, Oliveira MM, Fernandes LAF (2009). “A physiologically-based model for simulation of color vision deficiency.” IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. doi:10.1109/TVCG.2009.113.
Sharma G, Wu W, Dalal EN (2005). “The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations.” Color Research & Application, 30(1), 21–30. doi:10.1002/col.20070.
palette_safety() # Two colours a hair apart are flagged. palette_safety(c("#005b96", "#015c97")) # ggplot2's default palette of five colours, which passed under CIE76. hue5 <- c("#F8766D", "#A3A500", "#00BF7D", "#00B0F6", "#E76BF3") palette_safety(hue5)$safe palette_safety(hue5, metric = "cie76")$safepalette_safety() # Two colours a hair apart are flagged. palette_safety(c("#005b96", "#015c97")) # ggplot2's default palette of five colours, which passed under CIE76. hue5 <- c("#F8766D", "#A3A500", "#00BF7D", "#00B0F6", "#E76BF3") palette_safety(hue5)$safe palette_safety(hue5, metric = "cie76")$safe
Runs a principal component analysis on the numeric columns of a data frame
(or takes an existing stats::prcomp() object) and draws a biplot: the
observations projected onto two components, with the variable loadings shown
as arrows. Optionally colour the observations by a grouping variable.
pca_plot( x, cols = NULL, group = NULL, components = c(1, 2), scale = TRUE, loadings = TRUE, point_alpha = 0.7, palette = NULL, title = NULL )pca_plot( x, cols = NULL, group = NULL, components = c(1, 2), scale = TRUE, loadings = TRUE, point_alpha = 0.7, palette = NULL, title = NULL )
x |
A data frame, or a |
cols |
When |
group |
Optional grouping variable mapped to colour. When |
components |
Length-2 integer vector: which components to plot. |
scale |
Whether to scale the variables to unit variance before the PCA. This is advisable when the variables are on different scales. |
loadings |
Whether to draw variable-loading arrows. |
point_alpha |
Point transparency. |
palette |
Colours for the groups; defaults to |
title |
Plot title. |
Each arrow is a variable's loadings on the two components, taken from the
rotation matrix and multiplied by a factor that all the arrows share. The
factor makes the largest coordinate of any arrow three quarters of the
largest absolute score. Both axes use that one factor, as stats::biplot() uses one
ratio for both, and the aspect ratio is fixed at 1. An arrow's angle on the
page is therefore its angle in loading space, whatever the size of the
figure. The panel's height and width follow the ranges of its two axes, so a
figure of another shape leaves blank space around the panel. Because the
scores are drawn as they are and the arrows as loadings, the angle between
two arrows does not estimate the correlation between their variables. Arrows
and labels are drawn in a neutral dark grey (grey20), which is not in the
default palette, so no arrow reads as part of a group.
A ggplot2::ggplot object.
pca_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), group = "treatment")pca_plot(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield"), group = "treatment")
Displays posterior (or, more generally, bootstrap or simulation) draws as a distribution per parameter, in the style of a half-eye (a density slab with a point-and-interval beneath it), an interval-only forest plot, a gradient interval or a dotplot. The full shape of the posterior is shown, so skew, multimodality and the relative mass on either side of a reference value are all visible, rather than a point and two limits alone. The intervals are central (equal-tailed) quantile intervals, which leave the same share of the draws in either tail.
posterior_plot( draws, style = c("halfeye", "interval", "gradient", "dots"), point = c("median", "mean"), widths = c(0.66, 0.95), interaction = c("times", "asterisk", "colon", "space"), labels = NULL, reference_line = 0, rope = NULL, pd = FALSE, facet = FALSE, scales = c("fixed", "free"), colour = depictr_brand(), fill = depictr_brand(), rope_fill = depictr_reference(), rope_alpha = 0.15, title = NULL, x_lab = "Value", caption = NULL )posterior_plot( draws, style = c("halfeye", "interval", "gradient", "dots"), point = c("median", "mean"), widths = c(0.66, 0.95), interaction = c("times", "asterisk", "colon", "space"), labels = NULL, reference_line = 0, rope = NULL, pd = FALSE, facet = FALSE, scales = c("fixed", "free"), colour = depictr_brand(), fill = depictr_brand(), rope_fill = depictr_reference(), rope_alpha = 0.15, title = NULL, x_lab = "Value", caption = NULL )
draws |
Posterior draws: a fitted model ( |
style |
One of |
point |
Central summary: |
widths |
Two interval widths (inner and outer), as probabilities, each drawn as a central (equal-tailed) quantile interval. The outer width is used for the caption and the displayed interval mass. |
interaction |
Passed to |
labels |
Optional named character vector renaming parameters, e.g.
|
reference_line |
Position of a vertical reference line, or |
rope |
Optional length-2 numeric |
pd |
If |
facet |
Whether to give each parameter its own panel with a free
x-axis, laid out one per row. This keeps the small parameters legible when
a large one (typically the intercept) would otherwise stretch the shared
axis and squish the rest. Defaults to |
scales |
Either |
colour, fill
|
Colours for the point/interval and for the density slab. Default to the depictr brand blue. |
rope_fill, rope_alpha
|
Fill colour and opacity of the ROPE band. |
title, x_lab
|
Plot title and value-axis label. |
caption |
Plot caption. The default ( |
Draws may be supplied in many shapes: a fitted Bayesian model (brms or
rstanarm), a posterior draws object, a wide data frame or matrix with
one column of draws per parameter, or a long data frame (a parameter column
and a value column). For fitted models only the fixed-effect (population)
parameters are kept, with the brms b_ prefix stripped. A draws object
that holds b_ parameters, as posterior::as_draws_df() returns for a
brms fit, is read the same way, with a message naming the variables it
dropped. Pass it through as.data.frame() to keep the other variables, as a
model of your own whose names happen to start with b_ may need. The log
densities lp__ and lprior are never drawn as parameters. Two parameters
that would share a display label, such as the b_Intercept and centred
Intercept of a plain table of brms draws, are an error until one is
renamed with labels. A matrix of posterior summaries, such as
brms::fixef() returns, holds no draws and is refused.
The slab styles use the 'ggdist' package. When 'ggdist' is not installed the
function falls back to the point-and-nested-interval display (equivalent to
style = "interval") and emits an informative message.
A ggplot2::ggplot object.
# Wide draws: one column per parameter set.seed(1) draws <- data.frame( intercept = rnorm(400, 5, 0.3), slope = rnorm(400, 0.8, 0.15), `slope:group` = rnorm(400, -0.2, 0.2), check.names = FALSE ) posterior_plot(draws) # A region of practical equivalence and a probability-of-direction label posterior_plot(draws, rope = c(-0.1, 0.1), pd = TRUE) # When one parameter dwarfs the others, give each its own free-scaled panel: posterior_plot(draws, facet = TRUE)# Wide draws: one column per parameter set.seed(1) draws <- data.frame( intercept = rnorm(400, 5, 0.3), slope = rnorm(400, 0.8, 0.15), `slope:group` = rnorm(400, -0.2, 0.2), check.names = FALSE ) posterior_plot(draws) # A region of practical equivalence and a probability-of-direction label posterior_plot(draws, rope = c(-0.1, 0.1), pd = TRUE) # When one parameter dwarfs the others, give each its own free-scaled panel: posterior_plot(draws, facet = TRUE)
Draws statistical power against sample size, with a dashed line at a target
power (80% by default). The input is usually a power curve produced by
simr::powerCurve(), though a tidy data frame works equally well, allowing
the plot to be redrawn without repeating a power simulation that is often
slow to run.
power_curve_plot( x, target = 0.8, x_lab = "Sample size", x_breaks = NULL, x_expand = NULL, ribbon = TRUE, title = NULL, interaction = c("times", "asterisk", "colon", "space"), n = NULL, power = NULL, group = NULL, lower = NULL, upper = NULL, y_lab = NULL, solved = NULL )power_curve_plot( x, target = 0.8, x_lab = "Sample size", x_breaks = NULL, x_expand = NULL, ribbon = TRUE, title = NULL, interaction = c("times", "asterisk", "colon", "space"), n = NULL, power = NULL, group = NULL, lower = NULL, upper = NULL, y_lab = NULL, solved = NULL )
x |
A |
target |
Target power, drawn as a horizontal reference line. Use |
x_lab |
X-axis label. |
x_breaks |
Approximate number of x-axis breaks. |
x_expand |
Optional value(s) to extend the x-axis to. |
ribbon |
Whether to draw the confidence band as a shaded ribbon
( |
title |
Plot title. If |
interaction |
Passed to |
n, power
|
Columns holding the sample size and the power, as strings or
unquoted names. By default, they are found among the names listed under
|
group |
Column that separates several curves, as a string or an
unquoted name. Each level is drawn as its own line, in its own colour and
line type. By default, an |
lower, upper
|
Columns holding the lower and upper confidence limits.
Name both or neither. By default, they are found among the names listed
under |
y_lab |
Y-axis label. By default, "Power" when the power column is one
of the names listed under |
solved |
A sample size solved from the curve, with its confidence
interval: a list with numeric |
A table may hold several curves, one per effect or design, stacked by row.
Each is drawn as its own line, told apart by colour and line type. The
tables pilotr returns are read as they are: power_curve_mixed() gives
n_subject, effect, power, power_lo and power_hi, and
precision_curve() gives n_subject, param and the decision
probabilities p_meaningful and p_equivalent with their own _lo and
_hi bounds, which power can name. The sample size that pilotr's
target_n() solves from such a curve, and its interval, can be marked on
the plot with solved.
A ggplot2::ggplot object.
pc <- data.frame( nlevels = c(10, 20, 30, 40, 50, 60), mean = c(0.18, 0.34, 0.52, 0.66, 0.79, 0.88), lower = c(0.10, 0.25, 0.42, 0.56, 0.70, 0.81), upper = c(0.28, 0.44, 0.62, 0.75, 0.86, 0.93) ) power_curve_plot(pc, title = "Power for the condition effect") # Two curves in one table, laid out as pilotr's power_curve_mixed() returns # them: one row per sample size and effect. curves <- data.frame( n_subject = rep(c(20, 40, 60, 80), each = 2), effect = rep(c("condition", "frequency"), times = 4), power = c(0.31, 0.12, 0.58, 0.21, 0.78, 0.33, 0.90, 0.45), power_lo = c(0.23, 0.07, 0.48, 0.14, 0.69, 0.25, 0.83, 0.36), power_hi = c(0.41, 0.19, 0.67, 0.30, 0.85, 0.43, 0.94, 0.55) ) power_curve_plot(curves, group = "effect", x_lab = "Participants") # The sample size at which pc reaches 80% power, with its interval, in the # form pilotr's target_n() returns. The values here are illustrative. power_curve_plot(pc, solved = list(value = 51, lo = 44, hi = 60))pc <- data.frame( nlevels = c(10, 20, 30, 40, 50, 60), mean = c(0.18, 0.34, 0.52, 0.66, 0.79, 0.88), lower = c(0.10, 0.25, 0.42, 0.56, 0.70, 0.81), upper = c(0.28, 0.44, 0.62, 0.75, 0.86, 0.93) ) power_curve_plot(pc, title = "Power for the condition effect") # Two curves in one table, laid out as pilotr's power_curve_mixed() returns # them: one row per sample size and effect. curves <- data.frame( n_subject = rep(c(20, 40, 60, 80), each = 2), effect = rep(c("condition", "frequency"), times = 4), power = c(0.31, 0.12, 0.58, 0.21, 0.78, 0.33, 0.90, 0.45), power_lo = c(0.23, 0.07, 0.48, 0.14, 0.69, 0.25, 0.83, 0.36), power_hi = c(0.41, 0.19, 0.67, 0.30, 0.85, 0.43, 0.94, 0.55) ) power_curve_plot(curves, group = "effect", x_lab = "Participants") # The sample size at which pc reaches 80% power, with its interval, in the # form pilotr's target_n() returns. The values here are illustrative. power_curve_plot(pc, solved = list(value = 51, lo = 44, hi = 60))
Plots precision against recall for a binary classifier and reports the average precision (the area under the curve). The precision-recall curve is more informative than the ROC curve when the positive class is rare, because it ignores the many true negatives. A horizontal line marks the no-skill baseline (the positive-class prevalence). Pass a named list of models / (actual, score) pairs to overlay several colour-coded curves with a legend and per-curve average precision.
pr_curve_plot( x, score = NULL, colour = depictr_brand(), f1 = FALSE, title = NULL )pr_curve_plot( x, score = NULL, colour = depictr_brand(), f1 = FALSE, title = NULL )
x |
A binomial |
score |
When |
colour |
Curve colour for the single-model case. Defaults to the depictr brand blue. Ignored when several models are overlaid. |
f1 |
Logical; if |
title |
Plot title. |
A ggplot2::ggplot object. The average precision is stored in
attr(plot, "average_precision") (a named vector when several models are
supplied).
# The clinical-trial adverse event is rare (about 10% positive), which is # where a precision-recall curve is worth drawing. gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) pr_curve_plot(gfit) # Mark the maximum-F1 operating point. pr_curve_plot(gfit, f1 = TRUE) # Compare two models with per-curve average precision in the legend. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) pr_curve_plot(list(Full = gfit, Reduced = reduced))# The clinical-trial adverse event is rare (about 10% positive), which is # where a precision-recall curve is worth drawing. gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) pr_curve_plot(gfit) # Mark the maximum-F1 operating point. pr_curve_plot(gfit, f1 = TRUE) # Compare two models with per-curve average precision in the legend. reduced <- glm(adverse_event ~ biomarker, data = clinical_trial, family = binomial) pr_curve_plot(list(Full = gfit, Reduced = reduced))
A normal Q-Q plot for a numeric vector or for the standardised residuals of a
fitted model, with a reference line and an optional confidence band that makes
departures from normality easier to judge. Points that stray outside the band
are unusual under normality; under a normal sample roughly level of the
points fall inside a pointwise band.
qq_plot( x, colour = depictr_brand(), title = NULL, x_lab = "Theoretical quantiles", y_lab = NULL, band = TRUE, band_type = c("pointwise", "simulate"), level = 0.95, n_sim = 1000, band_fill = depictr_reference(), seed = NULL )qq_plot( x, colour = depictr_brand(), title = NULL, x_lab = "Theoretical quantiles", y_lab = NULL, band = TRUE, band_type = c("pointwise", "simulate"), level = 0.95, n_sim = 1000, band_fill = depictr_reference(), seed = NULL )
x |
A numeric vector, or a fitted |
colour |
Point colour. Defaults to the depictr brand blue. |
title, x_lab, y_lab
|
Title and axis labels. |
band |
Whether to draw a confidence band/envelope. |
band_type |
Band construction: |
level |
Confidence level for the band. |
n_sim |
Number of simulations for |
band_fill |
Fill colour of the band. |
seed |
Optional integer seed for the simulated envelope, for reproducibility. |
Two band constructions are offered. "pointwise" is analytic: it uses the
large-sample standard error of the -th order statistic,
, around
the fitted reference line. "simulate" builds a Monte-Carlo envelope by
repeatedly drawing normal samples of the same size and taking the empirical
quantiles of the simulated order statistics, which needs no large-sample
approximation.
The band measures departures from a normal distribution, which the
residuals of a linear model should follow. The standardised deviance
residuals of a non-Gaussian glm are not expected to be normal, even when
the model is correctly specified: those of a binary model take only two
values at each fitted value. Points outside the band then say little about
the fit. For a glm, residual_diagnostics_plot() and model_report()
plot randomised quantile residuals (Dunn & Smyth, 1996), which are standard
normal under a correct model.
A ggplot2::ggplot object.
Dunn PK, Smyth GK (1996). “Randomized quantile residuals.” Journal of Computational and Graphical Statistics, 5(3), 236–244. doi:10.1080/10618600.1996.10474708.
qq_plot(rnorm(100)) qq_plot(rt(100, df = 3), band_type = "simulate") fit <- lm(yield ~ rainfall + fertiliser, data = crop_yield) qq_plot(fit)qq_plot(rnorm(100)) qq_plot(rt(100, df = 3), band_type = "simulate") fit <- lm(yield ~ rainfall + fertiliser, data = crop_yield) qq_plot(fit)
A "raincloud" combines three views of a distribution: a half-violin density (the cloud), a narrow boxplot, and the raw jittered points (the rain). It conveys the shape, the summary and the individual observations together, giving a fuller and more transparent picture than a boxplot alone. The plot is built with base R and ggplot2 alone, with no extra package dependency.
raincloud_plot( data, y, group = NULL, width = 0.4, point_alpha = 0.4, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )raincloud_plot( data, y, group = NULL, width = 0.4, point_alpha = 0.4, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
y |
The numeric variable (string or unquoted name). |
group |
Optional grouping variable on the x-axis. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
width |
Maximum width of the half-violin. |
point_alpha |
Transparency of the rain points. |
palette |
Colours for the groups; defaults to |
title, x_lab, y_lab
|
Title and axis labels. |
Groups with fewer than two observations cannot have a density estimated, so their half-violin is omitted (the points and box are still drawn) and a warning is issued.
A ggplot2::ggplot object.
raincloud_plot(lexical_decision, RT, group = condition) raincloud_plot(crop_yield, yield, group = treatment)raincloud_plot(lexical_decision, RT, group = condition) raincloud_plot(crop_yield, yield, group = treatment)
Displays the conditional modes ("BLUPs") of a mixed model's random effects as a sorted point-and-interval ("caterpillar") plot. It is the usual way to inspect by-group departures from the average, and to identify unusual groups.
random_effects_plot( x, conf_level = 0.95, sort = TRUE, point_colour = depictr_brand(), title = NULL, x_lab = "Random effect" )random_effects_plot( x, conf_level = 0.95, sort = TRUE, point_colour = depictr_brand(), title = NULL, x_lab = "Random effect" )
x |
Either a mixed model fitted with 'lme4' ( |
conf_level |
Confidence level when intervals are derived from standard errors, a single number strictly between 0 and 1. |
sort |
Whether to order the levels by their estimate. |
point_colour |
Colour for the points and intervals. |
title, x_lab
|
Plot title and value-axis label. |
A ggplot2::ggplot object.
# From a data frame of by-group estimates: re <- data.frame( level = paste0("G", 1:10), estimate = sort(rnorm(10)), std.error = runif(10, 0.2, 0.5) ) random_effects_plot(re) if (requireNamespace("lme4", quietly = TRUE)) { m <- lme4::lmer(RT ~ condition + (1 | participant), data = lexical_decision) random_effects_plot(m) }# From a data frame of by-group estimates: re <- data.frame( level = paste0("G", 1:10), estimate = sort(rnorm(10)), std.error = runif(10, 0.2, 0.5) ) random_effects_plot(re) if (requireNamespace("lme4", quietly = TRUE)) { m <- lme4::lmer(RT ~ condition + (1 | participant), data = lexical_decision) random_effects_plot(m) }
Combines the classic regression diagnostic plots (residuals against fitted
values, a normal Q-Q plot of the standardised residuals, a scale-location
plot, and residuals against leverage) into a single panel using 'patchwork'.
The function works with lm and glm objects.
residual_diagnostics_plot( model, which = c("resid_fitted", "qq", "scale_location", "resid_leverage"), ncol = 2, point_alpha = 0.6, smooth = TRUE, title = NULL, glm_panels = TRUE, seed = NULL )residual_diagnostics_plot( model, which = c("resid_fitted", "qq", "scale_location", "resid_leverage"), ncol = 2, point_alpha = 0.6, smooth = TRUE, title = NULL, glm_panels = TRUE, seed = NULL )
model |
A fitted |
which |
Character vector choosing which panels to show, any of
|
ncol |
Number of columns in the panel layout. |
point_alpha |
Point transparency. |
smooth |
Whether to add a loess guide line to the residual panels. |
title |
Overall title for the panel. |
glm_panels |
For a |
seed |
Optional integer seed for the randomisation of discrete quantile
residuals, for reproducible |
For a glm, the default panels are made GLM-aware (glm_panels = TRUE): the
"residuals vs. fitted" panel is replaced by a binned-residual plot
(Gelman & Hill, 2007; see binned_residual_plot()), which is readable even
for binary or count models, and the Q-Q panel uses randomised quantile
residuals (Dunn & Smyth, 1996), which are standard normal under a correctly
specified model regardless of the response family. In a Gaussian or Gamma
model, prior weights enter these residuals as they enter the fit, through
each observation's variance. The scale-location and
leverage panels are unchanged. For an lm the behaviour is identical to
before. Set glm_panels = FALSE to force the classic lm-style panels for a
glm as well.
A 'patchwork' object (printable like a ggplot2::ggplot).
Gelman A, Hill J (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press, Cambridge, UK. ISBN 978-0-521-68689-1. doi:10.1017/CBO9780511790942.
Dunn PK, Smyth GK (1996). “Randomized quantile residuals.” Journal of Computational and Graphical Statistics, 5(3), 236–244. doi:10.1080/10618600.1996.10474708.
fit <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) residual_diagnostics_plot(fit) residual_diagnostics_plot(fit, which = c("resid_fitted", "qq")) # A logistic GLM gets binned and quantile-residual panels automatically: gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) residual_diagnostics_plot(gfit)fit <- lm(yield ~ rainfall + fertiliser + soil_ph, data = crop_yield) residual_diagnostics_plot(fit) residual_diagnostics_plot(fit, which = c("resid_fitted", "qq")) # A logistic GLM gets binned and quantile-residual panels automatically: gfit <- glm(adverse_event ~ biomarker + age + arm, data = clinical_trial, family = binomial) residual_diagnostics_plot(gfit)
Shows the distribution of a numeric variable across the levels of a grouping variable as a column of partially overlapping density curves, one ridge per group, stacked up the y-axis. It compares many distributions in a compact space, making shifts in location and shape easy to follow, and is a clearer choice than many overlaid densities once there are several groups.
ridgeline_plot( data, x, group, overlap = 1.4, alpha = 0.85, scale_height = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )ridgeline_plot( data, x, group, overlap = 1.4, alpha = 0.85, scale_height = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
x |
The numeric variable whose distribution is shown (string or unquoted name). |
group |
The grouping variable (string or unquoted name); one ridge per level. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
overlap |
How far each ridge extends into the next, as a multiple of the
row spacing. |
alpha |
Fill transparency of the ridges. |
scale_height |
Whether to scale every ridge to the same peak height
( |
palette |
Colours for the groups; defaults to |
title, x_lab, y_lab
|
Plot title and axis labels. |
Densities are computed with stats::density() and scaled to a common height;
the overlap argument controls how far each ridge reaches into the one above.
Groups with fewer than two non-missing values are dropped with a warning. The
implementation is base R + ggplot2, with no extra package dependency.
A ggplot2::ggplot object.
ridgeline_plot(wellbeing_survey, life_satisfaction, region) ridgeline_plot(lexical_decision, RT, condition)ridgeline_plot(wellbeing_survey, life_satisfaction, region) ridgeline_plot(lexical_decision, RT, condition)
Plots the receiver operating characteristic (ROC) curve for a binary
classifier and reports the area under the curve (AUC). The input can be a
fitted binomial glm, a pair of vectors (observed binary outcome and a
continuous score), or, to compare several models, a named list of
models or of (actual, score) pairs, which are overlaid as colour-coded curves
with a legend and a per-curve AUC. An optional bootstrap confidence band can
be drawn for the (single-model) curve and its AUC, and the Youden's J
operating point can be marked.
roc_curve_plot( x, score = NULL, colour = depictr_brand(), ci = FALSE, conf_level = 0.95, youden = FALSE, legend_inside = FALSE, title = NULL )roc_curve_plot( x, score = NULL, colour = depictr_brand(), ci = FALSE, conf_level = 0.95, youden = FALSE, legend_inside = FALSE, title = NULL )
x |
A binomial |
score |
When |
colour |
Curve colour for the single-model case. Defaults to the depictr
brand blue. Ignored when several models are overlaid (the colourblind-aware
|
ci |
Bootstrap confidence band for a single ROC curve and its AUC.
|
conf_level |
Confidence level for the bootstrap band, a single number strictly between 0 and 1. |
youden |
Logical; if |
legend_inside |
When |
title |
Plot title. |
A named list overlays one curve per element, e.g.
roc_curve_plot(list("Full" = fit_full, "Reduced" = fit_reduced)). Each
element may be a glm, a length-2 list/data frame of (actual, score), or an
outcome vector paired with the matching element of a score list. Single-model
calls are unchanged.
A ggplot2::ggplot object. The AUC(s) are stored in
attr(plot, "auc") (a named vector when several models are supplied).
gfit <- glm(accuracy ~ word_frequency + condition + RT, data = lexical_decision, family = binomial) roc_curve_plot(gfit) # Mark the Youden operating point and add a bootstrap band. roc_curve_plot(gfit, youden = TRUE, ci = 200) # Compare two models with a colour-coded legend and per-curve AUC. reduced <- glm(accuracy ~ word_frequency, data = lexical_decision, family = binomial) roc_curve_plot(list(Full = gfit, Reduced = reduced))gfit <- glm(accuracy ~ word_frequency + condition + RT, data = lexical_decision, family = binomial) roc_curve_plot(gfit) # Mark the Youden operating point and add a bootstrap band. roc_curve_plot(gfit, youden = TRUE, ci = 200) # Compare two models with a colour-coded legend and per-curve AUC. reduced <- glm(accuracy ~ word_frequency, data = lexical_decision, family = binomial) roc_curve_plot(list(Full = gfit, Reduced = reduced))
A convenience wrapper around ggplot2::ggsave() with sensible defaults for
figures in papers and reports: a moderate size, 300 dpi, and the output
directory created if needed. The device is inferred from the file extension,
and PDF, EPS and TIFF files are written to pass the technical checks
journals run on submitted figures. With check = TRUE, the figure is also
audited with check_figure() at the width it is saved at.
save_plot( filename, plot = ggplot2::last_plot(), width = 7, height = 4.5, units = "in", dpi = 300, check = FALSE, ... )save_plot( filename, plot = ggplot2::last_plot(), width = 7, height = 4.5, units = "in", dpi = 300, check = FALSE, ... )
filename |
Output file path. The extension sets the device (e.g. |
plot |
The plot to save; defaults to the last plot drawn. |
width, height
|
Dimensions. |
units |
Units for |
dpi |
Resolution for raster devices. An EPS rasterises its translucent layers, and whatever they overlap, at this resolution or at 600 dpi, whichever is higher. |
check |
Whether to audit the figure with |
... |
Passed to |
The filename, invisibly. With check = TRUE, the audit report is
attached to it as the attribute "check", which attr(, "check") reads.
When no device is given, three formats are written differently from
ggplot2's defaults.
A PDF is written with grDevices::cairo_pdf(), which embeds its fonts.
ggplot2's default, grDevices::pdf(), names the fonts without embedding
them.
An EPS or PostScript file is written with grDevices::cairo_ps(), with its
fonts embedded too. PostScript has no transparency, and
grDevices::postscript() leaves every translucent layer out of the file:
the points and the confidence band of the plot in the examples below
vanish. cairo_ps() rasterises those layers, together with whatever they
overlap, at fallback_resolution, by default max(dpi, 600) here. A
message says so when a layer of the plot, or of any panel of a patchwork, is
translucent. A PDF keeps such layers as vector graphics.
A TIFF is compressed with LZW, which is lossless. Uncompressed, a figure of
7 by 4.5 inches at 300 dpi takes 8.1 MiB, and one at the largest size PLOS
accepts takes more than the 10 MB PLOS allows. The TIFF is drawn on white
unless bg is given, so a transparent background, as in
ggplot2::theme_void(), leaves no alpha channel in the file.
Without cairo, a PDF is written with pdf() and a message says how to embed
its fonts with grDevices::embedFonts(), and an EPS is written with
postscript() and a warning. R for macOS from CRAN loads cairo only where
XQuartz (https://www.xquartz.org) is installed, and a Mac with neither
XQuartz nor ragg writes the TIFF uncompressed, with a warning from
grDevices::tiff(). A device, or a device argument such as
fallback_resolution, compression or bg, passed in ... is used as
given, so device = "pdf", device = "eps" or device = "tiff" restores
ggplot2's own behaviour.
Which formats three publishers accepted on 2 October 2026:
| Format | PLOS | Nature (final artwork) | Elsevier |
| No | Yes, as vector art | Yes | |
| EPS | Yes, with fonts embedded | Yes, as vector art | Yes, with fonts embedded |
| TIFF | Yes, LZW-compressed RGB or greyscale, no alpha channel, at most 10 MB | No | Yes |
Nature also asks for fonts embedded as TrueType 2 or 42, never Type 3, and
for line art and text that are not rasterised. A figure with translucent
layers therefore goes to Nature as a PDF. Elsevier asks for a graph sent as
a TIFF at 1000 dpi, so pass dpi = 1000 for one.
The Python twin takes the plot first, as save_plot(plot, filename).
PLOS ONE. Figures. https://journals.plos.org/plosone/s/figures
Nature. Guide to preparing final artwork. https://www.nature.com/documents/nature-final-artwork.pdf
Nature research figure guide. Preparing figures: our specifications. https://research-figure-guide.nature.com/figures/preparing-figures-our-specifications/
Elsevier. Artwork overview. https://www.elsevier.com/about/policies-and-standards/author/artwork-and-media-instructions/artwork-overview
p <- scatter_trend(crop_yield, fertiliser, yield) tmp <- file.path(tempdir(), "yield.png") save_plot(tmp, p) # A TIFF well within the 10 MB that PLOS allows. save_plot(file.path(tempdir(), "yield.tiff"), p) # Audited at the 8.9 cm column it is saved for, where its text prints at # the size it is drawn. saved <- save_plot(file.path(tempdir(), "yield-column.png"), p, width = 8.9, height = 6, units = "cm", check = TRUE) attr(saved, "check")p <- scatter_trend(crop_yield, fertiliser, yield) tmp <- file.path(tempdir(), "yield.png") save_plot(tmp, p) # A TIFF well within the 10 MB that PLOS allows. save_plot(file.path(tempdir(), "yield.tiff"), p) # Audited at the 8.9 cm column it is saved for, where its text prints at # the size it is drawn. saved <- save_plot(file.path(tempdir(), "yield-column.png"), p, width = 8.9, height = 6, units = "cm", check = TRUE) attr(saved, "check")
Discrete ggplot2 scales using depictr_palette(). These are the canonical
colour and fill scales used throughout the package. They honour the global
options(depictr.palette = ) (via depictr_palette()) and
options(depictr.na_value = ) settings; see depictr_options().
scale_colour_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... ) scale_color_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... ) scale_fill_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... )scale_colour_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... ) scale_color_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... ) scale_fill_depictr( n = NULL, palette = NULL, na.value = depictr_opt("na_value"), order = c("default", "contrast"), ... )
n |
Optional number of colours to draw from |
palette |
Optional palette override: a function of one argument (the
number of colours) returning a character vector of colours. Defaults to
|
na.value |
Colour for |
order |
|
... |
Passed to |
A ggplot2 scale that can be added to a plot.
library(ggplot2) ggplot(crop_yield, aes(rainfall, yield, colour = treatment)) + geom_point() + scale_colour_depictr() + theme_depictr() # The vermillion in the orange's place, at 3.87:1 against white ggplot(crop_yield, aes(rainfall, yield, colour = treatment, shape = treatment)) + geom_point() + scale_colour_depictr(order = "contrast") + theme_depictr()library(ggplot2) ggplot(crop_yield, aes(rainfall, yield, colour = treatment)) + geom_point() + scale_colour_depictr() + theme_depictr() # The vermillion in the orange's place, at 3.87:1 against white ggplot(crop_yield, aes(rainfall, yield, colour = treatment, shape = treatment)) + geom_point() + scale_colour_depictr(order = "contrast") + theme_depictr()
Plots y against x with an optional fitted trend line and confidence
band, optionally split by a grouping variable.
scatter_trend( data, x, y, group = NULL, method = "lm", se = TRUE, point_alpha = 0.6, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )scatter_trend( data, x, y, group = NULL, method = "lm", se = TRUE, point_alpha = 0.6, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
data |
A data frame. |
x, y
|
The variables for the horizontal and vertical axes (string or unquoted column name). |
group |
Optional grouping variable mapped to colour. A numeric code counts as a group, but a numeric column with more than 20 distinct values is refused as continuous. Rows with a missing group are dropped with a message, and levels without rows are left out of the plot and its palette. |
method |
Smoothing method passed to |
se |
Whether to draw the confidence band around the trend. |
point_alpha |
Point transparency. |
palette |
Colours for the groups; defaults to |
title, x_lab, y_lab
|
Title and axis labels (default to variable names). |
A ggplot2::ggplot object.
scatter_trend(crop_yield, fertiliser, yield) scatter_trend(crop_yield, fertiliser, yield, group = treatment, method = "lm")scatter_trend(crop_yield, fertiliser, yield) scatter_trend(crop_yield, fertiliser, yield, group = treatment, method = "lm")
Shows the proportion of variance explained by each principal component, as bars with a cumulative line. It is the customary aid for deciding how many components to retain.
scree_plot(x, cols = NULL, scale = TRUE, n = NULL, title = NULL)scree_plot(x, cols = NULL, scale = TRUE, n = NULL, title = NULL)
x |
A data frame, or a |
cols |
When |
scale |
Whether to scale variables to unit variance before the PCA. |
n |
Maximum number of components to display. |
title |
Plot title. |
A ggplot2::ggplot object.
scree_plot(wellbeing_survey, cols = c("age", "income", "stress", "sleep_hours", "exercise_days", "life_satisfaction"))scree_plot(wellbeing_survey, cols = c("age", "income", "stress", "sleep_hours", "exercise_days", "life_satisfaction"))
Draws a seasonal-subseries plot (also called a cycle plot): the series is split into one small panel per season (e.g. one panel per calendar month for monthly data), and within each panel the value is drawn across successive cycles (e.g. across years). A horizontal reference line in every panel marks that season's mean. This makes the seasonal pattern (differences between panels) and the within-season trend over time (the slope inside each panel) visible at the same time, which a single overlaid line cannot show.
seasonal_plot( x, frequency = NULL, style = c("subseries", "season"), season_labels = NULL, means = TRUE, point = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )seasonal_plot( x, frequency = NULL, style = c("subseries", "season"), season_labels = NULL, means = TRUE, point = TRUE, palette = NULL, title = NULL, x_lab = NULL, y_lab = NULL )
x |
A |
frequency |
Number of observations per period (e.g. 12 for monthly
data); taken from |
style |
|
season_labels |
Optional character vector of length |
means |
Whether to draw the per-season mean reference line
( |
point |
Whether to add points as well as the connecting line. |
palette |
Colours used for the cycle lines when |
title, x_lab, y_lab
|
Title and axis labels. |
Two layouts are offered. With style = "subseries" (the default) the panels
are faceted side by side, each tracing one season across cycles: the
classic Cleveland cycle plot, matching feasts::gg_subseries(). With
style = "season" every cycle is drawn as its own line over the seasons on a
shared axis (the seasonal-plot layout of forecast::ggseasonplot()), which is
handy for spotting an unusual year. The lines run from a mid blue for the
first cycle to a dark blue for the last, so they read in time order. Past
about six cycles, neighbouring cycles take colours too close to tell apart,
and the colours then show the order of the cycles more than which is which.
When individual cycles must be told apart, use style = "subseries", where
each cycle keeps its own place on the axis of every panel, or plot fewer
cycles.
A ggplot2::ggplot object.
decompose_plot(), timeseries_plot()
# Monthly air passengers: rising within-month trend, summer peak seasonal_plot(AirPassengers) seasonal_plot(AirPassengers, style = "season")# Monthly air passengers: rising within-month trend, summer peak seasonal_plot(AirPassengers) seasonal_plot(AirPassengers, style = "season")
Draws a silhouette plot for a clustering: one horizontal bar per observation,
grouped and sorted within cluster, with the average silhouette width shown
per cluster and overall. The silhouette width compares how close an
observation is to its own cluster against the nearest other cluster, and lies
in (higher is better; negative suggests the point may belong to
another cluster). It is a standard internal measure of cluster quality.
silhouette_plot( data, clusters, cols = NULL, scale = TRUE, distance = "euclidean", palette = NULL, title = NULL )silhouette_plot( data, clusters, cols = NULL, scale = TRUE, distance = "euclidean", palette = NULL, title = NULL )
data |
A data frame, a numeric matrix, or a stats::dist object. |
clusters |
A vector of cluster assignments, one per observation (per row
of |
cols |
When |
scale |
Whether to scale variables to unit variance before computing the
distances (ignored when |
distance |
Distance measure passed to |
palette |
Colours for the clusters; defaults to |
title |
Plot title. |
The widths are computed with cluster::silhouette() when the 'cluster'
package is installed, and otherwise with an equivalent base-R implementation,
so the function works with no extra dependency.
A ggplot2::ggplot object. The per-observation silhouette table is
attached as the attribute "silhouette" and the average width as
"avg_width". The table's cluster and neighbor columns carry the
assignments passed in clusters, as does each band label.
Rousseeuw, P. J. (1987). Silhouettes: A graphical aid to the interpretation and validation of cluster analysis. Journal of Computational and Applied Mathematics, 20, 53-65. doi:10.1016/0377-0427(87)90125-7
cl <- kmeans(scale(crop_yield[c("rainfall", "fertiliser", "soil_ph", "yield")]), 3)$cluster silhouette_plot(crop_yield, cl, cols = c("rainfall", "fertiliser", "soil_ph", "yield"))cl <- kmeans(scale(crop_yield[c("rainfall", "fertiliser", "soil_ph", "yield")]), 3)$cluster silhouette_plot(crop_yield, cl, cols = c("rainfall", "fertiliser", "soil_ph", "yield"))
Maps each colour to the colour a reader with a given form and severity of colour-vision deficiency would perceive, using the physiologically-based model of Machado, Oliveira and Fernandes (2009). The transformation is defined on linear-light RGB, so a colour is decoded from sRGB to linear RGB (the IEC 61966-2-1 transfer functions), transformed, then re-encoded.
simulate_cvd(colours, deficiency, severity = 1)simulate_cvd(colours, deficiency, severity = 1)
colours |
Character vector of colours, in any form
|
deficiency |
The deficiency to simulate: |
severity |
Severity in |
This is the simulator behind palette_preview()'s cvd argument, behind
palette_safety(), and behind the colour-separability rows of
check_figure(). simulate_figure() applies it to every colour of a
finished figure.
A character vector of lower-case hex colours, the same length as
colours.
Machado GM, Oliveira MM, Fernandes LAF (2009). “A physiologically-based model for simulation of color vision deficiency.” IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. doi:10.1109/TVCG.2009.113.
simulate_cvd(c("#005b96", "#e69f00"), "deutan") # Half severity moves the colours only part of the way. simulate_cvd(c("#005b96", "#e69f00"), "deutan", severity = 0.5)simulate_cvd(c("#005b96", "#e69f00"), "deutan") # Half severity moves the colours only part of the way. simulate_cvd(c("#005b96", "#e69f00"), "deutan", severity = 0.5)
simulate_cvd() simulates a set of colours, and this function the figure
they end up in, legend, colourbar, text and background included.
simulate_figure(plot, deficiency, severity = 1)simulate_figure(plot, deficiency, severity = 1)
plot |
A ggplot or a patchwork, as it would be saved. |
deficiency |
The deficiency to simulate: |
severity |
Severity in |
The plot is turned into the table of grobs that would be drawn, with
ggplot2::ggplotGrob() or, for a patchwork, patchwork::patchworkGrob().
Every colour in that table then goes through simulate_cvd(). That covers
the line and fill colours of each grob, at any depth, and each cell of a
raster, such as a colourbar, a ggplot2::geom_raster() layer or an image
added with ggplot2::annotation_raster(). Each colour keeps its alpha
channel. Missing and transparent colours are left as they are, and so are
gradient and pattern fills such as grid::linearGradient(), which keep
their original colours.
The colours are changed after the plot is built. Changing the colours of the data before it is built would leave the legend keys and any colourbar in their original colours, so the legend would no longer match the figure.
The figure stays vector graphics, so it can be saved at any size. The Python
twin's simulate_figure() uses the same matrices but rasterises the figure
and simulates its pixels, so it returns an image.
A gtable, which grid::grid.draw() draws and save_plot() saves.
Machado GM, Oliveira MM, Fernandes LAF (2009). “A physiologically-based model for simulation of color vision deficiency.” IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. doi:10.1109/TVCG.2009.113.
check_figure(), which measures what this shows.
library(ggplot2) p <- ggplot(crop_yield, aes(rainfall, yield, colour = treatment)) + geom_point() + scale_colour_depictr() + theme_depictr() # The figure as a reader with deuteranopia sees it. seen <- simulate_figure(p, "deutan") grid::grid.newpage() grid::grid.draw(seen) # Saved for a side-by-side comparison with the original. save_plot(file.path(tempdir(), "yield-deutan.png"), seen)library(ggplot2) p <- ggplot(crop_yield, aes(rainfall, yield, colour = treatment)) + geom_point() + scale_colour_depictr() + theme_depictr() # The figure as a reader with deuteranopia sees it. seen <- simulate_figure(p, "deutan") grid::grid.newpage() grid::grid.draw(seen) # Saved for a side-by-side comparison with the original. save_plot(file.path(tempdir(), "yield-deutan.png"), seen)
Builds the kind of descriptive table that opens many empirical papers:
numeric variables are summarised as mean (SD), categorical variables as
counts and percentages, optionally split into one column per level of a
grouping variable. The result is a plain data frame, ready to pass to
knitr::kable() or a table package.
summary_table( data, vars = NULL, group = NULL, digits = 1, missing = TRUE, max_levels = 20 )summary_table( data, vars = NULL, group = NULL, digits = 1, missing = TRUE, max_levels = 20 )
data |
A data frame. |
vars |
Columns to summarise. If |
group |
Optional grouping variable; one summary column is produced per
level, alongside an overall column. Records whose group value is missing
get a trailing |
digits |
Number of decimal places for numeric summaries. |
missing |
Whether to add a |
max_levels |
When |
The first row of the table always reports the sample size (N) overall and
per group. By default each variable is also followed by a Missing, n (%)
row whenever it contains missing values; set missing = FALSE to suppress
these.
A date or date-time column (Date, POSIXct) gets a single Range row,
written as <earliest> to <latest> in each column. The cells give the date
alone when every time in the column falls at midnight, and the time to the
minute, or to the second when any time has seconds. A duration (difftime)
is summarised as mean (SD) in its own units, which the statistic names, as
in Mean (SD), days.
When vars is NULL, high-cardinality character/factor columns (those whose
number of distinct levels approaches the number of rows, e.g. identifier
columns) are skipped automatically, with a message, rather than being
expanded into hundreds of one-per-row entries. Pass such a column explicitly
via vars to override this.
A data frame with columns variable, statistic, Overall, one
column per group level and, when the grouping variable has missing values,
a trailing Missing column. The first row reports N.
summary_table(crop_yield, vars = c("yield", "rainfall", "treatment")) summary_table(wellbeing_survey, vars = c("life_satisfaction", "education"), group = "region")summary_table(crop_yield, vars = c("yield", "rainfall", "treatment")) summary_table(wellbeing_survey, vars = c("life_satisfaction", "education"), group = "region")
Draws Kaplan-Meier survival curves, optionally by group, with stepwise
confidence limits and censoring marks. The Kaplan-Meier estimate and its
Greenwood standard error are computed with base R, so no modelling package is
required; a survfit object from the 'survival' package is also accepted.
survival_plot( time, status = NULL, group = NULL, conf_level = 0.95, censor_marks = TRUE, risk_table = FALSE, median_line = FALSE, logrank = FALSE, risk_breaks = NULL, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = "Time", y_lab = "Survival probability", conf_type = c("log-log", "log", "plain") )survival_plot( time, status = NULL, group = NULL, conf_level = 0.95, censor_marks = TRUE, risk_table = FALSE, median_line = FALSE, logrank = FALSE, risk_breaks = NULL, palette = NULL, legend_inside = FALSE, title = NULL, x_lab = "Time", y_lab = "Survival probability", conf_type = c("log-log", "log", "plain") )
time |
A numeric vector of follow-up times, a data frame with |
status |
Event indicator: a vector, or a column name when |
group |
Optional grouping variable: a vector, or a column name when
|
conf_level |
Confidence level for the limits, a single number strictly
between 0 and 1 ( |
censor_marks |
Whether to mark censoring times with a |
risk_table |
Whether to add a number-at-risk table beneath the curves
(composed with 'patchwork'). Defaults to |
median_line |
Whether to draw dashed guides to each group's median
survival time and label it. Defaults to |
logrank |
Whether to add a log-rank test of the group difference as a
subtitle (two or more groups only). Defaults to |
risk_breaks |
Optional numeric vector of times at which to report the number at risk. Defaults to the curve's x-axis breaks. |
palette |
Colours for the groups; defaults to |
legend_inside |
When |
title, x_lab, y_lab
|
Title and axis labels. |
conf_type |
The scale on which the confidence limits are built:
|
The confidence limits are pointwise. By default, they are built on the
log-log scale, as
,
where is the Greenwood standard error of
. These limits stay between 0 and 1, and in small
samples they cover better than the untransformed limits
(Borgan & Liestøl, 1990).
conf_type = "plain" draws the untransformed limits, clipped to that range,
and conf_type = "log" draws
,
capped at 1, as survival::survfit() does by default. The limits are 1
while the estimate is 1, and where a curve falls to 0, they keep the values
of the step before. The limits of a survfit object are recomputed from its
standard errors at conf_level and conf_type, so they equal those that
survfit() gives with the same conf.int and conf.type (conf_level
names the objects that keep their own).
For publication-ready figures the plot offers the three annotations that define a "survminer-style" Kaplan-Meier display, each behind its own argument and off by default so existing behaviour is unchanged:
Number-at-risk table (risk_table = TRUE): a small panel composed
beneath the curves with 'patchwork', giving the number of subjects still at
risk in each group at the x-axis breaks.
Median-survival guides (median_line = TRUE): dashed reference lines
from the 0.5 survival level down to each group's median survival time, with
the median value labelled. The median is the time at which the curve falls
below 0.5. Where the curve stays at 0.5 until a later drop, or until its
last time, the median is the midpoint of that stretch, as in
survival::quantile.survfit(). Groups whose curve never reaches 0.5
(median not estimable) are skipped.
Log-rank test (logrank = TRUE): for two or more groups, the
chi-squared log-rank statistic and its p-value are added as a subtitle.
survival::survdiff() is used when the 'survival' package is installed,
otherwise an equivalent base-R log-rank test is computed from the
risk/event tables.
A ggplot2::ggplot object or, when risk_table = TRUE, a
'patchwork' object stacking the curves above the risk table.
Kaplan EL, Meier P (1958). “Nonparametric estimation from incomplete observations.” Journal of the American Statistical Association, 53(282), 457–481. doi:10.1080/01621459.1958.10501452.
Greenwood M (1926). The natural duration of cancer, volume 33 of Reports on Public Health and Medical Subjects. His Majesty's Stationery Office, London.
Borgan Ø, Liestøl K (1990). “A note on confidence intervals and bands for the survival function based on transformations.” Scandinavian Journal of Statistics, 17(1), 35–41.
Mantel N (1966). “Evaluation of survival data and two new rank order statistics arising in its consideration.” Cancer Chemotherapy Reports, 50(3), 163–170.
set.seed(1) n <- 200 grp <- sample(c("control", "treated"), n, replace = TRUE) time <- rexp(n, rate = ifelse(grp == "treated", 0.05, 0.1)) cens <- runif(n, 0, 30) obs <- pmin(time, cens) event <- as.integer(time <= cens) survival_plot(obs, event, group = grp, title = "Survival by treatment") # survminer-style figure: risk table, median guides and a log-rank test data(clinical_trial) survival_plot(clinical_trial$time, clinical_trial$event, group = clinical_trial$arm, risk_table = TRUE, median_line = TRUE, logrank = TRUE, x_lab = "Months", title = "Overall survival")set.seed(1) n <- 200 grp <- sample(c("control", "treated"), n, replace = TRUE) time <- rexp(n, rate = ifelse(grp == "treated", 0.05, 0.1)) cens <- runif(n, 0, 30) obs <- pmin(time, cens) event <- as.integer(time <= cens) survival_plot(obs, event, group = grp, title = "Survival by treatment") # survminer-style figure: risk table, median guides and a log-rank test data(clinical_trial) survival_plot(clinical_trial$time, clinical_trial$event, group = clinical_trial$arm, risk_table = TRUE, median_line = TRUE, logrank = TRUE, x_lab = "Months", title = "Overall survival")
The minimal theme used by every plotting function in the package. It is
a light modification of ggplot2::theme_minimal() with subtle gridlines,
centred titles and comfortable margins.
theme_depictr( base_size = depictr_opt("base_size"), base_family = depictr_opt("base_family"), grid = "xy" )theme_depictr( base_size = depictr_opt("base_size"), base_family = depictr_opt("base_family"), grid = "xy" )
base_size |
Base font size, in points. Defaults to the
|
base_family |
Base font family. Defaults to the |
grid |
Which major gridlines to keep: |
The default base_size and base_family come from the global options
depictr.base_size and depictr.base_family (see depictr_options()), so
the package-wide font size can be set once; passing the arguments explicitly
overrides them. The title colour is the resolved depictr_brand(), which in
turn honours options(depictr.brand = ).
A ggplot2 theme object.
library(ggplot2) ggplot(crop_yield, aes(fertiliser, yield)) + geom_point() + theme_depictr()library(ggplot2) ggplot(crop_yield, aes(fertiliser, yield)) + geom_point() + theme_depictr()
Sweeps the decision threshold across the full range of scores and plots the chosen classification metrics - any of sensitivity (= recall), specificity, precision and F1 - as colour-coded curves. This is the natural companion to the ROC and precision-recall curves for choosing an operating point: it shows directly how each metric trades off as the cut-off moves, and (by default) marks the Youden's J and maximum-F1 optimal thresholds.
threshold_plot( x, score = NULL, metrics = c("sensitivity", "specificity", "precision", "f1"), mark = c("youden", "f1"), title = NULL )threshold_plot( x, score = NULL, metrics = c("sensitivity", "specificity", "precision", "f1"), mark = c("youden", "f1"), title = NULL )
x |
A binomial |
score |
When |
metrics |
Which metrics to draw: any subset of |
mark |
Which optimal operating points to mark with a dashed vertical
line: any subset of |
title |
Plot title. |
At threshold a case is predicted positive when its score is . The metrics are evaluated at every distinct score (the points at which the
confusion matrix can change), so the curves are exact step functions rather
than a coarse grid. Between two adjacent scores, each metric keeps its value
at the upper one, so each step changes at a score and runs flat below it.
A ggplot2::ggplot object. The Youden and max-F1 thresholds are
stored in attr(plot, "thresholds").
gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) threshold_plot(gfit) # Sensitivity / specificity trade-off only, no markers. threshold_plot(gfit, metrics = c("sensitivity", "specificity"), mark = NULL)gfit <- glm(accuracy ~ word_frequency + RT + condition, data = lexical_decision, family = binomial) threshold_plot(gfit) # Sensitivity / specificity trade-off only, no markers. threshold_plot(gfit, metrics = c("sensitivity", "specificity"), mark = NULL)
tidy_estimates() turns the output of a model (or an existing data frame of
results) into a single standardised table with the columns term,
estimate, std.error, conf.low and conf.high. It is the common
currency used by coefficient_plot() and compare_models(), but is useful
on its own.
tidy_estimates(x, conf_level = 0.95, exponentiate = FALSE, ...)tidy_estimates(x, conf_level = 0.95, exponentiate = FALSE, ...)
x |
A fitted model or a data frame of results. |
conf_level |
Confidence (or credible) level for the interval, a single number strictly between 0 and 1. |
exponentiate |
Whether to exponentiate the estimates and interval
limits, which turns log-odds into odds ratios and log rates into rate
ratios. The |
... |
Passed to methods (and to |
Methods are provided for lm, glm and merMod (mixed models fitted with
'lme4') objects, and for data frames. For any other model class the function
falls back to broom::tidy() when the 'broom' package is installed.
Confidence intervals are Wald intervals, estimate +/- multiplier * standard
error, with the multiplier the model's own tests use. An lm object gets
the t interval of stats::confint(). A glm whose dispersion is estimated
(Gaussian, Gamma, inverse Gaussian and quasi families) gets t on the
residual degrees of freedom, since stats::summary.glm() then tests with
t. One whose dispersion is fixed (binomial, Poisson and MASS::glm.nb())
gets z. The choice is read from the column the summary reports, "t value"
or "z value". At 12 residual degrees of freedom, the t interval is about
11% wider than the z interval, and at 100 about 1% wider. A merMod fit
gets z, since 'lme4' gives no degrees of freedom. Supply
a data frame with your own intervals (for example profiled or posterior
intervals) to override this.
A data frame with one row per term and the columns term,
estimate, std.error, conf.low and conf.high.
fit <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) tidy_estimates(fit) # Odds ratios from a logistic regression: gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) tidy_estimates(gfit, exponentiate = TRUE) # A data frame of pre-computed estimates is standardised, not re-fitted: df <- data.frame( parameter = c("a", "b"), Estimate = c(0.2, -0.4), "2.5 %" = c(0.1, -0.6), "97.5 %" = c(0.3, -0.2), check.names = FALSE ) tidy_estimates(df)fit <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) tidy_estimates(fit) # Odds ratios from a logistic regression: gfit <- glm(accuracy ~ word_frequency + condition, data = lexical_decision, family = binomial) tidy_estimates(gfit, exponentiate = TRUE) # A data frame of pre-computed estimates is standardised, not re-fitted: df <- data.frame( parameter = c("a", "b"), Estimate = c(0.2, -0.4), "2.5 %" = c(0.1, -0.6), "97.5 %" = c(0.3, -0.2), check.names = FALSE ) tidy_estimates(df)
Plots one or more series over time, with an optional moving-average overlay.
The input can be a ts object, a numeric vector or matrix, or a data frame
with a time column, a value column and an optional grouping column. A
multivariate ts, such as cbind(mdeaths, fdeaths), or a matrix with
several columns gives one series per column, named after its column.
timeseries_plot( x, time = NULL, value = NULL, group = NULL, rolling = NULL, forecast = NULL, frequency = NULL, level = 0.95, lambda = NULL, palette = NULL, point = FALSE, title = NULL, x_lab = NULL, y_lab = NULL )timeseries_plot( x, time = NULL, value = NULL, group = NULL, rolling = NULL, forecast = NULL, frequency = NULL, level = 0.95, lambda = NULL, palette = NULL, point = FALSE, title = NULL, x_lab = NULL, y_lab = NULL )
x |
A |
time |
When |
value |
When |
group |
When |
rolling |
Optional integer window for a centred moving-average overlay. The data are ordered by time (within each group) before the moving average is computed, so unsorted input is handled correctly. An odd window averages each observation with the same number of neighbours on either side. An even window, such as 12 for monthly data, gives the 2×12 moving average, a 12-point average followed by a 2-point one (Hyndman & Athanasopoulos, 2021, Section 3.3). The two observations at the ends of the 13 it spans count half, which centres the average on its observation, as equal weights over an even window cannot. It needs a series one observation longer than the window. |
forecast |
Optional forecast overlay for a single (non-grouped) series.
Either an integer horizon (number of future steps) to forecast with the
built-in STL plus random walk with drift (see |
frequency |
Number of observations per period, used only to coerce a
numeric |
level |
Prediction-interval coverage for the built-in forecast (a single
number strictly between 0 and 1, e.g. |
lambda |
Optional Box-Cox transformation for the built-in forecast,
passed to |
palette |
Colours for the groups; defaults to |
point |
Whether to add points as well as the line. |
title, x_lab, y_lab
|
Title and axis labels. |
A ggplot2::ggplot object.
Hyndman RJ, Athanasopoulos G (2021). Forecasting: Principles and practice, 3rd edition. OTexts, Melbourne, Australia. https://otexts.com/fpp3/.
ts_forecast(), decompose_plot(), seasonal_plot()
timeseries_plot(AirPassengers, rolling = 12, title = "Air passengers", y_lab = "Passengers") # One line per column of a multivariate ts timeseries_plot(cbind(mdeaths, fdeaths), y_lab = "Deaths") # 24-month forecast with a 90% prediction interval, made on the log scale # because the seasonal swing grows with the number of passengers timeseries_plot(AirPassengers, forecast = 24, level = 0.9, lambda = 0)timeseries_plot(AirPassengers, rolling = 12, title = "Air passengers", y_lab = "Passengers") # One line per column of a multivariate ts timeseries_plot(cbind(mdeaths, fdeaths), y_lab = "Deaths") # 24-month forecast with a 90% prediction interval, made on the log scale # because the seasonal swing grows with the number of passengers timeseries_plot(AirPassengers, forecast = 24, level = 0.9, lambda = 0)
A dependency-free forecaster for a single seasonal series, which forecasts
by decomposition (Hyndman & Athanasopoulos, 2021, Section 5.7).
stats::stl() with s.window = "periodic" estimates a seasonal component
that repeats unchanged in every cycle (Cleveland et al., 1990). The
seasonally adjusted series, the data minus that component, is forecast with a
random walk with drift, and the last cycle of the seasonal component is added
back. The result reproduces
forecast::stlf(x, method = "rwdrift", s.window = "periodic").
ts_forecast(x, h = 12, frequency = NULL, level = 0.95, lambda = NULL)ts_forecast(x, h = 12, frequency = NULL, level = 0.95, lambda = NULL)
x |
A |
h |
Forecast horizon: the number of future steps (a positive integer). |
frequency |
Number of observations per period; taken from |
level |
Prediction-interval coverage (a single number strictly between 0 and 1). |
lambda |
Optional Box-Cox transformation parameter. |
The prediction interval at horizon h is fit +/- z * sigma_h, with
z = qnorm((1 + level) / 2). The forecast standard deviation is that of a
random walk with drift, sigma_h = sigma * sqrt(h * (1 + h / (T - 1)))
(Hyndman & Athanasopoulos, 2021, Section 5.5), where sigma is the standard
deviation of the steps of the seasonally adjusted series and T the length
of the series. The band widens with the horizon, and the second term carries
the uncertainty in the estimated drift.
The model assumes a seasonal pattern that adds to the level of the series
and stays the same from cycle to cycle. When the seasonal swing grows with
the level, as in AirPassengers, forecast on the log scale with
lambda = 0. The band treats the seasonal component as known, so it leaves
out the uncertainty in estimating it. In a rolling-origin backtest on five
classic series whose seasonal swing does not grow with their level, the
nominal 95% band covered 92% of one-step-ahead outcomes
(dev/ts_forecast_backtest.R in the source repository). From three to 12
steps ahead, it covered 99%. The band therefore runs somewhat narrow at the
shortest horizons and wide further out, where the uncertainty of a random
walk keeps growing while these series stay close to their trend. For a
different model, fit it with, for example, forecast::forecast() and pass
its columns to timeseries_plot() as a data frame.
A data frame with one row per forecast step and columns time (the
future times, on the same scale as stats::time()), fit, lwr and
upr.
Cleveland RB, Cleveland WS, McRae JE, Terpenning I (1990). “STL: A seasonal-trend decomposition procedure based on loess.” Journal of Official Statistics, 6(1), 3–73.
Hyndman RJ, Athanasopoulos G (2021). Forecasting: Principles and practice, 3rd edition. OTexts, Melbourne, Australia. https://otexts.com/fpp3/.
timeseries_plot(), decompose_plot()
# The seasonal swing of AirPassengers grows with the number of passengers, # so it is forecast on the log scale. fc <- ts_forecast(AirPassengers, h = 12, lambda = 0) head(fc) # On that scale, the band widens with the horizon diff(log(fc$upr) - log(fc$lwr)) > 0# The seasonal swing of AirPassengers grows with the number of passengers, # so it is forecast on the log scale. fc <- ts_forecast(AirPassengers, h = 12, lambda = 0) head(fc) # On that scale, the band widens with the horizon diff(log(fc$upr) - log(fc$lwr)) > 0
Computes a variance inflation factor for each term in a model and shows
them as a bar chart, with a reference line at the usual rule of thumb
(threshold). High bars flag predictors whose coefficients are unstable
because they are collinear with the others. Values are computed from base R
(no 'car' dependency).
vif_plot(model, threshold = 5, palette = depictr_palette(2), title = NULL)vif_plot(model, threshold = 5, palette = depictr_palette(2), title = NULL)
model |
A fitted |
threshold |
Reference value for the ordinary VIF, drawn as a line. For
models with multi-column terms it is shown on the
|
palette |
Length-2 colours encoding terms below and above the threshold.
Defaults to the colourblind-safe |
title |
Plot title. |
For single-degree-of-freedom terms this is the ordinary VIF, .
For terms that span several design-matrix columns (multi-level factors, or
spline/polynomial bases) the function reports the generalised VIF of Fox and
Monette (1992): with the correlation matrix of the coefficient
estimates other than the intercept, the block for the term and
the block for the remaining coefficients,
When every term has a single degree of freedom the bars are the ordinary
VIFs, on a plain "Variance inflation factor" axis with the reference line at
threshold. If any term spans several columns the bars switch to the
comparable (which for a single-df
term equals ) and the reference line moves to
accordingly. The x-axis is kept tight to the
data: when every bar is comfortably below the threshold the line is reported
in the caption rather than drawn into a wide empty band.
The correlations come from the fit's unscaled coefficient covariance, so a
glm or a weighted lm is measured with the weights of its fit, and the
values agree with those of car::vif(). For an unweighted lm with an
intercept, they are the values the formula gives with the
correlation matrix of the predictor columns, as Fox and Monette state it. A
model with aliased coefficients has no VIF and is refused, with the terms
they belong to named. A model without an intercept warns, as car::vif()
does, because its VIFs then come from columns that are not centred.
A ggplot2::ggplot object.
Fox, J., & Monette, G. (1992). Generalized collinearity diagnostics. Journal of the American Statistical Association, 87(417), 178-183. doi:10.1080/01621459.1992.10475190
# Two deliberately collinear predictors: soil moisture is largely driven by # rainfall, so both carry an inflated VIF (around 6, above the line at 5) # while fertiliser stays near 1. set.seed(1) d <- crop_yield d$soil_moisture <- 0.05 * d$rainfall + rnorm(nrow(d), sd = 2) vif_plot(lm(yield ~ rainfall + soil_moisture + fertiliser, data = d)) # Multi-level factors get a single generalised VIF per term fit2 <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) vif_plot(fit2)# Two deliberately collinear predictors: soil moisture is largely driven by # rainfall, so both carry an inflated VIF (around 6, above the line at 5) # while fertiliser stays near 1. set.seed(1) d <- crop_yield d$soil_moisture <- 0.05 * d$rainfall + rnorm(nrow(d), sd = 2) vif_plot(lm(yield ~ rainfall + soil_moisture + fertiliser, data = d)) # Multi-level factors get a single generalised VIF per term fit2 <- lm(yield ~ rainfall + fertiliser + treatment, data = crop_yield) vif_plot(fit2)
A reproducibly simulated cross-sectional wellbeing survey for descriptive,
correlation, regression and missing-data examples. life_satisfaction
responds meaningfully to its predictors: higher stress lowers it, more sleep
and exercise raise it, log-income has a positive effect of about +0.4 per
natural-log unit, and age has a mild inverted-U (peaking in mid-life).
education is an ordered factor. Income is missing more often at higher
stress (missing at random), and because income predicts the outcome, this
missingness is informative for the missing-data examples.
wellbeing_surveywellbeing_survey
A data frame with 300 rows and 9 variables:
Respondent identifier.
Region of residence: North, South, East or West.
Age in years.
Highest education level (ordered factor:
secondary < undergraduate < postgraduate).
Annual income; contains missing values (more at high stress).
Self-reported stress (1-7).
Typical hours of sleep per night; contains missing values.
Days of exercise per week (0-7); contains missing values.
Life satisfaction (1-7); contains missing values.
The data are synthetic, generated by data-raw/generate_datasets.R with a
fixed seed; they do not describe any real individuals.
Simulated; see data-raw/generate_datasets.R.