UMAP is the modern default for nonlinear dimensionality reduction: fast, scalable, and able to embed new points. This page covers the graph-based algorithm, its main parameters, practical code including the UMAP + HDBSCAN clustering pattern, and how it compares to t-SNE and PCA.
UMAP (Uniform Manifold Approximation and Projection, McInnes, Healy and Melville 2018) is a nonlinear dimensionality-reduction method grounded in manifold learning and topological data analysis. Like t-SNE, it keeps local neighborhoods, but it is typically much faster, scales to millions of points, tends to preserve more of the global arrangement of clusters, can embed into any number of dimensions, and supports transform on new data. That makes it useful both for 2-D visualization and as a preprocessing step for clustering.
Graph construction. UMAP finds each point's k = n_neighbors nearest neighbors (approximately, with NN-descent). For point i, let ρi be the distance to its nearest neighbor and pick σi so the memberships sum to log2(k). Edge weights are wij = exp(-(d(xi, xj) - ρi) / σi), making every point connected to at least one neighbor with weight 1 — a local, adaptive notion of distance. Directed weights are combined with a fuzzy union: w = a + b - a·b.
Layout optimization. In the low-dimensional space, similarity is (1 + a·d2b)-1, with a, b fit from min_dist. UMAP minimizes the fuzzy-set cross-entropy between the two graphs with stochastic gradient descent: attractive forces along sampled edges, repulsive forces from negative samples. Unlike t-SNE's KL, the cross-entropy also penalizes placing dissimilar points close together, which is one reason more global structure survives. Initialization from a spectral embedding of the graph adds stability.
| Parameter | Effect | Typical |
|---|---|---|
n_neighbors | Local vs global trade-off, like perplexity | 15 (5–100) |
min_dist | How tightly points pack in the output; cosmetic for plots | 0.1 for plots, 0.0 before clustering |
n_components | Output dimension | 2 for plots, 5–15 for clustering |
metric | Input distance | cosine for text embeddings |
random_state | Reproducibility; disables parallelism | Set for reports |
# pip install umap-learn
import umap
from sklearn.datasets import load_digits
from sklearn.cluster import HDBSCAN
from sklearn.model_selection import train_test_split
X, y = load_digits(return_X_y=True)
X_tr, X_new = train_test_split(X, test_size=0.2, random_state=0)
# 2-D map for plotting
reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, n_components=2,
metric="euclidean", random_state=42)
Z_tr = reducer.fit_transform(X_tr)
Z_new = reducer.transform(X_new) # embed unseen points into the same map
# 10-D embedding for density clustering
Z10 = umap.UMAP(n_neighbors=30, min_dist=0.0, n_components=10,
random_state=42).fit_transform(X)
labels = HDBSCAN(min_cluster_size=30).fit_predict(Z10)
print("clusters:", labels.max() + 1, "noise points:", (labels == -1).sum())
# Supervised / semi-supervised UMAP: pass y (use -1 for unlabeled)
Z_sup = umap.UMAP(random_state=42).fit_transform(X, y=y)
transform, embeds into more than 3 dimensions, and arrangement between clusters is somewhat more informative, though still not metrically faithful.n_neighbors values and seeds; structure that appears in only one setting is suspect.Suppose n_neighbors = 4 and point i has neighbours at distances 1.0, 1.5, 2.0 and 3.0. Then ρi = 1.0, the nearest-neighbour distance. UMAP searches for σi so that the memberships sum to log2(4) = 2. Solving 1 + e−0.5/σ + e−1.0/σ + e−2.0/σ = 2 numerically gives σ ≈ 0.89 and memberships of about 1.00, 0.57, 0.33 and 0.11.
Two properties are visible. Subtracting ρ means the nearest neighbour always has weight 1, so even an isolated point is connected to something; this is the "locally uniform" assumption that each point sees its own neighbourhood as having unit density. And σ adapts per point: in a dense region where all four distances are small, the same target sum produces a small σ and a sharp falloff, so dense and sparse regions get comparable treatment. If point j assigns weight 0.6 to i while i assigns 0.3 to j, the symmetric fuzzy union gives 0.6 + 0.3 − 0.18 = 0.72. (The umap-learn implementation counts the point itself among its neighbours and has a few extra details, but the mechanics are the same.)
The layout loss is a cross-entropy between the high-dimensional weights w and the low-dimensional similarities v: an attractive term w · log(w / v) rewards placing strongly connected points close, and a repulsive term (1 − w) · log((1 − w) / (1 − v)) penalises placing unconnected points close. Computing the repulsive term over all pairs would be quadratic, so UMAP samples: each epoch it visits edges with probability proportional to their weight and, for each, draws a handful of random points as negatives (negative_sample_rate, default 5). The default number of epochs is 200 for large datasets and 500 for small ones. This sampling is why UMAP scales roughly linearly after the nearest-neighbour search, and why runs are stochastic unless random_state is fixed, which also disables some parallelism.
transform embeds new points by finding their neighbours among the training data and optimising only the new points' positions; it is much cheaper than refitting but assumes new data comes from the same distribution, so monitor how far new points land from training points. Supervised UMAP (fit(X, y)) mixes a label-based graph into the data graph, pulling same-class points together; target_weight controls the balance, and unlabeled points can be marked with -1 for semi-supervised use. It produces clean separation but can overstate how separable the classes really are, so do not judge class separability from a supervised embedding. Parametric UMAP trains a neural network encoder with the UMAP loss, giving fast, deterministic inference on new data. AlignedUMAP embeds related datasets, such as time slices, in a way that keeps corresponding points near each other across slices.
Because the picture is persuasive, measure it. Trustworthiness (available as sklearn.manifold.trustworthiness) penalises points that appear as neighbours in the embedding but were not neighbours in the input; values close to 1 mean few false neighbours. k-NN preservation is the fraction of each point's k input-space neighbours that remain among its k embedding neighbours. If labels exist, a k-NN classifier's accuracy on the embedding compared with the raw data shows how much class structure survived. For global structure, compare the correlation of pairwise distances between cluster centroids before and after embedding.
| Use case | Typical setup |
|---|---|
| Single-cell RNA sequencing | PCA to ~50 components, then UMAP for the standard cell-type plots |
| Topic discovery on text | Sentence embeddings → UMAP (5 to 10 dims, cosine) → HDBSCAN, as in BERTopic |
| Inspecting a model's representations | Penultimate-layer activations coloured by label or error, to find confused classes |
| Data quality audits | Embed images or rows and look for isolated islands of duplicates, mislabeled items or corrupt inputs |
Production notes: the nearest-neighbour search dominates time on large data; pynndescent handles this approximately, and GPU implementations (RAPIDS cuML) are available for millions of points. Save the fitted reducer together with its preprocessing; with low_memory and approximate neighbours, results are not bit-identical across versions or hardware.
Further reading: McInnes, Healy & Melville, "UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction" (2018, arXiv:1802.03426); Becht et al., "Dimensionality reduction for visualizing single-cell data using UMAP" (Nature Biotechnology, 2019); Sainburg, McInnes & Gentner, "Parametric UMAP" (2021); Narayan, Berger & Cho, densMAP (2021); the umap-learn documentation, especially "How UMAP Works" and the section on using UMAP for clustering.