Como rotular os marcadores Y como grupo / categoria no clustermap marítimo?

8

Quero fazer um mapa de cluster / mapa de calor dos dados de presença-ausência de genes de pacientes nos quais os genes serão agrupados em categorias (por exemplo, quimiotaxia, endotoxina etc.) e rotulados adequadamente. Não encontrei nenhuma opção na documentação marítima. Eu sei como gerar o mapa de calor, apenas não sei como rotular os yticks como categorias. Aqui está uma amostra (não relacionada ao meu trabalho) do que eu quero alcançar:

mapa de calor

Aqui, yticklabels janeiro, fevereiro e março recebem o rótulo de grupo inverno e outros yticklabels também são rotulados de forma semelhante.

Ahmed Abdullah
fonte
Você está tentando criar um dendrograma (por exemplo, janeiro, fevereiro e março ainda estão lá e um nó chamado "inverno" aparece acima dele)? Ou você está tentando se livrar dos meses e colocar a estação?
Gnahum 17/11/19
Não é um dendograma. Não quero agrupar as linhas (ou seja, janeiro, fevereiro etc.), quero mantê-las na sequência em que aparecem no quadro de dados. Eu só quero rotular meses (ou seja, janeiro, fevereiro e março como inverno).
Ahmed Abdullah
@ gnahum Não, eu também não quero substituir. Eu quero gerar uma imagem como o dado (mas polido, é claro :))
Ahmed Abdullah
você pode passar uma lista recém-formada? ou seja, `` sns.heatmap (df, yticklabels = ['inverno', None, None, None, 'spring', None, None, None, 'summer', None, None, None, 'fall', None, None]) ``
gnahum 18/11/19
@gnahum Isso simplesmente substitui os nomes dos meses. Mas não quero substituí-los.
Ahmed Abdullah

Respostas:

2

Reproduzi o exemplo que você deu no mar, adaptando a resposta de @ Stein daqui .

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

Dá:

Espero que ajude.

CDJB
fonte
Isso não parece funcionar. É isso que eu recebo. drive.google.com/open?id=1SRbVe9Bk25xiplkn64sZXfbruUrqt5Ro
Ahmed Abdullah
Que estranho, não tenho idéia do porquê disso acontecer - é como se o charset usado para gerar os rótulos do gráfico não incluísse o alfabeto latino por algum motivo. O que acontece se você alterar os rótulos dos grupos na função test_table?
CDJB 19/11/19
O alfabeto na função test_table mudou a mesma saída.
Ahmed Abdullah
Estou fazendo isso no python 3.6.7.
Ahmed Abdullah
1
Atualizei o matplotlib para 3.1.2 para corrigir o erro no matplotlib 3.1.1 com mapas de calor - as linhas agora estão alinhadas corretamente com os dados; veja o novo exemplo de saída.
CDJB
2

Ainda não testei isso com transoceânicos, mas o seguinte funciona com vanilla matplotlib.

insira a descrição da imagem aqui

#!/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()
Paul Brodersen
fonte
Esta solução também funciona com o mapa de calor marítimo! obrigado.
Ahmed Abdullah