Skip to content

curvilinearStress

function curvilinearStress(f): MetricResult & {
raw: number;
};

Defined in: metrics/embedding.ts:100

Stress that forgives tearing but not folding.

The weighting depends on the projected distance, so errors between points that end up close together are punished and errors between points that end up far apart are excused. That asymmetry matches how a viewer reads a scatterplot: things drawn together look related, things drawn apart simply look unrelated.

  • Needs: high-dimensional data and projection. No labels.
  • Range: [0, ∞), lower is better; 0 is exact.
  • Cost: O(1), from an O(N²·D) pass, which must be given ccaLambda.

The returned value is normalised: Σ(d − d̂)²·F(d̂) ÷ Σd²·F(d̂), where d is the high-dimensional distance, the projected one and F the weighting kernel. raw is the bare numerator Σ(d − d̂)²·F(d̂), the quantity as originally published.

The pass options control the kernel: ccaLambda is the neighbourhood width the weighting decays over, and ccaKernel picks its shape — "exponential" (default) uses F(d̂) = exp(−d̂/λ), "step" uses F(d̂) = 1 for d̂ ≤ λ and 0 beyond. A ccaLambda that is missing or not strictly positive leaves the CCA accumulators unfilled, and this read-out then throws.

ParameterType
fEmbeddingMoments

MetricResult & { raw: number; }

if the pass ran without a positive ccaLambda.

Demartines & Hérault, IEEE Trans. Neural Networks 8 (1997) https://doi.org/10.1109/72.554199

import { analyze, curvilinearStress } from "@saehrimnir/sickle";
// ccaLambda is required here, not on the read-out; ccaKernel defaults to
// "exponential". Without a positive ccaLambda, curvilinearStress throws.
const a = analyze(data, projection, { ccaLambda: 0.3, ccaKernel: "exponential" });
const s = curvilinearStress(a.embedding);
s.value; // 0.622 for a noisy 50-point circle cut open into a line
s.raw; // 847.19 — the unnormalised sum