A complete DBSCAN workflow — parameter selection via the k-distance plot, fitting, visualizing clusters and noise separately, and comparing directly against K-Means on the same data.
The Full Workflow
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import DBSCAN, KMeans
# A crescent-moon shaped dataset -- classic case where K-Means fails and DBSCAN succeeds
X, _ = make_moons(n_samples=300, noise=0.08, random_state=42)
X_scaled = StandardScaler().fit_transform(X)
dbscan_model = DBSCAN(eps=0.25, min_samples=5)
dbscan_labels = dbscan_model.fit_predict(X_scaled)
kmeans_model = KMeans(n_clusters=2, random_state=42, n_init=10)
kmeans_labels = kmeans_model.fit_predict(X_scaled)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].scatter(X[:,0], X[:,1], c=dbscan_labels, cmap="viridis")
axes[0].set_title("DBSCAN -- correctly traces the two crescents")
axes[1].scatter(X[:,0], X[:,1], c=kmeans_labels, cmap="viridis")
axes[1].set_title("K-Means -- forces round clusters, gets it wrong")
plt.show()
Expected result: DBSCAN correctly identifies the two crescent-moon shapes as separate clusters, since it follows local density rather than distance to a single centroid; K-Means, constrained to round clusters, typically splits the two crescents incorrectly (e.g. cutting each moon roughly in half instead of separating them).
Counting Clusters and Noise Points
n_clusters = len(set(dbscan_labels)) - (1 if -1 in dbscan_labels else 0)
n_noise = list(dbscan_labels).count(-1)
print(f"Clusters found: {n_clusters}")
print(f"Noise points: {n_noise}")
Tuning eps Systematically
from sklearn.neighbors import NearestNeighbors
neighbors = NearestNeighbors(n_neighbors=5).fit(X_scaled)
distances, _ = neighbors.kneighbors(X_scaled)
k_distances = np.sort(distances[:, -1])
plt.plot(k_distances)
plt.ylabel("5th nearest neighbor distance")
plt.xlabel("Points, sorted")
plt.show()
# Pick eps around the visually apparent "knee" in this curve
Common Mistakes
- Comparing DBSCAN and K-Means results without scaling features first for both — an unfair, misleading comparison.
- Not checking how many points ended up labeled noise — a very high noise count usually means
epsis too small ormin_samplestoo high for the data's actual density. - Assuming DBSCAN will always outperform K-Means — for genuinely round, evenly-sized, evenly-dense clusters, K-Means is often simpler, faster, and just as effective.
Interview Relevance
Q: "Show me a dataset shape where DBSCAN clearly outperforms K-Means." Two interleaved crescent moons (or concentric circles) — K-Means, constrained to round/convex clusters via centroid distance, cannot separate these shapes correctly regardless of tuning, while DBSCAN's density-based approach traces the actual non-convex cluster shapes directly.
Practice Question
Modify the workflow above to test eps values of 0.15, 0.25, and 0.4, and compare how the number of detected clusters and noise points changes.