笔记
单击此处 下载完整的示例代码
在 Matplotlib 中选择颜色图#
Matplotlib 有许多内置的颜色图,可通过
matplotlib.colormaps
. 还有一些外部库有许多额外的颜色图,可以在
Matplotlib 文档的第三方颜色图部分查看。在这里,我们简要讨论如何在众多选项之间进行选择。有关创建您自己的颜色图的帮助,请参阅
在 Matplotlib 中创建颜色图。
概述#
选择一个好的颜色图背后的想法是在 3D 颜色空间中为您的数据集找到一个好的表示。任何给定数据集的最佳颜色图取决于许多因素,包括:
无论是表示形式数据还是度量数据([Ware])
您对数据集的了解(例如,是否存在其他值偏离的临界值?)
如果您正在绘制的参数有直观的配色方案
如果该领域有一个标准,观众可能会期待
对于许多应用程序,感知一致的颜色图是最佳选择;即颜色图,其中数据中的相等步长被视为颜色空间中的相等步长。研究人员发现,人脑将亮度参数的变化视为数据的变化,比例如色调的变化要好得多。因此,通过颜色图具有单调增加亮度的颜色图将被观看者更好地解释。在第三方颜色图部分也可以找到感知统一颜色图的精彩示例 。
颜色可以以各种方式在 3D 空间中表示。一种表示颜色的方法是使用 CIELAB。在 CIELAB 中,色彩空间由明度表示, \(L^*\); 红绿,\(a^*\); 和黄蓝色,\(b^*\). 亮度参数\(L^*\)然后可以用来了解更多关于 matplotlib 颜色图将如何被观众感知的信息。
[IBM]是学习人类对色彩图感知的一个极好的起始资源。
颜色图的类#
颜色图通常根据其功能分为几个类别(参见, 例如,[Moreland]):
连续的:亮度变化和颜色的饱和度通常是递增的,通常使用单一色调;应该用于表示有顺序的信息。
发散:在中间以不饱和颜色相遇的两种不同颜色的亮度和可能饱和度的变化;当绘制的信息具有关键的中间值(例如地形)或数据偏离零附近时,应使用。
循环:两种不同颜色在中间相交并以不饱和颜色开始/结束的亮度变化;应该用于在端点处环绕的值,例如相位角、风向或一天中的时间。
定性:往往是杂色;应该用来表示没有顺序或关系的信息。
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from colorspacious import cspace_converter
首先,我们将显示每个颜色图的范围。请注意,有些似乎比其他人更“快速”地改变。
cmaps = {}
gradient = np.linspace(0, 1, 256)
gradient = np.vstack((gradient, gradient))
def plot_color_gradients(category, cmap_list):
# Create figure and adjust figure height to number of colormaps
nrows = len(cmap_list)
figh = 0.35 + 0.15 + (nrows + (nrows - 1) * 0.1) * 0.22
fig, axs = plt.subplots(nrows=nrows + 1, figsize=(6.4, figh))
fig.subplots_adjust(top=1 - 0.35 / figh, bottom=0.15 / figh,
left=0.2, right=0.99)
axs[0].set_title(f'{category} colormaps', fontsize=14)
for ax, name in zip(axs, cmap_list):
ax.imshow(gradient, aspect='auto', cmap=mpl.colormaps[name])
ax.text(-0.01, 0.5, name, va='center', ha='right', fontsize=10,
transform=ax.transAxes)
# Turn off *all* ticks & spines, not just the ones with colormaps.
for ax in axs:
ax.set_axis_off()
# Save colormap list for later.
cmaps[category] = cmap_list
顺序#
对于顺序图,亮度值通过颜色图单调增加。这很好。某些\(L^*\)颜色图中的值从 0 到 100(二进制和其他灰度),其他值从 0 到 100 \(L^*=20\). 那些范围较小的\(L^*\)相应地会有更小的感知范围。另请注意,\(L^*\)函数在颜色图中有所不同:有些在\(L^*\)和其他更弯曲。
plot_color_gradients('Perceptually Uniform Sequential',
['viridis', 'plasma', 'inferno', 'magma', 'cividis'])
plot_color_gradients('Sequential',
['Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'])
顺序2 #
许多\(L^*\)Sequential2 图中的值单调递增,但一些(秋季、凉爽、春季和冬季)高原甚至在\(L^*\)空间。其他(afmhot、copper、gist_heat 和 hot)在\(L^*\)功能。在处于高原或扭结的颜色图区域中表示的数据将导致在颜色图中的这些值中感知数据的条带(参见[mycarta-banding]以获得一个很好的例子)。
plot_color_gradients('Sequential (2)',
['binary', 'gist_yarg', 'gist_gray', 'gray', 'bone',
'pink', 'spring', 'summer', 'autumn', 'winter', 'cool',
'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper'])
发散#
对于发散图,我们希望单调递增\(L^*\) 值达到最大值,应该接近\(L^*=100\),然后单调递减\(L^*\)价值观。我们正在寻找大约相等的最小值\(L^*\)颜色图两端的值。通过这些措施,BrBG 和 RdBu 是不错的选择。coolwarm 是一个不错的选择,但它并不涵盖广泛的\(L^*\)值(见下面的灰度部分)。
plot_color_gradients('Diverging',
['PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu',
'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic'])
循环#
对于循环地图,我们希望以相同的颜色开始和结束,并在中间遇到一个对称的中心点。\(L^*\)应该从开始到中间单调变化,从中间到结束反向变化。它应该在增加和减少方面是对称的,并且仅在色调上有所不同。在末端和中间,\(L^*\)将反转方向,这应该是平滑的 \(L^*\)空间以减少伪影。有关循环映射设计的更多信息,请参阅[kovesi-colormaps]。
常用的 HSV 颜色图包含在这组颜色图中,尽管它与中心点不对称。此外,\(L^*\)值在整个颜色图中变化很大,使其成为表示数据以供查看者感知的糟糕选择。在[mycarta-jet]上查看这个想法的扩展 。
plot_color_gradients('Cyclic', ['twilight', 'twilight_shifted', 'hsv'])
定性#
定性颜色图并非旨在成为感知图,但查看亮度参数可以为我们验证这一点。这\(L^*\)值在整个颜色图中随处移动,并且显然不是单调递增的。这些不是用作感知色彩图的好选择。
plot_color_gradients('Qualitative',
['Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2',
'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b',
'tab20c'])
杂项#
一些杂项颜色图具有创建它们的特定用途。例如,gist_earth、海洋和地形似乎都是为了将地形(绿色/棕色)和水深(蓝色)绘制在一起而创建的。那么,我们希望在这些颜色图中看到差异,但多个扭结可能并不理想,例如 gist_earth 和地形。创建 CMRmap 是为了很好地转换为灰度,尽管它似乎确实有一些小问题 \(L^*\). cubehelix 被创建为在亮度和色调上平滑变化,但在绿色色调区域似乎有一个小驼峰。turbo 是为了显示深度和视差数据而创建的。
常用的 jet 颜色图包含在这组颜色图中。我们可以看到\(L^*\)值在整个颜色图中变化很大,使其成为表示数据以供查看者感知的糟糕选择。在[mycarta-jet]和[turbo]上查看这个想法的扩展。
plot_color_gradients('Miscellaneous',
['flag', 'prism', 'ocean', 'gist_earth', 'terrain',
'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap',
'cubehelix', 'brg', 'gist_rainbow', 'rainbow', 'jet',
'turbo', 'nipy_spectral', 'gist_ncar'])
plt.show()
Matplotlib 颜色图的亮度#
在这里,我们检查了 matplotlib 颜色图的亮度值。请注意,有一些关于颜色图的文档可用 ( [list-colormaps] )。
mpl.rcParams.update({'font.size': 12})
# Number of colormap per subplot for particular cmap categories
_DSUBS = {'Perceptually Uniform Sequential': 5, 'Sequential': 6,
'Sequential (2)': 6, 'Diverging': 6, 'Cyclic': 3,
'Qualitative': 4, 'Miscellaneous': 6}
# Spacing between the colormaps of a subplot
_DC = {'Perceptually Uniform Sequential': 1.4, 'Sequential': 0.7,
'Sequential (2)': 1.4, 'Diverging': 1.4, 'Cyclic': 1.4,
'Qualitative': 1.4, 'Miscellaneous': 1.4}
# Indices to step through colormap
x = np.linspace(0.0, 1.0, 100)
# Do plot
for cmap_category, cmap_list in cmaps.items():
# Do subplots so that colormaps have enough space.
# Default is 6 colormaps per subplot.
dsub = _DSUBS.get(cmap_category, 6)
nsubplots = int(np.ceil(len(cmap_list) / dsub))
# squeeze=False to handle similarly the case of a single subplot
fig, axs = plt.subplots(nrows=nsubplots, squeeze=False,
figsize=(7, 2.6*nsubplots))
for i, ax in enumerate(axs.flat):
locs = [] # locations for text labels
for j, cmap in enumerate(cmap_list[i*dsub:(i+1)*dsub]):
# Get RGB values for colormap and convert the colormap in
# CAM02-UCS colorspace. lab[0, :, 0] is the lightness.
rgb = mpl.colormaps[cmap](x)[np.newaxis, :, :3]
lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb)
# Plot colormap L values. Do separately for each category
# so each plot can be pretty. To make scatter markers change
# color along plot:
# https://stackoverflow.com/q/8202605/
if cmap_category == 'Sequential':
# These colormaps all start at high lightness but we want them
# reversed to look nice in the plot, so reverse the order.
y_ = lab[0, ::-1, 0]
c_ = x[::-1]
else:
y_ = lab[0, :, 0]
c_ = x
dc = _DC.get(cmap_category, 1.4) # cmaps horizontal spacing
ax.scatter(x + j*dc, y_, c=c_, cmap=cmap, s=300, linewidths=0.0)
# Store locations for colormap labels
if cmap_category in ('Perceptually Uniform Sequential',
'Sequential'):
locs.append(x[-1] + j*dc)
elif cmap_category in ('Diverging', 'Qualitative', 'Cyclic',
'Miscellaneous', 'Sequential (2)'):
locs.append(x[int(x.size/2.)] + j*dc)
# Set up the axis limits:
# * the 1st subplot is used as a reference for the x-axis limits
# * lightness values goes from 0 to 100 (y-axis limits)
ax.set_xlim(axs[0, 0].get_xlim())
ax.set_ylim(0.0, 100.0)
# Set up labels for colormaps
ax.xaxis.set_ticks_position('top')
ticker = mpl.ticker.FixedLocator(locs)
ax.xaxis.set_major_locator(ticker)
formatter = mpl.ticker.FixedFormatter(cmap_list[i*dsub:(i+1)*dsub])
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_tick_params(rotation=50)
ax.set_ylabel('Lightness $L^*$', fontsize=12)
ax.set_xlabel(cmap_category + ' colormaps', fontsize=14)
fig.tight_layout(h_pad=0.0, pad=1.5)
plt.show()
灰度转换#
重要的是要注意将彩色图转换为灰度,因为它们可能会在黑白打印机上打印。如果不仔细考虑,您的读者可能会得到难以理解的图,因为灰度通过颜色图发生不可预测的变化。
转换为灰度有许多不同的方式[bw]。一些更好的使用像素的 rgb 值的线性组合,但根据我们如何感知颜色强度进行加权。转换为灰度的非线性方法是使用\(L^*\)像素的值。一般来说,类似的原则适用于这个问题,就像它们在感知上呈现一个人的信息一样。也就是说,如果选择的颜色图在\(L^*\)值,它将以合理的方式打印为灰度。
考虑到这一点,我们看到顺序颜色图在灰度上有合理的表示。一些 Sequential2 颜色图具有足够好的灰度表示,尽管有些(秋季、春季、夏季、冬季)几乎没有灰度变化。如果在绘图中使用这样的颜色图,然后将绘图打印为灰度,则许多信息可能会映射到相同的灰度值。发散色图大多从外边缘的深灰色到中间的白色变化。一些(PuOr 和地震)在一侧的灰色明显比另一侧深,因此不是很对称。coolwarm 的灰度范围很小,会打印到更均匀的图上,会丢失很多细节。请注意,重叠的、标记的轮廓可以帮助区分颜色图的一侧与另一侧。另一个,因为一旦将绘图打印为灰度,就无法使用颜色。许多定性和杂项颜色图(例如 Accent、hsv、jet 和 turbo)在整个颜色图中从较暗变为较亮,然后又变为较深的灰色。一旦以灰度打印,这将使查看者无法解释绘图中的信息。
mpl.rcParams.update({'font.size': 14})
# Indices to step through colormap.
x = np.linspace(0.0, 1.0, 100)
gradient = np.linspace(0, 1, 256)
gradient = np.vstack((gradient, gradient))
def plot_color_gradients(cmap_category, cmap_list):
fig, axs = plt.subplots(nrows=len(cmap_list), ncols=2)
fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99,
wspace=0.05)
fig.suptitle(cmap_category + ' colormaps', fontsize=14, y=1.0, x=0.6)
for ax, name in zip(axs, cmap_list):
# Get RGB values for colormap.
rgb = mpl.colormaps[name](x)[np.newaxis, :, :3]
# Get colormap in CAM02-UCS colorspace. We want the lightness.
lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb)
L = lab[0, :, 0]
L = np.float32(np.vstack((L, L, L)))
ax[0].imshow(gradient, aspect='auto', cmap=mpl.colormaps[name])
ax[1].imshow(L, aspect='auto', cmap='binary_r', vmin=0., vmax=100.)
pos = list(ax[0].get_position().bounds)
x_text = pos[0] - 0.01
y_text = pos[1] + pos[3]/2.
fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10)
# Turn off *all* ticks & spines, not just the ones with colormaps.
for ax in axs.flat:
ax.set_axis_off()
plt.show()
for cmap_category, cmap_list in cmaps.items():
plot_color_gradients(cmap_category, cmap_list)
色觉缺陷#
有很多关于色盲的信息(例如, [colorblindness])。此外,还有一些工具可用于将图像转换为他们寻找不同类型的色觉缺陷的方式。
色觉缺陷最常见的形式包括区分红色和绿色。因此,避免使用红色和绿色的颜色图通常可以避免许多问题。
参考文献#
https://mycarta.wordpress.com/2012/10/06/the-rainbow-is-deadlong-live-the-rainbow-part-3/
脚本总运行时间:(0分14.251秒)