Distance
The question: if one gap in the plot is twice another, was it twice as big in the data?
This is a stricter claim than the neighbourhood family makes, and a different one. Neighbourhood measures only care about who is near whom; these care about how far.
The measures
Section titled “The measures”▲ higher is better · ▼ lower is better · △▽ hollow: unbounded, so the number is comparable only between projections of the same data.
| measure | what it compares | range | |
|---|---|---|---|
stress | ▽ | raw distances, as they are | |
scaleNormalizedStress | ▼ | distances after the optimal global rescaling | |
nonMetricStress | ▼ | distances after the optimal monotone transform | |
pearsonR | ▲ | linear correlation of the two distance sets | |
spearmanRho | ▲ | rank correlation — ordering only | |
residualVariance | ▼ | ; the variance the linear fit leaves |
They form a ladder of how much you are willing to forgive:
stressforgives nothing. A projection that is perfect but drawn at half scale scores badly.scaleNormalizedStressforgives a global rescaling: it minimises over , sooptimalScaleis the factor the projected distances are multiplied by, not divided by. Usually what you want, and what most papers mean when they say “stress”.nonMetricStressforgives any monotone transform — it only asks that the ordering survive. Equivalent in spirit tospearmanRho, computed as a stress.
Only raw stress is unbounded above. The other two each minimise their numerator
against a fit that includes the trivial one — for the rescaling, the
constant curve for the monotone fit — which is exactly the denominator, so neither
can exceed 1.
The shared distance pass, and spearmanRho, sum over the full matrix,
zero diagonal included — zadu’s convention, so the numbers line up with it. For the
stresses that changes nothing, since a diagonal term contributes zero to numerator and
denominator alike. For pearsonR, residualVariance and spearmanRho it changes the
answer: all three take means over the entries and the zeros shift them. Those
three will not agree with a condensed (upper-triangle) implementation on the same
data. Compare like with like. (nonMetricStress is the exception: it enumerates the
pairs only.)
Using them
Section titled “Using them”All but spearmanRho and nonMetricStress come free with the distance accumulators,
which analyze collects by default:
const a = analyze(X, Y);
stress(a.moments).value; // 0.0807scaleNormalizedStress(a.moments).value; // 0.0737pearsonR(a.moments).value; // 0.9967residualVariance(a.moments).value; // 0.0066 — the 1 − r² of the same fitoptimalScale(a.moments); // the factor the projection is multiplied byspearmanRho and nonMetricStress are separate calls, because both must
materialise every pair to sort them:
spearmanRho(hd, ld).value; // 0.9880nonMetricStress(hd, ld).value; // 0.0311An empty scatterplot.
- Stress
- —
- parameters
- none
- points
- —
- cost
O(1)
How far the projected distances are from the originals.
Where they disagree with each other
Section titled “Where they disagree with each other”When a projection keeps every ordering but gets the size of the gaps wrong — which is what t-SNE and UMAP do by construction — the metric measures condemn it and the rank-based ones approve:
Loading…
Three rigid clusters pushed together until the gaps between them are a quarter of
their true size. spearmanRho reads 0.9993 and nonMetricStress 0.008, because the
clusters are still further from each other than their own points are. But
scaleNormalizedStress reads 0.141 — seventeen times the non-metric figure — and raw
stress 0.747.
Nothing is wrong with any of them: they answer different questions, and only one of those questions is usually the one you meant.
stress, scaleNormalizedStress, pearsonR and residualVariance are
read-outs of the pass.
spearmanRho and nonMetricStress are and materialise every pair.
Both take a maxPairs guard, default 60e6 — but they count different things, so the
same number means different sizes.
spearmanRhobuilds the full matrix, diagonal included, and counts againstmaxPairs. It refuses past , so at it throws.nonMetricStressenumerates the pairs with and counts those. At that is — under the default, so it does not refuse, it runs. It then holds six arrays over those pairs (both distance sets, the sort order, the two sorted copies and the disparities), about 44 bytes per pair in total, and tries to allocate over 2 GB. LowermaxPairsif that is not what you want.
Verification
Section titled “Verification”Every measure here is checked against a reference fixture, at these tolerances:
stress against zadu to 1e-12, scaleNormalizedStress to 1e-10 (and optimalScale
against zadu’s own to 1e-12), pearsonR to 1e-11, residualVariance to
1e-10, spearmanRho against scipy.stats.spearmanr — tie-correction included — to
1e-10, and nonMetricStress against zadu’s non_metric_stress end to end to 1e-10.
The isotonic step is pool adjacent violators, matching
sklearn.isotonic.IsotonicRegression’s handling of tied inputs, but it is the
end-to-end value that is compared, not the fit in isolation.
References
Section titled “References”- Shepard, Psychometrika 27 (1962) spearmanRho
- Kruskal, Psychometrika 29 (1964) nonMetricStress, stress
- Tenenbaum, de Silva & Langford, Science 290 (2000) residualVariance