HDBSCAN
HDBSCAN finds clusters of any shape by following density, decides the cluster count itself, and is honest enough to label leftover points as noise.
- 7 min read
- 3 reading levels
- Published
Read these first
On this page 5
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
HDBSCAN groups points by following where the data is dense.
It finds clusters of any shape, picks the number of clusters itself, and files points that fit nowhere as noise.
Look at a satellite photo of India at night. Cities glow as bright patches — some round, some sprawling along a coastline or a river. Villages form smaller specks. And a few lone lights sit alone in the dark, belonging to no patch at all.
Your eye needs no one to announce "there are 47 cities". Bright-and-connected is a city; a lone light is noise. HDBSCAN is that instinct, written as an algorithm.
Why this matters
K-means has three quiet assumptions: you know the group count, groups are roughly round, and every point belongs somewhere. Real data breaks all three. A crescent-shaped cluster gets sliced. A count you guessed wrong shreds real structure. And a fraud case gets stuffed into a "customer segment" as if it were normal.
Density-based clustering drops all three assumptions at once. A cluster is a region where points crowd together, whatever its shape. Sparse points between crowds are labelled noise — a refusal to force membership.
The H stands for hierarchical. An older method, DBSCAN, needed you to pick one fixed density cut-off. One cut-off cannot suit a dense city and a sparse town at the same time. HDBSCAN examines all density levels and keeps the groupings that persist across them.
How it works
. .. .....::::..... . <- dense crescent = cluster
. ...:::::::::... .
x <- lone point = noise
....:::....
..:::::::::.. <- second cluster, different density
....:::....Three ideas, in order. First, measure how crowded each point's neighbourhood is. Second, connect points that sit in each other's crowded regions — clusters grow along chains of density, which is why shape does not matter. Third, watch clusters appear and dissolve as the density bar rises, and keep the ones that survive longest. Points never caught in a surviving cluster are noise.
You state one preference: the smallest group size you would call a real cluster. That is a question about your problem, not about geometry — far easier to answer than "how many clusters exist?"
A real example you have seen
Map apps mark "busy areas" for restaurants and traffic. Those regions are irregular blobs following roads and markets — never neat circles — and isolated pins must not invent a hotspot. Density clustering handles exactly this.
Remember this
- Clusters are dense regions, so any shape works and the count is discovered, not guessed.
- Points that fit nowhere are labelled noise — that honesty is a feature.
- You choose only the minimum cluster size worth caring about.
What to learn next
- Clustering mixed numeric and categorical data — when your columns are not all numbers.
- Outliers vs novelties — the noise label, promoted to a discipline of its own.
- How many clusters? — the question HDBSCAN answers for you, and when to still ask it.
Developer — Code and libraries.
Setup
pip install scikit-learnHDBSCAN has lived in scikit-learn itself since version 1.3 — no separate hdbscan package needed. Outputs verified with scikit-learn 1.7.2 on CPU.
Two moons and some litter
Two crescent shapes — K-means' classic failure case — plus 20 random noise points:
import numpy as np
from sklearn.cluster import HDBSCAN
from sklearn.datasets import make_moons
X, _ = make_moons(n_samples=200, noise=0.07, random_state=3)
rng = np.random.default_rng(0)
X = np.vstack([X, rng.uniform(-1.5, 2.5, size=(20, 2))]) # sprinkle random noise
hdb = HDBSCAN(min_cluster_size=10).fit(X)
labels = hdb.labels_
print("clusters found:", sorted(set(labels) - {-1}))
for c in sorted(set(labels) - {-1}):
print(f" cluster {c}: {int((labels == c).sum())} points")
print("flagged as noise:", int((labels == -1).sum()), "points")clusters found: [0, 1] cluster 0: 103 points cluster 1: 99 points flagged as noise: 18 points
The walkthrough
Nobody told it "2". It recovered both crescents — 103 and 99 points against the 100-each truth — from density alone. K-means with n_clusters=2 splits these moons down the middle instead, gluing each half-moon to the wrong partner.
labels_ == -1 means noise. Not "cluster minus one" — it is the deliberate no-cluster verdict. 18 of our 20 planted noise points were caught; a couple landed close enough to a crescent to be absorbed. Count your -1 points before doing anything downstream.
min_cluster_size=10 is the one decision that matters: groups smaller than 10 are never promoted to clusters. Raise it and small clusters dissolve into noise; lower it and noise starts crystallising into micro-clusters.
min_samples (defaults to min_cluster_size) sets how conservative the density estimate is. Higher values declare more points noise. Tune it second, and gently.
Probabilities exist too. hdb.probabilities_ gives each point's strength of membership — 1.0 deep inside a cluster, falling toward 0 at the fringe.
Common mistakes
Feeding unscaled features. Density is measured with distances, so a big-ranged column dictates the density landscape. Scale features first, exactly as with K-means.
Treating noise as an error to eliminate. Tempted to shrink min_cluster_size until every point gets a label? You are then inventing clusters out of scatter. Noise is the algorithm telling the truth about your data.
Expecting stable labels across reruns with edited data. Add or drop points and cluster ids may renumber and borders shift. Compare groupings with proper metrics, not by id.
Using HDBSCAN in high dimensions and trusting the result blindly. Past a few dozen dimensions, distances concentrate and "density" loses meaning. Reduce dimensions first — this pairs naturally with embedding-based workflows like text embeddings.
Try it yourself
Set min_cluster_size=50 and rerun — watch both moons survive but the noise count grow. Then set it to 5 and count the phantom micro-clusters. Find the range where the answer is stable; stability across settings is the real sign of structure.
What to learn next
- Clustering mixed numeric and categorical data — when your columns are not all numbers.
- Outliers vs novelties — the noise label, promoted to a discipline of its own.
- How many clusters? — the question HDBSCAN answers for you, and when to still ask it.
Researcher — Mathematics and papers.
From DBSCAN to a hierarchy
DBSCAN (Ester, Kriegel, Sander, Xu, 1996) fixes a radius $\varepsilon$ and a count minPts, defines core points as those with $\ge$ minPts neighbours within $\varepsilon$, and forms clusters as connected components of core points. Its weakness is the single global $\varepsilon$: no one value handles clusters of differing densities.
HDBSCAN (Campello, Moulavi, Sander, 2013; expanded with Zimek in 2015) removes $\varepsilon$ by building the entire density hierarchy. Define the core distance $\mathrm{core}_k(x)$ as the distance from $x$ to its $k$-th nearest neighbour, and the mutual reachability distance:
$$ d_{\mathrm{mreach}}(a, b) = \max{\mathrm{core}_k(a),\, \mathrm{core}_k(b),\, d(a, b)} $$
Where:
- $d(a,b)$ — the base metric (Euclidean unless chosen otherwise).
- $\mathrm{core}_k(\cdot)$ — a point's local density scale; sparse points get inflated distances.
- $k$ — the
min_samplesparameter.
This inflation pushes sparse points away from everything, so chance bridges through low-density regions break.
The algorithm
- Build the minimum spanning tree of the complete graph under $d_{\mathrm{mreach}}$.
- Remove edges in decreasing weight order — this sweeps a density threshold and yields a dendrogram of connected components.
- Condense the tree: a component that falls below
min_cluster_sizewhen it splits is treated as the parent shedding points, not as a genuine split. - Score each candidate cluster by stability — the integral of its membership over the density parameter $\lambda = 1/\varepsilon$ — and select a non-overlapping set of clusters maximising total stability. Points outside every selected cluster are noise.
Complexity: the MST via dual-tree Borůvka runs near $O(n \log n)$ for low-dimensional Euclidean data; worst case $O(n^2)$. Memory is $O(n)$ beyond the neighbour queries.
Relations and extensions
- Single-linkage connection: the mutual-reachability MST is exactly robust single linkage in the sense of Chaudhuri and Dasgupta (2010), giving HDBSCAN consistency arguments for recovering the density cluster tree of the underlying distribution.
- OPTICS (Ankerst et al., 1999) is the earlier all-$\varepsilon$ ordering approach; HDBSCAN supersedes it with a principled flat extraction.
- Soft clustering and prediction: the reference implementation (McInnes, Healy, Astels, JOSS 2017) adds membership vectors and
approximate_predictfor new points; scikit-learn's port covers the core estimator. - GLOSH outlier scores fall out of the same hierarchy, connecting this lesson directly to outlier detection.
- Standard pairing in modern NLP pipelines: UMAP to ~5 dimensions, then HDBSCAN — the topic-modelling recipe popularised by BERTopic (Grootendorst, 2022).
What to learn next
- Clustering mixed numeric and categorical data — when your columns are not all numbers.
- Outliers vs novelties — the noise label, promoted to a discipline of its own.
- How many clusters? — the question HDBSCAN answers for you, and when to still ask it.