【问题标题】:How to label Y ticklabels as group/category in seaborn clustermap?如何在seaborn clustermap中将Y ticklabels标记为组/类别?
【发布时间】:2020-03-10 06:25:16
【问题描述】:

我想制作来自患者的基因存在-缺失数据的聚类图/热图,其中基因将被分组(例如趋化性、内毒素等)并适当标记。我在 seaborn 文档中没有找到任何这样的选项。我知道如何生成热图,只是不知道如何将 yticks 标记为类别。以下是我想要实现的示例(与我的工作无关):

在这里,yticklabels January、February 和 March 被赋予组标签 Winter,其他 yticklabels 也被类似地标记。

【问题讨论】:

  • 您是否正在尝试制作树状图(即,1 月、2 月、3 月仍然存在,并且在其上方出现一个名为“winter”的节点)?还是您想摆脱月份并改为季节?
  • 不是树状图。我不想对行进行聚类(即一月、二月等),我想将它们保持在它们出现在数据框中的顺序中。我只想标记月份(即一月、二月、三月为冬季)。
  • @gnahum 不,我也不想替换。我想生成一个像给定的图像(但当然是抛光的:))
  • 你能传递一个新形成的列表吗?即``` sns.heatmap(df, yticklabels=['winter',None, None, 'spring', None, None, 'summer', None, None, 'fall',None, None]) ```
  • @gnahum 这只是替换了月份名称。但我不想替换它们。

标签: python matplotlib plot graph seaborn


【解决方案1】:

我已经复制了你在 seaborn 中给出的例子,改编自 here 的 @Stein 的回答。

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from itertools import groupby
import datetime
import seaborn as sns

def test_table():
    months = [datetime.date(2008, i+1, 1).strftime('%B') for i in range(12)]
    seasons = ['Winter',]*3 + ['Spring',]*2 + ['Summer']*3 + ['Pre-Winter',]*4
    tuples = list(zip(months, seasons))
    index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
    d = {i: [np.random.randint(0,50) for _ in range(12)] for i in range(1950, 1960)}
    df = pd.DataFrame(d, index=index)
    return df

def add_line(ax, xpos, ypos):
    line = plt.Line2D([ypos, ypos+ .2], [xpos, xpos], color='black', transform=ax.transAxes)
    line.set_clip_on(False)
    ax.add_line(line)

def label_len(my_index,level):
    labels = my_index.get_level_values(level)
    return [(k, sum(1 for i in g)) for k,g in groupby(labels)]

def label_group_bar_table(ax, df):
    xpos = -.2
    scale = 1./df.index.size
    for level in range(df.index.nlevels):
        pos = df.index.size
        for label, rpos in label_len(df.index,level):
            add_line(ax, pos*scale, xpos)
            pos -= rpos
            lypos = (pos + .5 * rpos)*scale
            ax.text(xpos+.1, lypos, label, ha='center', transform=ax.transAxes) 
        add_line(ax, pos*scale , xpos)
        xpos -= .2

df = test_table()

fig = plt.figure(figsize = (10, 10))
ax = fig.add_subplot(111)
sns.heatmap(df)

#Below 3 lines remove default labels
labels = ['' for item in ax.get_yticklabels()]
ax.set_yticklabels(labels)
ax.set_ylabel('')

label_group_bar_table(ax, df)
fig.subplots_adjust(bottom=.1*df.index.nlevels)
plt.show()

给:

希望对您有所帮助。

【讨论】:

  • 这似乎不起作用。这就是我得到的。 drive.google.com/open?id=1SRbVe9Bk25xiplkn64sZXfbruUrqt5Ro
  • 多么奇怪,我不知道为什么会发生这种情况——就像用于生成图形标签的字符集由于某种原因不包括拉丁字母一样。如果更改 test_table 函数中的组标签会怎样?
  • 更改了 test_table 函数中的字母表仍然是相同的输出。
  • 我在 python 3.6.7 中这样做。
  • 我已将 matplotlib 更新到 3.1.2 以修复 matplotlib 3.1.1 中带有热图的错误 - 现在线条与数据正确对齐;查看新的示例输出。
【解决方案2】:

我还没有用 seaborn 测试过这个,但是下面的适用于 vanilla matplotlib。

#!/usr/bin/env python
"""
Annotate a group of y-tick labels as such.
"""

import matplotlib.pyplot as plt
from matplotlib.transforms import TransformedBbox

def annotate_yranges(groups, ax=None):
    """
    Annotate a group of consecutive yticklabels with a group name.

    Arguments:
    ----------
    groups : dict
        Mapping from group label to an ordered list of group members.
    ax : matplotlib.axes object (default None)
        The axis instance to annotate.
    """
    if ax is None:
        ax = plt.gca()

    label2obj = {ticklabel.get_text() : ticklabel for ticklabel in ax.get_yticklabels()}

    for ii, (group, members) in enumerate(groups.items()):
        first = members[0]
        last = members[-1]

        bbox0 = _get_text_object_bbox(label2obj[first], ax)
        bbox1 = _get_text_object_bbox(label2obj[last], ax)

        set_yrange_label(group, bbox0.y0 + bbox0.height/2,
                         bbox1.y0 + bbox1.height/2,
                         min(bbox0.x0, bbox1.x0),
                         -2,
                         ax=ax)


def set_yrange_label(label, ymin, ymax, x, dx=-0.5, ax=None, *args, **kwargs):
    """
    Annotate a y-range.

    Arguments:
    ----------
    label : string
        The label.
    ymin, ymax : float, float
        The y-range in data coordinates.
    x : float
        The x position of the annotation arrow endpoints in data coordinates.
    dx : float (default -0.5)
        The offset from x at which the label is placed.
    ax : matplotlib.axes object (default None)
        The axis instance to annotate.
    """

    if not ax:
        ax = plt.gca()

    dy = ymax - ymin
    props = dict(connectionstyle='angle, angleA=90, angleB=180, rad=0',
                 arrowstyle='-',
                 shrinkA=10,
                 shrinkB=10,
                 lw=1)
    ax.annotate(label,
                xy=(x, ymin),
                xytext=(x + dx, ymin + dy/2),
                annotation_clip=False,
                arrowprops=props,
                *args, **kwargs,
    )
    ax.annotate(label,
                xy=(x, ymax),
                xytext=(x + dx, ymin + dy/2),
                annotation_clip=False,
                arrowprops=props,
                *args, **kwargs,
    )


def _get_text_object_bbox(text_obj, ax):
    # https://stackoverflow.com/a/35419796/2912349
    transform = ax.transData.inverted()
    # the figure needs to have been drawn once, otherwise there is no renderer?
    plt.ion(); plt.show(); plt.pause(0.001)
    bb = text_obj.get_window_extent(renderer = ax.get_figure().canvas.renderer)
    # handle canvas resizing
    return TransformedBbox(bb, transform)


if __name__ == '__main__':

    import numpy as np

    fig, ax = plt.subplots(1,1)

    # so we have some extra space for the annotations
    fig.subplots_adjust(left=0.3)

    data = np.random.rand(10,10)
    ax.imshow(data)

    ticklabels = 'abcdefghij'
    ax.set_yticks(np.arange(len(ticklabels)))
    ax.set_yticklabels(ticklabels)

    groups = {
        'abc' : ('a', 'b', 'c'),
        'def' : ('d', 'e', 'f'),
        'ghij' : ('g', 'h', 'i', 'j')
    }

    annotate_yranges(groups)

    plt.show()

【讨论】:

  • 此解决方案也适用于 seaborn 热图!谢谢。
  • 您使用的是哪个版本的 matplotlib / seaborn?因为发布的示例在最新版本上都不起作用。它不显示组部分
  • @ddomingo matplotlib 3.2.1
  • 你还能重现这个数字吗?因为我尝试过不同的版本(现在是 3.2.1)并且没有显示其他组,所以只有热图和 x/y 滴答声
  • 我发现了错误。
猜你喜欢
  • 2016-04-06
  • 2018-08-21
  • 1970-01-01
  • 2020-05-11
  • 1970-01-01
  • 2020-09-14
  • 2015-12-28
  • 2015-09-12
  • 2019-10-24
相关资源
最近更新 更多