Logotipo de Zephyrnet

DBSCAN con Scikit-Learn en Python

Fecha:

Introducción

Estás trabajando en una empresa de consultoría como científico de datos. El proyecto al que fuiste asignado actualmente tiene datos de estudiantes que recientemente terminaron cursos sobre finanzas. La empresa financiera que imparte los cursos desea comprender si existen factores comunes que influyen en los estudiantes para que compren los mismos cursos o diferentes cursos. Al comprender esos factores, la empresa puede crear un perfil de estudiante, clasificar a cada estudiante por perfil y recomendar una lista de cursos.

Al inspeccionar datos de diferentes grupos de estudiantes, se encontró con tres disposiciones de puntos, como en 1, 2 y 3 a continuación:

Observe que en la gráfica 1, hay puntos morados organizados en un semicírculo, con una masa de puntos rosas dentro de ese círculo, una pequeña concentración de puntos naranjas fuera de ese semicírculo y cinco puntos grises que están lejos de todos los demás.

En la parcela 2 hay una masa redonda de puntos morados, otra de puntos naranjas y también cuatro puntos grises que están lejos de todos los demás.

Y en la gráfica 3, podemos ver cuatro concentraciones de puntos, púrpura, azul, naranja, rosa y tres puntos grises más distantes.

Ahora, si tuviera que elegir un modelo que pudiera comprender nuevos datos de estudiantes y determinar grupos similares, ¿existe un algoritmo de agrupación que podría dar resultados interesantes para ese tipo de datos?

Al describir las tramas, mencionamos términos como masa de puntos y concentración de puntos, indicando que hay áreas en todos los gráficos con mayor densidad. También nos referimos a circular y semicircular formas, que son difíciles de identificar dibujando una línea recta o simplemente examinando los puntos más cercanos. Además, hay algunos puntos distantes que probablemente se desvíen de la distribución de datos principal, presentando más desafíos o ruido al determinar los grupos.

Un algoritmo basado en la densidad que puede filtrar el ruido, como DBSCAN (Densidad-Based Sespacial Cbrillo de Aaplicaciones con Noise), es una buena opción para situaciones con áreas más densas, formas redondeadas y ruido.

Acerca de DBSCAN

DBSCAN es uno de los algoritmos más citados en investigación, su primera publicación aparece en 1996, este es el papel DBSCAN original. En el documento, los investigadores demuestran cómo el algoritmo puede identificar grupos espaciales no lineales y manejar datos con dimensiones más altas de manera eficiente.

La idea principal detrás de DBSCAN es que hay un número mínimo de puntos que estarán dentro de una determinada distancia o radius desde el punto de agrupación más "central", llamado punto central. Los puntos dentro de ese radio son los puntos de vecindad, y los puntos en el borde de esa vecindad son los puntos fronterizos or puntos limítrofes. El radio o distancia vecinal se llama barrio épsilon, ε-barrio o solo ε (el símbolo de la letra griega épsilon).

Adicionalmente, cuando existen puntos que no son núcleo o borde porque superan el radio por pertenecer a un determinado clúster y además no cuentan con el número mínimo de puntos para ser núcleo, se consideran puntos de ruido.

Esto significa que tenemos tres tipos diferentes de puntos, a saber, core, frontera y ruido. Además, es importante tener en cuenta que la idea principal se basa fundamentalmente en un radio o distancia, lo que hace que DBSCAN, como la mayoría de los modelos de agrupamiento, dependa de esa métrica de distancia. Esta métrica podría ser euclidiana, Manhattan, Mahalanobis y muchas más. Por lo tanto, es fundamental elegir una métrica de distancia adecuada que tenga en cuenta el contexto de los datos. Por ejemplo, si está utilizando datos de distancia de conducción de un GPS, podría ser interesante utilizar una métrica que tenga en cuenta los diseños de las calles, como la distancia de Manhattan.

Nota: Dado que DBSCAN mapea los puntos que constituyen el ruido, también se puede utilizar como un algoritmo de detección de valores atípicos. Por ejemplo, si está tratando de determinar qué transacciones bancarias pueden ser fraudulentas y la tasa de transacciones fraudulentas es pequeña, DBSCAN podría ser una solución para identificar esos puntos.

Para encontrar el punto central, DBSCAN primero seleccionará un punto al azar, mapeará todos los puntos dentro de su vecindario ε y comparará la cantidad de vecinos del punto seleccionado con la cantidad mínima de puntos. Si el punto seleccionado tiene un número igual o más de vecinos que el número mínimo de puntos, se marcará como un punto central. Este punto central y sus puntos de vecindad constituirán el primer grupo.

Luego, el algoritmo examinará cada punto del primer grupo y verá si tiene un número igual o más de puntos vecinos que el número mínimo de puntos dentro de ε. Si lo hace, esos puntos vecinos también se agregarán al primer grupo. Este proceso continuará hasta que los puntos del primer grupo tengan menos vecinos que el número mínimo de puntos dentro de ε. Cuando eso sucede, el algoritmo deja de agregar puntos a ese grupo, identifica otro punto central fuera de ese grupo y crea un nuevo grupo para ese nuevo punto central.

DBSCAN luego repetirá el proceso del primer grupo de encontrar todos los puntos conectados a un nuevo punto central del segundo grupo hasta que no haya más puntos para agregar a ese grupo. Luego encontrará otro punto central y creará un tercer grupo, o iterará a través de todos los puntos que no ha mirado previamente. Si estos puntos están a una distancia ε de un grupo, se agregan a ese grupo y se convierten en puntos fronterizos. Si no lo son, se consideran puntos de ruido.

Consejo: Hay muchas reglas y demostraciones matemáticas involucradas en la idea detrás de DBSCAN, si desea profundizar más, puede echar un vistazo al documento original, que está vinculado arriba.

Es interesante saber cómo funciona el algoritmo DBSCAN, aunque, afortunadamente, no es necesario codificar el algoritmo, una vez que la biblioteca Scikit-Learn de Python ya tiene una implementación.

¡Veamos cómo funciona en la práctica!

Importación de datos para agrupamiento

Para ver cómo funciona DBSCAN en la práctica, cambiaremos un poco los proyectos y utilizaremos un pequeño conjunto de datos de clientes que tenga el género, la edad, los ingresos anuales y la puntuación de gastos de 200 clientes.

El puntaje de gasto varía de 0 a 100 y representa con qué frecuencia una persona gasta dinero en un centro comercial en una escala de 1 a 100. En otras palabras, si un cliente tiene un puntaje de 0, nunca gasta dinero, y si el puntaje es 100, son los que más gastan.

Nota: Puede descargar el conjunto de datos esta página.

Después de descargar el conjunto de datos, verá que es un archivo CSV (valores separados por comas) llamado compras-datos.csv, lo cargaremos en un DataFrame usando Pandas y lo almacenaremos en el customer_data variable:

import pandas as pd path_to_file = '../../datasets/dbscan/dbscan-with-python-and-scikit-learn-shopping-data.csv'
customer_data = pd.read_csv(path_to_file)

Para echar un vistazo a las primeras cinco filas de nuestros datos, puede ejecutar customer_data.head():

Esto resulta en:

 CustomerID Genre Age Annual Income (k$) Spending Score (1-100)
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40

Al examinar los datos, podemos ver los números de identificación de los clientes, el género, la edad, los ingresos en miles de dólares y los puntajes de gasto. Tenga en cuenta que algunas o todas estas variables se utilizarán en el modelo. Por ejemplo, si tuviéramos que usar Age y Spending Score (1-100) como variables para DBSCAN, que utiliza una métrica de distancia, es importante llevarlas a una escala común para evitar introducir distorsiones ya que Age se mide en años y Spending Score (1-100) tiene un rango limitado de 0 a 100. Esto significa que realizaremos algún tipo de escalado de datos.

También podemos verificar si los datos necesitan más preprocesamiento además de escalar al ver si el tipo de datos es consistente y verificar si faltan valores que deban tratarse mediante la ejecución de Panda. info() método:

customer_data.info()

Esto muestra:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 CustomerID 200 non-null int64 1 Genre 200 non-null object 2 Age 200 non-null int64 3 Annual Income (k$) 200 non-null int64 4 Spending Score (1-100) 200 non-null int64 dtypes: int64(4), object(1)
memory usage: 7.9+ KB

Podemos observar que no faltan valores porque hay 200 entradas no nulas para cada característica del cliente. También podemos ver que solo la columna de género tiene contenido de texto, ya que es una variable categórica, que se muestra como object, y todas las demás características son numéricas, del tipo int64. Por lo tanto, en términos de consistencia del tipo de datos y ausencia de valores nulos, nuestros datos están listos para un análisis posterior.

Podemos proceder a visualizar los datos y determinar qué características serían interesantes de usar en DBSCAN. Después de seleccionar esas características, podemos escalarlas.

Este conjunto de datos de clientes es el mismo que se usa en nuestra guía definitiva para la agrupación en clústeres jerárquicos. Para obtener más información sobre estos datos, cómo explorarlos y sobre las métricas de distancia, puede consultar Guía definitiva de agrupamiento jerárquico con Python y Scikit-Learn!

Visualizando datos

Mediante el uso de Seaborn pairplot(), podemos trazar un gráfico de dispersión para cada combinación de características. Desde CustomerID es solo una identificación y no una característica, la eliminaremos con drop() antes de trazar:

import seaborn as sns customer_data = customer_data.drop('CustomerID', axis=1) sns.pairplot(customer_data);

Esto produce:

Al observar la combinación de características producidas por pairplot, la gráfica de Annual Income (k$) Spending Score (1-100) parece mostrar alrededor de 5 grupos de puntos. Esta parece ser la combinación de características más prometedora. Podemos crear una lista con sus nombres, seleccionarlos de la customer_data DataFrame y almacene la selección en el customer_data variable de nuevo para su uso en nuestro modelo futuro.

selected_cols = ['Annual Income (k$)', 'Spending Score (1-100)']
customer_data = customer_data[selected_cols]

Después de seleccionar las columnas, podemos realizar el escalado discutido en la sección anterior. Para llevar las características a la misma escala o estandarizar ellos, podemos importar Scikit-Learn's StandardScaler, créelo, ajuste nuestros datos para calcular su media y desviación estándar, y transforme los datos restando su media y dividiéndolos por la desviación estándar. Esto se puede hacer en un solo paso con el fit_transform() método:

from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_data = ss.fit_transform(customer_data)

Las variables ahora están escaladas, y podemos examinarlas simplemente imprimiendo el contenido de la scaled_data variable. Alternativamente, también podemos agregarlos a un nuevo scaled_customer_data DataFrame junto con los nombres de las columnas y use el head() método de nuevo:

scaled_customer_data = pd.DataFrame(columns=selected_cols, data=scaled_data)
scaled_customer_data.head()

Esto produce:

 Annual Income (k$) Spending Score (1-100)
0 -1.738999 -0.434801
1 -1.738999 1.195704
2 -1.700830 -1.715913
3 -1.700830 1.040418
4 -1.662660 -0.395980 

¡Estos datos están listos para agruparse! Al presentar DBSCAN, mencionamos el número mínimo de puntos y el épsilon. Estos dos valores deben seleccionarse antes de crear el modelo. Veamos cómo se hace.

Elegir mín. Muestras y Epsilon

Para elegir el número mínimo de puntos para el agrupamiento de DBSCAN, existe una regla general que establece que debe ser igual o mayor que el número de dimensiones en los datos más uno, como en:

$$
texto{mín. puntos} >= texto{dimensiones de datos} + 1
$$

Las dimensiones son la cantidad de columnas en el marco de datos, estamos usando 2 columnas, por lo que el min. los puntos deben ser 2+1, que es 3, o más. Para este ejemplo, usemos 5 minutos. puntos.

$$
texto{5 (puntos mín.)} >= texto{2 (dimensiones de datos)} + 1
$$

Consulte nuestra guía práctica y práctica para aprender Git, con las mejores prácticas, los estándares aceptados por la industria y la hoja de trucos incluida. Deja de buscar en Google los comandos de Git y, de hecho, aprenden ella!

Ahora bien, para elegir el valor de ε existe un método en el que Vecinos mas cercanos algoritmo se emplea para encontrar las distancias de un número predefinido de puntos más cercanos para cada punto. Este número predefinido de vecinos es el min. puntos que acabamos de elegir menos 1. Entonces, en nuestro caso, el algoritmo encontrará los 5-1, o 4 puntos más cercanos para cada punto de nuestros datos. esos son los k-vecinos y nuestro k es igual a 4.

$$
texto{k-vecinos} = texto{min. puntos} – 1
$$

Después de encontrar los vecinos, ordenaremos sus distancias de mayor a menor y trazaremos las distancias del eje y y los puntos en el eje x. Mirando la gráfica, encontraremos dónde se asemeja a la flexión de un codo y el punto del eje y que describe esa flexión del codo es el valor sugerido de ε.

Note: es posible que la gráfica para encontrar el valor de ε tenga uno o más “codos”, ya sea grandes o pequeños, cuando eso suceda, puede encontrar los valores, probarlos y elegir aquellos con resultados que mejor describan los conglomerados, ya sea mirando las métricas de las parcelas.

Para realizar estos pasos, podemos importar el algoritmo, ajustarlo a los datos y luego podemos extraer las distancias y los índices de cada punto con kneighbors() método:

from sklearn.neighbors import NearestNeighbors
import numpy as np nn = NearestNeighbors(n_neighbors=4) nbrs = nn.fit(scaled_customer_data)
distances, indices = nbrs.kneighbors(scaled_customer_data)

Después de encontrar las distancias, podemos ordenarlas de mayor a menor. Dado que la primera columna de la matriz de distancias es del punto a sí mismo (lo que significa que todos son 0), y la segunda columna contiene las distancias más pequeñas, seguida por la tercera columna que tiene distancias más grandes que la segunda, y así sucesivamente, podemos elegir solo el valores de la segunda columna y almacenarlos en el distances variable:

distances = np.sort(distances, axis=0)
distances = distances[:,1] 

Ahora que tenemos nuestras distancias más pequeñas ordenadas, podemos importar matplotlib, marque las distancias y dibuje una línea roja donde está la "flexión del codo":

import matplotlib.pyplot as plt plt.figure(figsize=(6,3))
plt.plot(distances)
plt.axhline(y=0.24, color='r', linestyle='--', alpha=0.4) plt.title('Kneighbors distance graph')
plt.xlabel('Data points')
plt.ylabel('Epsilon value')
plt.show();

Este es el resultado:

Note que al dibujar la línea, encontraremos el valor de ε, en este caso, es 0.24.

Finalmente tenemos nuestros puntos mínimos y ε. Con ambas variables, podemos crear y ejecutar el modelo DBSCAN.

Creación de un modelo DBSCAN

Para crear el modelo, podemos importarlo desde Scikit-Learn, crearlo con ε que es lo mismo que el eps argumento, y los puntos mínimos a los que es el mean_samples argumento. Luego podemos almacenarlo en una variable, llamémoslo dbs y ajústelo a los datos escalados:

from sklearn.cluster import DBSCAN dbs = DBSCAN(eps=0.24, min_samples=5)
dbs.fit(scaled_customer_data)

¡Así de simple, nuestro modelo DBSCAN ha sido creado y entrenado en los datos! Para extraer los resultados, accedemos a la labels_ propiedad. También podemos crear un nuevo labels columna en el scaled_customer_data marco de datos y rellénelo con las etiquetas predichas:

labels = dbs.labels_ scaled_customer_data['labels'] = labels
scaled_customer_data.head()

Este es el resultado final:

 Annual Income (k$) Spending Score (1-100) labels
0 -1.738999 -0.434801 -1
1 -1.738999 1.195704 0
2 -1.700830 -1.715913 -1
3 -1.700830 1.040418 0
4 -1.662660 -0.395980 -1

Observe que tenemos etiquetas con -1 valores; estos son los puntos de ruido, las que no pertenecen a ningún clúster. Para saber cuántos puntos de ruido encontró el algoritmo, podemos contar cuántas veces aparece el valor -1 en nuestra lista de etiquetas:

labels_list = list(scaled_customer_data['labels'])
n_noise = labels_list.count(-1)
print("Number of noise points:", n_noise)

Esto produce:

Number of noise points: 62

Ya sabemos que 62 puntos de nuestros datos originales de 200 puntos se consideraban ruido. Esto es mucho ruido, lo que indica que tal vez el agrupamiento de DBSCAN no consideró muchos puntos como parte de un agrupamiento. Entenderemos lo que sucedió pronto, cuando registremos los datos.

Inicialmente, cuando observamos los datos, parecía tener 5 grupos de puntos. Para saber cuántos clústeres se han formado DBSCAN, podemos contar el número de etiquetas que no son -1. Hay muchas formas de escribir ese código; aquí, hemos escrito un bucle for, que también funcionará para datos en los que DBSCAN ha encontrado muchos grupos:

total_labels = np.unique(labels) n_labels = 0
for n in total_labels: if n != -1: n_labels += 1
print("Number of clusters:", n_labels)

Esto produce:

Number of clusters: 6

Podemos ver que el algoritmo predijo que los datos tendrían 6 grupos, con muchos puntos de ruido. Visualicemos eso al trazarlo con seaborn's scatterplot:

sns.scatterplot(data=scaled_customer_data, x='Annual Income (k$)', y='Spending Score (1-100)', hue='labels', palette='muted').set_title('DBSCAN found clusters');

Esto resulta en:

Mirando el gráfico, podemos ver que DBSCAN ha capturado los puntos que estaban más densamente conectados, y los puntos que podrían considerarse parte del mismo grupo eran ruido o se consideraba que formaban otro grupo más pequeño.

Si resaltamos los clústeres, observe cómo DBSCAN obtiene el clúster 1 por completo, que es el clúster con menos espacio entre puntos. Luego obtiene las partes de los clusters 0 y 3 donde los puntos están muy juntos, considerando como ruido los puntos más espaciados. También considera los puntos de la mitad inferior izquierda como ruido y divide los puntos de la parte inferior derecha en 3 grupos, capturando una vez más los grupos 4, 2 y 5 donde los puntos están más juntos.

Podemos comenzar a llegar a la conclusión de que DBSCAN fue excelente para capturar las áreas densas de los clústeres, pero no tanto para identificar el esquema más grande de los datos, las delimitaciones de los 5 clústeres. Sería interesante probar más algoritmos de agrupamiento con nuestros datos. A ver si alguna métrica corrobora esta hipótesis.

Evaluación del algoritmo

Para evaluar DBSCAN usaremos el puntaje de silueta el cual tomará en consideración la distancia entre puntos de un mismo cluster y las distancias entre clusters.

Nota: Actualmente, la mayoría de las métricas de agrupación en clúster no están realmente preparadas para evaluar DBSCAN porque no se basan en la densidad. Aquí, usamos la puntuación de la silueta porque ya está implementada en Scikit-learn y porque trata de ver la forma del clúster.

Para tener una evaluación más ajustada, puedes usarlo o combinarlo con el Validación de agrupamiento basado en densidad (DBCV), que se diseñó específicamente para la agrupación en clústeres basada en la densidad. Hay una implementación para DBCV disponible en este GitHub.

Primero, podemos importar silhouette_score de Scikit-Learn, luego, pásele nuestras columnas y etiquetas:

from sklearn.metrics import silhouette_score s_score = silhouette_score(scaled_customer_data, labels)
print(f"Silhouette coefficient: {s_score:.3f}")

Esto produce:

Silhouette coefficient: 0.506

Según este puntaje, parece que DBSCAN podría capturar aproximadamente el 50% de los datos.

Conclusión

Ventajas y desventajas de DBSCAN

DBSCAN es un algoritmo o modelo de agrupamiento muy singular.

Si nos fijamos en sus ventajas, es muy bueno para captar zonas densas en datos y puntos alejados unos de otros. Esto significa que los datos no tienen que tener una forma específica y pueden estar rodeados por otros puntos, siempre que también estén densamente conectados.

Requiere que especifiquemos puntos mínimos y ε, pero no es necesario especificar el número de grupos de antemano, como en K-Means, por ejemplo. También se puede usar con bases de datos muy grandes ya que fue diseñado para datos de alta dimensión.

En cuanto a sus desventajas, hemos visto que no podía capturar diferentes densidades en un mismo clúster, por lo que le cuesta mucho con grandes diferencias de densidades. También depende de la métrica de distancia y la escala de los puntos. Esto significa que si los datos no se comprenden bien, con diferencias de escala y con una métrica de distancia que no tiene sentido, es probable que no los comprenda.

Extensiones DBSCAN

Hay otros algoritmos, como DBSCAN jerárquico (HDBSCAN) y Puntos de pedido para identificar la estructura de agrupamiento (ÓPTICA), que se consideran extensiones de DBSCAN.

Tanto HDBSCAN como OPTICS generalmente pueden funcionar mejor cuando hay grupos de densidades variables en los datos y también son menos sensibles a la elección o al mínimo inicial. puntos y parámetros ε.

punto_img

Información más reciente

punto_img

Habla con nosotros!

¡Hola! ¿Le puedo ayudar en algo?