Skip to content

StressMDS

Weighted metric MDS. It minimizes

for a weighting you choose, which turns a family of separately-named methods into one parameter.

How It Works

Setting weights to an exponent gives :

weightsobjectivealso known as
0raw stressthe objective SMACOF minimizes
-1Sammon stressthe objective Sammon minimizes
-2elastic scalingKamada-Kawai energy, see KKMDS

A more negative exponent concentrates the objective on short distances. Optimization is Jacobi-preconditioned gradient descent with a backtracking line search, warm-started from classical MDS on the same distances.

Why or When to Use

StressMDS does not replace SMACOF or Sammon. Those solve the same objectives at 0 and -1 with their own historical algorithms and reach different local minima, so swapping them out would silently change existing layouts. Reach for StressMDS when you want something they cannot express:

  • exponents between or beyond the three named ones, as a continuous local↔global dial;
  • explicit weight matrices, where a weight of zero drops the pair from the objective — the standard way to handle missing or untrusted dissimilarities, which nothing else here offers;
  • fewer iterations: on a benchmark suite it reached lower Sammon stress in 52 iterations than Sammon's own optimizer reached in 200.

Example

The same Iris data at three weightings. The difference is subtle on data this small and clean, which is itself worth knowing.

weights: 0
weights: -1
weights: -2

How-to (Code)

javascript
import * as druid from "@saehrimnir/druidjs";

const data = [
  /* ... multi-dimensional data ... */
];

// An exponent
const projection = new druid.StressMDS(data, { weights: -1 }).transform();

// Or the named constants
const elastic = new druid.StressMDS(data, { weights: druid.WEIGHTS_ELASTIC }).transform();

// Or a function of the target distance
const custom = new druid.StressMDS(data, { weights: (d) => 1 / (1 + d) }).transform();

Missing data — a zero weight removes the pair from the objective entirely:

javascript
const W = new druid.Matrix(N, N, (i, j) => (wasObserved(i, j) ? 1 : 0));
const projection = new druid.StressMDS(D, { metric: "precomputed", weights: W }).transform();

Parameters

ParameterDefaultMeaning
weights-2Exponent, matrix, or function. Zero weights drop the pair.
init_DR"MDS"Starting configuration. The objective is non-convex, and classical MDS on the same distances keeps the descent out of poor local minima.
learning_rate0.1Dimensionless — the gradient is preconditioned by weighted degree, so this needs no rescaling for the data or the weighting.
iterations300Maximum gradient steps.
epsilon1e-6Stop once the relative stress improvement falls below this.

What the exponent actually buys

Worth stating plainly, because the obvious guess is wrong. Holding the solver, initialization and budget fixed and varying only the exponent, over 5 datasets × 5 seeds:

weights10-NN preserveddistance correlation
056.3%0.8382
-157.9%0.8309
-259.1%0.8144
-357.9%0.7962

Global fidelity falls monotonically as the exponent drops — that part is reliable. Local neighborhood preservation, however, peaks near -2 rather than improving without limit, and on data with no manifold or cluster structure it moves the other way throughout (an isotropic 30-dimensional Gaussian ran 19.6% at 0 down to 13.1% at -3). So -2 is a reasonable default rather than a maximum, and on structureless data 0 is the better choice.

See also the StressMDS API reference.