web-dev-qa-db-fra.com

comment tracer et annoter des dendrogrammes de clustering hiérarchiques dans scipy / matplotlib

J'utilise dendrogram de scipy pour tracer le clustering hiérarchique en utilisant matplotlib comme suit:

mat = array([[1, 0.5, 0.9],
             [0.5, 1, -0.5],
             [0.9, -0.5, 1]])
plt.subplot(1,2,1)
plt.title("mat")
dist_mat = mat
linkage_matrix = linkage(dist_mat,
                         "single")
print "linkage2:"
print linkage(1-dist_mat, "single")
dendrogram(linkage_matrix,
           color_threshold=1,
           labels=["a", "b", "c"],
           show_leaf_counts=True)
plt.subplot(1,2,2)
plt.title("1 - mat")
dist_mat = 1 - mat
linkage_matrix = linkage(dist_mat,
                         "single")
dendrogram(linkage_matrix,
           color_threshold=1,
           labels=["a", "b", "c"],
           show_leaf_counts=True)

Mes questions sont: d'abord, pourquoi mat et 1-mat donnez des clusters identiques ici? et deuxièmement, comment puis-je annoter la distance le long de chaque branche de l'arbre en utilisant dendrogram afin de pouvoir comparer les distances entre paires de nœuds?

enfin il semble que show_leaf_counts L'indicateur est ignoré, existe-t-il un moyen de l'activer pour que le nombre d'objets dans chaque classe soit affiché? Merci.enter image description here

41
user248237

L'entrée de linkage() est soit un tableau nxm, représentant n points dans l'espace m-dimensionnel, soit un tableau unidimensionnel contenant le condensé matrice de distance . Dans votre exemple, mat est 3 x 3, vous regroupez donc trois points 3D. Le regroupement est basé sur la distance entre ces points.

Pourquoi mat et 1-mat donnent-ils ici des regroupements identiques?

Les tableaux mat et 1-mat produit le même regroupement car le regroupement est basé sur les distances entre les points, et ni sur une réflexion (-mat) ni une traduction (mat + offset) de l'ensemble de données modifie les distances relatives entre les points.

Comment puis-je annoter la distance le long de chaque branche de l'arbre en utilisant un dendrogramme afin de pouvoir comparer les distances entre paires de nœuds?

Dans le code ci-dessous, je montre comment vous pouvez utiliser les données renvoyées par dendrogramme pour étiqueter les segments horizontaux du diagramme avec la distance correspondante. Les valeurs associées aux touches icoord et dcoord donnent les coordonnées x et y de chaque U à trois segments inversé de la figure. Dans augmented_dendrogram ces données sont utilisées pour ajouter une étiquette de la distance (c'est-à-dire la valeur y) de chaque segment de ligne horizontale dans le dendrogramme.

from scipy.cluster.hierarchy import dendrogram
import matplotlib.pyplot as plt


def augmented_dendrogram(*args, **kwargs):

    ddata = dendrogram(*args, **kwargs)

    if not kwargs.get('no_plot', False):
        for i, d in Zip(ddata['icoord'], ddata['dcoord']):
            x = 0.5 * sum(i[1:3])
            y = d[1]
            plt.plot(x, y, 'ro')
            plt.annotate("%.3g" % y, (x, y), xytext=(0, -8),
                         textcoords='offset points',
                         va='top', ha='center')

    return ddata

Pour votre tableau mat, le dendrogramme augmenté est

dendrogram for three points

Ainsi, les points "a" et "c" sont séparés de 1,01 unité, et le point "b" est à 1,57 unité de la grappe ["a", "c"].

Il semble que show_leaf_counts L'indicateur est ignoré, existe-t-il un moyen de l'activer pour que le nombre d'objets dans chaque classe soit affiché?

Le drapeau show_leaf_counts s'applique uniquement lorsque tous les points de données d'origine ne sont pas représentés sous forme de feuilles. Par exemple, lorsque trunc_mode = "lastp", seuls les derniers noeuds p sont affichés.

Voici un exemple avec 100 points:

import numpy as np
from scipy.cluster.hierarchy import linkage
import matplotlib.pyplot as plt
from augmented_dendrogram import augmented_dendrogram


# Generate a random sample of `n` points in 2-d.
np.random.seed(12312)
n = 100
x = np.random.multivariate_normal([0, 0], np.array([[4.0, 2.5], [2.5, 1.4]]),
                                  size=(n,))

plt.figure(1, figsize=(6, 5))
plt.clf()
plt.scatter(x[:, 0], x[:, 1])
plt.axis('equal')
plt.grid(True)

linkage_matrix = linkage(x, "single")

plt.figure(2, figsize=(10, 4))
plt.clf()

plt.subplot(1, 2, 1)
show_leaf_counts = False
ddata = augmented_dendrogram(linkage_matrix,
               color_threshold=1,
               p=6,
               truncate_mode='lastp',
               show_leaf_counts=show_leaf_counts,
               )
plt.title("show_leaf_counts = %s" % show_leaf_counts)

plt.subplot(1, 2, 2)
show_leaf_counts = True
ddata = augmented_dendrogram(linkage_matrix,
               color_threshold=1,
               p=6,
               truncate_mode='lastp',
               show_leaf_counts=show_leaf_counts,
               )
plt.title("show_leaf_counts = %s" % show_leaf_counts)

plt.show()

Ce sont les points de l'ensemble de données:

scatter plot of 100 points

Avec p=6 et trunc_mode="lastp", dendrogram n'affiche que le "haut" du dendrogramme. Ce qui suit montre l'effet de show_leaf_counts.

Show effect of show_leaf_counts

69
Warren Weckesser

Je pense qu'il y a quelques malentendus quant à l'utilisation des fonctions que vous essayez d'utiliser. Voici un extrait de code pleinement fonctionnel pour illustrer mes points:

import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import dendrogram, linkage
from numpy import array
import numpy as np


mat = array([184, 222, 177, 216, 231,
             45, 123, 128, 200,
             129, 121, 203,
             46, 83,
             83])

dist_mat = mat

linkage_matrix = linkage(dist_mat, 'single')
print linkage_matrix

plt.figure(101)
plt.subplot(1, 2, 1)
plt.title("ascending")
dendrogram(linkage_matrix,
           color_threshold=1,
           truncate_mode='lastp',
           labels=array(['a', 'b', 'c', 'd', 'e', 'f']),
           distance_sort='ascending')

plt.subplot(1, 2, 2)
plt.title("descending")
dendrogram(linkage_matrix,
           color_threshold=1,
           truncate_mode='lastp',
           labels=array(['a', 'b', 'c', 'd', 'e', 'f']),
           distance_sort='descending')


def make_fake_data():
    amp = 1000.
    x = []
    y = []
    for i in range(0, 10):
        s = 20
        x.append(np.random.normal(30, s))
        y.append(np.random.normal(30, s))
    for i in range(0, 20):
        s = 2
        x.append(np.random.normal(150, s))
        y.append(np.random.normal(150, s))
    for i in range(0, 10):
        s = 5
        x.append(np.random.normal(-20, s))
        y.append(np.random.normal(50, s))

    plt.figure(1)
    plt.title('fake data')
    plt.scatter(x, y)

    d = []
    for i in range(len(x) - 1):
        for j in range(i+1, len(x) - 1):
            d.append(np.sqrt(((x[i]-x[j])**2 + (y[i]-y[j])**2)))
    return d

mat = make_fake_data()


plt.figure(102)
plt.title("Three Clusters")

linkage_matrix = linkage(mat, 'single')
print "three clusters"
print linkage_matrix

dendrogram(linkage_matrix,
           truncate_mode='lastp',
           color_threshold=1,
           show_leaf_counts=True)

plt.show()

Tout d'abord, le calcul m -> m - 1 n'a pas vraiment changé votre résultat puisque la matrice de distance, qui décrit essentiellement les distances relatives entre toutes les paires uniques, n'a pas changé dans votre cas spécifique. (Dans mon exemple de code ci-dessus, toutes les distances sont euclidiennes donc toutes sont positives et cohérentes à partir de points sur un plan 2D.)

Pour votre deuxième question, vous devrez probablement déployer votre propre routine d'annotation pour faire ce que vous voulez, car je ne pense pas que le dendromgram le supporte nativement ...

Pour la dernière question, show_leaf_counts semble fonctionner uniquement lorsque vous essayez d'afficher des nœuds terminaux non singleton avec l'option truncate_mode = 'lastp'. Fondamentalement, les feuilles sont si rapprochées qu'elles ne sont pas faciles à voir. Vous avez donc la possibilité d'afficher simplement une feuille mais vous avez la possibilité d'afficher (entre parenthèses) combien sont regroupés dans cette feuille.

J'espère que cela t'aides.

14
Taro Sato