如何在热力图上为特定值分配颜色

3

我正在使用 seaborn 制作热力图。我正在使用 'viridis',但对它进行了轻微修改,使一些值具有特定的颜色。在我的最小可行示例中, .set_over 用于将值大于90的部分设置为 'black',而 .set_under 用于将值小于10的部分设置为 'white'。我还遮罩了热力图的一部分。这一切都运行良好。

我如何将中间范围的值20映射到 'orange',而不影响当前的色条外观?正如你所看到的,.set_over.set_under 不会改变色条的外观。

import matplotlib
import seaborn as sns
import numpy as np
np.random.seed(7)
A = np.random.randint(0,100, size=(20,20))
mask_array = np.zeros((20, 20), dtype=bool)
mask_array[:, :5] = True
cmap = matplotlib.colormaps["viridis"]
# Set the under color to white
cmap.set_under("white")
# Set the voer color to white
cmap.set_over("black")
# Set the background color

g = sns.heatmap(A, vmin=10, vmax=90, cmap=cmap, mask=mask_array)
# Set color of masked region
g.set_facecolor('lightgrey')

enter image description here

我看到了如何在seaborn热图中将值映射到特定颜色, 但我不确定如何将其用于解决我的问题。

4个回答

3
请考虑以下内容:
import matplotlib as mpl
import seaborn as sns
import numpy as np

A = np.random.randint(0,100, size=(20,20))
mask_array = np.zeros((20, 20), dtype=bool)
mask_array[:, :5] = True
cmap = mpl.colormaps["viridis"]

newcolors = cmap(np.linspace(0, 1, 100))
newcolors[:10] = np.array([1,1,1,1])
newcolors[90:] = np.array([0,0,0,1])
newcolors[20] = mpl.colors.to_rgb('tab:orange') + (1,)

newcmap = mpl.colors.ListedColormap(newcolors)

g = sns.heatmap(A, cmap=newcmap, mask=mask_array)
# Set color of masked region
g.set_facecolor('lightgrey')

示例结果:

enter image description here

以下内容(附加到上述脚本)将使得色条具有可见的轮廓。
cbar_ax = g.figure.axes[-1]

for spine in cbar_ax.spines.values():
    spine.set(visible=True)

带大纲的示例结果:

enter image description here


为了遮盖热力图的颜色,但不显示更新的颜色条,请设置cbar=False,然后附加自定义颜色条,如独立颜色条所示。
g = sns.heatmap(A, cmap=newcmap, mask=mask_array, cbar=False)

# add a new axes of the desired shape
cb = g.figure.add_axes([0.93, 0.11, 0.025, 0.77])

# attach a new colorbar to the axes
mpl.colorbar.ColorbarBase(cb, cmap='viridis', norm=mpl.colors.Normalize(10, 90),  # vmax and vmin
                          label=None, ticks=range(10, 91, 10))

enter image description here


2
困難在於需要使用超過一個遮罩的要求。 - 如果陣列只是int,那麼需要一個用於A == 20的遮罩。 - 如果陣列是float,則需要一個用於20 <= A < 21的遮罩。 matplotlib.colors.Colormap僅提供3種設置顏色的方法: 1. set_under 2. set_over 3. set_bad - 設置遮罩值的顏色,已用於mask_array。 with_extremes將以上三種方法結合在一起使用: - cmap = mpl.colormaps['viridis'].with_extremes(bad='orange', under='w', over='k') - enter image description here 使用這些方法不會影響色條的外觀。

导入和示例数据

import numpy as np
import seaborn as sns
import matplotlib as mpl

# set for repeatable sample
np.random.seed(2023)

# random 20 x 20 array
# A = np.random.randint(0, 100, size=(20, 20))  # random integers
A = np.random.random(size=(20, 20)) * 100  # random floats

1. 不要显示切割的列

  • 这是最简单的选项,因为它释放了mask
# remove the unneeded columns
A = A[:, 5:]

# create the value mask
mask = np.logical_and(A >= 20, A < 21)

# create the colormap with extremes
cmap = mpl.colormaps["viridis"].with_extremes(bad='orange', under='w', over='k')

# plot
g = sns.heatmap(A, vmin=10, vmax=90, cmap=cmap, mask=mask)

# reset the xticklabels to show the correct column labels
_ = g.set_xticks(ticks=g.get_xticks(), labels=range(5, 20))

enter image description here

2. 显示所有列

  • 这是更繁琐的选项,因为像Ben提供的答案一样,它需要手动将颜色添加到cmap并添加自定义颜色条。
    • 使用mask或数组中的任何np.nan值都会被set_bad着色。
  • 这个方法重用了我之前添加到Ben答案中的颜色条创建方法,该方法来自独立颜色条
  • 在Matplotlib中创建Colormaps中所示,可以将新颜色添加到从重新采样的颜色映射中切片的颜色序列中。
    • 如果使用vminvmax,这不会得到正确的值,因为这些选项会改变颜色条的范围。
# create the column mask
mask = np.zeros((20, 20), dtype=bool)
mask[:, :5] = True  # A[:, :5] = np.nan has the same effect

# slice the colors into the range of values in the array
colors = mpl.colormaps["viridis"].resampled(100).colors

# map a specific value range to a color; use a range for floats, and / or set a hight number to resampled
colors[19:21] = mpl.colors.to_rgba('tab:orange')

# create the new colormap with extremes
cmap = mpl.colors.ListedColormap(colors).with_extremes(bad='lightgray', under='w', over='k')

# draw the heatmap
g = sns.heatmap(A, cmap=cmap, mask=mask, cbar=False)

# add a new axes of the desired shape
cb_ax = g.figure.add_axes([0.93, 0.11, 0.025, 0.77])

# attach a new colorbar to the axes without an outline
cb = mpl.colorbar.ColorbarBase(cb_ax, cmap='viridis', norm=mpl.colors.Normalize(10, 90),  # vmax and vmin
                               label=None, ticks=range(10, 91, 10)).outline.set_visible(False)

enter image description here

3. 让列和坏值共享颜色

  • enter image description here
# create the value mask
mask = np.logical_and(A >= 20, A < 21)

# add the unwanted column to the mask as np.nan
mask[:, :5] = np.nan

# create the colormap with extremes
cmap = mpl.colormaps["viridis"].with_extremes(bad='orchid', under='w', over='k')

# plot
g = sns.heatmap(A, vmin=10, vmax=90, cmap=cmap, mask=mask)

enter image description here


2
这个答案中借鉴,这里提供了一个使用掩码而不是自定义色条的解决方案:
import matplotlib
import seaborn as sns
import numpy as np
from matplotlib.colors import ListedColormap

np.random.seed(7)
A = np.random.randint(0,100, size=(20,20))
mask_array = np.zeros((20, 20), dtype=bool)
mask_array[:, :5] = True
# cmap = matplotlib.colormaps["viridis"]
cmap = matplotlib.cm.get_cmap('viridis')


# Set the under color to white
cmap.set_under("white")

# Set the voer color to white
cmap.set_over("black")

# Set the background color

g = sns.heatmap(A, vmin=10, vmax=90, cmap=cmap, mask=mask_array)
# Set color of masked region
g.set_facecolor('lightgrey')

special_data = np.ma.masked_where(A==20, A)
sns.heatmap(special_data, cmap=ListedColormap(['orange']), 
            mask=(special_data != 1), cbar=False)

0
使用matplotlib.colors.LinearSegmentedColormap.from_list创建自定义颜色映射。当使用连续的颜色映射(如'viridis')时,没有直接的方法将特定值(如20)设置为特定颜色(如橙色)。
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import seaborn as sns
import numpy as np

np.random.seed(7)
A = np.random.randint(0,100, size=(20,20))
mask_array = np.zeros((20, 20), dtype=bool)
mask_array[:, :5] = True

# Create a colormap for each part
colormap_under_10 = plt.cm.viridis(np.linspace(0, 0.1, 10))
colormap_10_30 = plt.cm.Oranges(np.linspace(0.5, 1, 20))
colormap_above_30 = plt.cm.viridis(np.linspace(0.1, 1, 70))

# Combine them and build a new colormap
colors_combined = np.vstack((colormap_under_10, colormap_10_30, colormap_above_30))
mymap = colors.LinearSegmentedColormap.from_list('colormap_combined', colors_combined)

ax = sns.heatmap(A, vmin=0, vmax=100, cmap=mymap, mask=mask_array)

# Set color of masked region
ax.set_facecolor('lightgrey')

plt.show()

enter image description here


这不是我想要的。@BenGrossmann的答案很完美,除了颜色条。 - Simd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接