Matplotlib is hiring a Research Software Engineering Fellow! See discourse for details. Apply by January 3, 2020
Matplotlib有许多内置的颜色映射,可通过 matplotlib.cm.get_cmap
. 还有一些外部库,比如 [palettable] 有很多额外的彩色地图。在这里,我们将简要讨论如何在许多选项之间进行选择。有关创建自己的颜色映射的帮助,请参见 在Matplotlib中创建颜色映射 .
选择一个好的颜色映射的想法是为你的数据集在3D颜色空间中找到一个好的表示。任何给定数据集的最佳颜色映射取决于许多因素,包括:
对于许多应用程序来说,感知一致的颜色映射是最佳选择——其中数据中的相等步骤被视为颜色空间中的相等步骤。研究人员发现,人脑感知到亮度参数的变化是数据变化,比色调变化要好得多。因此,通过颜色映射单调增加亮度的颜色映射将更好地被观察者解释。一个很好的例子是感知一致的色彩图 [colorcet].
颜色可以用各种方式在三维空间中表示。一种表示颜色的方法是使用CIELAB。在cielab中,颜色空间用亮度表示, \(L^*\) 红绿色 \(a^*\) 和黄蓝, \(b^*\) . 亮度参数 \(L^*\) 然后可以用来了解更多关于Matplotlib彩色地图如何被观众感知的信息。
学习人类对彩色地图感知的一个很好的开始资源是 [IBM].
颜色映射通常根据其功能分为几个类别(请参见, e.g. , [Moreland]) :
# sphinx_gallery_thumbnail_number = 2
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
from colorspacious import cspace_converter
from collections import OrderedDict
cmaps = OrderedDict()
对于顺序图,亮度值通过颜色映射单调增加。这很好。一些 \(L^*\) 颜色映射中的值从0到100(二进制和其他灰度)不等,其他值从0开始。 \(L^*=20\) . 范围较小的 \(L^*\) 因此会有一个较小的感知范围。还要注意的是 \(L^*\) 不同颜色映射的函数不同:有些在 \(L^*\) 还有一些更弯曲。
cmaps['Perceptually Uniform Sequential'] = [
'viridis', 'plasma', 'inferno', 'magma', 'cividis']
cmaps['Sequential'] = [
'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']
许多 \(L^*\) 顺序图中的数值是单调递增的,但有些(秋、凉、春、冬)是高原,甚至在 \(L^*\) 空间。其他(afmhot、铜、gist_heat和hot)在 \(L^*\) 功能。在处于平稳或扭结状态的颜色映射区域中表示的数据将导致对颜色映射中这些值中的数据进行带状感知(请参见 [mycarta-banding] 举个很好的例子)。
cmaps['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^*\) 值(请参见下面的灰度部分)。
cmaps['Diverging'] = [
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']
对于循环映射,我们希望以相同的颜色开始和结束,并在中间满足对称的中心点。 \(L^*\) 应该从开始到中间单调变化,从中间到结束反向变化。它应该是对称的,在增加和减少方面,只有色调不同。在两端和中间, \(L^*\) 会反转方向,应该平滑 \(L^*\) 空间以减少伪影。见 [kovesi-colormaps] 有关循环图设计的更多信息。
通常使用的hsv颜色映射包含在这组颜色映射中,尽管它与中心点不对称。另外, \(L^*\) 在整个颜色图中,值变化很大,因此对于表示数据以供观看者感知是一个糟糕的选择。关于这个想法的扩展见 [mycarta-jet].
cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv']
定性的色彩图并不是针对感性的地图,但是观察亮度参数可以为我们验证这一点。这个 \(L^*\) 值在整个颜色图中到处移动,并且明显不是单调递增的。这些将不是好的选择作为知觉色彩图使用。
cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']
其中一些杂项颜色映射具有创建它们的特定用途。例如,地球、海洋和地形似乎都是为绘制地形(绿色/棕色)和水深(蓝色)而创建的。我们希望在这些彩色地图上看到分歧,但是多个扭结可能不是理想的,例如在主要的地球和地形上。创建cmrmap是为了将Well转换为灰度,尽管它在 \(L^*\) . Cubehelix的创建是为了在亮度和色调上平滑地变化,但在绿色色调区域似乎有一个小驼峰。
常用的Jet颜色映射包含在这组颜色映射中。我们可以看到 \(L^*\) 在整个颜色图中,值变化很大,因此对于表示数据以供观看者感知是一个糟糕的选择。关于这个想法的扩展见 [mycarta-jet].
cmaps['Miscellaneous'] = [
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']
首先,我们将显示每个颜色映射的范围。请注意,有些变化似乎比其他变化更快。
nrows = max(len(cmap_list) for cmap_category, cmap_list in cmaps.items())
gradient = np.linspace(0, 1, 256)
gradient = np.vstack((gradient, gradient))
def plot_color_gradients(cmap_category, cmap_list, nrows):
fig, axes = plt.subplots(nrows=nrows)
fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99)
axes[0].set_title(cmap_category + ' colormaps', fontsize=14)
for ax, name in zip(axes, cmap_list):
ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(name))
pos = list(ax.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 axes:
ax.set_axis_off()
for cmap_category, cmap_list in cmaps.items():
plot_color_gradients(cmap_category, cmap_list, nrows)
plt.show()
这里我们检查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, axes = plt.subplots(nrows=nsubplots, squeeze=False,
figsize=(7, 2.6*nsubplots))
for i, ax in enumerate(axes.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 = cm.get_cmap(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:
# http://stackoverflow.com/questions/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(axes[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_xlabel(cmap_category + ' colormaps', fontsize=14)
fig.text(0.0, 0.55, 'Lightness $L^*$', fontsize=12,
transform=fig.transFigure, rotation=90)
fig.tight_layout(h_pad=0.0, pad=1.5)
plt.show()
对于彩色图,注意转换为灰度是很重要的,因为它们可以用黑白打印机打印。如果不仔细考虑,你的读者可能会以不可分辨的绘图而告终,因为灰度在彩色地图中的变化是不可预测的。
转换为灰度有很多不同的方法 [bw]. 其中一些更好的使用像素的RGB值的线性组合,但根据我们如何感知颜色强度进行加权。一种非线性的灰度转换方法是使用 \(L^*\) 像素值。一般来说,类似的原则也适用于这个问题,因为它们可以感知地呈现一个人的信息;也就是说,如果选择了一个颜色映射,它在 \(L^*\) 值,它将以合理的方式打印成灰度。
考虑到这一点,我们可以看到连续颜色映射在灰度上有合理的表示。一些连续的2色地图有相当好的灰度表示,尽管一些(秋季、春季、夏季、冬季)的灰度变化非常小。如果在绘图中使用了这样的颜色映射,然后将绘图打印为灰度,则许多信息可能映射到相同的灰度值。发散色差图主要从外边缘的暗灰色到中间的白色变化。有些(puor和seismic)的一侧的灰色明显较深,因此不是很对称。CoolWarm的灰度范围很小,打印时会更加均匀,丢失很多细节。请注意,重叠的、有标签的轮廓有助于区分颜色图的一侧与另一侧,因为一旦打印到灰度,就不能使用颜色。许多定性和复杂的颜色图,如重音、hsv和jet,在整个颜色图中从较暗变为较浅,再变回较暗的灰色。这将使观众不可能解释的信息,一旦打印成灰度图。
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, axes = 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(axes, cmap_list):
# Get RGB values for colormap.
rgb = cm.get_cmap(plt.get_cmap(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=plt.get_cmap(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 axes.flat:
ax.set_axis_off()
plt.show()
for cmap_category, cmap_list in cmaps.items():
plot_color_gradients(cmap_category, cmap_list)
有很多关于色盲的信息( e.g. , [colorblindness]) . 此外,还有一些工具可以将图像转换为不同类型的彩色视觉缺陷。( e.g. , [vischeck]) .
最常见的色觉缺陷包括区分红色和绿色。因此,避免同时使用红色和绿色的彩色地图可以避免一般情况下的许多问题。
[colorcet] | https://github.com/bokeh/colorcet |
[Ware] | http://ccom.unh.edu/sites/default/files/publications/Ware_1988_CGA_Color_sequences_univariate_maps.pdf |
[Moreland] | http://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf |
[list-colormaps] | https://gist.github.com/endolith/2719900_id7 |
[mycarta-banding] | https://mycarta.wordpress.com/2012/10/14/the-rainbow-is-deadlong-live-the-rainbow-part-4-cie-lab-heated-body/ |
[mycarta-jet] | (1, 2) https://mycarta.wordpress.com/2012/10/06/the-rainbow-is-deadlong-live-the-rainbow-part-3/ |
[kovesi-colormaps] | https://arxiv.org/abs/1509.03700 |
[bw] | http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ |
[colorblindness] | http://www.color-blindness.com/ |
[vischeck] | http://www.vischeck.com/vischeck/ |
[IBM] | https://dx.doi.org/10.1109/VISUAL.1995.480803 |
[palettable] | https://jiffyclub.github.io/palettable/ |
脚本的总运行时间: (0分7.691秒)