Python & Data Science
Machine Learning Under review

Reference: Distance Metrics

Distance metrics quantify how “far apart” two data points are. They are the backbone of KNN classification, k-means clustering, hierarchical clustering, anomaly detection, information retrieval, and most similarity-based recommender systems. The same pair of points can yield wildly different distance values depending on the metric you choose, and the “right” choice depends on the geometry of your data — not on habit.

Roster

MetricFormula (1-line)RangeWhen to useUsed in corpus article
Euclidean (L2)i(xiyi)2\sqrt{\sum_i (x_i - y_i)^2}[0,)[0, \infty)Continuous features of similar scale, isotropic geometry, the default for physical coordinatesBuilding your first RAG pipeline), SHAP values
Manhattan (L1, cityblock)ixiyi\sum_i \lvert x_i - y_i \rvert[0,)[0, \infty)High-dim sparse data, grid-aligned motion, features with outliers (robust to large single-axis spikes)KNN in high dimensions)
Minkowski (general Lp)(ixiyip)1/p\left(\sum_i \lvert x_i - y_i \rvert^p\right)^{1/p}[0,)[0, \infty)A family — p=2p=2 is Euclidean, p=1p=1 is Manhattan; tunable when you want something betweenKNN in high dimensions)
Chebyshev (L∞, max)maxixiyi\max_i \lvert x_i - y_i \rvert[0,)[0, \infty)Chessboard distance, worst-case per-axis deviation, log-normal or heavy-tailed per-axis errorsKNN in high dimensions)
Cosine1xyxy1 - \frac{x \cdot y}{\lVert x \rVert \lVert y \rVert}[0,2][0, 2] (often [0,1][0, 1] for non-negative)Embeddings, text vectors, TF-IDF — direction matters more than magnitudeBuilding your first RAG pipeline), Vector databases compared)
Jaccard1ABAB1 - \frac{\lvert A \cap B \rvert}{\lvert A \cup B \rvert}[0,1][0, 1]Sets, token n-grams, binary bags, deduplicationBuilding your first RAG pipeline)
Hamming1ni1[xiyi]\frac{1}{n}\sum_i \mathbb{1}[x_i \neq y_i][0,1][0, 1]Categorical features, binary strings, error-correcting codes, edit-distance proxiesBuilding your first RAG pipeline)
Mahalanobis(xy)TΣ1(xy)\sqrt{(x-y)^T \Sigma^{-1} (x-y)}[0,)[0, \infty)Correlated, differently-scaled continuous features; multivariate outlier detectionVector databases compared), SHAP values

All metrics above except cosine, Jaccard, Hamming and Mahalanobis reduce to a “straight-line” or “axis-aligned” geometry. Cosine measures the angle between vectors (a similarity converted to a distance by 1 - cos). Jaccard and Hamming operate on sets and symbols respectively. Mahalanobis is the only one in the roster that accounts for feature covariance — it is Euclidean distance measured in a whitened, decorrelated space.

Decision tree: which distance for which data

Use this as a first-pass selector. When two leaves apply, prefer the one higher on the list.

  • Is your data a set or bag of tokens?
    • Yes → Jaccard. (Add a count weighting if you need it; that’s the generalized Jaccard / Tanimoto.)
  • Is your data a fixed-length binary string or categorical one-hot vector?
    • Yes → Hamming. (For strings of different lengths, use Levenshtein instead — Hamming requires equal-length inputs.)
  • Is your data a high-dim embedding from a neural model (BERT, OpenAI, SBERT, …) or a TF-IDF vector?
    • Yes, and the vectors are normalized (unit norm) → cosine or Euclidean — they give the same ranking under unit-norm normalization, but cosine is the convention.
    • Yes, and you want to ignore magnitude (only direction matters) → cosine.
    • Yes, and magnitude carries signal (e.g., tf-idf weighting reflects term importance) → Euclidean, or cosine on L2-normalized vectors which collapses to the same ranking.
  • Are your features correlated and on different scales?
    • Yes, and you have enough data to estimate a covariance matrix (≥ ~10× the dimension) → Mahalanobis.
    • Yes, but you cannot estimate covariance reliably → standardize features, then use Euclidean (a poor-man’s Mahalanobis).
  • Are your features independent, continuous, and roughly the same scale?
    • Yes → Euclidean (the default; do not default to Manhattan out of habit).
  • Are you in very high dimensions (> ~50 dense features, > ~5000 sparse)?
    • Yes → see the curse-of-dimensionality sidebar below. Prefer cosine for sparse text, Manhattan for moderate-dim dense data, or fractional Lp with p < 1 if you must stay in the Minkowski family.
  • Do you care only about the single worst-aligned axis (chessboard / Chebyshev metric)?
    • Yes → Chebyshev.
  • Do you want a tunable compromise between Manhattan and Euclidean?
    • Yes → Minkowski with 1<p<21 < p < 2.

A worked Python example: one pair, eight measurements

Below, the same pair of points is measured with each of the eight core metrics. The point is not to compare the numbers head-to-head (they are not commensurable across metrics) — it is to show that a “distance” between two fixed points is a function of the metric, and that the choice has real downstream consequences for which neighbours KNN picks, which cluster k-means forms, and which docs a vector DB returns.

import numpy as np
from scipy.spatial.distance import (
    euclidean, cityblock, minkowski, chebyshev, cosine,
    jaccard, hamming, mahalanobis
)

# Two points in a 4-dim continuous space
x = np.array([1.0, 2.0, 0.0, 5.0])
y = np.array([3.0, 0.0, 0.0, 1.0])

# A toy covariance matrix (for Mahalanobis) — diagonal-ish, slightly correlated
S = np.array([
    [2.0,  0.5,  0.0,  0.0],
    [0.5,  1.0,  0.0,  0.0],
    [0.0,  0.0,  0.5,  0.0],
    [0.0,  0.0,  0.0,  3.0],
])
S_inv = np.linalg.inv(S)

print("Euclidean  :", round(euclidean(x, y), 4))      # 4.899
print("Manhattan  :", round(cityblock(x, y), 4))      # 8.0
print("Minkowski p=1.5:", round(minkowski(x, y, p=1.5), 4))  # 5.7135
print("Minkowski p=3 :", round(minkowski(x, y, p=3), 4))    # 4.3089
print("Chebyshev  :", round(chebyshev(x, y), 4))      # 4.0
print("Cosine     :", round(cosine(x, y), 4))         # 0.5381
print("Mahalanobis:", round(mahalanobis(x, y, S_inv), 4))  # 3.8048

# scipy's jaccard() expects boolean *vectors*, not Python sets directly —
# passing sets raises `ValueError: Input vector should be 1-D.` because
# np.asarray(some_set) wraps the whole set in a single 0-D object array.
# Encode each set as a boolean membership vector over the shared vocabulary:
A = set(['apple', 'pear', 'banana'])
B = set(['apple', 'kiwi', 'banana'])
vocab = sorted(A | B)
vec_a = np.array([w in A for w in vocab])
vec_b = np.array([w in B for w in vocab])
print("Jaccard (sets) :", round(jaccard(vec_a, vec_b), 4))     # 0.5
print("Jaccard (1 - IoU):", round(1 - len(A & B) / len(A | B), 4))  # 0.5

bin_x = np.array([1, 0, 1, 1, 0, 1])
bin_y = np.array([1, 1, 0, 1, 0, 1])
print("Hamming    :", round(hamming(bin_x, bin_y), 4))  # 0.3333

The code block above does the same thing eight times: it picks two specific points x and y and asks each metric to report a distance. The eight outputs are not directly comparable to each other — the only fair comparison is the same metric applied to different pairs — but the exercise shows what each metric is sensitive to.

  • Euclidean sums squared per-axis differences and takes the square root. Axis 0 contributes 4, axis 1 contributes 4, axis 2 contributes 0, axis 3 contributes 16; the squared sum is 24, and sqrt(24) ≈ 4.899. Note how axis 3 dominates the squared budget — Euclidean is sensitive to the largest single-axis displacement.
  • Manhattan sums absolute per-axis differences directly: |1-3| + |2-0| + |0-0| + |5-1| = 2 + 2 + 0 + 4 = 8. No squaring, so no axis dominates — the largest axis contributes its raw value, not its square. This is what makes Manhattan more robust to outliers and to high-dimensional sparsity.
  • Minkowski with p=1.5 is a tunable compromise: more axis-weighted than Manhattan, less than Euclidean (5.7135, sitting between Manhattan’s 8.0 and Euclidean’s 4.899). Minkowski with p=3 is more axis-dominant than Euclidean — it gives more weight to the largest axis, pushing the result toward Chebyshev (4.3089, between Euclidean’s 4.899 and Chebyshev’s 4.0).
  • Chebyshev ignores everything except the single largest per-axis difference: max(2, 2, 0, 4) = 4. It is the limit of Minkowski as pp \to \infty.
  • Cosine ignores magnitude entirely. It computes 1 - (x · y) / (|x| |y|). With x · y = 1·3 + 2·0 + 0·0 + 5·1 = 8, |x| = sqrt(30) ≈ 5.477, |y| = sqrt(10) ≈ 3.162, the cosine of the angle is 8 / 17.318 ≈ 0.462, so the cosine distance is 1 - 0.462 ≈ 0.538 — matching the printed 0.5381.
  • Mahalanobis plugs the difference vector (x - y) = [-2, 2, 0, 4] into the inverse covariance. Σ⁻¹ couples axes 0 and 1 (because the off-diagonal 0.5 entries in Σ do), so those two axes contribute jointly: Δ[:2] @ Σ⁻¹[:2,:2] @ Δ[:2] ≈ 9.143. Axis 2 contributes 0² × 2.0 = 0 (its displacement is zero, so its large inverse-variance weight doesn’t matter here). Axis 3 contributes 4² × (1/3) ≈ 5.333less than its raw squared value of 16 would suggest, because Σ gives axis 3 a large variance (3.0) and Mahalanobis downweights high-variance axes. Summing: 9.143 + 0 + 5.333 ≈ 14.476, and sqrt(14.476) ≈ 3.805 — matching the printed 3.8048. The full element-wise expansion is in the Math toggle below; the key intuition is that Mahalanobis re-scales each axis by its own typical spread and removes correlations between axes.
  • Jaccard is computed on the set view of the data, not the numeric view — scipy’s jaccard() needs boolean vectors, so the code above first encodes each set as a boolean membership vector over the shared vocabulary {apple, banana, kiwi, pear}. With A = {apple, pear, banana} and B = {apple, kiwi, banana}, the intersection is {apple, banana} (size 2) and the union is {apple, banana, kiwi, pear} (size 4), so Jaccard = 1 - 2/4 = 0.5 — matching both printed lines.
  • Hamming counts positions where two equal-length binary (or symbol) strings disagree, divided by the length. With bin_x = [1,0,1,1,0,1] and bin_y = [1,1,0,1,0,1], the disagreements are at indices 1 and 2, so 2 / 6 ≈ 0.3333.

The pattern to take away: the same two points look close under some metrics and far apart under others. A KNN classifier built with cosine will return a different set of neighbours than one built with Euclidean, on the same data.

Each metric above can be written as a function d:Rn×RnR0d: \mathbb{R}^n \times \mathbb{R}^n \to \mathbb{R}_{\geq 0} satisfying (for true metrics) the four axioms:

  1. Non-negativity: d(x,y)0d(x, y) \geq 0
  2. Identity: d(x,y)=0    x=yd(x, y) = 0 \iff x = y
  3. Symmetry: d(x,y)=d(y,x)d(x, y) = d(y, x)
  4. Triangle inequality: d(x,z)d(x,y)+d(y,z)d(x, z) \leq d(x, y) + d(y, z)

Most of the roster above satisfies all four; cosine distance (1cosθ1 - \cos\theta) satisfies (1), (2), (3) but violates the triangle inequality in general — it is a pseudometric on the unit sphere and a divergence off it. Jaccard distance is a true metric on sets. Hamming is a true metric on equal-length binary strings.

Minkowski family

dp(x,y)=(i=1nxiyip)1/p,p1d_p(x, y) = \left( \sum_{i=1}^{n} |x_i - y_i|^p \right)^{1/p}, \quad p \geq 1

Plain EnglishSymbolPython (scipy.spatial.distance)
The p-th power of per-axis differencesxiyip\lvert x_i - y_i \rvert^p(x - y) ** p (element-wise)
Sum across axesi\sum_inp.sum(...)
The p-th root of that sum()1/p(\cdot)^{1/p}np.power(..., 1/p) or np.sqrt(...) for p=2p=2

Special cases:

  • p=1p = 1: Manhattan (cityblock)
  • p=2p = 2: Euclidean (the default euclidean)
  • pp \to \infty: Chebyshev (chebyshev = np.max(np.abs(x - y)))

For p<1p < 1 the Minkowski formula is not a metric (triangle inequality fails) — but the fractional Lp distances with 0<p<10 < p < 1 are sometimes used in high-dimensional settings to counteract the curse of dimensionality (see Aggarwal, Hinneburg & Keim 2001).

Cosine

dcos(x,y)=1cosθ=1xyxy=1ixiyiixi2iyi2d_{\cos}(x, y) = 1 - \cos\theta = 1 - \frac{x \cdot y}{\lVert x \rVert \, \lVert y \rVert} = 1 - \frac{\sum_i x_i y_i}{\sqrt{\sum_i x_i^2} \sqrt{\sum_i y_i^2}}

Plain EnglishSymbolPython equivalent
Dot product of xx and yyxyx \cdot ynp.dot(x, y)
L2 norm of xxx\lVert x \rVertnp.linalg.norm(x)
Cosine of the anglecosθ\cos\thetanp.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))
Cosine distancedcosd_{\cos}1 - np.dot(x, y) / (... norms ...)

When xx and yy are both unit-norm, dcos(x,y)=1xyd_{\cos}(x, y) = 1 - x \cdot y, and cosine distance and Euclidean distance give the same ranking because xy2=2(1xy)\lVert x - y \rVert^2 = 2(1 - x \cdot y) for unit vectors.

Jaccard

dJ(A,B)=1ABABd_J(A, B) = 1 - \frac{|A \cap B|}{|A \cup B|}

Plain EnglishSymbolPython equivalent
Set intersectionABA \cap Bset(A) & set(B)
Set unionABA \cup Bset(A) | set(B)
Jaccard similarityJ(A,B)J(A, B)len(A & B) / len(A | B)
Jaccard distancedJd_J1 - len(A & B) / len(A | B)

For multiset / count vectors, the generalized Jaccard (Tanimoto coefficient) replaces the set cardinalities with imin(xi,yi)imax(xi,yi)\frac{\sum_i \min(x_i, y_i)}{\sum_i \max(x_i, y_i)}.

Mahalanobis

dM(x,y)=(xy)Σ1(xy)d_M(x, y) = \sqrt{(x - y)^\top \Sigma^{-1} (x - y)}

Plain EnglishSymbolPython equivalent
Difference vectorΔ=xy\Delta = x - ydelta = x - y
Covariance matrix of the feature distributionΣ\Sigmanp.cov(X.T) for data matrix X
Inverse covariance (a.k.a. precision matrix)Σ1\Sigma^{-1}np.linalg.inv(Sigma) or np.linalg.pinv if singular
Mahalanobis distancedMd_Mnp.sqrt(delta @ S_inv @ delta)

Geometrically, Mahalanobis is Euclidean distance applied after whitening the data: if Σ=UΛU\Sigma = U \Lambda U^\top is the eigendecomposition, then Σ1=UΛ1U\Sigma^{-1} = U \Lambda^{-1} U^\top, and the transform z=Λ1/2U(xy)z = \Lambda^{-1/2} U^\top (x - y) rescales each principal axis by 1/λi1/\sqrt{\lambda_i}. The result is a vector whose components are uncorrelated and unit-variance, and the Euclidean norm of zz equals dM(x,y)d_M(x, y).

A concrete worked value for the toy example in the code block: with Δ=(2,2,0,4)\Delta = (-2, 2, 0, 4) and

Σ1(0.57140.2857000.28571.142900002.000000.3333)\Sigma^{-1} \approx \begin{pmatrix} 0.5714 & -0.2857 & 0 & 0 \\ -0.2857 & 1.1429 & 0 & 0 \\ 0 & 0 & 2.0 & 0 \\ 0 & 0 & 0 & 0.3333 \end{pmatrix}

we have

dM2=ΔΣ1Δ14.476,dM3.805.d_M^2 = \Delta^\top \Sigma^{-1} \Delta \approx 14.476, \qquad d_M \approx 3.805.

The full expansion splits into the coupled axis-0/axis-1 block (9.143\approx 9.143 — the off-diagonal 0.50.5 entries in Σ\Sigma mean the inversion mixes those two axes, so the contribution of axis 0 is not simply Δ02/Σ00\Delta_0^2 / \Sigma_{00}), plus the independent axis-2 term (02×2.0=00^2 \times 2.0 = 0), plus the independent axis-3 term (42×0.33335.3334^2 \times 0.3333 \approx 5.333).

Curse of dimensionality sidebar

As the number of dimensions nn grows, all of the Minkowski-family distances converge on the same value for any pair of points drawn from the same distribution. The intuition:

  • The expected squared Euclidean distance between two i.i.d. points in nn dimensions grows as Θ(n)\Theta(n).
  • The variance of that distance also grows as Θ(n)\Theta(n).
  • But the relative spread std(d)E[d]\frac{\text{std}(d)}{\mathbb{E}[d]} shrinks as Θ(1/n)\Theta(1/\sqrt{n}).

So in very high dimensions, every pair of points is (in a normalized sense) about equally far from every other pair — the nearest neighbour and the farthest neighbour become nearly indistinguishable. KNN, hierarchical clustering, and most density-based methods become unreliable.

Mitigations:

  1. Reduce dimensionality first: PCA, UMAP, or autoencoders down to a few dozen dims before computing distances.
  2. Use cosine for sparse high-dimensional text: it normalizes out the magnitude that grows with nn.
  3. Use fractional Lp with p<1p < 1 (Aggarwal, Hinneburg & Keim 2001): the smaller pp, the more weight is given to close axes rather than far axes, partially counteracting the concentration effect.
  4. Use feature selection to drop irrelevant dimensions — a noisy feature in high-dim data is worse than no feature at all, because it adds distance without adding signal.
  5. Accept it: some methods (random forests, gradient-boosted trees, linear models with regularization) do not depend on distances at all and bypass the issue entirely.

For a deeper treatment, see the KNN/curse-of-dimensionality article in the corpus.

Edge cases + common mistakes

Scale, magnitude, and covariance issues

1. Euclidean on un-scaled features. If feature A ranges over [0,1][0, 1] and feature B over [0,1000][0, 1000], Euclidean distance is dominated by B. Standardize (z-score) or min-max scale before computing distances. Or use Mahalanobis, which scales each axis by its variance automatically.

2. Cosine on zero vectors. Cosine is undefined when either vector has zero norm — the denominator is zero. Libraries return nan or raise; in pipelines, filter zero rows first or add a tiny epsilon.

3. Mahalanobis with a singular covariance. If you have more features than samples, or perfectly correlated features, Σ\Sigma is singular and Σ1\Sigma^{-1} does not exist. Use the pseudo-inverse (np.linalg.pinv) or regularize: Σreg=Σ+ϵI\Sigma_{\text{reg}} = \Sigma + \epsilon I for small ϵ\epsilon.

4. Mahalanobis with a stale covariance. Σ\Sigma must be estimated from a representative sample of your data. If you estimate it on a training set that has different scaling or correlation structure than the points you later measure, the distances are meaningless. Re-estimate when the data distribution drifts.

Set, string, and exponent edge cases

5. Jaccard on multisets. Vanilla Jaccard treats inputs as sets — duplicates are lost. If you have count vectors (e.g., a bag of words with weights), use the generalized Tanimoto coefficient min(xi,yi)/max(xi,yi)\sum \min(x_i, y_i) / \sum \max(x_i, y_i) instead.

6. Hamming on unequal-length strings. Hamming requires inputs of equal length. For variable-length strings, use Levenshtein (edit distance), which is the minimum number of single-character edits (insertions, deletions, substitutions) needed to turn one string into another.

7. Minkowski with p<1p < 1. It is not a true metric (triangle inequality fails). It is sometimes useful in high-dim settings per Aggarwal et al., but do not pass it to a KNN implementation that assumes a true metric — the result may not even satisfy the triangle inequality, breaking neighbour-search optimizations like ball trees.

Conventions and comparability

8. Cosine distance vs. cosine similarity. Many libraries (scipy, sklearn) return 1 - cos as “cosine distance” — that is a distance-like quantity (smaller = more similar). Other libraries (pytorch, some transformers) return the raw cos as “cosine similarity” (larger = more similar). Mixing the two conventions silently flips your ranking. Always check the sign and the range before plugging a cosine into a downstream metric.

9. Treating cosine as a true metric. Cosine distance does not satisfy the triangle inequality in general. It is fine for similarity search and for KNN-style retrieval (where you only care about top-k ranking, not metric properties), but it is not safe for methods that assume a metric — e.g., metric multidimensional scaling, ball-tree indexing with a metric assumption, or any algorithm that relies on the triangle inequality to prune candidates.

10. Using the metric the library defaults to. sklearn’s KNeighborsClassifier defaults to p=2 (Euclidean). That is a reasonable default for low-dim isotropic data, but it is not a universal best choice. Always pick the metric to match the data geometry, not the library default.

11. Comparing distances across metrics. A Euclidean distance of 4.899 and a Manhattan distance of 8.0 between the same pair are not “the same distance” in different units — they are different functions that happen to share the word “distance” in their name. Only compare rankings produced by the same metric across different candidate points, never the raw values of different metrics to each other.

Cross-references

  • Why your KNN model fails in high dimensions) — the curse-of-dimensionality article this reference cross-links into; explains in depth why Minkowski distances concentrate as nn \to \infty and what to do about it.
  • Vector databases compared) — covers cosine vs. Euclidean for ANN (approximate nearest neighbour) indexing, including the HNSW / IVF / PQ trade-offs that depend on the distance metric.
  • Building your first RAG pipeline) — uses cosine similarity over OpenAI or SBERT embeddings as the retrieval step; the choice of cosine (over Euclidean) is justified by the unit-norm normalization that most embedding providers apply.
  • SHAP values — uses a distance-weighted kernel (a variant of Mahalanobis-like weighting) to approximate local model behaviour around a single prediction; the choice of kernel bandwidth is essentially a choice of distance scale.

Further reading

  • Deza, M.M. & Deza, E. (2009). Encyclopedia of Distances. Springer. The comprehensive reference: every named distance metric in the literature, organized by family. Use as a dictionary, not as a textbook.
  • Aggarwal, C.C., Hinneburg, A. & Keim, D.A. (2001). On the Surprising Behavior of Distance Metrics in High Dimensional Space. ICDT 2001. The foundational paper on the curse of dimensionality for distance metrics; motivates the use of fractional LpL_p norms with p<1p < 1 in high-dim data.
  • Cosine similarity and the triangle inequality — NIST/AMS technical discussions on the metric properties of cosine; useful when deciding whether cosine is safe for your specific algorithm.
  • scipy.spatial.distance — the canonical Python reference for the metrics above; docs include the exact formulas and edge-case behaviour.
  • sklearn.neighbors — KNN implementations supporting Minkowski with arbitrary pp; useful for experimenting with the impact of pp on neighbour selection.

For a Kaggle competition where the choice of distance metric materially affects results, see the Spotify Million Playlist Dataset recommender-style exercises: cosine over track-count vectors consistently outperforms Euclidean there because the data is sparse and high-dimensional.

  • Machine Learning Under review

    Reference: Feature Engineering

    A standalone lookup for feature engineering: pick the right encoder, scaler, derived feature, time-series construct, and distance metric for any model family.

  • Machine Learning Under review

    Reference: Regularization

    A comprehensive reference covering L1, L2, dropout, BatchNorm, early stopping, and data augmentation — when to use each, with worked Python code.

  • Machine Learning Under review

    What Is Cross-Validation, and How Do You Avoid Doing It Wrong?

    Learn cross-validation the right way: stop overfitting, prevent data leakage with pipelines, read the standard deviation, and handle time-series correctly.

  • Machine Learning Under review

    Reference: Evaluation Metrics

    A one-stop reference of ML evaluation metrics — classification, regression, calibration, and information theory — with formulas, use cases, and blind spots.

Looking for something else?

Search every article by title, summary or topic.