III-18 · Distance Metrics and Curse of Dimensionality

Nearness depends on a measuring rule

Change the metric and its reference scales, then ask what adding dimensions actually does to a particular distribution.

Chapters in Part III
  1. III-17 · k-Nearest Neighbors
  2. III-18 · Distance Metrics and Curse of Dimensionality
  3. III-19 · Naive Bayes

A metric chooses which differences matter

Scaled metric geometry in two feature coordinatesPair query x 0, y 0. Pair distance ≈ 2.2361. Seed 42, dimension 2, cube, raw. Mean ≈ 0.6868, relative spread ≈ 1.5516, coefficient of variation ≈ 0.4701. Hollow diamond: query q; square: comparison point z. Dashed contour is scaled distance one. Pair axes have metres and seconds; the lower batch has dimensionless coordinates.

Drag the hollow query diamond or focus it and use arrow keys: 0.1 per key, Shift for 0.5. Pointer movement snaps to a 0.1 lattice and stops at ±3. Numeric entries retain finer precision. Only the handle captures touch gestures; the page can scroll elsewhere.

Dashed contour: scaled distance 1. Dotted segment: q to z. Coordinates have different units; dividing by their reference scales makes the displayed distance dimensionless.

dr(q,z)=(∑j∣qj−zjsj∣r)1/r,r≈2,dr(q,z)≈2.2361d_r(q,z)=\left(\sum_j\left|\frac{q_j-z_j}{s_j}\right|^r\right)^{1/r},\quad r\approx2,\quad d_r(q,z)\approx2.2361
Seeded dimension experiment · dimensionless coordinates, origin query

LCG seed 42; 24 rows, 32 draws per row, prefixes retained; cube model: pseudo-random uniform-coordinate draws in [−1,1]. Two-point reference scales affect the upper diagram only; this simulation uses unit scales.

Observed distance spread in the seeded sampleEach dot is one of 24 rows; horizontal position is its distance to the origin. Vertical offsets separate overlapping dots and do not encode another quantity. Vertical line is the sample mean.min ≈ 0.128max ≈ 1.1937mean ≈ 0.6868
Scaled pair distance
≈ 2.2361
Mean origin distance
≈ 0.6868
(max − min) / mean
≈ 1.5516
Population SD / mean
≈ 0.4701

Compare dimensions with the same seed, metric and distribution. An observed reduction in relative spread depends on those choices. Adding zero coordinates to the embedded line changes no raw distance.

Pair feature unitsReference scales carry those units; scaled differences and distance are dimensionless.
metres, seconds
Simulation X
24 × 2
Origin query
2 × 1
Distances
24 × 1
Seed
42
NormalizationLp norm
raw
All 24 active rows; deterministic coordinate prefixes. Sample vectors are columns; the batch stores one sample per row.
IDFeature rowRaw distanceDisplayed distance
d1-0.49530965043231845, -0.8237499091774225≈ 0.9612≈ 0.9612
d20.6599205289967358, 0.6906642373651266≈ 0.9553≈ 0.9553
d3-0.7940545375458896, 0.8430273737758398≈ 1.1581≈ 1.1581
d40.9574624155648053, 0.5994039531797171≈ 1.1296≈ 1.1296
d5-0.48958134604617953, 0.08210842870175838≈ 0.4964≈ 0.4964
d60.24201144324615598, 0.5697052534669638≈ 0.619≈ 0.619
d70.3106880490668118, 0.4970088806003332≈ 0.5861≈ 0.5861
d80.6561457370407879, -0.5449162367731333≈ 0.8529≈ 0.8529
d9-0.000668227206915617, 0.1912443544715643≈ 0.1912≈ 0.1912
d100.8424434219487011, 0.6090551074594259≈ 1.0395≈ 1.0395
d11-0.5310720498673618, -0.23166952468454838≈ 0.5794≈ 0.5794
d12-0.0565173770301044, -0.11486508883535862≈ 0.128≈ 0.128
d130.1120547060854733, 0.3317828681319952≈ 0.3502≈ 0.3502
d14-0.3981585348956287, -0.3631612006574869≈ 0.5389≈ 0.5389
d15-0.17870983434841037, -0.514882842078805≈ 0.545≈ 0.545
d16-0.03990192664787173, 0.717682396993041≈ 0.7188≈ 0.7188
d170.9892124538309872, -0.6681510303169489≈ 1.1937≈ 1.1937
d18-0.3391694272868335, 0.48118132911622524≈ 0.5887≈ 0.5887
d190.508399695623666, 0.47549392841756344≈ 0.6961≈ 0.6961
d20-0.15338291181251407, 0.7808512207120657≈ 0.7958≈ 0.7958
d21-0.22856998397037387, 0.019567659124732018≈ 0.2294≈ 0.2294
d220.16003574477508664, -0.0297923032194376≈ 0.1628≈ 0.1628
d230.6708815400488675, -0.43241421319544315≈ 0.7982≈ 0.7982
d240.7436646674759686, 0.9027663823217154≈ 1.1696≈ 1.1696

Frame 1 / 1 · edits begin at the displayed state.

The upper diagram compares two feature vectors. The lower experiment uses 24 seeded vectors and an origin query in the selected dimension; its distribution and normalization are explicit.

A distance encodes a judgment

A difference of two metres and a difference of one second cannot be added as if they had the same physical unit. The upper sheet states one reference scale for each coordinate. Dividing each difference by its reference scale produces dimensionless quantities that can enter a norm.

At the original pair q=(0,0)q=(0,0) and z=(2,1)z=(2,1), with reference scales one metre and one second, the scaled differences are (2,1)(2,1). L1 gives 33; L2 gives 5≈2.2361\sqrt5\approx2.2361. The same points therefore have different numerical distances under different rules.

The dashed unit-distance contour changes shape when you choose a metric or change a scale. It is a geometric explanation of the rule, not a new source of mathematical state. The table and numeric distance use the same active rule as the contour.

Three norms, one explicit representation

For positive reference scales sjs_j, the Minkowski family is

dr(q,z)=(∑j=1d∣qj−zjsj∣r)1/r,r≥1.d_r(q,z)=\left(\sum_{j=1}^d \left|\frac{q_j-z_j}{s_j}\right|^r\right)^{1/r}, \qquad r\ge1.

L1, or Manhattan distance, uses r=1r=1. L2, or Euclidean distance, uses r=2r=2. The Lp control supplies another finite power from 1 to 8. When L1 or L2 is selected, its fixed power applies; the stored Lp control is preserved for switching back.

Absolute values make each coordinate contribution nonnegative. The final root returns the norm from its powered sum. If r<1r<1, this expression can violate the triangle inequality, which is why that range is excluded here. As r becomes very large, the largest scaled component increasingly dominates; the infinity norm is a limiting connection, not an implemented control.

With unit scales, differences (3,4)(3,4) give d1=7d_1=7 and d2=9+16=5d_2=\sqrt{9+16}=5. For differences (1,1)(1,1), d3=23d_3=\sqrt[3]{2}.

For the sheet’s (2,1)(2,1) example, changing the first reference scale to two metres gives scaled differences (1,1)(1,1) and L2 distance 2\sqrt2. This says that a two-metre difference should count as one reference unit. It changes the rule.

A pure unit conversion is different. Writing two metres as 200 centimetres while converting its reference scale from one metre to 100 centimetres keeps 200/100=2200/100=2. Converting the coordinate but leaving its numerical reference scale at 1 would assign a new meaning to nearness.

What the dimension experiment actually samples

The lower sheet fixes 24 row identities. The shared 32-bit LCG uses

ti+1=(1664525ti+1013904223) mod 232,ui=ti/232.t_{i+1}=(1664525t_i+1013904223)\bmod2^{32}, \qquad u_i=t_i/2^{32}.

It draws 32 coordinates per row, then maps each draw to 2ui−12u_i-1. A selected dimension uses that row’s first d coordinates. Changing d keeps the same prefixes rather than replacing the observations with unrelated draws.

The cube model represents uniform-coordinate sampling in [−1,1]d[-1,1]^d. The generator is deterministic pseudorandom arithmetic; a seed is a reproducibility specification, not evidence that a dataset came from a real population. The embedded line keeps only the first generated coordinate and sets the rest to zero.

The origin query is a d×1d\times1 column. The simulation batch is 24×d24\times d, with a 24×124\times1 distance column. These dimensionless simulation coordinates are separate from the metres/seconds example above. Changing its two reference scales affects the pair diagram, while the simulation retains unit reference scales.

Derive a distribution-specific tendency

For genuinely independent uniform coordinates UjU_j on [−1,1][-1,1], let S=∥U∥22=∑jUj2S=\|U\|_2^2=\sum_j U_j^2. The second and fourth moments are

E[Uj2]=12∫−11u2 du=13,E[Uj4]=15.\mathbb E[U_j^2]=\frac12\int_{-1}^{1}u^2\,du=\frac13,\qquad \mathbb E[U_j^4]=\frac15.

Independence makes the variances add, giving

E[S]=d/3,Var⁡(S)=d(1/5−1/9)=4d/45.\mathbb E[S]=d/3,\qquad \operatorname{Var}(S)=d(1/5-1/9)=4d/45.

Consequently the coefficient of variation of squared Euclidean distance under this model is 2/5d2/\sqrt{5d}. This is a statement about that distribution and that quantity. It is not the coefficient of variation of Euclidean distance itself, and it does not describe every finite dataset.

The experiment reports ordinary distances with their mean, population standard deviation, relative spread (max⁡−min⁡)/mean⁡(\max-\min)/\operatorname{mean} and coefficient of variation SD⁡/mean⁡\operatorname{SD}/\operatorname{mean}. “Population” here means dividing by the 24 displayed rows; it is not an unbiased estimator of an unknown population variance.

Inspect a worked seeded result and its counterexample

For seed 42, L2, raw normalization and cube data, d=2 gives mean distance approximately 0.68680.6868 and coefficient of variation approximately 0.47010.4701. At d=32, the same coordinate-prefix experiment gives mean approximately 3.20453.2045 and coefficient of variation approximately 0.08510.0851. The observed relative spread changes from approximately 1.55161.5516 to 0.37730.3773.

Those are committed results for this finite experiment. Another seed or distribution can produce different comparisons, and every intermediate dimension need not change monotonically.

Switch to the embedded line. At every dimension,

∥(U1,0,…,0)∥r=∣U1∣.\|(U_1,0,\ldots,0)\|_r=|U_1|.

For seed 42 its raw mean is approximately 0.43990.4399 and coefficient of variation approximately 0.66680.6668 at both d=1 and d=32. Ambient dimension increased, but intrinsic variation did not. This is a concrete counterexample to a claim that adding dimensions alone forces distances to become nearly equal.

Select per-coordinate normalization. It displays

∥x∥rd1/r=(1d∑j∣xj∣r)1/r.\frac{\|x\|_r}{d^{1/r}} =\left(\frac1d\sum_j|x_j|^r\right)^{1/r}.

This divides every distance in the current sample by the same positive factor. Magnitudes change; neighbor ordering, relative spread and coefficient of variation do not. A new normalization should not be mistaken for a new sample distribution.

Execute and reproduce the mechanism

Run .venv/bin/python -B code/python/p03/distance_metrics.py. It emits before/after states and all dimension-replay frames, including actual coordinates and distances.

code/python/p03/distance_metrics.py
"""Feature units, scaled Lp distances and a bounded seeded dimension experiment."""
import copy
import json
import numpy as np
from common import distance, lcg, number, power_for

CANONICAL = {"query":{"x":0,"y":0},"point":{"x":2,"y":1},"metric":"L2","p":2,"scales":[1,1],
             "seed":42,"dimension":2,"distribution":"cube","normalization":"raw"}

def summarize(values):
    if not isinstance(values, (list,tuple)) or not values:
        raise ValueError("A distance summary needs observations.")
    array = np.asarray([number(v,"Summary distance",0) for v in values],dtype=np.float64).reshape(-1,1)
    lo, hi = float(np.min(array)), float(np.max(array))
    if hi == 0:
        return {"min":lo,"max":hi,"mean":0.0,"sd":0.0,"relativeSpread":None,"coefficientOfVariation":None,
                "meanUnderflow":False,"sdUnderflow":False,
                "reason":"All distances are zero; relative spread and coefficient of variation are undefined."}
    unit = array/hi
    unit_mean = float(np.mean(unit))
    unit_sd = float(np.sqrt(np.mean((unit-unit_mean)**2)))
    mean, sd = hi*unit_mean, hi*unit_sd
    mean_underflow, sd_underflow = mean == 0, unit_sd > 0 and sd == 0
    return {"min":lo,"max":hi,"mean":mean,"sd":sd,
            "relativeSpread":(1-lo/hi)/unit_mean,"coefficientOfVariation":unit_sd/unit_mean,
            "meanUnderflow":mean_underflow,"sdUnderflow":sd_underflow,
            "reason":"Positive mean or standard deviation underflowed to zero; relative metrics use normalized arithmetic." if mean_underflow or sd_underflow else None}

def derive(state):
    power = power_for(state["metric"],state["p"])
    seed = int(number(state["seed"],"Seed",0,2**32-1,True))
    dimension = int(number(state["dimension"],"Dimension",1,32,True))
    if state["distribution"] not in ("cube","line") or state["normalization"] not in ("raw","per-coordinate"):
        raise ValueError("Choose a supported distribution and normalization.")
    pair = distance([state["point"]["x"],state["point"]["y"]],[state["query"]["x"],state["query"]["y"]],state["metric"],state["p"],state["scales"])
    draws = lcg(seed)
    full = np.asarray([[2*next(draws)-1 for _ in range(32)] for _ in range(24)],dtype=np.float64)
    X = full[:,:dimension].copy()
    if state["distribution"] == "line":
        X[:,1:] = 0
    divisor = 1 if state["normalization"] == "raw" else dimension**(1/power)
    rows = []
    for j,column in enumerate(X):
        raw = distance(column.tolist(),[0]*dimension,state["metric"],state["p"])
        rows.append({"id":"d"+str(j+1),"features":column.tolist(),"raw":raw,"distance":raw/divisor})
    return {"state":copy.deepcopy(state),"power":power,"pairDistance":pair,"rows":rows,
            "summary":summarize([row["distance"] for row in rows]),
            "shapes":{"X":[24,dimension],"query":[dimension,1],"distances":[24,1]},
            "provenance":f"LCG seed {seed}; 24 rows, 32 draws per row, prefixes retained; "+
                ("cube model: pseudo-random uniform-coordinate draws in [−1,1]" if state["distribution"] == "cube" else "one uniform coordinate, remaining coordinates exactly zero")}

def trace(state):
    states = [copy.deepcopy(state)] + [dict(copy.deepcopy(state),dimension=d) for d in (1,2,8,32)]
    return [{"index":j,"state":value,"derived":derive(value)} for j,value in enumerate(states)]

if __name__ == "__main__":
    print(json.dumps({"before":derive(CANONICAL),"after":derive(dict(CANONICAL,dimension=32)),"trace":trace(CANONICAL)},allow_nan=False))
code/python/p03/common.py
"""Part III boundary checks. Numbers are quantities, never bools or numeric strings."""
import math
import numpy as np

def number(value, name, lower=None, upper=None, integer=False):
    try:
        valid = isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(float(value))
    except (OverflowError, TypeError):
        valid = False
    normalized = float(value) if valid else 0
    if not valid or (lower is not None and normalized < lower) or (upper is not None and normalized > upper) or (integer and normalized != math.floor(normalized)):
        raise ValueError(f"{name} must be a finite {'integer' if integer else 'number'} in its supported range.")
    return int(normalized) if integer else normalized

def power_for(metric, p):
    number(p, "Minkowski power", 1, 8)
    if metric not in ("L1", "L2", "Lp"):
        raise ValueError("Choose L1, L2 or Lp.")
    return 1 if metric == "L1" else 2 if metric == "L2" else p

_UNIT_SCALES = object()

def distance(a, b, metric="L2", p=2, scales=_UNIT_SCALES):
    """Column-vector norm; rescale before powers. Scales carry the feature units."""
    if not isinstance(a, (list, tuple)) or not isinstance(b, (list, tuple)) or not 1 <= len(a) <= 32 or len(a) != len(b):
        raise ValueError("Distance needs matching vectors of 1 to 32 components.")
    if scales is not _UNIT_SCALES and (not isinstance(scales, (list, tuple)) or len(scales) != len(a)):
        raise ValueError("Distance needs a matching scale vector.")
    q = power_for(metric, p)
    differences = []
    for j, (left, right) in enumerate(zip(a, b)):
        left = number(left, "First component"); right = number(right, "Second component")
        scale = 1 if scales is _UNIT_SCALES else number(scales[j], "Scale")
        if scale <= 0:
            raise ValueError("Scales must be positive.")
        delta = number(left-right, "Coordinate difference")
        differences.append(number(abs(delta)/scale, "Scaled difference"))
    column = np.asarray(differences, dtype=np.float64).reshape(-1, 1)
    largest = float(np.max(column))
    if largest == 0:
        return 0.0
    result = largest * float(np.sum((column/largest)**q))**(1/q)
    return float(number(result, "Distance arithmetic"))

def id_key(value):
    return value.encode("utf-16-be", errors="surrogatepass")

def lcg(seed):
    number(seed, "Seed", 0, 2**32-1, True)
    state = int(seed)
    while True:
        state = (1664525*state+1013904223) % 2**32
        yield state/2**32

Independent goldens use a closed-form modular expression for each generator state instead of translating the stepwise LCG. Fraction coordinates and high-precision powered sums check the distances. Exact small-summary tests cover tiny values and subnormal representation limits.

Explore and connect the measurements

Compare L1, L2 and Lp with the same seed and distribution. Change the seed and check whether your explanation still names the distribution, norm, query and normalization. Compare cube data with the line before interpreting a dimension effect.

For k-NN, small relative distance differences can make neighborhoods sensitive to features or noise. This does not establish that all high-dimensional prediction is impossible. Useful structure, sparsity, representation, sample size and the target relationship matter.

Replay selects complete mathematical states; controls, features, shapes, diagrams and summaries all follow the selected frame. A manual edit starts there and ends replay. Reset restores the original pair and seeded experiment.

Numerical limits

The plot accepts pair coordinates in [−3,3][-3,3] and reference scales from 0.10.1 to 1010. The simulation uses 24 rows, dimensions 1–32 and unsigned 32-bit seeds. Lp power is 1–8. Unsupported numeric entries are rejected without altering the valid state.

The norm rescales coordinate differences before powers. Finite inputs can still yield an overflowing subtraction, scale division or final norm; those cases have an explicit arithmetic error. Given coordinates retain full decimal text; displayed distances are approximate.

Summary calculations normalize by the largest actual distance before computing mean and deviation. This preserves, for example, the SD 5×10−2015\times10^{-201} and CV 1 for [0,10−200][0,10^{-200}]. Relative metrics are undefined only when every distance is actually zero. If a positive absolute mean or SD rounds to zero by underflow, a separate flag/reason distinguishes that representation limit from zero observations; relative metrics remain calculated at the normalized scale.

This is a bounded synthetic origin-distance experiment, not a universal concentration theorem, a benchmark of search algorithms or an estimate of a task’s prediction error.

Check your understanding

For differences (3,4) with unit reference scales, what are L1 and L2?
A coordinate changes from 2 metres to 200 centimetres. How can the scaled distance remain the same?
What happens to raw distances when a line is embedded by adding only zero coordinates?
The sheet divides every L2 distance by sqrt(d). Does that change its coefficient of variation?
Under the independent uniform-coordinate model, which quantity has expectation d/3?

Checkpoints

You can state a distance with its representation, units and scales; distinguish changing a measuring rule from changing units; reproduce the seeded data; and describe a dimension effect with its assumptions. A claim about nearness is incomplete until the geometry and distribution are named.