web-dev-qa-db-fra.com

format de liaison scipy

J'ai écrit ma propre routine de regroupement et j'aimerais produire un dendrogramme. Le moyen le plus simple de le faire serait d’utiliser la fonction dendrogramme de scipy. Toutefois, cela nécessite que l’entrée soit dans le même format que celui produit par la fonction de liaison Scipy. Je ne trouve pas d'exemple de la façon dont le résultat est formaté. Je me demandais si quelqu'un pouvait m'éclairer. 

31
geo_pythoncl

Cela provient de la documentation de scipy.cluster.hierarchy.linkage () function, je pense que c'est une description assez claire du format de sortie:

A ( n - 1) par 4 la matrice Z est renvoyée. À la i - ème itération, les grappes d’indices Z [i, 0] et Z [i, 1] sont combinées pour former la grappe n + i . Un groupe d'indices inférieur à n correspond à l'une des observations originales. La distance entre les groupes Z [i, 0] et Z [i, 1] est donnée par Z [i, 2]. La quatrième valeur Z [i, 3] représente le nombre d'observations originales dans le groupe nouvellement formé.

Avez-vous besoin de quelque chose de plus?

29
dkar

Je suis d’accord avec https://stackoverflow.com/users/1167475/mortonjt que la documentation n’explique pas complètement l’indexation des grappes intermédiaires, alors que je suis d’accord avec le https: // stackoverflow. com/users/1354844/dkar que le format est autrement expliqué avec précision.

Utilisation des exemples de données de cette question: Didacticiel pour scipy.cluster.hierarchy

A = np.array([[0.1,   2.5],
              [1.5,   .4 ],
              [0.3,   1  ],
              [1  ,   .8 ],
              [0.5,   0  ],
              [0  ,   0.5],
              [0.5,   0.5],
              [2.7,   2  ],
              [2.2,   3.1],
              [3  ,   2  ],
              [3.2,   1.3]])

Une matrice de liaison peut être construite en utilisant l'unique (c'est-à-dire les points correspondants les plus proches):

z = hac.linkage(a, method="single")

 array([[  7.        ,   9.        ,   0.3       ,   2.        ],
        [  4.        ,   6.        ,   0.5       ,   2.        ],
        [  5.        ,  12.        ,   0.5       ,   3.        ],
        [  2.        ,  13.        ,   0.53851648,   4.        ],
        [  3.        ,  14.        ,   0.58309519,   5.        ],
        [  1.        ,  15.        ,   0.64031242,   6.        ],
        [ 10.        ,  11.        ,   0.72801099,   3.        ],
        [  8.        ,  17.        ,   1.2083046 ,   4.        ],
        [  0.        ,  16.        ,   1.5132746 ,   7.        ],
        [ 18.        ,  19.        ,   1.92353841,  11.        ]])

Comme l'explique la documentation, les groupes ci-dessous n (ici: 11) sont simplement les points de données de la matrice A d'origine. Les groupes intermédiaires à venir sont indexés successivement.

Ainsi, les grappes 7 et 9 (la première fusion) sont fusionnées dans la grappe 11, les grappes 4 et 6 en 12. Observez ensuite la ligne trois, fusionnant les grappes 5 (de A) et 12 (de la grappe intermédiaire 12 non représentée), résultant en: une distance intra-grappe (WCD) de 0,5. La méthode unique implique que le nouveau WCS est égal à 0,5, soit la distance entre A [5] et le point le plus proche du groupe 12, A [4] et A [6]. Allons vérifier:

 In [198]: norm([a[5]-a[4]])
 Out[198]: 0.70710678118654757
 In [199]: norm([a[5]-a[6]])
 Out[199]: 0.5

Ce cluster devrait maintenant être le cluster intermédiaire 13, qui est ensuite fusionné avec A [2]. Ainsi, la nouvelle distance devrait être la plus proche entre les points A [2] et A [4,5,6].

 In [200]: norm([a[2]-a[4]])
 Out[200]: 1.019803902718557
 In [201]: norm([a[2]-a[5]])
 Out[201]: 0.58309518948452999
 In [202]: norm([a[2]-a[6]])
 Out[202]: 0.53851648071345048

Comme on peut le voir, cela vérifie également et explique le format intermédiaire des nouveaux clusters.

16
user1603472

La documentation scipy est exacte, comme l'a souligné dkar ... mais il est un peu difficile de transformer les données renvoyées en un élément utilisable pour une analyse plus approfondie. 

À mon avis, ils devraient inclure la possibilité de renvoyer les données dans une arborescence semblable à la structure de données. Le code ci-dessous va parcourir la matrice et construire un arbre:

from scipy.cluster.hierarchy import linkage
import numpy as np

a = np.random.multivariate_normal([10, 0], [[3, 1], [1, 4]], size=[100,])
b = np.random.multivariate_normal([0, 20], [[3, 1], [1, 4]], size=[50,])
centers = np.concatenate((a, b),)

def create_tree(centers):
    clusters = {}
    to_merge = linkage(centers, method='single')
    for i, merge in enumerate(to_merge):
        if merge[0] <= len(to_merge):
            # if it is an original point read it from the centers array
            a = centers[int(merge[0]) - 1]
        else:
            # other wise read the cluster that has been created
            a = clusters[int(merge[0])]

        if merge[1] <= len(to_merge):
            b = centers[int(merge[1]) - 1]
        else:
            b = clusters[int(merge[1])]
        # the clusters are 1-indexed by scipy
        clusters[1 + i + len(to_merge)] = {
            'children' : [a, b]
        }
        # ^ you could optionally store other info here (e.g distances)
    return clusters

print create_tree(centers)
8
Salik Syed

Voici un autre morceau de code qui remplit la même fonction. Cette version suit la distance (taille) de chaque cluster (node_id) et confirme le nombre de membres.

Ceci utilise la fonction scipy linkage () qui est la même base que le clusterer Aggregator.

from scipy.cluster.hierarchy import linkage
import copy
Z = linkage(data_x, 'ward')

n_points = data_x.shape[0]
clusters = [dict(node_id=i, left=i, right=i, members=[i], distance=0, log_distance=0, n_members=1) for i in range(n_points)]
for z_i in range(Z.shape[0]):
    row = Z[z_i]
    cluster = dict(node_id=z_i + n_points, left=int(row[0]), right=int(row[1]), members=[], log_distance=np.log(row[2]), distance=row[2], n_members=int(row[3]))
    cluster["members"].extend(copy.deepcopy(members[cluster["left"]]))
    cluster["members"].extend(copy.deepcopy(members[cluster["right"]]))
    clusters.append(cluster)

on_split = {c["node_id"]: [c["left"], c["right"]] for c in clusters}
up_merge = {c["left"]: {"into": c["node_id"], "with": c["right"]} for c in clusters}
up_merge.update({c["right"]: {"into": c["node_id"], "with": c["left"]} for c in clusters})
0
David Bernat