Asymmetric, GAM-based smoothed-association matrices: a directional
y ~ s(x) / x ~ s(y) fit for every pair of continuous variables, so
shape and direction survive where a Pearson correlation matrix collapses
both away.
Asymmetric, GAM-based smoothed-association matrices for continuous
variables. Each off-diagonal cell shows a directional
mgcv::gam(y ~ s(x)) fit, so the upper and lower triangles tell
different stories whenever the relationship is genuinely asymmetric —
precisely where a scalar Pearson correlation loses information.
Two novelties, paired with the asymmetry story:
A = |EDF_yx − EDF_xy| / (EDF_yx + EDF_xy) ∈ [0, 1] — a
single-number summary of the directional disparity per pair.linear_up, skewed_peak, bimodal,
bi_wave, rippled_monotone, …) via a (T, I) dispatch on
turning-point and inflection counts, with monotonicity/convexity
indices for disambiguation. The taxonomy rolls up through three
broader tiers: archetype (7), monotonic (3), linear (2) —
grounded in shape-constrained regression (Pya & Wood 2015),
dose-response pharmacology (Calabrese 2008), and Morse critical-
point classification (Milnor 1963).corrplot and XICOR give one number per pair, and a GGally plot matrix shows each pair’s data. janusplot fits a smooth in each direction for every pair, draws both fits in the matrix, and reports how much the two directions differ and what shape each curve takes.
# released version from CRAN
install.packages("janusplot")
# development version from GitHub
# pak::pak("max578/janusplot")
install.packages("janusplot", repos = c('https://max578.r-universe.dev', 'https://cloud.r-project.org'))
Installing from GitHub does not build the vignettes. To read them
locally from a GitHub install, use
remotes::install_github("max578/janusplot", build_vignettes = TRUE, dependencies = TRUE).
The CRAN release ships them built.
library(janusplot)
# Palmer penguins — four continuous traits
d <- na.omit(palmerpenguins::penguins[,
c("bill_length_mm", "bill_depth_mm",
"flipper_length_mm", "body_mass_g")])
janusplot(d)
Default encoding:
RdBu palette
symmetric around zero (override via colour_by = "spearman" /
"kendall" / "edf" / "deviance_gap" / "none").A = ... (asymmetry index) stacked over
EDF = ....· * ** ***).<name> (<code>).Opt into a per-cell shape marker via annotations:
# Two-letter shape code, top-left (ASCII — safe on any font / PDF):
janusplot(d, annotations = c("edf", "A", "code"))
# Unicode shape glyph, bottom-right:
janusplot(d, annotations = c("edf", "A", "shape"),
glyph_style = "unicode")
| Archetype (7) | Categories | Example |
|---|---|---|
monotone_linear |
linear_up linear_down
|
y = x |
monotone_curved |
convex_up concave_up convex_down concave_down s_shape rippled_monotone
|
tanh, sqrt, exp(−x)
|
unimodal |
u_shape inverted_u skewed_peak broad_peak rippled_peak
|
(x−.5)², x·exp(−3x), plateau |
wave |
wave warped_wave rippled_wave complex_wave
|
sin(2πx) family |
multimodal |
bimodal bimodal_ripple bi_wave bi_wave_ripple
|
two-peak mix, sin(4πx)
|
chaotic |
complex |
≥ 5 extrema / inflections |
degenerate |
flat indeterminate
|
constant / fit failure |
Full table (with 2-letter codes + monotonic / linear rollups + glyph +
gloss) via janusplot_shape_hierarchy().
The classifier’s recovery behaviour is characterised across a full factorial of sample sizes × noise levels × ground-truth shapes:
# Precomputed 2160-fit demo sweep — zero wait
data("shape_sensitivity_demo")
janusplot_shape_sensitivity_plot(shape_sensitivity_demo, "recovery_curves")
janusplot_shape_sensitivity_plot(shape_sensitivity_demo, "confusion_archetype")
# Run your own — full grid in parallel
future::plan(future::multisession, workers = 4L)
res <- janusplot_shape_sensitivity(parallel = TRUE)
Four diagnostic plots ("confusion_fine" / "confusion_archetype" /
"accuracy_grid" / "recovery_curves") + summary aggregations at fine
and archetype levels. Design, pre-registered hypotheses, and full
walk-through in the
shape-recognition-sensitivity
vignette.
mgcv — EDF, F-test p-values, confidence
envelopes, random effects via s(g, bs = "re").janusplot_data() output.adjust = formula — propagate covariates and random effects into
every cell’s smooth.janusplot_data() — per-pair raw fits, correlations, EDFs,
p-values, shape metrics + hierarchy columns, without the plot.janusplot(..., with_data = TRUE) — plot + tidy data frame in one
call.future.apply for large matrices and
sensitivity sweeps.2026L pins the shipped demo + vignette
figures; user sweeps accept seed =.A Pearson correlation discards both the shape of an association and
the direction information that a non-linear data-generating process
leaves in its residuals. Under the additive-noise causal discovery
setting (Hoyer et al. 2009; Peters et al. 2014) the forward regression
y ~ s(x) and its inverse x ~ s(y) are generically asymmetric when
the underlying DGP is non-linear, and that asymmetry identifies the
causal direction under mild conditions. janusplot surfaces this
asymmetry as a visual pre-discovery diagnostic — not a causal
inference procedure. See the vignette and the accompanying paper for
scope and explicit non-claims.
vignette("janusplot") — quickstart + feature tourvignette("shape-recognition-sensitivity") — design, hypotheses,
every diagnostic plotR CMD check --as-cran clean (0 errors, 0 warnings; 2
local-environmental NOTEs only). Comprehensive testthat suite
including vdiffr visual-regression snapshots; coverage tracked via the
Codecov badge above.
Bug reports and suggestions are welcome through the GitHub
issues. See
CONTRIBUTING.md for how to propose a change.
citation("janusplot")
MIT. See LICENSE.md for the full text.