UMAP
Build a neighbour graph, scale each point's edges by its own nearest neighbour, then lay the graph out. Faster than t-SNE, and the gaps still are not distances.
Why Does This Exist?
An insurance team holds 400,000 claim descriptions, each turned into 384 numbers by a language model. They want a map: which claims resemble which, where the odd ones sit, whether known fraud cases clump anywhere.
t-SNE is the obvious tool and it hits three walls here. Hours at this size. Gaps between groups that carry no measurement. And no way to place tomorrow's claim without refitting everything, which keeps it out of a pipeline. UMAP keeps the same promise, neighbours stay neighbours, by a different route that runs faster and reuses the fit.
One idea first. A graph is points with weighted connections between them, and the basics live in graph fundamentals. UMAP is a graph method wearing a plotting tool coat.
Think of It Like This
A pinboard of elastic bands and weak magnets
Pin all 400,000 claims to a corkboard. For each claim run an elastic band to its fifteen closest relatives, tighter the closer the relative. Every other pair gets a weak magnet pushing them apart. Then let go. Banded pins pull together, everything else drifts, the board settles.
The clever part is how bands get measured. Cut them all to one absolute length and the crowded corner of near identical windscreen claims becomes one solid mass, while the sparse corner gets almost no bands. So each pin measures its bands against its own closest relative, and a crowded pin and a lonely pin both end up properly connected.
One knob only moves pins. The rule for how close two pins may sit changes the white space a lot. The bands never changed.
Where it stops. Bands and magnets settle once and stay put. Real UMAP runs gradient steps on a computer, and the layout can land differently from the same graph on a rerun.
How It Actually Works
From distances to a weighted graph
For each claim find its nearest neighbours, where is a count you pick such as 15. Draw a weighted edge to each. The weight falls as distance grows, but first subtract the distance to the claim own nearest neighbour, called , and scale the falloff by a per point width, called , so each point weights sum to a fixed amount.
That subtraction is the density trick. A claim in a dense patch and a claim out on its own produce comparable weights even when raw distances differ threefold. No global radius exists anywhere, which is exactly what DBSCAN struggles without.
Edges disagree, since may list while in a crowd does not list , so the two directions combine into one symmetric weight. The result is a weighted graph, the only thing computed from the data.
Worked example
Take a crowded point with neighbour distances and a thin point with . Here each list holds , a raw distance, and is the first entry. With and the mean of , the crowded weights are and the thin weights are .
Raw, the two points live at different scales. After subtracting each own nearest distance and rescaling, both report much the same neighbourhood. One model now covers a crowded region and an empty one. The diagram above draws the and edges as the focal pair.
From graph to layout
Place points in two dimensions so connected pairs sit close and unconnected pairs sit apart, by gradient descent. Two shortcuts make it fast. The neighbour graph is approximate, and repulsion is sampled with random non neighbours per step rather than all 400,000. Minutes rather than hours.
More global arrangement survives than in t-SNE, because the graph connects the whole set instead of assembling independent per point neighbourhoods. Two groups drawn adjacent are more plausibly related than two at opposite corners. Some is not metric. Do not quote a between group distance, and do not read group areas.
Two dials matter. neighbours sets how much each point sees. At 5 expect local detail and small islands. At 200 expect broad shape with fine structure smoothed away. It changes the graph, so it changes the answer. Minimum distance, called dist, sets how tightly points may pack in the drawing, typically to . Low gives crisp blobs, high spreads a haze. It touches only the drawing. Hold onto that split. One dial changes what was computed, the other changes how convincing the picture looks.
Code
The density trick on the two worked distance lists, with no fitted model needed for the point.
import math
def edge_weights(dists: list[float]) -> list[float]: rho = dists[0] # this point own nearest distance sigma = sum(d - rho for d in dists) / len(dists) # per point scale return [round(math.exp(-(d - rho) / sigma), 2) for d in dists]
crowded = [0.04, 0.05, 0.05, 0.06, 0.07]thin = [0.12, 0.20, 0.23, 0.24, 0.25]print(edge_weights(crowded)) # -> [1.0, 0.49, 0.49, 0.24, 0.12]print(edge_weights(thin)) # -> [1.0, 0.4, 0.29, 0.26, 0.23]Both lists start at by construction, since is zero for the nearest neighbour. The rest fall at comparable rates although raw distances differ threefold.
Watch Out For
Turning packing down until groups look separated
Symptom: a slide shows six crisply separated islands as proof the claim types are distinct. The call used minimum distance . A rerun at melts the islands into one smear.
Fix: treat minimum distance as a floor on how close points may sit in the drawing and nothing more. To claim separation measure it in the original 384 numbers with neighbour purity, and report both neighbours and minimum distance beside every plot.
Fitting the map on train and test together
Symptom: UMAP runs on all 400,000 claims, then a fraud model trained on the UMAP columns scores better than anything on raw features. Cross validation looks excellent and production does not.
Fix: fit on training rows only, then transform the rest, which is data leakage avoided. Inside cross validation put UMAP in the pipeline so it refits per fold. Supervised mode is for looking, never for features you then score.
The Quick Version
- UMAP builds a weighted nearest neighbour graph, then lays it out by pulling connected points together and pushing others apart.
- Each point scales edges by its own nearest distance, so dense and sparse regions report comparable weights.
- The worked crowded weights are and the thin weights are .
- It runs faster than t-SNE at scale and keeps more global arrangement, but drawn gaps are not measurements.
- Neighbour count changes the graph. Minimum distance only changes how tightly the drawing packs.
- It embeds into any dimension count and transforms new rows, so it fits in a pipeline.