Projects two or more classification images (CIs) into a low-
dimensional Euclidean scatter using classical MDS
(stats::cmdscale). Distances between points in the scatter
reproduce the original Euclidean distances between CIs as
faithfully as the chosen number of dimensions allows.
Use this to see at a glance which condition CIs cluster together and which sit far apart, especially when you have more than a handful of conditions to compare side by side.
Usage
plot_ci_mds(
cis,
img_dims = NULL,
mask = c("none", "face", "upper_face", "lower_face"),
distance = c("euclidean_raw", "euclidean_normalised"),
k = "auto",
stress_threshold = 0.05,
k_max = 4L,
groups = NULL,
shapes = NULL,
point_size = 2.5,
point_alpha = 0.85,
label = TRUE,
label_cex = 0.85,
show_axes_at_zero = TRUE,
show_gof = TRUE,
main = NULL,
file = NULL,
width = NULL,
height = NULL,
...
)Arguments
- cis
CIs to project. The recommended form is a numeric matrix
n_pixels x n_ciswith named columns; each column is one CI (a group mean, a single producer's CI, or any mix). Build it outside the call withcbind(name = rowMeans(cis$signal_matrix), ...), or use the output ofgroup_ci()(a named matrix) directly. A named list of CIs is also accepted (vectors of lengthprod(img_dims), single-column matrices, or per-producersignal_matrixobjects fromci_from_responses_*(), which are reduced to group means internally). At least three CIs are required for a meaningful 2D projection.- img_dims
Integer
c(nrow, ncol). IfNULL, inferred fromattr(cis[[1]], "img_dims")or fromsqrt(n_pixels).- mask
One of
"none"(default),"face","upper_face", or"lower_face". Restricts the pixels included in the distance computation viamake_face_mask().- distance
One of
"euclidean_raw"(default; absolute Euclidean distance) or"euclidean_normalised"(raw / sqrt(n_pixels_used)). The MDS projection is fit to whichever distance is selected.- k
Either
"auto"(default; pick the smallestkwhose Kruskal stress reachesstress_threshold) or an integer (forces that many dimensions; bypasses auto- selection). Usek = 2Lfor a single paper-figure panel.- stress_threshold
Numeric. The Kruskal stress level the auto-selector treats as "good enough". Default
0.05(Kruskal's "good" band). Lower it to0.025for "excellent" fidelity; raise to0.10for "fair" if the data are too noisy to reach 0.05 at smallk.- k_max
Integer. Maximum dimensionality the auto-selector will try. Default
4L, capping the multi-panel grid atchoose(4, 2) = 6pairs. Silently capped tolength(cis) - 1L(the maximum embeddable dimension).- groups
Optional named character vector mapping CI names to a categorical group label. Used to color the points. Names must match
names(cis)exactly.- shapes
Optional named character vector with the same shape as
groups, mapping CI names to a second categorical label used for point shapes (pch).- point_size, point_alpha
Numeric. Point cex and alpha.
- label
Logical. Draw CI names above each point. Default
TRUE.- label_cex
Numeric. Cex for point labels.
- show_axes_at_zero
Logical. Draw faint zero-reference lines on each panel. Default
TRUE.- show_gof
Logical. Render the GOF summary above the figure (selected
k, stress + band, per-axis variance). DefaultTRUE.- main
Optional plot title.
- file
Optional output path. If
NULL(default), plots to the current open device. If a path ending in.pngor.pdf(case-insensitive), saves at 600 dpi (PNG) or as vector PDF.- width, height
Optional output dimensions in inches. Defaults scale with the number of CIs and the number of panels.
- ...
Currently unused; reserved for future arguments.
Auto-selecting the number of dimensions
By default (k = "auto"), the function fits classical MDS at
every dimensionality from 2 up to k_max and picks the
smallest k whose Kruskal stress-1 against the original
Euclidean distances reaches the "good" threshold (default
stress_threshold = 0.05).
Kruskal's (1964) interpretive bands for stress-1:
0.025excellent (the 2D / kD map is essentially exact)0.05good (small distortions; safe to interpret)0.10fair (interpret carefully; check the trace)0.20poor (the projection is hiding more than it shows)> 0.20very poor
When the selected k > 2, the figure becomes a grid of all
choose(k, 2) pairwise dimension panels (Dim 1 vs 2, Dim 1
vs 3, Dim 2 vs 3, ...) so no information is hidden by a
premature flattening to 2D.
The companion goodness-of-fit metric (Mardia's GOF1,
cumulative eigenvalue variance) is reported alongside stress
in $variance_pct_by_k. Values near 100% mean the kD map
captures essentially all of the positive eigenmass; below
~60% the kD map is hiding more than it shows. Stress is
usually the more interpretable of the two.
Theory-driven dimensionality
Real research often has a theoretical reason to fix k to a
specific number (typically 2, for a paper-style scatter that
matches a two-axis hypothesis like warmth/dominance, or a
single panel for visual comparison with prior work). Pass an
integer k to bypass auto-selection:
plot_ci_mds(ci_list, mask = "face", k = 2L)The function still computes stress at every k in
[2, k_max] and exposes the trace via $stress_by_k. When
the forced dimensionality has high stress, report
out$stress_by_k alongside the 2D figure in the paper so
readers see what the theory-driven projection is hiding;
interpret point positions in relative terms (which
conditions cluster together) rather than as absolute
distances.
Force a higher specific k (e.g., k = 3L) when a richer
projection is theoretically motivated; the function will
render the corresponding choose(k, 2)-panel grid.
Reading the plot
Each panel is a scatter of CIs in two MDS dimensions. Points
close together represent CIs that are similar in pixel space;
distant points represent dissimilar CIs. With groups,
points color by category; with shapes, points carry
distinct marker shapes by another category. The aspect ratio
is fixed (asp = 1) so visual distances faithfully reflect
the MDS distances.
The line at the top of the figure reports the selected k,
the Kruskal stress at that k and its interpretive band,
and the per-axis variance percentages.
Reading the result
The returned object (class rcisignal_mds) contains:
$mds_points: the coordinates of each CI in the Euclidean MDS space. Ann_cis x k_selectednamed numeric matrix (rows = CI names, columns = orthogonal MDS axes). Pull these directly to compute custom downstream analyses (e.g. distances between specific CIs in the reduced space, your own scatter, clustering).$distance_matrix: the pairwise distances the projection was fit to (raw or normalised perdistance).$stress_by_k,$variance_pct_by_k: per-kKruskal stress and cumulative Mardia GOF1. Inspect these to audit the auto-selectedk.$k_selected,$stress_1,$stress_band,$variance_pct: summaries at the selectedk.$panel_pairs: the2 x choose(k, 2)matrix of dimension pairs the figure rendered.
Call print() or summary() on the returned object for a
one-screen human-readable view.
References
Kruskal, J. B. (1964). Multidimensional scaling by optimizing goodness of fit to a nonmetric hypothesis. Psychometrika, 29(1), 1-27.
See also
plot_ci_distance_matrix() for the all-vs-all
distance matrix that this function projects;
plot_ci_correlogram() for the Pearson-r version of the
same multi-CI summary; rel_dissimilarity() for a
two-condition Euclidean distance with bootstrap CI.
Examples
if (FALSE) { # \dontrun{
# Minimal: synthetic CIs to see the function's call signature
# and inspect the output shape.
set.seed(1)
n_pix <- 32L * 32L
ci_list <- list(
A = rnorm(n_pix),
B = rnorm(n_pix) + 0.3,
C = rnorm(n_pix) - 0.2,
D = rnorm(n_pix) + 0.5,
E = rnorm(n_pix)
)
# Auto-selects the smallest k reaching "good" stress (<= 0.05).
out <- plot_ci_mds(ci_list, img_dims = c(32L, 32L))
# Human-readable view of the dimensionality selection trace:
print(out)
# Extract the coordinates of each CI in the Euclidean MDS space.
out$mds_points
# Distance between CI "A" and CI "C" in the reduced space:
sqrt(sum((out$mds_points["A", ] - out$mds_points["C", ])^2))
# Per-k stress and variance traces (was the auto-choice sensible?):
out$stress_by_k
out$variance_pct_by_k
} # }
if (FALSE) { # \dontrun{
# Realistic: simulate four conditions with planted signals,
# build CIs, then compare in MDS space.
sim_eyes <- simulate_briefrc_data(
n_per_condition = 20, n_trials = 60, conditions = "eyes",
signal_region = "eyes", signal_strength = "strong", seed = 1
)
sim_mouth <- simulate_briefrc_data(
n_per_condition = 20, n_trials = 60, conditions = "mouth",
signal_region = "mouth", signal_strength = "strong", seed = 2
)
sim_nose <- simulate_briefrc_data(
n_per_condition = 20, n_trials = 60, conditions = "nose",
signal_region = "nose", signal_strength = "strong", seed = 3
)
sim_flat <- simulate_briefrc_data(
n_per_condition = 20, n_trials = 60, conditions = "control",
signal_region = NULL, seed = 4
)
cis_eyes <- ci_from_responses_briefrc(sim_eyes$data,
noise_matrix = sim_eyes$noise_matrix)
cis_mouth <- ci_from_responses_briefrc(sim_mouth$data,
noise_matrix = sim_mouth$noise_matrix)
cis_nose <- ci_from_responses_briefrc(sim_nose$data,
noise_matrix = sim_nose$noise_matrix)
cis_flat <- ci_from_responses_briefrc(sim_flat$data,
noise_matrix = sim_flat$noise_matrix)
ci_list <- list(
"Eyes" = cis_eyes$signal_matrix,
"Mouth" = cis_mouth$signal_matrix,
"Nose" = cis_nose$signal_matrix,
"Control" = cis_flat$signal_matrix
)
out <- plot_ci_mds(ci_list, mask = "face")
# Force a single 2D paper figure once you have audited fidelity:
plot_ci_mds(ci_list, mask = "face", k = 2L,
file = "fig_mds.pdf")
# Add a grouping variable for point color:
plot_ci_mds(
ci_list, mask = "face",
groups = c(Eyes = "feature", Mouth = "feature",
Nose = "feature", Control = "control")
)
} # }