#### Overview of this book

Unsupervised learning is a useful and practical solution in situations where labeled data is not available. Applied Unsupervised Learning with Python guides you in learning the best practices for using unsupervised learning techniques in tandem with Python libraries and extracting meaningful information from unstructured data. The book begins by explaining how basic clustering works to find similar data points in a set. Once you are well-versed with the k-means algorithm and how it operates, you’ll learn what dimensionality reduction is and where to apply it. As you progress, you’ll learn various neural network techniques and how they can improve your model. While studying the applications of unsupervised learning, you will also understand how to mine topics that are trending on Twitter and Facebook and build a news recommendation engine for users. Finally, you will be able to put your knowledge to work through interesting activities such as performing a Market Basket Analysis and identifying relationships between different products. By the end of this book, you will have the skills you need to confidently build your own models using Python.
Applied Unsupervised Learning with Python
Preface
Free Chapter
Introduction to Clustering
Hierarchical Clustering
Neighborhood Approaches and DBSCAN
Dimension Reduction and PCA
Autoencoders
t-Distributed Stochastic Neighbor Embedding (t-SNE)
Topic Modeling
Hotspot Analysis

## Chapter 3: Neighborhood Approaches and DBSCAN

### Activity 4: Implement DBSCAN from Scratch

Solution:

1. Generate a random cluster dataset as follows:

```from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

X_blob, y_blob = make_blobs(n_samples=500, centers=4, n_features=2, random_state=800)```
2. Visualize the generated data:

```plt.scatter(X_blob[:,0], X_blob[:,1])
plt.show()```

The output is as follows:

Figure 3.14: Plot of generated data

3. Create functions from scratch that allow you to call DBSCAN on a dataset:

```def scratch_DBSCAN(x, eps, min_pts):
"""
param x (list of vectors): your dataset to be clustered
param eps (float): neigborhood radius threshold
param min_pts (int): minimum number of points threshold for a nieghborhood to be a cluster
"""

# Build a label holder that is comprised of all 0s
labels = [0]* x.shape[0]

# Arbitrary starting "current cluster" ID
C = 0

# For each point p in x...
# ('p' is the index of the datapoint, rather than the datapoint itself.)
for p in range(0, x.shape[0]):

# Only unvisited points can be evaluated as neighborhood centers
if not (labels[p] == 0):
continue

# Find all of p's neighbors.
neighbors = neighborhood_search(x, p, eps)

# If there are not enough neighbor points, then it is classified as noise (-1).
# Otherwise we can use this point as a neighborhood cluster
if len(neighbors) < min_pts:
labels[p] = -1
else:
C += 1
neighbor_cluster(x, labels, p, neighbors, C, eps, min_pts)

return labels

def neighbor_cluster(x, labels, p, neighbors, C, eps, min_pts):

# Assign the cluster label to original point
labels[p] = C

# Look at each neighbor of p (by index, not the points themselves) and evaluate
i = 0
while i < len(neighbors):

# Get the next point from the queue.
potential_neighbor_ix = neighbors[i]

# If potential_neighbor_ix is noise from previous runs, we can assign it to current cluster
if labels[potential_neighbor_ix] == -1:
labels[potential_neighbor_ix] = C

# Otherwise, if potential_neighbor_ix is unvisited, we can add it to current cluster
elif labels[potential_neighbor_ix] == 0:
labels[potential_neighbor_ix] = C

# Further find neighbors of potential neighbor
potential_neighbors_cluster = neighborhood_search(x, potential_neighbor_ix, eps)

if len(potential_neighbors_cluster) >= min_pts:
neighbors = neighbors + potential_neighbors_cluster

# Evaluate next neighbor
i += 1

def neighborhood_search(x, p, eps):
neighbors = []

# For each point in the dataset...
for potential_neighbor in range(0, x.shape[0]):

# If a nearby point falls below the neighborhood radius threshold, add to neighbors list
if np.linalg.norm(x[p] - x[potential_neighbor]) < eps:
neighbors.append(potential_neighbor)

return neighbors```
4. Use your created DBSCAN implementation to find clusters in the generated dataset. Feel free to use hyperparameters as you see fit, tuning them based on their performance in step five:

`labels = scratch_DBSCAN(X_blob, 0.6, 5)`
5. Visualize the clustering performance of your DBSCAN implementation from scratch:

```plt.scatter(X_blob[:,0], X_blob[:,1], c=labels)
plt.title("DBSCAN from Scratch Performance")
plt.show()```

The output is as follows:

Figure 3.15: Plot of DBSCAN implementation

As you may have noticed, it takes quite some time for a custom implementation to run. This is because we explored the non-vectorized version of this algorithm for the sake of clarity. Moving forward, you should aim to use the DBSCAN implementation provided by scikit-learn, as it is highly optimized.

### Activity 5: Comparing DBSCAN with k-means and Hierarchical Clustering

Solution:

1. Import the necessary packages:

```from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN
from sklearn.metrics import silhouette_score
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline```
2. Load the wine dataset from Chapter 2, Hierarchical Clustering and familiarize yourself again with what the data looks like:

```# Load Wine data set

# Show sample of data set

The output is as follows:

Figure 3.16: First five rows of wine dataset

3. Visualize the data:

```plt.scatter(wine_df.values[:,0], wine_df.values[:,1])
plt.title("Wine Dataset")
plt.ylabel("Proline")
plt.show()```

The output is as follows:

Figure 3.17: Plot of the data

4. Generate clusters using k-means, agglomerative clustering, and DBSCAN:

```# Generate clusters from K-Means
km = KMeans(3)
km_clusters = km.fit_predict(wine_df)

# Generate clusters using Agglomerative Hierarchical Clustering
ac = AgglomerativeClustering(3, linkage='average')
ac_clusters = ac.fit_predict(wine_df)```
5. Evaluate a few different options for DSBSCAN hyperparameters and their effect on the silhouette score:

```db_param_options = [[20,5],[25,5],[30,5],[25,7],[35,7],[35,3]]

for ep,min_sample in db_param_options:
# Generate clusters using DBSCAN
db = DBSCAN(eps=ep, min_samples = min_sample)
db_clusters = db.fit_predict(wine_df)
print("Eps: ", ep, "Min Samples: ", min_sample)
print("DBSCAN Clustering: ", silhouette_score(wine_df, db_clusters))```

The output is as follows:

Figure 3.18: Printing the silhouette score for clusters

6. Generate the final clusters based on the highest silhouette score (eps: 35, min_samples: 3):

```# Generate clusters using DBSCAN
db = DBSCAN(eps=35, min_samples = 3)
db_clusters = db.fit_predict(wine_df)```
7. Visualize clusters generated using each of the three methods:

```plt.title("Wine Clusters from K-Means")
plt.scatter(wine_df['OD_read'], wine_df['Proline'], c=km_clusters,s=50, cmap='tab20b')
plt.show()

plt.title("Wine Clusters from Agglomerative Clustering")
plt.scatter(wine_df['OD_read'], wine_df['Proline'], c=ac_clusters,s=50, cmap='tab20b')
plt.show()

plt.title("Wine Clusters from DBSCAN")
plt.scatter(wine_df['OD_read'], wine_df['Proline'], c=db_clusters,s=50, cmap='tab20b')
plt.show()```

The output is as follows:

Figure 3.19: Plot of clusters using different algorithms

8. Evaluate the silhouette score of each approach:

```# Calculate Silhouette Scores
print("Silhouette Scores for Wine Dataset:\n")
print("K-Means Clustering: ", silhouette_score(wine_df, km_clusters))
print("Agg Clustering: ", silhouette_score(wine_df, ac_clusters))
print("DBSCAN Clustering: ", silhouette_score(wine_df, db_clusters))```

The output is as follows:

Figure 3.20: Silhouette score

As you can see, DBSCAN isn't automatically the best choice for your clustering needs. One key trait that makes it different form other algorithms is the use of noise as a potential clustering. In some cases, this is great, as it removes outliers, however, there may be situations where it is not tuned well enough and classifies too many points as noise. Can you improve the silhouette score by tuning the hyperparameters?