【问题标题】:Python heatmap with intermediate color by percentile百分位中间颜色的Python热图
【发布时间】:2018-12-21 11:38:43
【问题描述】:

我正在尝试在 python 中使用 50% 百分位的中间颜色制作热图。我经常用 excel 做这件事,但我不能让它在我的自动化 python 代码上工作。

在 (red,yellow,Green) 你可以看到我的 excel 版本,黄色和蓝色是我的 python 版本。

澄清一下,我不介意两种颜色的退化,我只想对 50% 的顶部百分位数和底部给予同样的重视。

我的代码简化代码是:

import pandas as pd
import seaborn as sns

data = {
        'row1': [90,95,99,50,50,45,0],
        'row2': [99,98,100,100,98,99,80],
        'row3': [98,97,99,100,96,95,98],
        'row4': [99,98,100,100,98,99,100]
        }
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')
sns.heatmap(df.round(), annot=True,ax=ax, cmap="YlGnBu")

感谢您的任何帮助!

【问题讨论】:

  • 我不明白这个问题。您是否只对 Python 绘图的颜色不满意?您可以简单地将cmap 更改为您喜欢的内容。这是一个可供选择的列表:matplotlib.org/tutorials/colors/colormaps.html 如果需要,您也可以创建自己的列表。顺便说一句,对于最常见的色盲形式的人来说,红绿色是一个糟糕的选择。
  • 嗨 StefanS,我认为以下段落:“澄清一下,我不介意两种颜色的退化,我只想对 50% 的最高百分位数给予同样的重视至于底部。”很清楚地说明问题,我希望底部的 50% 百分位具有重要意义。
  • 但是“重要性”只通过颜色来表示,对吧?
  • 是的。换一种说法,我希望颜色分级的中间值(50%)是中值,而不是平均值。在我的示例中,中位数为 97.5,因此低于该值我将拥有与上面相同的“颜色数”。
  • 我没有时间写一个完整的答案,但如果你想玩,这里有一个关于如何制作你自己的颜色图的指南:matplotlib.org/examples/pylab_examples/custom_cmap.html

标签: python pandas heatmap seaborn percentile


【解决方案1】:

你可以这样做:

import matplotlib as mpl
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')
cmap1 = mpl.colors.ListedColormap(['y'])
sns.heatmap(df.round(), annot=True,ax=ax, cmap="YlGnBu")
sns.heatmap(df.round(), mask=df.round() > 50, cmap=cmap1, cbar=True)
plt.show()

【讨论】:

  • 嗨 SudipM,谢谢你的想法,我一直在玩它,但我认为它不适合我,我不喜欢有两个颜色条的想法,我认为这会令人困惑。我正在寻找的是一种以百分位数而不是值线性进行颜色插值的方法。但再次感谢您的想法!
【解决方案2】:

按照@StefanS 提供的链接,我想出了以下方法来注册我自己的 cmap,在我的例子中,使用中位数:

median = df.median().median()/100.0
c_red_yl_ = {'red':   ((0.0, 0.8, 0.8),
                   (median, 1.0, 1.0),
                   (1.0, 0.0, 0.0)),

         'green': ((0.0, 0.0, 0.0),
                   (median, 1.0, 1.0),
                   (1.0, 0.8, 0.8)),

         'blue':  ((0.0, 0.0, 0.0),
                   (median, 0.0, 0.0),
                   (1.0, 0.0, 0.0))
        }
plt.register_cmap(name='custom', data=cdict1)

我希望它对其他人有用。

【讨论】:

  • 您为什么要这样做? Matplotlib 带有大量不同的颜色图 (matplotlib.org/examples/color/colormaps_reference.html),它们都将中点作为其自身的清晰可识别的颜色(白色)。它们在色彩空间中的插值也比您的自定义贴图更自然。阅读这篇文章,了解为什么您可能不想自己制作 (vis4.net/blog/2013/09/mastering-multi-hued-color-scales),而是坚持使用 color brewer 的建议
  • 是的,颜色不是最佳选择,但您可以使用任何给定的颜色集@Dan,谁在乎?我要求(并回答)是关于使用中值作为热图中的中点。
  • “但是您可以使用任何给定的颜色集@Dan,谁在乎?”。当然,你可以为所欲为。但是,如果您希望人们能够理解您创建的内容,那么在其他人已经完成的工作的基础上进行构建是值得的。这不仅仅是使用不同颜色的情况,而是您的代码无法控制的插值方式。
  • @Dan,我不明白你的意思,代码完全符合我的要求,我可以按照我需要的方式控制着色,即着色的中间值在中位数,是的,当然,我不会在演示文稿中使用它们的颜色,但谁在乎呢?这不是关于特定颜色,而是关于渐变的变化位置,而我的代码正是这样做的。
  • 我猜关键是改变颜色图本身通常不是改变映射的最佳方式。相反,可以使用不同的归一化,如 in my answer 所示。
【解决方案3】:

通常不希望更改颜色图本身。相反,人们会将值的标准化更改为颜色。为此,可以使用midpoint normalization。明显的优势是,此概念适用于任何颜色图,无需为使用中的每个不同中值创建自定义。

不幸的是,seaborn 不允许使用自定义规范化。但是使用 matplotlib 本身创建热图同样容易,如 annotated_heatmap 示例所示。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors

class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

data = {
        'row1': [90,95,99,50,50,45,0],
        'row2': [99,98,100,100,98,99,80],
        'row3': [98,97,99,100,96,95,98],
        'row4': [99,98,100,100,98,99,100]
        }
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')

norm =  MidpointNormalize(midpoint=np.median(df.values))

im = ax.imshow(df.values, cmap="YlGnBu", norm=norm)
fig.colorbar(im)

# Loop over data dimensions and create text annotations.
textcolors = ["k" ,"w"]
threshold = 55
for i in range(len(df)):
    for j in range(len(df.columns)):
        text = ax.text(j, i, df.values[i, j],
                       ha="center", va="center", 
                       color=textcolors[df.values[i, j] > threshold])

plt.show()

【讨论】:

  • 感谢@ImportanceOfBeingErnest,这是一个非常有建设性的答案(再次)。感谢您的帮助。
猜你喜欢
  • 2013-09-16
  • 2016-04-21
  • 2018-01-23
  • 2022-12-16
  • 2016-07-20
  • 1970-01-01
  • 2019-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多