janusplot janusplot logo

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:

  1. Asymmetry index A = |EDF_yx − EDF_xy| / (EDF_yx + EDF_xy) ∈ [0, 1] — a single-number summary of the directional disparity per pair.
  2. 24-category shape taxonomy — each fitted smooth is classified into one of 24 named shapes (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).

Related packages

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.

Install

# 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.

Quick start

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:

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")

Shape taxonomy — what gets classified

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().

Sensitivity study — built-in

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.

Key features

Why asymmetric?

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.

Documentation

Status

R 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.

Contributing

Bug reports and suggestions are welcome through the GitHub issues. See CONTRIBUTING.md for how to propose a change.

Citation

citation("janusplot")

License

MIT. See LICENSE.md for the full text.