DBSCAN Clustering - Unsupervised Learning
Implement DBSCAN Clustering in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.
DBSCAN Clustering
What you'll build
dbscan(X, eps, min_samples): density-based clustering that returns a cluster id for every point, or -1 for noise, with the same labels as scikit-learn's DBSCAN(eps=eps, min_samples=min_samples) (Euclidean metric).
Background
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) finds clusters of any shape as regions where points are packed densely, and labels points in sparse regions as noise. Unlike k-means you don't choose the number of clusters, and outliers don't drag centroids around. It is a common choice for anomaly detection and geospatial data.
Definitions
- The eps-neighbourhood of point
p: all pointsqwith||p - q|| <= eps, includingpitself. pis a core point if its neighbourhood has at leastmin_samplespoints (countingp), as in scikit-learn.- A border point is not core but lies in the neighbourhood of some core point.
- Everything else is noise (label
-1).
Algorithm
labels = [-1] * n; cluster = 0
for p in 0 .. n-1: # scan in index order
if labels[p] != -1 or p is not core: continue
labels[p] = cluster; queue = [p]
while queue:
q = queue.pop(0) # q is always a core point
for each neighbour r of q:
if labels[r] == -1:
labels[r] = cluster
if r is core: queue.append(r)
cluster += 1
- Only core points expand a cluster; border points join a cluster but don't spread it.
- Clusters are numbered in order of discovery: cluster
0contains the lowest-indexed core point, cluster1the lowest-indexed core point not in cluster0, and so on.
Inputs
X: data, shape(n, d)eps: neighbourhood radius (> 0); a distance exactly equal toepscountsmin_samples: minimum neighbourhood size (point included) for a core point,>= 1
Expected Return Format
An int array of shape (n,): -1 for noise, otherwise the cluster id 0, 1, 2, ....
Edge cases and conventions
- A border point within reach of two clusters keeps the label of the first cluster that reached it (the one discovered earlier) and is never reassigned. This is also what scikit-learn does; it is the one order-dependent part of DBSCAN.
min_samples = 1: every point is core, so isolated points become singleton clusters and nothing is noise.- If no point is core, every label is
-1.
Constraints
- NumPy only (no sklearn). Vectorise the distance matrix; the cluster expansion is a plain loop.
Follow-up
- How would you pick
eps? (Plot each point's distance to itsmin_samples-th neighbour, sorted, and look for the knee.) - The distance matrix is
O(n^2)memory. How do spatial indexes (k-d trees, ball trees) help?
Examples
Input: dbscan(np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [10.0, 10.0], [10.0, 11.0], [11.0, 10.0], [50.0, 50.0]]), 1.5, 3) Output: [0, 0, 0, 1, 1, 1, -1]
Each corner triangle has 3 points within 1.5 of each other, so all are core; (50, 50) has only itself and is noise.
Input: dbscan(np.array([[0.0], [0.3], [0.6], [1.4], [2.2], [2.5], [2.8], [10.0]]), 0.9, 4) Output: [0, 0, 0, 0, 1, 1, 1, -1]
Only 0.6 and 2.2 are core (4 neighbours each, counting themselves). 1.4 is a border point of both and stays in cluster 0, which reaches it first.
Practice in Python
import numpy as np
def dbscan(X, eps, min_samples):
"""
DBSCAN clustering (Euclidean distance, same labels as sklearn).
Args:
X: Data, shape (n, d)
eps: Neighbourhood radius (distance <= eps counts, the point itself included)
min_samples: Minimum neighbourhood size for a core point
Returns:
Int array of shape (n,): -1 for noise, clusters numbered 0, 1, ...
in order of discovery when scanning points by index
"""
# Your code here
pass
The interactive editor loads with Python and NumPy. Run your implementation against 7 tests, or use the progressive hints.