Initial public release of janusplot. The package renders
a pairwise, asymmetric smoothed-association matrix of continuous
variables, with each cell showing the fitted spline from an
mgcv generalised additive model. Upper-triangle cells plot
gam(x_j ~ s(x_i)); lower-triangle cells plot
gam(x_i ~ s(x_j)). The intentional asymmetry surfaces
heteroscedasticity, leverage, and directional non-linearity that a
single scalar correlation hides.
Public surface includes:
janusplot() — the matrix-plot entry point, with
per-cell fit / CI / raw-data controls, hclust-ordered variables,
random-effects adjustment via s(g, bs = "re"), and opt-in
parallel fits via future.apply.janusplot_data() — sibling returning the fitted-model
metadata (EDF, F-test p, asymmetry index, shape classification) without
rendering.janusplot_shape_metrics() +
janusplot_shape_cutoffs() +
janusplot_shape_hierarchy() — the 24-category shape
taxonomy.janusplot_shape_sensitivity() + helpers — recovery-rate
simulation over 12 ground-truth shapes × 7 sample sizes × 500
replicates, with the precomputed shape_sensitivity_demo
dataset for quick inspection.The remainder of this file documents pre-release development history, retained for provenance.
User-visible changes:
data.table dependency removed.
janusplot(..., with_data = TRUE)$data and any other tabular
returns are now always a plain data.frame — no runtime or
documented fallback to data.table::as.data.table(). The
package charter bans data.table as a dependency (plotting
function, overhead unearned). data.table is no longer
listed in Suggests:.janusplot() example no longer passes
show_asymmetry = TRUE (which has been deprecated since
0.0.0.9000 and emitted a soft deprecation warning on every
example render). The default annotations = c("edf", "A")
already surfaces the asymmetry index, so the example is materially
unchanged.Internal / house-style fixes:
match.arg() →
rlang::arg_match() in the two remaining sites
(.shape_glyph() in R/shape-metrics.R and
.display_title() in R/janusplot.R). Brings
every enumerated-argument gate to the same rlang discipline
CONTRIBUTING already documents. Function signatures updated so the enum
set lives on the formal default (as rlang::arg_match()
requires).@family tags added to 7 exports —
janusplot_shape_cutoffs(),
janusplot_shape_hierarchy(),
janusplot_shape_metrics(),
janusplot_shape_sensitivity(),
janusplot_shape_sensitivity_shapes(),
janusplot_shape_sensitivity_summary(),
janusplot_shape_sensitivity_plot(). The
_pkgdown.yml reference index now organises these two
families (shape-metrics and shape-sensitivity)
alongside the existing smooth-associations family,
replacing an alphabetical dump.inst/WORDLIST extended with the 24
Phase-F shape-category names (linear_up,
linear_down, convex_up,
convex_down, concave_up,
concave_down, s_shape, u_shape,
inverted_u, skewed_peak,
broad_peak, rippled_peak,
rippled_monotone, rippled_wave,
warped_wave, complex_wave,
bimodal_ripple, bi_wave,
bi_wave_ripple, …). Keeps
devtools::spell_check() clean.inst/CITATION Article bibentry now
carries email= and comment = c(ORCID = ...)
for consistency with the Manual bibentry, CITATION.cff, and
codemeta.json.DESCRIPTION, CITATION.cff, and
inst/CITATION at 0.0.0.9001.Deferred to a later maintenance sweep (not blocking 0.0.0.9001):
renv.lock snapshot (/rpkg env
nice-to-have).covr::package_coverage() > 0.85 CI-side floor
assertion.adr/ relocation to docs/adr/ per
/rpkg governance convention.barheight = grid::unit(0.6, "npc") override inside
the guide_colourbar() for both diverging and sequential
scales, and set legend.key.height = grid::unit(1, "null")
in the legend plot’s theme. The bar now tracks the matrix panel height
robustly across figure sizes (corrplot-like behaviour), closing the
visible proportional-scaling mismatch that appeared between narrow and
wide renders. Added a thin frame.colour = "grey50" around
the bar for visual weight.man/figures/lifecycle-*.svg added.
Running usethis::use_lifecycle() copied
lifecycle-experimental, lifecycle-stable,
lifecycle-superseded, and lifecycle-deprecated
into man/figures/. The HTML help pages for every
lifecycle::badge()-tagged function now render the badge
instead of the broken-image placeholder (? in a box) that
appeared when the SVG file was absent.display parameter on
janusplot(). Scalar — one of "fit"
(default, unchanged behaviour for legacy callers), "d1", or
"d2". Controls which single quantity is rendered in every
off-diagonal cell of the matrix. A matrix-level title
("Direct fit", "First derivative f'(x)", or
"Second derivative f''(x)") names the displayed quantity.
To compare fit vs derivative, issue two or three
janusplot() calls and place them side-by-side; each call
keeps its own with_data = TRUE summary table, which now
carries a display column tagging the rendered mode.mgcv’s linear-predictor matrix
via D %*% coef(fit) with variance
D %*% Vp %*% t(D). The method of Wood (2017, §7.2.4), as
popularised for GAMs by Simpson (2018, Frontiers in Ecology and
Evolution). No new dependency (gratia is not
required).derivative_ci parameter on
janusplot() and janusplot_data(). One
of "none" (default), "pointwise", or
"simultaneous". Default is "none" — no CI
ribbon is drawn on derivative panels unless the caller opts in, because
pointwise derivative ribbons over-read local features.
"pointwise" draws fit ± 1.96 * se from the
LP-matrix SE. "simultaneous" draws Monte Carlo
critical-multiplier bands per Simpson (2018): draw \(\tilde{\boldsymbol\beta}_b \sim
N(\hat{\boldsymbol\beta}, V_p)\) and use the \((1-\alpha)\) quantile of the normalised
max-deviation statistic as the critical multiplier on the pointwise
SE.derivative_ci_nsim parameter.
Integer number of Monte Carlo samples used when
derivative_ci = "simultaneous". Default 1000L
(Simpson 2018 uses 10000; 1000 is a throughput-quantile accuracy
compromise affordable for medium matrices).n_grid parameter on
janusplot() and janusplot_data().
Prediction-grid resolution. NULL (default) resolves to 100
when display = "fit" and 200 otherwise. Callers may
override. Larger grids shift the shape-metric values (M,
C, turning / inflection counts) slightly because they are
computed on this same grid — a deliberate side-effect, flagged in
?janusplot and in the Limitations section of the vignette.
Shapes and asymmetry are the primary matrix reading; M/C/counts are
secondary diagnostics.derivatives parameter on
janusplot_data(). Integer vector of orders in
1:2 (multi-order is allowed — unlike
janusplot()’s scalar display, the data
companion returns arbitrary requested orders in a single call). Every
per-pair list in the return carries deriv_yx and
deriv_xy named lists keyed by order, each a data frame with
columns x, fit, se,
lo, hi, ci_type. When
derivative_ci = "simultaneous" each derivative frame also
carries a "crit_multiplier" attribute.references/janusplot-derivatives.bib for re-use in the R
Journal paper.display enforces the
scalar choice via rlang::arg_match();
n_grid < 10 is an error and n_grid > 500
raises an informational note. Orders ≥ 3 are refused by design —
higher-order derivatives of penalised regression splines amplify noise
beyond usable signal at realistic sample sizes (Eilers, Marx &
Durbán 2015).display as a vector (display = c("fit", "d1"))
that stacked panels inside every cell. That pattern is removed; the
scalar API is the only one supported. Callers that reached the
stacked-panel intermediate must switch to one janusplot()
call per quantity.labels parameter with three modes:
"border" (default — variable names along the top + left
margins, mirroring corrplot’s tl.pos = "lt"
convention), "diagonal" (previous in-matrix layout), and
"none" (suppressed). Default flipped to
"border" because border labels free the diagonal cells and
scale better to k > 4 variables.label_srt — rotation of top labels
when labels = "border". Default 45° matches
the visual reference; 0 and 90 are
accepted.label_cex — positive multiplier on
border-label font size. Default 1.labels != "diagonal", giving the matrix a uniform grid
reading.M and C columns renamed to
monotonicity_index and convexity_index across
every data surface: the flat data frame from
janusplot(..., with_data = TRUE),
janusplot_data() per-pair lists
(monotonicity_index_yx / convexity_index_yx /
_xy variants), the return of
janusplot_shape_metrics(), the raw output of
janusplot_shape_sensitivity(), and the precomputed
shape_sensitivity_demo dataset. Paper symbols
M / C remain as mnemonics in the
documentation. Internal classifier parameter names (M,
C) are unchanged because they match the math
convention.janusplot
vignette.result$M / result$C
must update to result$monotonicity_index /
result$convexity_index.janusplot_shape_sensitivity() — new
public function that runs a full-factorial shape-recognition sensitivity
sweep (shapes × sample sizes × noise levels × replicates) and returns a
tidy raw data frame. Optional parallel dispatch via
future.apply.janusplot_shape_sensitivity_shapes() —
lists the 14 canonical ground-truth shapes available to the sweep.janusplot_shape_sensitivity_summary()
— per-cell accuracy aggregation at the fine (24-category) or archetype
(7-family) level.janusplot_shape_sensitivity_plot() —
four built-in diagnostic plots: fine / archetype confusion matrices,
per-shape accuracy grid, recovery curves.shape_sensitivity_demo dataset —
precomputed 2160-fit sweep shipped with the package so the vignette and
examples don’t need to re-run the sweep. Regenerated deterministically
via data-raw/shape_sensitivity_demo.R.shape-recognition-sensitivity.Rmd — presents the sweep
design, the pre-registered hypotheses, and every diagnostic plot on the
precomputed demo dataset.LazyData: true in DESCRIPTION so
shape_sensitivity_demo is accessible without an explicit
data() call under default user expectations.colour_by options.corr
for all three correlation encodings; actual method remains visible in
janusplot_data() column names and the caption.(T, I) dispatch. New fine categories:
skewed_peak, broad_peak,
rippled_peak, wave, warped_wave,
rippled_wave, complex_wave,
bimodal, bimodal_ripple, bi_wave,
bi_wave_ripple, rippled_monotone.label (code). The old right-margin Unicode-glyph list is
retired.annotations default is c("edf", "A"); opt into
per-cell shape markers with annotations = c(..., "shape")
(glyph) or annotations = c(..., "code") (2-letter ASCII —
safer on any font / PDF pipeline). When both are passed,
"code" wins.janusplot(..., with_data)$data gains
shape_code, shape_archetype,
shape_monotonic, shape_linear.
janusplot_data() pairs gain per-direction
shape_code_yx / shape_code_xy and the matching
shape_archetype_*, shape_monotonic_*,
shape_linear_*.janusplot_shape_hierarchy() — returns the 24-row
taxonomy with hierarchy columns (category,
code, archetype, monotonic,
linear, label, gloss). Intended
for downstream group-bys and for cross-referencing the compact 2-letter
codes when they appear in cells or the data table.complex.Academic framing. The broader-tier vocabulary
(linear / non-linear, monotone / non-monotone, convex / concave) is
standard calculus; the archetype layer is anchored by Pya & Wood
(2015) Stat & Comput (shape-constrained additive models)
and Calabrese (2008) Env Tox Chem (dose-response taxonomy). The
(T, I) dispatch is a coarsened Morse-theoretic
critical-point classification (Milnor 1963).
colour_by argument with a diverging RdBu
palette symmetric around zero. Choose colour_by = "pearson"
/ "kendall" for other correlation flavours or
colour_by = "edf" to restore the legacy encoding.fill_by is deprecated — use
colour_by. When supplied, fill_by fires a soft
deprecation warning and forwards to colour_by for one minor
version.annotations — a character vector (subset of
c("edf", "A", "shape")) now controls which corner labels
render on each cell. "A" (asymmetry index) replaces the old
n = ... annotation; "edf" keeps EDF visible as
text; "shape" draws the new shape-category glyph
bottom-right.show_asymmetry is deprecated — use
annotations. When supplied, the legacy argument fires a
soft deprecation warning and is merged into
annotations.M, convexity
C), two discrete counts (n_turning_points,
n_inflections), and a discrete shape_category
from a 12-category taxonomy (linear_up,
linear_down, convex_up,
concave_up, convex_down,
concave_down, u_shape,
inverted_u, s_shape, complex,
flat, indeterminate). Cutoffs are tunable via
janusplot_shape_cutoffs().janusplot_shape_metrics() — public
function to compute the shape descriptor for any fitted
mgcv::gam with a single s() term.janusplot_shape_cutoffs() — public
function returning the default threshold list; callers override
individual thresholds via ....annotations
includes "shape", a compact legend listing every category
present in the matrix is attached alongside the colour bar. Toggle with
show_shape_legend.glyph_style argument ("unicode" default,
"ascii" fallback) for pipelines that lack Unicode curve
glyphs.janusplot(..., with_data = TRUE)$data gains columns
cor_pearson, cor_spearman,
cor_kendall, tie_ratio, M,
C, n_turning_points,
n_inflections, flat_range_ratio,
shape_category, colour_value. The legacy
fill_value column is renamed
colour_value.janusplot_data() pairs — each
per-pair element gains Pearson / Spearman / Kendall correlations, tie
ratio, and per-direction shape descriptors (M_yx,
C_yx, M_xy, C_xy,
n_turning_*, n_inflect_*,
shape_yx, shape_xy).janusplot() — asymmetric smoothed-association matrix
visualisation. For each pair of numeric variables
(X_i, X_j) with i != j, the cell at matrix
position [i, j] renders the fitted spline from
mgcv::gam(X_j ~ s(X_i) + <adjust>). Upper and lower
triangles show the two directional fits. Diagonal cells carry variable
labels.janusplot_data() — programmatic companion returning raw
GAM fits and per-pair metrics (EDF, F-test p-value, deviance explained,
asymmetry index) without constructing a ggplot. Set
keep_fits = TRUE to retain the full mgcv::gam
objects.|EDF_yx − EDF_xy| / (EDF_yx + EDF_xy), bounded in
[0, 1], exposed both via
janusplot_data()$pairs[[i]]$asymmetry_index and
(optionally) as a cell corner annotation via
show_asymmetry = TRUE.adjust = — any
one-sided formula RHS (e.g. ~ s(z) + s(g, bs = "re"))
propagates to every pairwise GAM, producing covariate-adjusted smooths
and supporting random effects out of the box.order = "hclust" — reorder variables
by hierarchical clustering of 1 - |cor| (matching the
corrplot convention) to group strongly correlated variables
visually.na_action = "pairwise" (default) vs
"complete" — per-pair complete observations with annotated
n_used per cell, vs listwise deletion.future.apply — set
parallel = TRUE to dispatch pair fits across a
user-configured future::plan().palette =
accepts one of 16 options:
viridis
(default), magma, inferno,
plasma, cividis, mako,
rocket;turbo (not
colourblind-safe);RdYlBu,
RdBu, PuOr;Spectral;YlOrRd,
YlGnBu, Blues, Greens.fill_by != "none", placed in a dedicated fixed-width column
(1.8 cm) so cells remain square.k —
n and EDF labels shrink sublinearly as the
number of variables grows, keeping small-cell plots legible.n,
EDF, A, fill encoding, and significance
glyphs), showing only the keys actually displayed.with_data = TRUE — optional flat
per-cell summary table returned alongside the ggplot, as a
data.table when the package is installed or a
data.frame otherwise.vdiffr visual-regression
snapshots.R CMD check --as-cran with 0 ERRORs, 0 WARNINGs;
2 NOTEs remain (both environmental: new-submission status; local HTML
Tidy version).r = 0.93, janusplot recovers an asymmetry
index ≈ 0.56 (IQR [0.52, 0.65]), exposing hidden directional structure a
scalar correlation misses.max578/janusplot). An
earlier plan to merge into AAGI-AUS/effectsurf was
superseded on 2026-04-21; this package now ships on its own.paper/ in the dev workspace).Imports kept minimal: mgcv, ggplot2,
patchwork, grid, stats,
cli, rlang. Optional: data.table,
future.apply, vdiffr, withr,
palmerpenguins, MASS, agridat,
knitr, rmarkdown.