Ejemplo DBSCAN#

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import seaborn as sns

Generar datos en forma de espiral:#

def generate_spiral_data(n_points, noise=0.5):
    theta = np.sqrt(np.random.rand(n_points)) * 2 * np.pi  # Random theta
    r_a = 2 * theta + np.pi  # Radius for spiral A
    data_a = np.array([np.cos(theta) * r_a, np.sin(theta) * r_a]).T
    data_a += np.random.randn(n_points, 2) * noise

    r_b = -2 * theta - np.pi  # Radius for spiral B
    data_b = np.array([np.cos(theta) * r_b, np.sin(theta) * r_b]).T
    data_b += np.random.randn(n_points, 2) * noise

    res_a = np.append(data_a, np.zeros((n_points, 1)), axis=1)
    res_b = np.append(data_b, np.ones((n_points, 1)), axis=1)
    res = np.append(res_a, res_b, axis=0)
    np.random.shuffle(res)
    return res[:, :2], res[:, 2]

# Generar datos
X, y = generate_spiral_data(1000)

# Visualizar los datos y los clusters
plt.figure(figsize=(10, 7))
plt.scatter(X[:, 0], X[:, 1], cmap='rainbow', alpha=0.7, edgecolors='b')
plt.title("Espiral")
plt.xlabel("X1")
plt.ylabel("X2")
plt.show()
../../../_images/output_3_0.png

Aplicar DBSCAN#

# Estandarizar los datos
X = StandardScaler().fit_transform(X)

# Aplicar DBSCAN
db = DBSCAN(eps=0.3, min_samples=10).fit(X)
labels = db.labels_

Clusters:

set(labels)
{0, 1}

En caso de existir ruido, puntos de ruido, el cluster se denota -1

# Número de clusters en las etiquetas, ignorando el ruido si está presente.
n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0) # Tener en cuenta que el cluster -1 es el ruido.
n_noise_ = list(labels).count(-1)

print(f'Número de clusters: {n_clusters_}')
print(f'Número de puntos de ruido: {n_noise_}')
Número de clusters: 2
Número de puntos de ruido: 0
# Colores para los diferentes clusters, y el color negro para el ruido.
unique_labels = set(labels)
colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]

dbscan_labels = db.fit_predict(X)

# Calcular el índice de silueta
silhouette_scores = silhouette_score(X, dbscan_labels)
print(f'Puntuación de Silueta: {silhouette_scores:.4f}')

plt.figure(figsize=(10, 7))
for k, col in zip(unique_labels, colors):
    if k == -1:
        # Negro es usado para el ruido.
        col = [0, 0, 0, 1] # RGB y alfa

    class_member_mask = (labels == k)

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

plt.title('Clustering DBSCAN')
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
Puntuación de Silueta: 0.1849
../../../_images/output_10_12.png

Determinar los valores de eps y min_samples:#

# Definir los valores de eps y min_samples para evaluar
eps_values = np.arange(0.1, 0.6, 0.1)
min_samples_values = range(5, 16, 5)

# Almacenar las puntuaciones de silueta
results = []

for eps in eps_values:
    for min_samples in min_samples_values:
        db = DBSCAN(eps=eps, min_samples=min_samples)
        dbscan_labels = db.fit_predict(X)

        if len(set(dbscan_labels)) > 1:  # Asegurarse de que hay más de un cluster
            silhouette_avg = silhouette_score(X, dbscan_labels)
            results.append((eps, min_samples, silhouette_avg))
            print(f'eps: {eps}, min_samples: {min_samples}, Puntuación de Silueta: {silhouette_avg:.4f}')
        else:
            results.append((eps, min_samples, -1))
            print(f'eps: {eps}, min_samples: {min_samples}, Puntuación de Silueta: No aplicable')

# Convertir los resultados a un DataFrame
import pandas as pd
results_df = pd.DataFrame(results, columns=['eps', 'min_samples', 'silhouette_score'])

# Visualizar los resultados en un heatmap
pivot_table = results_df.pivot('eps', 'min_samples', 'silhouette_score')
plt.figure(figsize=(10, 7))
sns.heatmap(pivot_table, annot=True, fmt=".4f", cmap='viridis')
plt.title('Puntuación de Silueta para diferentes combinaciones de eps y min_samples')
plt.show()
eps: 0.1, min_samples: 5, Puntuación de Silueta: 0.0545
eps: 0.1, min_samples: 10, Puntuación de Silueta: -0.1781
eps: 0.1, min_samples: 15, Puntuación de Silueta: -0.2842
eps: 0.2, min_samples: 5, Puntuación de Silueta: 0.1849
eps: 0.2, min_samples: 10, Puntuación de Silueta: 0.1849
eps: 0.2, min_samples: 15, Puntuación de Silueta: -0.1053
eps: 0.30000000000000004, min_samples: 5, Puntuación de Silueta: 0.1849
eps: 0.30000000000000004, min_samples: 10, Puntuación de Silueta: 0.1849
eps: 0.30000000000000004, min_samples: 15, Puntuación de Silueta: 0.1849
eps: 0.4, min_samples: 5, Puntuación de Silueta: No aplicable
eps: 0.4, min_samples: 10, Puntuación de Silueta: No aplicable
eps: 0.4, min_samples: 15, Puntuación de Silueta: No aplicable
eps: 0.5, min_samples: 5, Puntuación de Silueta: No aplicable
eps: 0.5, min_samples: 10, Puntuación de Silueta: No aplicable
eps: 0.5, min_samples: 15, Puntuación de Silueta: No aplicable
../../../_images/output_12_12.png