0
$\begingroup$

So I am trying to generate a clustered graph using DBScan. I am able to do so but somehow gotten the clustered graph with more than 10+ clusters. It can be seen below.

DBScan graph 1

What I was expecting was something more like this with 2 clusters:

What I want

Here's my code:

from sklearn.neighbors import NearestNeighbors
from sklearn import datasets

import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler

%matplotlib inline

n_samples = 1500
X,y= datasets.make_circles(n_samples=n_samples, factor=.5,noise=.05)
X = StandardScaler().fit_transform(X)

neigh = NearestNeighbors(n_neighbors=2)
nbrs = neigh.fit(X)
distances, indices = nbrs.kneighbors(X)

distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.plot(distances)
plt.grid()
plt.show()

eps = distances[1400]
print("Optimal eps is",eps)
#source on how to find the optimal eps : https://towardsdatascience.com/machine-learning-clustering-dbscan-determine-the-optimal-value-for-epsilon-eps-python-example-3100091cfbc

Some explanation on deriving the optimal epsilon. From the article, what I understand is KNN is used to derive the array of points with shortest distance to its corresponding neighbour. The array is then plotted, and point at which the plotted curve is at its max curvature is the optimal epsilon value.

The model code:

db = DBSCAN(eps=eps,min_samples=5).fit(X)
core_samples_mask = np.zeros_like(db.labels_, dtype=bool) # create array same size as db.labels_ with zeros
core_samples_mask[db.core_sample_indices_] = True
labels = db.labels_
n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0)

print('Estimated number of clusters: %d' % n_clusters_)
plot_dbscan(X, labels, core_samples_mask)

The function to plot the dbscan:

def plot_dbscan (X,labels, core_samples_mask):
    unique_labels = set(labels)
    colors = [plt.cm.Spectral(each)for each in np.linspace(0, 1, len(unique_labels))]
    for k, col in zip(unique_labels, colors):
        if k == -1:
        # White used for noise.
            col = [0, 1,1,1]

        class_member_mask = (labels == k)

        xy = X[class_member_mask & core_samples_mask]
        plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
                         markeredgecolor='k', markersize=14)

        xy = X[class_member_mask & ~core_samples_mask]
        plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
                         markeredgecolor='k', markersize=6)

    plt.title('Estimated number of clusters: %d' % n_clusters_)
    plt.show()

How do I achieve the expected graph? Thank you for reading.

EDIT: I got it the graph to print what I want as seen below. Got it boys

However, it was through, playing around with the $e$ value and not through the described estimation technique found in the article. Why did it not give a rough estimation to near 0.14? Any insight would be greatly appreciated.

$\endgroup$
0

1 Answer 1

1
$\begingroup$

It really depends on your data. From the website you showed, the clusters are don't have the same structure as the ring.

From review paper on DBSCAN:

  1. The model introduced by DBSCAN uses a simple minimum density level estimation, based on a threshold for the number of neighbors, minPts, within the radius ε (with an arbitrary distance measure).

  2. Objects with more than minPts neighbors within this radius (including the query point) are considered to be a core point. The intuition of DBSCAN is to find those areas, which satisfy this minimum density, and which are separated by areas of lower density.

  3. All neighbors within the ε radius of a core point are considered to be part of the same cluster as the core point (called direct density reachable). If any of these neighbors is again a core point, their neighborhoods are transitively included (density reachable).

So with a lower eps, it has no problem finding the inner ring. If you want to capture the outer ring, it's a matter of finding the distance that allows the core points of in the outer to be connected:

import seaborn as sns
fig, axs = plt.subplots(2, 3,figsize = (12,6))
axs = axs.reshape(-1)
EPS = [0.08,0.1,0.12,0.14,0.16,0.18]
for i in range(len(EPS)):
    db = DBSCAN(eps=EPS[i],min_samples=5).fit(X)
    labels = db.labels_
    sns.scatterplot(x=X[:,0],y=X[:,1],ax=axs[i],hue=pd.Categorical(labels))
    axs[i].set_title("eps = "+str(EPS[i]))
    axs[i].legend(loc='upper right', ncol=3,prop={'size': 6})

enter image description here

So we can see that as we increase eps, the members of the outer rings are connected. In this example, most likely something like 0.14 would work (you have some unassigned).

$\endgroup$
4
  • $\begingroup$ So for the estimation of ε to work. The data must spherical so that NN can estimate an approximate optimal ε? Are there ways to estimate the ε of non-spherical data? @StupidWolf $\endgroup$
    – digdigdoot
    Commented May 15, 2020 at 9:54
  • $\begingroup$ It's no necessary spherical, but how close are the data points to one another in k dimensional space.. The plt.plot(distances) plot is a good point to start, you can where you start getting some separation. I started from 0.08 because thats where the elbow is $\endgroup$
    – StupidWolf
    Commented May 15, 2020 at 10:17
  • $\begingroup$ For most clustering, there's no definite way to say this is the threshold to use.. it's part of exploratory data analysis, where you cluster and see whether it makes sense for your purpose $\endgroup$
    – StupidWolf
    Commented May 15, 2020 at 10:19
  • $\begingroup$ I see. Thank you for your time. @StupidWolf $\endgroup$
    – digdigdoot
    Commented May 15, 2020 at 10:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.