03.3 Otros métodos de agrupación - Clustering Jerárquico y Densidad

import matplotlib.pyplot as plt 
%matplotlib inline
import pandas as pd
import numpy as np
from skimage import io
from IPython import display
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
output_notebook()
Loading BokehJS ...

En este cuaderno vamos a necesitar dibujar un dendograma

Se realiza, por ejemplo, con la siguiete función a medida:

import numpy as np

from matplotlib import pyplot as plt
from scipy.cluster.hierarchy import dendrogram
from sklearn.datasets import load_iris
from sklearn.cluster import AgglomerativeClustering


def plot_dendrogram(model, **kwargs):
    # Create linkage matrix and then plot the dendrogram

    # create the counts of samples under each node
    counts = np.zeros(model.children_.shape[0])
    n_samples = len(model.labels_)
    for i, merge in enumerate(model.children_):
        current_count = 0
        for child_idx in merge:
            if child_idx < n_samples:
                current_count += 1  # leaf node
            else:
                current_count += counts[child_idx - n_samples]
        counts[i] = current_count

    linkage_matrix = np.column_stack([model.children_, model.distances_,
                                      counts]).astype(float)
    # Plot the corresponding dendrogram
    dendrogram(linkage_matrix, **kwargs)

Descripción general del método de agrupación jerárquica

  • A diferencia de K-means, son métodos deterministas.

  • Parten de una matriz \(D = (d_{ij})\) de distancias o \(S = (s_{ij})\) de similitudes, matrices \(N \times N\) entre los \(N\) elementos del conjunto de instancias.

  • Si todas las variables son continuas, la distancia más utilizada es la euclídea (con variables estandarizadas univariantemente en caso de ser conveniente).

  • Si hay variables continuas y categóricas no suele ser aceptable trabajar con distancias y se usan similitudes.

El algoritmo tiene los siguientes pasos:

  • Comenzamos con N grupos (uno para cada instancia) y la matriz D o S.

  • Buscamos los elementos más próximos (mínimo valor \(d_{ij}\) o \(s_{ij}\)) y los unimos en un grupo.

  • Recalculamos la matriz de distancias (o similitudes) definiendo una distancia o similitud entre este nuevo grupo y el resto.

  • Repetimos los dos pasos anteriores hasta que todas las observaciones estén unidas en un solo grupo

La forma de calcular la distancia entre grupos de puntos puede hacerse de diferentes formas como:

  • Por encadenamiento simple tomando como distancia de 2 grupos la de los 2 puntos más próximos. Tiende a encadenar individuos sueltos y no grupos.

  • Por encadenamiento completo tomando como distancia de 2 grupos la de los 2 puntos más alejados. Es menos sensible a atípicos.

  • Método de Ward que maximiza la homogeneidad intra-grupo. Como medida de homogeneidad se usa la suma de cuadrados de los errores. Tiende a formar grupos esféricos aunque los grupos no lo sean.

Una forma de visualizar la estructura del grupo gerárquico es con el dendograma.

_images/clusterJerarq.png

Ejemplo simple de una agrupación jerárquica

Un ejemplo simple del proceso de agrupación jerárquica donde se utiliza la distancia euclídea para calcular la matriz de distancias se muestra a continuación.

En cada paso se forma un nuevo grupo que implica el recálculo de las coordenadas de su centroide (en verde).

_images/Proceso_Jerarquico.png

Dendograma con el resultado del ejemplo

Se muestran las posibles soluciones en función del nivel de agrupación

_images/Dendograma.png

Caso de uso de la similitud

En el caso de usar una similitud el algoritmo sería igual pero sustituyendo la matriz de distancia por una de similitud. La diagonal principal serían unos y el cálculo de las similitudes entre dos elementos sería como se indica en el ejemplo que aparece a continuación.

Ejemplo sencillo sobre un conjunto de puntos generados aleatoriamente

from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=150, n_features=2, centers=3, cluster_std=0.5, shuffle=True, random_state=0)
### Se dibujan los grupos 
p = figure(width=500, plot_height=300)
p.circle(X[:,0], X[:,1], size=4, color="navy", alpha=0.5, legend_label="Puntos")
##p.line(X[:,0], lr.predict(Xd), line_color="green", line_width=3, line_alpha=0.6, legend_label="Cuadratica")
p.legend.location='top_left'
p.legend.click_policy="hide"
show(p)

Se resuelve un cluster aglomerativo con scikit-learn que es AgglomerativeClustering, cuyos parámetros más importantes son:

  • n_clusters (int o None, default=2). El número de clústeres a buscar. Debe ser None si distance_threshold no es None.

  • affinity (str o función llamable, default=’euclidean’). Métrica utilizada. Puede ser “euclidian”, “l1”, “l2”, “manhattan”, “cosine” o “precomputed”. Si la linkage es “ward”, solo se acepta “euclidean”. Si es “precomputed”, se necesita una matriz de distancia (en lugar de una matriz de similitud) como entrada para el método de ajuste.

  • compute_full_tree (‘auto’ or bool, default=’auto’). Para la ejecución hasta construir n_clusters.

  • linkage (‘ward’, ‘complete’, ‘average’, ‘single’, default=’ward’).

  • distance_threshold (float, default=None), es el umbral de distancia de vinculación por encima del cual los clústeres no se fusionarán. Si no es None, n_clusters debe ser None y compute_full_tree debe ser True.

  • compute_distances (bool, default=False). Calcula las distancias entre los clústeres incluso si no se utiliza la distancia umbral (distance_threshold). Sirve para hacer la visualización de dendrogramas, pero introduce una sobrecarga computacional y de memoria. (Nuevo en versión 0.24)

Según los valores de linkage:

  • ‘ward’ minimiza la varianza de los grupos que se fusionan.

  • ‘average’ usa el promedio de las distancias de cada observación de los dos conjuntos.

  • ‘complete’ utiliza las distancias máximas entre todas las observaciones de los dos conjuntos.

  • ‘single’ utiliza el mínimo de las distancias entre todas las observaciones de los dos conjuntos.

from sklearn.cluster import AgglomerativeClustering
ac = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='complete')
y_ag = ac.fit_predict(X)
etiquetas=np.unique(y_ag) 
import matplotlib.pyplot as plt
names = ['grupo 0', 'grupo 1', 'grupo 2']
marcas = ['*', 'o', 's']
color = ['red', 'green', 'blue']
plt.figure(figsize=(7, 5), dpi=80)
for i in range(len(names)):
    cl = i
    plt.scatter(X[y_ag==cl,0], X[y_ag==cl,1], c=color[i], alpha=0.5, marker='o', label=names[i])
plt.xlabel("Área")
plt.ylabel("Perímetro")
plt.legend(loc='upper left')
plt.show()
_images/03.3_Clustering-Jerarquicosydensidad_12_0.png

Es posible visualizar el dendograma con la siguiente ejecución

cluster_dist = AgglomerativeClustering(distance_threshold=0, n_clusters=None)
cluster_dist.fit(X)
AgglomerativeClustering(distance_threshold=0, n_clusters=None)
plt.title('Dendograma de Clustering Jerárquico')
# plot the top three levels of the dendrogram
plot_dendrogram(cluster_dist, truncate_mode='level', p=3)
plt.xlabel("Número de puntos en el nodo (o índice del punto si no hay paréntesis).")
plt.show()
_images/03.3_Clustering-Jerarquicosydensidad_15_0.png

El encadenamiento simple: tiende a encadenar individuos sueltos y no grupos.

_images/jerarquico_single.png

El encadenamiento completo: es menos sensible a atípicos.

_images/jerarquico_complete.png

Ward tiende a formar grupos esféricos aunque los grupos no lo sean

_images/jerarquico_ward.png

Clustering por densidad. DBSCAN

Un tercer modo de clustering es el Density-Based Spatial Clustering of Applications with Noise (DBSCAN). Agrupa áreas de alta densidad quitando los puntos atípicos o outliers. Cada grupo se forma por la unión de puntos centrales R-vecinos junto con sus puntos frontera. Los puntos outliers se descartan. La densidad se basa en R, el radio de vecindad y M, mínimo número de vecinos para definir un cluster. Un punto se denomina central si dentro de su R-vecindad hay al menos M puntos. Un punto se denomina frontera si está en la R-vecindad de un central, pero no tiene M R-vecinos. Un punto atípico o outlier es aquel que ni es central, ni frontera.

_images/dbscan.png
from sklearn.cluster import DBSCAN 
db = DBSCAN(eps=1, min_samples=2, metric='euclidean')
y_db = db.fit_predict(X)
np.unique(y_db)
array([0, 1, 2])
from bokeh.palettes import viridis
colors = viridis(len(np.unique(y_db)))

plt.figure(figsize=(7, 5), dpi=80)
for i in range(len(np.unique(y_db))):
    cl = i
    grupo = "grupo " + str(i)
    plt.scatter(X[y_db==cl,0], X[y_db==cl,1], c=colors[i], alpha=0.5, marker='o', label=grupo)
cl=-1
plt.scatter(X[y_db==cl,0], X[y_db==cl,1], c="black", alpha=0.9, marker='*', label="outliers")

plt.legend(loc='lower left')
plt.show()
_images/03.3_Clustering-Jerarquicosydensidad_22_0.png