【问题标题】:Plot dendrogram using sklearn.AgglomerativeClustering使用 sklearn.AgglomerativeClustering 绘制树状图
【发布时间】:2015-05-21 12:42:54
【问题描述】:

我正在尝试使用AgglomerativeClustering 提供的children_ 属性构建树状图,但到目前为止我运气不佳。我不能使用scipy.cluster,因为scipy 中提供的凝聚集群缺少一些对我很重要的选项(例如指定集群数量的选项)。我将非常感谢那里的任何建议。

    import sklearn.cluster
    clstr = cluster.AgglomerativeClustering(n_clusters=2)
    clusterer.children_

【问题讨论】:

  • 请发布代码示例以增加获得好答案的机会
  • 这能回答你的问题吗? link

标签: python plot cluster-analysis dendrogram


【解决方案1】:

来自the official docs

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)


iris = load_iris()
X = iris.data

# setting distance_threshold=0 ensures we compute the full tree.
model = AgglomerativeClustering(distance_threshold=0, n_clusters=None)

model = model.fit(X)
plt.title('Hierarchical Clustering Dendrogram')
# plot the top three levels of the dendrogram
plot_dendrogram(model, truncate_mode='level', p=3)
plt.xlabel("Number of points in node (or index of point if no parenthesis).")
plt.show()

请注意,目前(从 scikit-learn v0.23 开始)仅在使用 distance_threshold 参数调用 AgglomerativeClustering 时才有效,但从 v0.24 开始,您将能够通过设置 @987654325 来强制计算距离@ 为真 (see nightly build docs)。

【讨论】:

    【解决方案2】:

    改用凝聚集群的 scipy 实现。这是一个例子。

    from scipy.cluster.hierarchy import dendrogram, linkage
    
    data = [[0., 0.], [0.1, -0.1], [1., 1.], [1.1, 1.1]]
    
    Z = linkage(data)
    
    dendrogram(Z)  
    

    您可以找到 linkage here 的文档和 dendrogram here 的文档。

    【讨论】:

    • 这个答案很有用,因为它指出了通过 scipy 创建和可视化层次聚类的另一种方法,所以我赞成它。然而,这并没有回答最初的问题,即如何可视化由 scikit-learn 创建的聚类的树状图。如果你添加一个函数来获取 scikit-learn 的输出并创建一个像 Z 这样的数据结构,那就太好了。
    • @conradlee 实际上这就是plot_dendrogram() 函数在这里所做的——除了最后一行:scikit-learn.org/stable/auto_examples/cluster/… 最后一行中调用的dendrogram 函数是从scipy.cluster.hierarchy 导入的跨度>
    • @tozCSS 感谢您指出这一点。现在投票率最高的答案确实通过链接到现在是 scikit-learn 文档的一部分的plot_dendrogram sn-p 来回答这个问题。我很高兴看到文档有所改进。我现在在这里删除了我的赞成票。
    【解决方案3】:

    这是一个simple function,用于从 sklearn 获取层次聚类模型并使用 scipy dendrogram 函数对其进行绘图。似乎sklearn中通常不直接支持图形功能。您可以找到与此 plot_dendrogram 代码 sn-p here 的拉取请求相关的有趣讨论。

    我要澄清的是,您描述的用例(定义集群数量)在 scipy 中可用:在您使用 scipy 的linkage 执行层次聚类之后,您可以将层次结构切割成您想要使用的任何数量的集群fclustert 参数和 criterion='maxclust' 参数中指定的簇数。

    【讨论】:

      【解决方案4】:

      对于那些愿意放弃 Python 并使用强大的 D3 库的人来说,使用 d3.cluster()(或者,我猜是 d3.tree())API 来实现一个不错的、可定制的结果并不是很困难。

      查看jsfiddle 进行演示。

      children_ 数组幸运地很容易用作 JS 数组,唯一的中间步骤是使用 d3.stratify() 将其转换为分层表示。具体来说,我们需要每个节点都有一个id 和一个parentId

      var N = 272;  // Your n_samples/corpus size.
      var root = d3.stratify()
        .id((d,i) => i + N)
        .parentId((d, i) => {
          var parIndex = data.findIndex(e => e.includes(i + N));
          if (parIndex < 0) {
            return; // The root should have an undefined parentId.
          }
          return parIndex + N;
        })(data); // Your children_
      

      由于findIndex 行,您最终在这里至少会出现 O(n^2) 行为,但在您的 n_samples 变得巨大之前,这可能并不重要,在这种情况下,您可以预先计算更有效的索引。

      除此之外,d3.cluster() 几乎就是即插即用。请参阅 mbostock 的 canonical block 或我的 JSFiddle。

      注意对于我的用例,仅显示非叶节点就足够了;将样本/叶子可视化有点棘手,因为它们可能并不都明确地在 children_ 数组中。

      【讨论】:

        【解决方案5】:

        前段时间我遇到了完全相同的问题。我设法绘制该死的树状图的方法是使用软件包ete3。该软件包能够灵活地绘制具有各种选项的树。唯一的困难是将sklearnchildren_ 输出转换为Newick Tree format 可以被ete3 阅读和理解。此外,我需要手动计算树突的跨度,因为 children_ 没有提供该信息。这是我使用的代码的 sn-p。它计算 Newick 树,然后显示 ete3 Tree 数据结构。更多关于如何绘图的详细信息,请查看here

        import numpy as np
        from sklearn.cluster import AgglomerativeClustering
        import ete3
        
        def build_Newick_tree(children,n_leaves,X,leaf_labels,spanner):
            """
            build_Newick_tree(children,n_leaves,X,leaf_labels,spanner)
        
            Get a string representation (Newick tree) from the sklearn
            AgglomerativeClustering.fit output.
        
            Input:
                children: AgglomerativeClustering.children_
                n_leaves: AgglomerativeClustering.n_leaves_
                X: parameters supplied to AgglomerativeClustering.fit
                leaf_labels: The label of each parameter array in X
                spanner: Callable that computes the dendrite's span
        
            Output:
                ntree: A str with the Newick tree representation
        
            """
            return go_down_tree(children,n_leaves,X,leaf_labels,len(children)+n_leaves-1,spanner)[0]+';'
        
        def go_down_tree(children,n_leaves,X,leaf_labels,nodename,spanner):
            """
            go_down_tree(children,n_leaves,X,leaf_labels,nodename,spanner)
        
            Iterative function that traverses the subtree that descends from
            nodename and returns the Newick representation of the subtree.
        
            Input:
                children: AgglomerativeClustering.children_
                n_leaves: AgglomerativeClustering.n_leaves_
                X: parameters supplied to AgglomerativeClustering.fit
                leaf_labels: The label of each parameter array in X
                nodename: An int that is the intermediate node name whos
                    children are located in children[nodename-n_leaves].
                spanner: Callable that computes the dendrite's span
        
            Output:
                ntree: A str with the Newick tree representation
        
            """
            nodeindex = nodename-n_leaves
            if nodename<n_leaves:
                return leaf_labels[nodeindex],np.array([X[nodeindex]])
            else:
                node_children = children[nodeindex]
                branch0,branch0samples = go_down_tree(children,n_leaves,X,leaf_labels,node_children[0])
                branch1,branch1samples = go_down_tree(children,n_leaves,X,leaf_labels,node_children[1])
                node = np.vstack((branch0samples,branch1samples))
                branch0span = spanner(branch0samples)
                branch1span = spanner(branch1samples)
                nodespan = spanner(node)
                branch0distance = nodespan-branch0span
                branch1distance = nodespan-branch1span
                nodename = '({branch0}:{branch0distance},{branch1}:{branch1distance})'.format(branch0=branch0,branch0distance=branch0distance,branch1=branch1,branch1distance=branch1distance)
                return nodename,node
        
        def get_cluster_spanner(aggClusterer):
            """
            spanner = get_cluster_spanner(aggClusterer)
        
            Input:
                aggClusterer: sklearn.cluster.AgglomerativeClustering instance
        
            Get a callable that computes a given cluster's span. To compute
            a cluster's span, call spanner(cluster)
        
            The cluster must be a 2D numpy array, where the axis=0 holds
            separate cluster members and the axis=1 holds the different
            variables.
        
            """
            if aggClusterer.linkage=='ward':
                if aggClusterer.affinity=='euclidean':
                    spanner = lambda x:np.sum((x-aggClusterer.pooling_func(x,axis=0))**2)
            elif aggClusterer.linkage=='complete':
                if aggClusterer.affinity=='euclidean':
                    spanner = lambda x:np.max(np.sum((x[:,None,:]-x[None,:,:])**2,axis=2))
                elif aggClusterer.affinity=='l1' or aggClusterer.affinity=='manhattan':
                    spanner = lambda x:np.max(np.sum(np.abs(x[:,None,:]-x[None,:,:]),axis=2))
                elif aggClusterer.affinity=='l2':
                    spanner = lambda x:np.max(np.sqrt(np.sum((x[:,None,:]-x[None,:,:])**2,axis=2)))
                elif aggClusterer.affinity=='cosine':
                    spanner = lambda x:np.max(np.sum((x[:,None,:]*x[None,:,:]))/(np.sqrt(np.sum(x[:,None,:]*x[:,None,:],axis=2,keepdims=True))*np.sqrt(np.sum(x[None,:,:]*x[None,:,:],axis=2,keepdims=True))))
                else:
                    raise AttributeError('Unknown affinity attribute value {0}.'.format(aggClusterer.affinity))
            elif aggClusterer.linkage=='average':
                if aggClusterer.affinity=='euclidean':
                    spanner = lambda x:np.mean(np.sum((x[:,None,:]-x[None,:,:])**2,axis=2))
                elif aggClusterer.affinity=='l1' or aggClusterer.affinity=='manhattan':
                    spanner = lambda x:np.mean(np.sum(np.abs(x[:,None,:]-x[None,:,:]),axis=2))
                elif aggClusterer.affinity=='l2':
                    spanner = lambda x:np.mean(np.sqrt(np.sum((x[:,None,:]-x[None,:,:])**2,axis=2)))
                elif aggClusterer.affinity=='cosine':
                    spanner = lambda x:np.mean(np.sum((x[:,None,:]*x[None,:,:]))/(np.sqrt(np.sum(x[:,None,:]*x[:,None,:],axis=2,keepdims=True))*np.sqrt(np.sum(x[None,:,:]*x[None,:,:],axis=2,keepdims=True))))
                else:
                    raise AttributeError('Unknown affinity attribute value {0}.'.format(aggClusterer.affinity))
            else:
                raise AttributeError('Unknown linkage attribute value {0}.'.format(aggClusterer.linkage))
            return spanner
        
        clusterer = AgglomerativeClustering(n_clusters=2,compute_full_tree=True) # You can set compute_full_tree to 'auto', but I left it this way to get the entire tree plotted
        clusterer.fit(X) # X for whatever you want to fit
        spanner = get_cluster_spanner(clusterer)
        newick_tree = build_Newick_tree(clusterer.children_,clusterer.n_leaves_,X,leaf_labels,spanner) # leaf_labels is a list of labels for each entry in X
        tree = ete3.Tree(newick_tree)
        tree.show()
        

        【讨论】:

          猜你喜欢
          • 2022-01-19
          • 1970-01-01
          • 1970-01-01
          • 2013-06-09
          • 2020-06-06
          • 2020-02-07
          • 2021-06-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多