Подтвердить что ты не робот

Как построить и аннотировать иерархические кластерные дендрограммы в scipy/matplotlib

Я использую dendrogram из scipy для построения иерархической кластеризации с помощью matplotlib следующим образом:

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)

Мои вопросы: во-первых, почему здесь mat и 1-mat дают одинаковые кластеры? и во-вторых, как я могу аннотировать расстояние вдоль каждой ветки дерева с помощью dendrogram, чтобы сравнить расстояния между парами узлов?

Наконец, кажется, что флаг show_leaf_counts игнорируется, есть ли способ включить его, чтобы показать количество объектов в каждом классе? спасибо. enter image description here

4b9b3361

Ответ 1

Вход в linkage() представляет собой либо массив n x m, представляющий n точек в m-мерное пространство или одномерный массив, содержащий уплотненную матрицу расстояний. В вашем примере mat - 3 x 3, поэтому вы кластеризуете три 3-х точки. Кластеризация основана на расстоянии между этими точками.

Почему mat и 1-mat дают одинаковые кластеры здесь?

Массивы mat и 1-mat производят одну и ту же кластеризацию, поскольку кластеризация основано на расстояниях между точками, и ни отражение (-mat), ни перевод (mat + offset) всего набора данных не изменяют относительный расстояния между точками.

Как я могу аннотировать расстояние вдоль каждой ветки дерева с помощью дендрограммы, чтобы сравнить расстояния между парами узлов?

В приведенном ниже коде I покажите, как вы можете использовать данные, возвращаемые dendrogram, для обозначения горизонтальной сегментов диаграммы с соответствующим расстоянием. Сопоставленные значения с ключами icoord и dcoord дают координаты x и y каждого трехсегментный инвертированный-U фигуры. В augmented_dendrogram эти данные используется для добавления метки расстояния (то есть значения y) каждой горизонтали сегмент линии в дендрограмме.

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

Для вашего массива mat расширенная дендрограмма

dendrogram for three points

Таким образом, точки "a" и "c" равны 1.01 единицам, а точка "b" составляет 1,57 единицы от кластер ['a', 'c'].

Кажется, что флаг show_leaf_counts игнорируется, есть ли способ включить его так что показано количество объектов в каждом классе?

Флаг show_leaf_counts применяется только тогда, когда не все исходные данные точки показаны как листья. Например, когда trunc_mode = "lastp", отображаются только последние узлы p.

Вот пример со 100 точками:

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()

Это точки в наборе данных:

scatter plot of 100 points

С p=6 и trunc_mode="lastp", dendrogram показывает только "верхний" дендрограммы. Ниже показано влияние show_leaf_counts.

Show effect of show_leaf_counts

Ответ 2

Я думаю, что есть пара недоразумений в отношении использования функций, которые вы пытаетесь использовать. Здесь приведен полный фрагмент кода для иллюстрации моих моментов:

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()

Прежде всего, вычисление m → m - 1 действительно не изменило ваш результат, поскольку матрица расстояний, которая в основном описывает относительные расстояния между всеми уникальными парами, не изменилась в вашем конкретном случае. (В приведенном выше примере кода все расстояния являются евклидовыми, поэтому все они являются положительными и согласованными с точками на плоскости 2d.)

Для вашего второго вопроса вам, вероятно, придется развернуть свою собственную процедуру аннотации, чтобы делать то, что вы хотите, так как я не думаю, что dendromgram поддерживает ее...

В последнем вопросе show_leaf_counts работает только тогда, когда вы пытаетесь отобразить не-одиночные листовые узлы с опцией truncate_mode = 'lastp'. В основном листья сгруппированы так близко друг к другу, что их нелегко увидеть. Таким образом, у вас есть возможность просто отображать лист, но у вас есть возможность показать (в скобках), сколько из них сгруппировано в этом листе.

Надеюсь, что это поможет.