如何在不使用scipy的情况下在matplotlib中绘制树状图?

时间:2019-05-14 05:10:22

标签: python-3.x matplotlib tree treeview dendrogram

我想使用matplotlib绘制树状图而不使用scipy。一个类似的问题has been posted here;但是,标记的解决方案建议使用scipy,而其他答案中的链接则建议使用ETE。使用this example,我已经验证了我自己的方法(即非scipy方法)使用单链接标准应用聚集层次聚类的准确性。

使用上面链接的相同示例,我具有创建我自己的树状图的必要参数。原始的distance_matrix由以下方式给出:

 .. DISTANCE MATRIX (SHAPE=(6, 6)):
[[  0 662 877 255 412 996]
 [662   0 295 468 268 400]
 [877 295   0 754 564   0]
 [255 468 754   0 219 869]
 [412 268 564 219   0 669]
 [996 400   0 869 669   0]]

使用distance_matrix的掩码数组,以使上方的对角线条目不算作最小值。原始distance_matrix的掩码如下:

 .. MASKED (BEFORE) DISTANCE MATRIX (SHAPE=(6, 6)):
[[-- 662 877 255 412 996]
 [662 -- 295 468 268 400]
 [877 295 -- 754 564 0]
 [255 468 754 -- 219 869]
 [412 268 564 219 -- 669]
 [996 400 0 869 669 --]]

distance_matrix在算法的每次迭代中就地更改。算法完成后,distance_matrix的计算公式为:

 .. MASKED (AFTER) DISTANCE MATRIX (SHAPE=(1, 1)):
[[--]]

级别(每次合并的最小距离)由下式给出:

 .. 5 LEVELS:
[138, 219, 255, 268, 295]

我们还可以在每次迭代时查看合并数据点的索引;这些索引与原始distance_matrix相对应,因为缩小尺寸会改变索引位置。这些索引由以下方式给出:

 .. 5x2 LOCATIONS:
[(2, 5), (3, 4), (0, 3), (0, 1), (0, 2)]

从这些索引中,树状图的xtick标签的顺序按时间顺序为:

.. 6 XTICKLABELS
[2 5 3 4 0 1]

关于链接的示例

0 = BA
1 = FI 
2 = MI 
3 = NA 
4 = RM 
5 = TO

使用这些参数,我想生成一个树状图,如下图所示(从链接的示例中借用):

example dendrogram

我尝试使用matplotlib复制此树状图的尝试如下:

fig, ax = plt.subplots()
for loc, level in zip(locations, levels):
    x = np.array(loc)
    y = level * np.ones(x.size)
    ax.step(x, y, where='mid')
    ax.set_xticks(xticklabels)
    # ax.set_xticklabels(xticklabels)
    plt.show()
    plt.close(fig)

我在上面的尝试产生了下图:

attempted dendrogram

我意识到我必须对xticklabels进行重新排序,以使第一个合并点出现在右边缘,随后的每个合并点都向左移动;这样做必然意味着要调整连接线的宽度。另外,我使用的是ax.step而不是ax.bar,以便使线条看起来更有条理(与各处的矩形条相对);我唯一想做的就是使用ax.axhlineax.axvline绘制水平和垂直线。我希望有一种更简单的方法来完成我想要的。是否有使用matplotlib的简单方法?

1 个答案:

答案 0 :(得分:1)

虽然肯定更容易依靠scipy ,但这是我“手动”执行的方式,即没有它:

import matplotlib.pyplot as plt
import numpy as np

def mk_fork(x0,x1,y0,y1,new_level):
    points=[[x0,x0,x1,x1],[y0,new_level,new_level,y1]]
    connector=[(x0+x1)/2.,new_level]
    return (points),connector

levels=[138, 219, 255, 268, 295]
locations=[(2, 5), (3, 4), (0, 3), (0, 1), (0, 2)]
label_map={
    0:{'label':'BA','xpos':0,'ypos':0},
    1:{'label':'FI','xpos':3,'ypos':0},
    2:{'label':'MI','xpos':4,'ypos':0},
    3:{'label':'NA','xpos':1,'ypos':0},
    4:{'label':'RM','xpos':2,'ypos':0},
    5:{'label':'TO','xpos':5,'ypos':0},
}

fig,ax=plt.subplots()

for i,(new_level,(loc0,loc1)) in enumerate(zip(levels,locations)):

    print('step {0}:\t connecting ({1},{2}) at level {3}'.format(i, loc0, loc1, new_level ))

    x0,y0=label_map[loc0]['xpos'],label_map[loc0]['ypos']
    x1,y1=label_map[loc1]['xpos'],label_map[loc1]['ypos']

    print('\t points are: {0}:({2},{3}) and {1}:({4},{5})'.format(loc0,loc1,x0,y0,x1,y1))

    p,c=mk_fork(x0,x1,y0,y1,new_level)

    ax.plot(*p)
    ax.scatter(*c)

    print('\t connector is at:{0}'.format(c))


    label_map[loc0]['xpos']=c[0]
    label_map[loc0]['ypos']=c[1]
    label_map[loc0]['label']='{0}/{1}'.format(label_map[loc0]['label'],label_map[loc1]['label'])
    print('\t updating label_map[{0}]:{1}'.format(loc0,label_map[loc0]))

    ax.text(*c,label_map[loc0]['label'])

_xticks=np.arange(0,6,1)
_xticklabels=['BA','NA','RM','FI','MI','TO']

ax.set_xticks(_xticks)
ax.set_xticklabels(_xticklabels)

ax.set_ylim(0,1.05*np.max(levels))

plt.show()

这主要取决于创建字典label_map,该字典将原始的“位置”(即(2,5))映射到“ xtick顺序”(即(4,5))。在每个步骤i中使用mk_fork()创建一个“叉子”,它返回points(随后在折线图中连接)以及connector点,然后将其存储为'xpos','ypos'label_map的新值。

我添加了多个print()语句以强调每个步骤发生的情况,并添加了.text()以突出显示每个“连接器”的位置。

结果: a simple dendrogram