Contents
Deep Breakdown
What it tells me (Situation Awareness): Mahalanobis distance measures how many standard deviations a point lies from the mean of a distribution, accounting for the full covariance structure (correlations between features, and different variances per feature). It is the "distribution-aware" generalization of Euclidean distance: Euclidean assumes all features have equal variance and zero correlation; Mahalanobis corrects both. In DA/DS: Mahalanobis distance is the core of multivariate outlier detection, anomaly scoring, Gaussian discriminant analysis (LDA/QDA), the Hotelling test, and any scenario where features have different scales and/or are correlated.
Flowchart
Real-world use
Where it is used
Multivariate outlier detection (flag observations with ), Gaussian anomaly detection in manufacturing/fraud/network intrusion, Linear Discriminant Analysis (LDA uses class-conditional Mahalanobis distances for classification), Hotelling control charts in quality control, and feature-space distance in Gaussian mixture models.
Problem it solves
Euclidean distance treats all features equally and assumes they are uncorrelated. In practice: height (cm) and weight (kg) are correlated and have different scales. A point that is "far" in Euclidean space might be completely normal if it lies along a high-variance direction. Mahalanobis rescales by variance and de-correlates, so "far" means genuinely unusual.
How it solves the problem
is the precision matrix. Pre-multiplying the centered vector by removes the correlation structure and rescales each component by its inverse variance. This is equivalent to computing the Euclidean distance of after the whitening transform : .
Example in a business
A DS monitors transaction fraud. Features: transaction amount, velocity (transactions/hour), and account age. These are correlated and on different scales. For each new transaction , they compute from the "normal" transaction distribution fitted on historical non-fraud data. Transactions with are flagged for manual review.
The formula, piece by piece
- : the query point
- : the mean vector of the reference distribution
- : the covariance matrix (must be positive definite for to exist)
- : centers relative to the distribution mean
- : precision matrix; scales by inverse variance and removes correlations
- : the Mahalanobis squared distance ; a quadratic form
- : take square root to get in the original units
- Special case : (Euclidean distance from mean)
- Two-sample version: where is the pooled covariance
- Under multivariate Gaussian: — enables principled p-value thresholds
The formula in words
"Center the point relative to the mean. Stretch and rotate the space to remove correlations and equalize variances (using ). Compute the Euclidean length in this corrected space. A large value means the point is genuinely far from the center of the distribution, accounting for the natural spread and correlations." Tiny example: , (feature 1 has 4x more variance). : . . : . Same Mahalanobis distance! Euclidean would give , wrongly suggesting is more unusual.
Critical Concepts
- Whitening equivalence: where (Cholesky or SVD-based whitening). Mahalanobis = Euclidean after whitening.
- Requires invertible: if features are perfectly correlated or , is singular. Use the pseudo-inverse or regularized covariance ().
- under Gaussian: use distribution for thresholds. For features, 99% threshold is , meaning .
- Robust estimation: standard sample covariance is sensitive to outliers. Use Minimum Covariance Determinant (MCD) or other robust estimators for contaminated data.
- Connection to LDA: LDA classifies to the class with the smallest Mahalanobis distance to the class mean (using pooled covariance).
- Scale invariance: is invariant to linear transformations of the features.
Rescaling any feature does not change (the covariance matrix absorbs the scaling).
Step-by-step calculation (tiny example)
Step 1: Set up the problem , , . .
Step 2: Invert the covariance matrix . .
Step 3: Compute the quadratic form and take square root . . . Euclidean: . The correlation structure reduces the effective distance.
Code examples
Beginner example (Python)
import numpy as np
from scipy.spatial.distance import mahalanobis # Covariance and mean
mu = np.array([1.0, 2.0])
Sigma = np.array([[2.0, 1.0], [1.0, 1.0]])
x = np.array([3.0, 3.0]) # Manual computation
v = x - mu
Sigma_inv = np.linalg.inv(Sigma)
D_M_sq = float(v @ Sigma_inv @ v)
D_M = np.sqrt(D_M_sq)
D_eucl = float(np.linalg.norm(v))
print(f"Sigma_inv =\n{Sigma_inv}")
print(f"D_M^2 = {D_M_sq:.4f}")
print(f"D_M = {D_M:.4f}")
print(f"D_Euc = {D_eucl:.4f}") # Verify with scipy (scipy uses Cholesky of the inverse)
VI = np.linalg.inv(Sigma)
print(f"\nscipy Mahalanobis: {mahalanobis(x, mu, VI):.4f}")Beginner example (R)
# -- Mahalanobis distance ---
mu <- c(1, 2)
Sigma <- matrix(c(2, 1, 1, 1), nrow=2, byrow=TRUE)
x <- c(3, 3)
v <- x - mu
Sigma_inv <- solve(Sigma)
D_M_sq <- as.numeric(t(v) %*% Sigma_inv %*% v)
D_M <- sqrt(D_M_sq)
cat(sprintf("D_M^2 = %.4f\n", D_M_sq))
cat(sprintf("D_M = %.4f\n", D_M)) # R built-in mahalanobis (works on multiple rows)
# mahalanobis(x,
center=mu, cov=Sigma)
cat(sprintf("R mahalanobis: %.4f\n", sqrt(mahalanobis(x, center=mu, cov=Sigma))))Professional example (Python)
import numpy as np
import pandas as pd
from scipy.stats import chi2
from sklearn.covariance import MinCovDet
np.random.seed(42) # -- Business scenario: multivariate outlier detection for fraud monitoring ---
# Features: normalized transaction amount, velocity (tx/hr), days since last tx
N_NORMAL = 400
N_FRAUD = 15
D = 3 # Simulate normal transaction data (correlated features)
Sigma_true = np.array([[1.0, 0.6, 0.3], [0.6, 1.0, 0.1], [0.3, 0.1, 1.0]])
mu_true = np.array([0.0, 0.0, 0.0])
L = np.linalg.cholesky(Sigma_true)
X_normal = (np.random.randn(N_NORMAL, D) @ L.T) + mu_true # Fraud: outliers injected at unusual corner of feature space
X_fraud = np.random.randn(N_FRAUD, D) + np.array([3.0, 2.5, -2.0])
X_all = np.vstack([X_normal, X_fraud])
labels_true = np.array([0] * N_NORMAL + [1] * N_FRAUD) # Fit robust covariance (MCD) to training data (normal only)
mcd = MinCovDet(random_state=42).fit(X_normal)
mu_hat = mcd.location_
Sigma_hat = mcd.covariance_
Sigma_inv = np.linalg.inv(Sigma_hat) # Compute Mahalanobis distance for all observations
D_M_sq = np.array([ float((x - mu_hat) @ Sigma_inv @ (x - mu_hat)) for x in X_all ])
D_M = np.sqrt(D_M_sq) # Chi-squared threshold for 99% confidence (D=3 features)
THRESHOLD = chi2.ppf(0.99, df=D)
is_flagged = D_M_sq > THRESHOLD
fraud_detected = is_flagged[N_NORMAL:].sum()
false_positives = is_flagged[:N_NORMAL].sum()
print(f"Mahalanobis anomaly detection (chi2 threshold @ 99%, df={D}):")
print(f" Threshold: D_M^2 > {THRESHOLD:.2f} (D_M > {np.sqrt(THRESHOLD):.2f})")
print(f" Fraud detected: {fraud_detected} / {N_FRAUD}")
print(f" False positives (normal flagged): {false_positives} / {N_NORMAL}")
results = pd.DataFrame({ "obs_id": range(len(X_all)), "D_M": D_M.round(3), "D_M_sq": D_M_sq.round(3), "flagged": is_flagged, "true_fraud": labels_true.astype(bool),
})
print(f"\nTop flagged observations:")
print(results[results["flagged"]].sort_values("D_M", ascending=False) .head(10).to_string(index=False))Source: domain knowledge