如何在Scipy/Matplotlib中绘制和注释层次聚类树状图

45

我正在使用来自scipydendrogram,使用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)

我的问题是:首先,为什么在这里mat1-mat给出相同的聚类结果?其次,如何使用dendrogram注释沿每个分支的距离,以便可以比较节点对之间的距离?

最后,似乎忽略了show_leaf_counts标志,有没有办法打开它,以显示每个类中对象的数量?谢谢。enter image description here

2个回答

70
linkage() 的输入可以是一个 n x m 数组,表示在 m 维空间中的 n 个点,或者是包含 压缩 距离矩阵 的一维数组。在您的示例中,mat 是 3 x 3,因此正在对三个三维点进行聚类。聚类基于这些点之间的距离。
为什么这里的 mat 和 1-mat 产生相同的聚类?
数组mat1-mat产生相同的聚类,因为聚类是基于点之间的距离计算的,整个数据集的反射(-mat)或平移(mat+offset)都不会改变点之间的相对距离。
如何使用树状图注释每个分支上的距离,以便比较节点对之间的距离?
在下面的代码中,我展示了如何使用 dendrogram 返回的数据来标注图表的水平线段,并将其与相应的距离关联起来。与键icoorddcoord相关联的值给出了图形中每个三段反向U的x和y坐标。在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'距离群集['a','c']有1.57个单位。 似乎忽略了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()

这些是数据集中的点:

100个点的散点图

p=6trunc_mode="lastp" 时,dendrogram 只显示树形图的“顶部”。以下内容展示了 show_leaf_counts 的效果。

显示 show_leaf_counts 的效果


你的回答第一部分是正确的,但不完整。linkage的输入也可以是“压缩或冗余的距离矩阵。压缩距离矩阵是一个包含距离矩阵上三角形的平面数组。这是pdist返回的形式。”来源:https://docs.scipy.org/doc/scipy-0.18.0/reference/generated/scipy.cluster.hierarchy.linkage.html - Featherlegs
@Featherlegs 感谢您指出这一点。实际上,linkage的docstring最近已经被更正以反映代码的现实情况。更正后的docstring尚未发布。linkage接受包含压缩距离矩阵的1-d数组或点的2-d数组。它不接受密集距离矩阵。我会更新我的答案以反映这一点。 - Warren Weckesser
这是linkage文档的开发版本:http://scipy.github.io/devdocs/generated/scipy.cluster.hierarchy.linkage.html - Warren Weckesser
能否在同一个标签的不同部分使用两种不同的颜色?我的意思是,假设我们想要用红色显示'faa',用蓝色显示'foo',并将它们作为同一叶子的标签。 - Sigur
1
@Sigur,我认为这并不容易——这可能需要相当多的matplotlib编程技巧。 - Warren Weckesser

15

我认为你正在尝试使用的函数有一些误解。这里是一个完全可行的代码片段来说明我的观点:

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并不会改变你的结果,因为距离矩阵基本上描述了所有唯一配对之间的相对距离,在你的特定情况下并没有改变。(在我上面的示例代码中,所有距离都是欧几里得距离,因此从二维平面上的点到点的距离都是正的且一致的。)
对于你的第二个问题,你可能需要编写自己的注释程序来实现你想要的功能,因为我认为树形图不支持这种注释...
对于最后一个问题,当您尝试使用truncate_mode ='lastp'选项显示非单例叶节点时,show_leaf_counts似乎只能起作用。基本上,叶子被紧密地聚集在一起,以至于它们不容易看到。因此,您可以选择仅显示一个叶子,或者选择显示(在括号中)有多少叶子聚集在该叶子上。
希望这可以帮助你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接