DBSCAN Clustering
Keep that in mind: when a DBSCAN result surprises you, the first suspects are the parameter values and the density they imply, not the algorithm itself.
A Result With No Clusters at All
DBSCAN stands for density-based spatial clustering of applications with noise. It groups points by how crowded their neighborhoods are, and it can also flag points that belong to no group at all. The fastest way to learn it is to run an example and look at what comes back, then work out why it came back that way.
Suppose you run DBSCAN on a very small dataset and every point comes back labeled as noise. That is not a bug in the algorithm. It is a signal that the two parameters decide everything, and a toy dataset this small will rarely satisfy the default density rule. Keep that in mind: when a DBSCAN result surprises you, the first suspects are the parameter values and the density they imply, not the algorithm itself.
The example below is the one the source uses to show DBSCAN working well. It creates the two_moons dataset, which is two interleaved half-circles, rescales the features to zero mean and unit variance, and clusters with the default settings. Read it first and decide which parts you would check if the labels came back wrong.
make_moonscreates the two interleaved half-circles- StandardScaler rescales each feature to zero mean and unit variance before clustering
DBSCAN()uses default settings; eps andmin_samplesare not passed explicitlyfit_predictreturns one label per point
With these default settings the source reports that DBSCAN finds the two half-circles and separates them, which is the desired result. Notice that you did not tell DBSCAN how many clusters to find. The number of clusters is an outcome of the density rule, not an input.
Two Parameters That Control Everything
DBSCAN has exactly two parameters, and every label it returns is a consequence of them. The first, eps, is a distance. Two points count as close when the distance between them is eps or less. Think of eps as the radius of a circle drawn around a point: any other point inside that circle is a close neighbor.
The second parameter, min_samples, is a count. It is the minimum number of data points that must lie within eps of a given point before that point counts as belonging to a dense region. Where eps sets how far a neighbor can be, min_samples sets how many neighbors are needed to call a spot crowded.
eps: the distance that defines closeness. Two points are close when the distance between them is eps or less.
min_samples: the neighbor count that defines a dense region. A point needs at least this many points within eps to be a core sample.
Dense Regions Instead of a Preset Count
DBSCAN looks for points that sit in crowded regions of the feature space, where many data points lie close together. These crowded regions are called dense regions. The core idea is that a cluster is a dense region of data, and clusters are separated from one another by regions that are comparatively empty.
This is the key contrast with agglomerative clustering. In scikit-learn, agglomerative clustering requires you to supply n_clusters yourself, and the algorithm returns that many groups whether or not the data contains that many natural groups. DBSCAN never asks for a cluster count. Its output is whatever dense regions the two parameters reveal, and some points may belong to none of them.
The price for this flexibility is speed. The source notes that DBSCAN is somewhat slower than agglomerative clustering and k-means, although it still scales to relatively large datasets.
Core, Boundary, and Noise Points
After DBSCAN finishes, every point falls into exactly one of three kinds. A core sample has at least min_samples points within eps. A boundary point lies within eps of a core sample but does not satisfy the min_samples rule itself. A noise point is the rest: it belongs to no cluster, and scikit-learn labels it -1.
The distinction between core and boundary points matters for reproducibility. Running DBSCAN repeatedly on the same dataset always gives the same clustering of core points, and the same points are always labeled noise. A boundary point, however, may sit next to core samples from more than one cluster. Which cluster it joins then depends on the order in which points are visited. The source points out that there are usually only a few boundary points, so this dependence is rarely important in practice, but it is worth knowing when two runs disagree on a single point.
A boundary point can sit within eps of core samples from two different clusters. Its label is then determined by visit order, so two runs on identical data can disagree on that one point while agreeing on everything else.
Tracing the Expansion Procedure
DBSCAN does not look at all points at once. It works through the dataset one unvisited point at a time, deciding for each point whether it starts a new cluster, joins an existing one, or is noise. The procedure below follows the order the source gives it.
- 1Pick an unvisited point and count the points within eps of it.
- 2If that count is below
min_samples, set the point aside as noise and move on to the next unvisited point. - 3If the count meets
min_samples, start a new cluster from this core sample. - 4Grow the cluster outward through core samples only: each neighbor that is itself a core sample extends the cluster further.
- 5A neighbor that is not a core sample joins the cluster as a boundary point but does not extend it further.
- 6When no more core samples can extend the cluster, move on to the next unvisited point and repeat.
Follow one starting point through this flow to see the core and boundary distinction in action. If the starting point has too few neighbors, it is set aside and the algorithm moves on. If it has enough neighbors, a cluster is born, and growth proceeds outward through core samples only. Boundary points are reached and labeled, but the search does not continue from them. That asymmetry is the mechanism behind the difference between the two kinds of points.
Tracing one point through the expansion
Use eps = 1.0 and min_samples = 3. Points A, B, C, D, E lie on a line at positions 0.0, 0.6, 1.2, 1.8, 5.0. Trace which points become core samples, boundary points, or noise.
- 1Check A at 0.0
Points within 1.0 of A are A itself and B at 0.6. That is 2 points, fewer than 3, so A is not a core sample. Since A has no core within eps yet, it is set aside as noise for now.
- 2Check B at 0.6
Points within 1.0 of B are A (0.0), B (0.6), and C (1.2). That is 3 points, which meets
min_samples, so B is a core sample and starts a cluster. - 3Grow from B
A is within eps of core B, so A joins the cluster. Its own neighborhood has only 2 points, so A is a boundary point and does not extend the cluster.
- 4Check C at 1.2
Points within 1.0 of C are B (0.6), C (1.2), and D (1.8). That is 3 points, so C is a core sample and joins the cluster. D is within eps of C and becomes a boundary point.
- 5Check E at 5.0
Only E itself lies within 1.0 of E, so E is noise.
Cluster: A (boundary), B (core), C (core), D (boundary). E is noise. The result depends on these hand-counted neighborhoods; the same procedure applied to any dataset gives the same labels.
Reading the Labels Returned by DBSCAN
DBSCAN returns one integer label per point. The label -1 is reserved for noise. Every other label is a cluster identifier, and the non-negative values only name groups; they carry no meaning about size or order. When you read a result, first separate the noise from the clusters, because a naive count of distinct labels will include -1 as if it were a cluster.
- labels != -1 builds a boolean mask that keeps only clustered points
- Indexing other arrays with the same mask keeps them aligned with the clustered labels
np.uniqueon the masked labels lists the real cluster identifiers without -1
This matters whenever you use the labels to index another array, such as the original feature rows or a column of targets. If you index with raw labels, the -1 values silently select the last element of the array instead of marking a noise point. Always handle -1 explicitly before using labels as positions.
Common mistake Counting -1 as a cluster
What it looks likeA result with labels 0, 1, and -1 is reported as three clusters.
Why it fails-1 marks noise points, which belong to no cluster.
Do this insteadCount only non-negative labels as clusters, and report the noise count separately.
Common mistake Indexing arrays with raw labels
What it looks likeUsing labels[i] as a position into a feature table without removing -1 first.
Why it failsNegative indices in Python select elements from the end of the array, so noise points pick the wrong rows.
Do this insteadBuild a mask such as labels != -1 and apply it before indexing.
Why Feature Scale Changes the Neighbors
Because eps is a single distance, DBSCAN depends on how features are measured. The neighbors of a point are determined by the distance between points in the raw feature space. If one feature has values in the thousands and another has values between zero and one, the large-scale feature dominates the distance, and the small-scale feature barely affects which points count as close.
This is why the two_moons example rescales the data to zero mean and unit variance before clustering. After rescaling, each feature contributes comparably to the distance, so an eps value means the same thing across dimensions. Without rescaling, the same eps can produce a very different set of neighbors and therefore very different labels.
Changing eps and min_samples
Once you know that eps sets the neighborhood radius and min_samples sets the density threshold, you can predict how a change to either one will reshape the clusters. Lowering eps shrinks each neighborhood, so fewer points qualify as core samples and more points are left as noise or split into separate groups. Raising eps grows the neighborhoods, so separate dense regions can join into one cluster.
What do you think happens?
On the two_moons data with feature scaling, if you lower eps from 0.5 to 0.2 and keep min_samples at its default, how many clusters do you expect?
Reveal answer
Eight clusters, which the source describes as clearly too many.
A smaller eps shrinks each neighborhood, so the dense regions break into many small pieces. Each piece that meets the density rule becomes its own cluster.
min_samples works in the opposite direction from eps for the density rule. Raising min_samples demands more neighbors before a point counts as a core sample, so sparse regions are more likely to be labeled noise. Lowering it lets thinner groups of points qualify as dense, which can produce clusters from scattered points. Because eps and min_samples interact, change one at a time and compare the labels before and after.
Where a DBSCAN Result Diverges
When a DBSCAN result looks wrong, locate the step where it stopped matching what you expected. Start with the parameters, then the scaling, then the labels, and only then the algorithm. The list below covers the mistakes that account for most surprising results.
Common mistake Blaming the algorithm before checking eps and min_samples
What it looks likeA tiny dataset returns all noise and the user concludes DBSCAN is broken.
Why it failsAll-noise output means the density rule was not met under the chosen eps and
min_samples.Do this insteadCheck the parameter values and the density they imply before changing algorithms.
Common mistake Expecting a fixed number of clusters
What it looks likeSetting an expectation of three clusters and assuming DBSCAN will return three.
Why it failsDBSCAN has no cluster-count parameter; the count is an outcome of eps and
min_samples.Do this insteadAdjust eps and min_samples and inspect how the cluster count responds.
Common mistake Treating boundary point labels as fixed
What it looks likeTwo runs disagree on one point and the user assumes a bug in the code.
Why it failsA boundary point can border several clusters, so its membership can depend on visit order.
Do this insteadExpect occasional disagreement on boundary points and focus on core-sample labels when comparing runs.
Common mistake Forgetting that -1 is noise rather than a cluster
Common mistake Clustering unscaled features and then tuning eps to fix the result
DBSCAN Compared With Agglomerative Clustering
Agglomerative clustering starts with every point as its own cluster and joins clusters step by step until one final cluster remains. Each stage is itself a valid clustering, and choosing a cluster count amounts to stopping the process at the matching stage. The source states that agglomerative clustering still fails at separating complex shapes, and it gives the two_moons dataset as an example. DBSCAN does not share that particular failure, because it grows clusters from dense regions rather than merging clusters by distance.
| Aspect | Agglomerative clustering | DBSCAN |
|---|---|---|
| How clusters form | Joins clusters step by step from single points upward | Grows clusters from dense regions of points |
| Cluster count | Must be supplied as n_clusters | Not a parameter; emerges from eps and min_samples |
| Noise points | Every point is assigned to a cluster | Points in sparse regions are labeled -1 |
| Complex shapes such as two_moons | The source reports it fails to recover the groups | The source reports it finds and separates the two half-circles with defaults |
| Speed | Faster, per the source | Somewhat slower, still scales to relatively large datasets |
Contrast between the two approaches as described in the source
Practice: Predict Before You Run
You run DBSCAN on the scaled two_moons data and get two large clusters, but about a dozen points are labeled -1 along the edges of each half-circle. Before changing anything, predict which parameter you would adjust first and whether you would raise or lower it. Then explain which kinds of points the -1 labels most likely are.
Hints
- Noise points are the points that have no core sample within eps.
- Lowering eps shrinks neighborhoods, which makes more points fail the density rule.
- Ask whether the noisy points sit at the sparse edges of the crescents.
Take the labels array from the label-handling example and count the clusters and the noise points without using a loop. Then state which expression would give you the cluster labels only.
Hints
np.uniquecan list the distinct values in an array.- A boolean mask such as labels != -1 removes noise.
Key Takeaways
- DBSCAN builds clusters from dense regions of the feature space rather than from a preset cluster count, and it labels points in sparse regions as noise with -1.
- eps is the distance that defines closeness, and
min_samplesis the neighbor count that defines a dense region; together they decide every label DBSCAN returns. - Core samples meet the
min_samplesrule, boundary points lie within eps of a core sample without meeting it, and noise points belong to no cluster. - Changing eps alone can change the cluster count dramatically, and unscaled features change which points count as neighbors, so check scaling and eps before blaming the algorithm.
- Handle the -1 label explicitly before indexing other arrays, and expect occasional disagreement on boundary points because their membership can depend on visit order.