Sam Austinon October 24, 202619 min read

DBSCAN vs Gaussian Mixture: Noise Handling and Soft Clustering in Scikit-learn

DBSCAN vs Gaussian Mixture: Noise Handling and Soft Clustering in Scikit-learn
Contents

Figure 1: DBSCAN vs Gaussian Mixture — one names its noise, the other gives you probabilities

DBSCAN and Gaussian mixture models (GMMs) are often presented as two options on the same menu, but they answer different questions.

DBSCAN asks, "where are the dense regions, and which points belong to none of them?" A GMM asks, "what combination of bell-shaped groups most plausibly generated this data, and how likely is each point to belong to each group?" One gives you hard labels and an explicit noise category, the other gives you probabilities and no concept of noise.

This guide compares them on the dimensions that matter in practice, runs both on data where each should win, shows how to combine them, and covers the tuning and evaluation mistakes that trip people up.

How DBSCAN Works

DBSCAN defines clusters as regions where points are packed densely, separated by sparser regions. It has two parameters:

  • eps: the radius of a point's neighbourhood. Scikit-learn's documentation calls it the most important parameter to choose appropriately for your dataset and distance function, and stresses that it is not an upper bound on the distances within a cluster.
  • min_samples: how many points (including the point itself) must fall within eps for a point to count as a core point. Higher values find denser clusters, and lower values allow sparser ones.

Every point ends up as one of three types: a core point (dense neighbourhood), a border point (within eps of a core point but not dense itself), or noise (neither). Clusters grow by chaining core points whose neighbourhoods overlap. Noise points get the label -1.

Strengths and Limitations

Strengths Limitations
You do not specify the number of clusters One eps applies everywhere, so clusters of very different densities suffer
Clusters can take any shape (crescents, rings, blobs) Results are sensitive to eps and to feature scaling
Outliers are identified by the algorithm itself Distance-based density degrades in high dimensions
— No predict method for new points, only fit and fit_predict
— Worst-case memory O(n²) when eps is large and min_samples is small

That last point surprises people who know the literature. Scikit-learn's implementation has a worst-case memory use of O(n²), which can occur when eps is large and min_samples is small, even though the original algorithm uses linear memory. If you are already comfortable with the mechanics, the DBSCAN walkthrough covers the neighbourhood expansion step by step.

How a Gaussian Mixture Works

A GMM models the data as a weighted sum of Gaussian distributions. Each component has a mean, a covariance (its shape and orientation), and a weight (its share of the data). The EM algorithm alternates between estimating each point's probability of belonging to each component (the E-step) and updating the components to fit those soft assignments (the M-step), until the likelihood stops improving.

Strengths Limitations
Soft assignments through predict_proba You must choose the number of components
Elliptical and overlapping clusters handled naturally Assumes Gaussian-shaped clusters: a crescent becomes several Gaussians
A proper probabilistic model with a likelihood (score_samples) EM can land in local optima
Model selection through BIC and AIC Covariances can collapse on tiny clusters or when features outnumber samples
Can score new points No explicit noise category

Outliers do not get labelled as noise: they get absorbed into a component, often inflating its covariance.

Covariance Types

The covariance_type parameter controls how flexible the cluster shapes are.

Value Shape Parameters per component When to use
full Arbitrary ellipses Most Hundreds of rows per component
tied One shared shape Fewer Components you believe share geometry
diag Axis-aligned ellipses Few Wide data with correlated-free features
spherical One radius Fewest Tiny clusters, or a k-means-like baseline

Fewer parameters means more stability on small data and less flexibility.

Head to Head

Dimension DBSCAN Gaussian mixture
Cluster shape Arbitrary Ellipses
Number of clusters Discovered Required, usually via BIC
Noise Labelled explicitly as -1 None built in; low likelihood can flag candidates
Assignments Hard labels Probabilities
Overlapping clusters Tends to merge them Separates them probabilistically
Varying density Struggles: one eps cannot fit all Per-component covariances absorb different spreads
New data No predict Predicts and scores
Cost Neighbour search, heavy memory with large eps Grows with components and covariance complexity
High dimensions Distance concentration Covariance estimation
Key tuning risk eps Number of components and initialisation

Both degrade in high dimensions, DBSCAN through distance concentration and the GMM through covariance estimation, so consider dimensionality reduction first.

A Demonstration

We will use two datasets: three overlapping elliptical clusters plus uniform background noise (where a GMM should do well on the clusters and DBSCAN should do well on the noise), and two interleaved moons (where DBSCAN should win on shape).

import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs, make_moons
from sklearn.metrics import adjusted_rand_score
from sklearn.mixture import GaussianMixture
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

rng = np.random.default_rng(0)

# Dataset A: three overlapping ellipses + 5% uniform background noise (label -1)
X_a, y_a = make_blobs(n_samples=900, centers=[[0, 0], [3, 1], [1.5, 4]],
                      cluster_std=[0.8, 0.6, 0.9], random_state=0)
X_a = X_a @ np.array([[1.0, 0.6], [0.0, 0.8]])               # stretch into ellipses
noise = rng.uniform(X_a.min(0) - 1, X_a.max(0) + 1, size=(45, 2))
X_a = np.vstack([X_a, noise])
y_a = np.concatenate([y_a, -np.ones(45, dtype=int)])

# Dataset B: two interleaved moons
X_b, y_b = make_moons(n_samples=600, noise=0.07, random_state=0)

def knee_eps(X, k=10, q=0.9):
    """Heuristic eps: a high quantile of k-th neighbor distances (see the k-distance plot below)."""
    d, _ = NearestNeighbors(n_neighbors=k).fit(X).kneighbors(X)
    return float(np.quantile(d[:, -1], q))

def compare(X, y, k_true, name, min_samples=10):
    Xs = StandardScaler().fit_transform(X)
    eps = knee_eps(Xs, k=min_samples)
    db = DBSCAN(eps=eps, min_samples=min_samples).fit(Xs)
    gmm = GaussianMixture(n_components=k_true, covariance_type="full",
                          n_init=5, random_state=0).fit(Xs)
    g = gmm.predict(Xs)

    real = y >= 0                                    # score cluster quality on non-noise points
    n_clusters = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
    print(f"{name} | DBSCAN eps={eps:.2f}: {n_clusters} clusters, "
          f"{(db.labels_ == -1).mean():.1%} flagged noise, "
          f"ARI={adjusted_rand_score(y[real], db.labels_[real]):.2f}")
    print(f"{name} | GMM k={k_true}:      ARI={adjusted_rand_score(y[real], g[real]):.2f}")
    if (~real).any():
        caught = (db.labels_[~real] == -1).mean()
        print(f"{name} | DBSCAN caught {caught:.0%} of the true background noise")

compare(X_a, y_a, 3, "A: ellipses + noise")
compare(X_b, y_b, 2, "B: moons")

Expect the pattern the theory predicts, though exact numbers vary. On the overlapping ellipses with noise, the GMM separates the clusters better, while DBSCAN flags much of the background noise but may merge touching clusters or mislabel their sparser edges as noise. On the moons, DBSCAN recovers the two crescents almost perfectly, and a two-component GMM cuts across them, because Gaussian blobs cannot follow a curve.

ARI is computed on non-noise points only here, since a GMM cannot label noise, and comparing it on the noise points would measure something else.

Choosing DBSCAN's Parameters

Scale Features First

DBSCAN is distance-based, so unscaled features distort the neighbourhoods, and a default eps=0.5 means nothing until your data is on a known scale.

Pick min_samples

A common rule of thumb is at least the number of dimensions plus one, often around twice the dimensions, with larger values for noisy data. Larger values make the model more robust to noise but may discard small clusters.

Pick eps With a k-Distance Plot

Compute each point's distance to its k-th nearest neighbour (k = min_samples), sort the values, and look for the elbow, where distances start rising sharply:

import matplotlib.pyplot as plt

Xs = StandardScaler().fit_transform(X_a)
d, _ = NearestNeighbors(n_neighbors=10).fit(Xs).kneighbors(Xs)
plt.plot(np.sort(d[:, -1]))
plt.ylabel("distance to 10th neighbor"); plt.xlabel("points, sorted")
plt.show()

The elbow marks the transition from dense regions to noise. The knee_eps heuristic above is a rough stand-in, so for real work, read the plot.

Non-Euclidean Data

Use the metric parameter, such as haversine on latitude and longitude in radians for geographic data, with a ball-tree algorithm.

When Densities Vary: HDBSCAN

DBSCAN's single eps is its main weakness. Scikit-learn includes HDBSCAN, a hierarchical density-based method that, per its documentation, can find clusters of varying densities and is more robust to parameter choice.

It drops eps in favour of min_cluster_size (the smallest group that counts as a cluster) and min_samples, which defaults to min_cluster_size and, in scikit-learn's version, includes the point itself, unlike the standalone hdbscan package, so values differ by one between them. It also provides a membership strength for each point through probabilities_:

from sklearn.cluster import HDBSCAN

hdb = HDBSCAN(min_cluster_size=25, copy=True).fit(Xs)       # copy=True avoids a FutureWarning on recent versions
labels = hdb.labels_                                         # -1 = noise
strength = hdb.probabilities_                                # 0 for noise, near 1 deep inside a cluster

This is a form of soft output, but it is not the same thing as GMM probabilities. It measures how firmly a point sits inside its cluster's density structure, not the posterior probability of belonging to each of several clusters, so do not interpret it as a calibrated probability.

Soft Clustering and Outliers With a GMM

Soft assignments are the GMM's signature feature:

gmm = GaussianMixture(n_components=3, covariance_type="full",
                      n_init=5, random_state=0).fit(Xs)

proba = gmm.predict_proba(Xs)                    # shape (n_samples, 3), rows sum to 1
ambiguous = proba.max(axis=1) < 0.7              # points that sit between clusters
print(f"{ambiguous.mean():.1%} of points are ambiguous")

Ambiguous points are often the interesting ones: borderline customers, transition states, overlapping segments. Remember that these are probabilities under the model. If the Gaussian assumption is wrong, the probabilities can be overconfident.

For noise, a GMM gives you a likelihood. Points the model finds improbable are outlier candidates:

log_lik = gmm.score_samples(Xs)
outliers = log_lik < np.percentile(log_lik, 5)   # flag the least likely 5%

This is a threshold you choose, not a discovered noise class, and the outliers themselves distort the fit before you flag them, since extreme points stretch component covariances. That is why a robust pipeline often removes obvious noise first.

Choose the Number of Components

bics = {k: GaussianMixture(n_components=k, covariance_type="full",
                           n_init=3, random_state=0).fit(Xs).bic(Xs)
        for k in range(1, 9)}
best_k = min(bics, key=bics.get)

Lower BIC is better, and it penalises complexity. Plot the curve instead of trusting the minimum blindly, since BIC often picks more components than the "natural" number when clusters are not Gaussian.

If you would rather not choose k, BayesianGaussianMixture with a Dirichlet-process-style prior can shrink unused components toward zero weight, so you set an upper bound and let the model prune. If selecting k on the plain version is the step you keep getting wrong, the k-means guide to choosing k covers the elbow and BIC logic in more depth.

Combining Them

The two methods complement each other. DBSCAN or HDBSCAN removes noise and finds the dense structure, and a GMM then provides soft assignments and the ability to score new data:

hdb = HDBSCAN(min_cluster_size=25, copy=True).fit(Xs)
clean = Xs[hdb.labels_ >= 0]
k = len(set(hdb.labels_)) - (1 if -1 in hdb.labels_ else 0)

gmm = GaussianMixture(n_components=k, n_init=5, random_state=0).fit(clean)
threshold = np.percentile(gmm.score_samples(clean), 1)       # likelihood floor from clean data

def assign(X_new):
    ll = gmm.score_samples(X_new)
    proba = gmm.predict_proba(X_new)
    labels = np.where(ll >= threshold, proba.argmax(axis=1), -1)   # -1 = outside every component
    return labels, proba

This gives you the best of both: noise-robust fitting, probabilistic membership, and a working predict for new points, with an outlier rule calibrated on clean data. It still assumes the dense clusters found are roughly Gaussian, so check that assumption on your data.

Evaluating the Results

Measure Answers Do not use it for
Silhouette Compactness and separation of convex clusters Comparing DBSCAN with GMM
DBCV (standalone hdbscan package) Validity of arbitrary-shaped density clusters Gaussian models
BIC / AIC Which GMM fits better: components or covariance type Density-based results
Bootstrap ARI Whether the clusters are stable across resamples Proving the clusters are meaningful
ARI / AMI on labels Agreement with known ground truth Anything when the labels are absent
Domain profiles Whether a group is actionable Ranking algorithms

Silhouette score favours compact, convex clusters and ignores noise handling, so it can rate a correct DBSCAN result on crescents as worse than a wrong k-means result. Do not use it to compare DBSCAN with GMM.

Density-based validity measures such as DBCV (available in the standalone hdbscan package) are designed for arbitrary-shaped clusters. BIC and AIC compare GMMs with different component counts or covariance types.

Stability: re-cluster on bootstrap samples or subsamples and measure agreement with ARI. Unstable clusters are probably artifacts.

External labels: if you have any ground truth, even for a sample, use ARI or AMI, and report noise handling separately.

Domain validation: look at cluster profiles and examples, and ask whether the groups are meaningful and actionable.

Choosing Between Them

Your situation The choice
Irregular shapes and meaningful noise DBSCAN or HDBSCAN
Overlapping, roughly elliptical groups where uncertainty matters GMM
Varying densities HDBSCAN, or a GMM if the shapes are Gaussian
Need probabilities or soft membership GMM, or HDBSCAN strengths if rough confidence is enough
Need to assign new points in production GMM, or a classifier trained on DBSCAN labels
Need to flag outliers DBSCAN or HDBSCAN directly, or GMM likelihood thresholds
Do not know the number of clusters DBSCAN or HDBSCAN, or a GMM with BIC or a Bayesian mixture
Very high-dimensional data Reduce dimensions first (PCA, or an embedding model for text and images)

In very high dimensions, distances and covariances both become unreliable, which is why reducing dimensions before clustering is usually the right first move rather than a refinement.

If neither fits, consider k-means for simple compact clusters, OPTICS for density at multiple scales, spectral or agglomerative clustering for specific structures.

Common Pitfalls

  1. Running DBSCAN with the default eps on unscaled data.
  2. Setting min_samples too low, which chains separate clusters together through stray points.
  3. Expecting DBSCAN to give new-point predictions.
  4. Treating a GMM's probabilities as ground truth when the Gaussian assumption is poor.
  5. Fitting a GMM on data with heavy outliers and wondering why a component is huge.
  6. Choosing the number of components from the lowest BIC without looking at the curve or the clusters.
  7. Ignoring initialisation: a single EM run can land in a local optimum, so use n_init above 1.
  8. Using many features with few samples and getting singular covariances. Try diag, tied, or spherical covariance types, or raise reg_covar.
  9. Comparing methods with silhouette score.
  10. Clustering in raw high-dimensional space and trusting the distances.
  11. Interpreting cluster labels as ground truth categories, rather than as a data-driven description.

Best Book to Read

CoverBookDescriptionGet it
Cover of “Finding Groups in Data” Finding Groups in Databy Leonard Kaufman and Peter Rousseeuw the book that gave us the silhouette, and unusually honest about when a clustering index misleads, which is exactly the argument this post makes about comparing density methods with convex metrics. View on Amazon

As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.

Wrapping Up

DBSCAN finds dense regions of any shape and labels everything else as noise, giving hard assignments, no need to choose k, and no way to score new points. A Gaussian mixture models the data as overlapping ellipses and gives soft membership probabilities and a likelihood, but needs k and has no native notion of noise. HDBSCAN removes DBSCAN's single-eps limitation, and pipelines that clean with a density method and then fit a GMM can combine robustness, soft assignments, and prediction on new data.

How do you decide? Plot your data, decide whether noise, shape, or probabilities matter most, scale your features, and try both on a sample. Judge them by stability and by whether the clusters mean something in your domain, not by any single score.

Frequently Asked Questions

What is the main difference between DBSCAN and a Gaussian mixture?

They answer different questions. DBSCAN asks where the dense regions are and which points belong to none of them, so it returns hard labels plus an explicit noise class of -1. A Gaussian mixture asks what combination of bell-shaped groups most plausibly generated the data, so it returns probabilities for each component and a likelihood per point, but no concept of noise at all.

Can I predict new points with DBSCAN?

No. Scikit-learn's DBSCAN exposes fit and fit_predict only, with no predict method, because a new point's assignment depends on whether it falls within eps of a core point in the training set. In production you either refit, apply the rule manually with a k-d tree of the core points, or train a supervised classifier on the DBSCAN labels. A Gaussian mixture scores new data directly through predict and predict_proba.

Why does silhouette score give the wrong answer for DBSCAN?

Silhouette favours compact, convex clusters and ignores noise handling entirely. On two-crescent data a correct DBSCAN result can score worse than a wrong k-means partition, because the geometric assumption behind silhouette is exactly the one DBSCAN exists to escape. Use a density-based measure such as DBCV instead, and judge the noise class separately with precision or recall against known bad rows.

How do I choose eps for DBSCAN?

Use a k-distance plot: compute each point's distance to its k-th nearest neighbour with k equal to min_samples, sort the values, and look for the elbow where distances start climbing sharply. That elbow marks the boundary between dense regions and noise. The 90th-percentile heuristic used in this post is only a stand-in for exploratory work, not a replacement for reading the plot.

Does HDBSCAN give soft clustering like a GMM?

Partly. HDBSCAN exposes probabilities_, the membership strength of each point in its cluster, which is 0 for noise and approaches 1 deep inside a dense region. That measures how firmly a point sits in the density structure, not the posterior probability of belonging to each of several competing clusters, so it must not be read as a calibrated probability.

What does a GMM do with outliers?

It absorbs them. There is no noise category, so an extreme point is assigned to some component and stretches that component's covariance, which can make a whole cluster look larger and looser than it is. You can flag low-likelihood points after fitting with score_samples and a chosen percentile, but the fit was already distorted before you flagged anything, which is why cleaning with a density method first often pays off.

Sources

  • [1] scikit-learn, DBSCAN — the eps, min_samples and metric parameters, the core/border/noise assignment rules, and the note that eps is not an upper bound on within-cluster distances: scikit-learn.org/.../DBSCAN
  • [2] scikit-learn, GaussianMixture — covariance_type, n_init, predict_proba, score_samples, bic and aic, plus the reg_covar guard against singular covariances: scikit-learn.org/.../GaussianMixture
  • [3] scikit-learn, "Clustering" user guide — the comparison of clustering algorithms, the O(n²) worst-case memory note for DBSCAN, and the guidance on when density-based and mixture methods each apply: scikit-learn.org/modules/clustering
  • [4] scikit-learn, HDBSCAN — min_cluster_size, the min_samples default, probabilities_, and the copy parameter that silences the recent FutureWarning: scikit-learn.org/.../HDBSCAN
  • [5] scikit-learn, BayesianGaussianMixture — the Dirichlet-process-style prior with weight_concentration_prior, the automatic pruning of unused components, and the dirichlet versus dirichlet_process weight modes: scikit-learn.org/.../BayesianGaussianMixture

What are You Looking For?

esc