使用 Matplotlib 在带有图例的热图中显示不同大小的圆圈

Posted

技术标签:

【中文标题】使用 Matplotlib 在带有图例的热图中显示不同大小的圆圈【英文标题】:Showing different size circles in heatmap with legend using Matplotlib 【发布时间】:2021-04-14 07:33:24 【问题描述】:

我在问一个源于这篇原始帖子Heatmap with circles indicating size of population的问题

我正在尝试使用我的数据框来复制它,但是,我的圈子与情节不一致。其次,我还想创建一个图例,指示相对于圆圈大小的值。

   x= 'ID': 0: 'GO:0002474',
      1: 'GO:0052548',
      2: 'GO:0002483',
      3: 'GO:0043062',
      4: 'GO:0060333',
     'TERM': 0: 'antigen processing and presentation of peptide antigen via MHC class I',
      1: 'regulation of endopeptidase activity',
      2: 'antigen processing and presentation of endogenous peptide antigen',
      3: 'extracellular structure organization',
      4: 'interferon-gamma-mediated signaling pathway',
     'Count': 0: 11, 1: 17, 2: 5, 3: 15, 4: 6,
     'Ratio': 0: 18.64, 1: 14.53, 2: 8.47, 3: 12.82, 4: 10.17,
     'pvalue': 0: -15.83, 1: -11.39, 2: -9.67, 3: -9.05, 4: -7.41,
     'qvalue': 0: -11.63, 1: -7.49, 2: -6.52, 3: -5.63, 4: -4.55,
     'Label': 0: 'NODAL', 1: 'NODAL', 2: 'NODAL', 3: 'SHARED', 4: 'NODAL'

A2780_GOBP= pd.DataFrame(x)

尝试的代码:

ylabels = A2780_GOBP["TERM"]
xlabels = ["GFP","SHARED","NODAL"]
x, y = np.meshgrid(np.arange(len(xlabels)), np.arange(len(ylabels)))
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values

fig, ax = plt.subplots()

R = s/s.max()/2
circles = [plt.Circle((j,i), radius=r) for r, j, i in zip(R.flat, x.flat, y.flat)]
col = PatchCollection(circles, array=c.flatten(), cmap=cmap)
ax.add_collection(col)

ax.set(xticks=np.arange(3), yticks=np.arange(10),
       xticklabels=xlabels, yticklabels=ylabels)
ax.set_xticks(np.arange(3+1)-0.5, minor=True)
ax.set_yticks(np.arange(10+1)-0.5, minor=True)
ax.grid(which='minor')


fig.colorbar(col)
plt.show()

任何帮助将不胜感激!

【问题讨论】:

@Mr. T 如何将数据框导入此处? 打印df.head(N).to_dict(),复制粘贴。更多信息here。但是,这可能确实无关紧要。我以为你完全复制了代码,但罪魁祸首很可能是np.meshgrid(np.arange(len(xlabels)), np.arange(len(ylabels)))。如果确实只是这个错字,我建议删除这个问题。 它在某种程度上修复了它,但圆圈没有定位在正确的 x 和 y 坐标上。我想问题是“circles = [plt.Circle((j,i), radius=r) for r, j, i in zip(R.flat, x.flat, y.flat)]”,但我不熟悉如何将字符串用作 x 和 y。 好的,这不是微不足道的。我去看看。 @Mr.非常感谢! 【参考方案1】:

问题在于复制的代码填充了所有字段,而您的代码不一定在每个框中都有一个条目。我们必须向上看,每个圆圈都必须绘制在哪里:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import pandas as pd

x= 'ID': 0: 'GO:0002474',
      1: 'GO:0052548',
      2: 'GO:0002483',
      3: 'GO:0043062',
      4: 'GO:0060333',
     'TERM': 0: 'antigen processing and presentation of peptide antigen via MHC class I',
      1: 'regulation of endopeptidase activity',
      2: 'antigen processing and presentation of endogenous peptide antigen',
      3: 'extracellular structure organization',
      4: 'interferon-gamma-mediated signaling pathway',
     'Count': 0: 11, 1: 17, 2: 5, 3: 15, 4: 6,
     'Ratio': 0: 18.64, 1: 14.53, 2: 8.47, 3: 12.82, 4: 10.17,
     'pvalue': 0: -15.83, 1: -11.39, 2: -9.67, 3: -9.05, 4: -7.41,
     'qvalue': 0: -11.63, 1: -7.49, 2: -6.52, 3: -5.63, 4: -4.55,
     'Label': 0: 'NODAL', 1: 'GFP', 2: 'NODAL', 3: 'SHARED', 4: 'NODAL'

A2780_GOBP= pd.DataFrame(x)
cmap = "plasma"
 
#retrieve unique labels
ylabels = A2780_GOBP["TERM"].unique().tolist()
xlabels = A2780_GOBP["Label"].unique().tolist()
xn = len(xlabels)
yn = len(ylabels)
#retrieve size and color information    
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values


#preparation of the figure with its grid
fig, ax = plt.subplots(figsize=(10, 5))
ax.set_xlim(-0.5, xn-0.5)
ax.set_ylim(-0.5, yn-0.5)
ax.set(xticks=np.arange(xn), yticks=np.arange(yn),
       xticklabels=xlabels, yticklabels=ylabels)
ax.set_xticks(np.arange(xn)-0.5, minor=True)
ax.set_yticks(np.arange(yn)-0.5, minor=True)
ax.grid(which='minor')
#ensure circles are displayed as circles
ax.set_aspect("equal", "box")

#create circles patches and colorbar
R = s/s.max()/2
circles = [plt.Circle((xlabels.index(A2780_GOBP.loc[i, "Label"]), ylabels.index(A2780_GOBP.loc[i, "TERM"])), radius=r) for i, r in enumerate(R)]
col = PatchCollection(circles, array=c, cmap=cmap)
ax.add_collection(col)
fig.colorbar(col)

plt.show()

样本输出:

代码不会检查原始数据库的完整性,即每个标签-词对确实只出现一次。

【讨论】:

要创建第二个圆圈大小的图例,我可以简单地使用 plt.legend() 吗? 你是个科学家。你没有尝试测试它是否有效吗? (提示:这不是因为我们不提供任何标签。)This 和 this 向您展示了如何从头开始创建图例的原则,但这可能必须适应您当前的代码。 我意识到这个问题是多么幼稚。谢谢你的链接! 相反。回想起来,大多数问题看起来都很幼稚。关键是从答案中学习。祝你好运实现传奇。如果它不起作用 - 再问一个问题。 恐怕他们不是。 Marker sizes and patch radius will most certainly be differently interpreted(他们是:我用较小的样本试了一下,但尺寸根本不匹配)。您应该插入正确大小的圆形补丁作为图例句柄。不难。对不起,如果是我让你走错了路。【参考方案2】:

@Mr. 的改编答案。 T 包括图例生成器

from matplotlib.legend_handler import HandlerPatch
import matplotlib.patches as mpatches

ylabels = A2780_GOBP["TERM"].unique().tolist()
xlabels = A2780_GOBP["Label"].unique().tolist()
xn = len(xlabels)
yn = len(ylabels)    
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values

fig, ax = plt.subplots(figsize=(20,10))
ax.set_xlim(-0.5, xn-0.5)
ax.set_ylim(-0.5, yn-0.5)
ax.set(xticks=np.arange(xn), yticks=np.arange(yn), yticklabels=ylabels)
ax.set_xticklabels(xlabels, rotation='vertical')
ax.set_xticks(np.arange(xn)-0.5, minor=True)
ax.set_yticks(np.arange(yn)-0.5, minor=True)
ax.grid(which='minor')
ax.set_aspect("equal", "box")

R = s/s.max()/2
circles = [plt.Circle((xlabels.index(A2780_GOBP.loc[i, "Label"]), ylabels.index(A2780_GOBP.loc[i, "TERM"])), radius=r) for i, r in enumerate(R)]
col = PatchCollection(circles, array=c, cmap=cmap)
sc=ax.add_collection(col)
cbar=fig.colorbar(col).set_label('$-log_10(p-value)$', rotation=270, size=16,labelpad=15)

smax=s.max()
smin=s.min()
smid=(smax+smin)/2
texts = ["3","10","17"]


class HandlerEllipse(HandlerPatch):
    def create_artists(self, legend, orig_handle,
                       xdescent, ydescent, width, height, fontsize, trans):
        center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
        p = mpatches.Ellipse(xy=center, width=orig_handle.width,
                                        height=orig_handle.height)
        self.update_prop(p, orig_handle, legend)
        p.set_transform(trans)
        return [p]
    
c = [mpatches.Ellipse((), width=smin, height=smin, color="grey"),
     mpatches.Ellipse((), width=smid, height=smid, color="grey"),
     mpatches.Ellipse((), width=smax, height=smax, color="grey"),
    ]

legend = ax.legend(c,texts, handler_map=mpatches.Ellipse: HandlerEllipse(),title="Number of Proteins",bbox_to_anchor=(3.50, 0.82, 1.0, .102),fontsize="large")
plt.setp(legend.get_title(),fontsize='large')
plt.show()

输出:

【讨论】:

哇哦,加油! 改进可能是将 smin/mid/max 列为文本列表中的字符串。同样,创建 c 的更简洁的方法。 我很抱歉我说过将补丁包含到图例中很容易。后来我发现这对圈子来说不是微不足道的。很高兴您看到了不受支持的补丁的解决方法。恕我直言,代码现在看起来不错。回到移液器。 不需要道歉。学习如何做很有趣。 我发现的一件事是它不会转移到具有不同值的相似数据集。图例中的圆圈必须手动缩放。我会玩弄它,但我可能不得不发布另一个问题来解决这个问题。

以上是关于使用 Matplotlib 在带有图例的热图中显示不同大小的圆圈的主要内容,如果未能解决你的问题,请参考以下文章

带有 X、Y 数据的 Matplotlib 热图

每个单元格中带有文本的热图,带有 matplotlib 的 pyplot

如何在 Python matplotlib 子图中显示图例

matplotlib + wxpython 没有用图例正确调整大小

ComplexHeatmap:如何以不同的方式放置热图图例和注释图例?

Matplotlib 相关热图中缺少标签