在Matplotlib中选择颜色映射

Matplotlib有许多内置的颜色映射,可通过 matplotlib.cm.get_cmap . 还有一些外部库,比如 [palettable][colorcet] 有很多额外的颜色图。在这里,我们将简要讨论如何在众多选项中进行选择。有关创建自己颜色贴图的帮助,请参见 在Matplotlib中创建颜色映射 .

概述

选择一个好的颜色映射的想法是为你的数据集在3D颜色空间中找到一个好的表示。任何给定数据集的最佳颜色映射取决于许多因素,包括:

  • 表示形式或度量数据 ([Ware])
  • 您对数据集的了解( e.g. ,是否存在其他值偏离的临界值?)
  • 如果要绘制的参数有直观的颜色方案
  • 如果该领域有一个标准,观众可能会期待

对于许多应用,感知一致的颜色映射是最佳选择;即,将数据中的相等步长视为颜色空间中的相等步长的颜色映射。研究人员发现,人脑将亮度参数的变化视为数据的变化,比例如色调的变化要好得多。因此,通过颜色映射具有单调增加的亮度的颜色映射将被观看者更好地解释。一个很好的例子是感知一致的颜色映射 [colorcet].

颜色可以用各种方式在三维空间中表示。一种表示颜色的方法是使用CIELAB。在cielab中,颜色空间用亮度表示, \(L^*\) 红绿色 \(a^*\) 和黄蓝, \(b^*\) . 亮度参数 \(L^*\) 然后可以用来了解更多关于Matplotlib彩色地图如何被观众感知的信息。

学习人类对彩色地图感知的一个很好的开始资源是 [IBM].

颜色图的分类

颜色映射通常根据其功能分为几个类别(请参见, e.g.[Moreland]) :

  1. 顺序:亮度的变化和颜色的饱和度的增加,通常使用一个单一的色调;应该用来表示有顺序的信息。
  2. 偏色:两种不同颜色在中间以不饱和颜色相交时的亮度变化和可能的饱和度变化;当所绘制的信息具有临界中间值时,如地形或当数据偏离零时,应使用偏色。
  3. 循环:两种不同颜色的亮度变化,在中间和开始/结束时以不饱和颜色相遇;应用于在端点处环绕的值,如相位角、风向或一天中的时间。
  4. 定性:通常是各种颜色;应该用来表示没有顺序或关系的信息。
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']

序列2

许多 \(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的设计使其在明度和色调上都有平滑的变化,但在绿色色调区域似乎有一个小隆起。turbo是用来显示深度和视差数据的。

常用的Jet颜色映射包含在这组颜色映射中。我们可以看到 \(L^*\) 在整个颜色图中,值变化很大,因此对于表示数据以供观看者感知是一个糟糕的选择。关于这个想法的扩展见 [mycarta-jet][turbo].

cmaps['Miscellaneous'] = [
            'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
            'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
            'gist_rainbow', 'rainbow', 'jet', 'turbo', '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()
  • Perceptually Uniform Sequential colormaps
  • Sequential colormaps
  • Sequential (2) colormaps
  • Diverging colormaps
  • Cyclic colormaps
  • Qualitative colormaps
  • Miscellaneous colormaps

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, 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_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()
  • colormaps
  • colormaps
  • colormaps
  • colormaps
  • colormaps
  • colormaps
  • colormaps

灰度转换

对于彩色图,注意转换为灰度是很重要的,因为它们可以用黑白打印机打印。如果不仔细考虑,你的读者可能会以不可分辨的绘图而告终,因为灰度在彩色地图中的变化是不可预测的。

转换为灰度有很多不同的方法 [bw]. 其中一些更好的使用像素的RGB值的线性组合,但根据我们如何感知颜色强度进行加权。一种非线性的灰度转换方法是使用 \(L^*\) 像素值。一般来说,类似的原则也适用于这个问题,因为它们可以感知地呈现一个人的信息;也就是说,如果选择了一个颜色映射,它在 \(L^*\) 值,它将以合理的方式打印成灰度。

考虑到这一点,我们可以看到顺序颜色映射在灰度中有合理的表示。有些序列2颜色图有足够好的灰度表示,尽管有些(秋天、春天、夏天、冬天)的灰度变化很小。如果在绘图中使用这样的颜色映射,然后将绘图打印为灰度,则许多信息可能映射为相同的灰度值。发散色差图主要从外边缘的暗灰色到中间的白色变化。有些(PuOr和seismic)的一侧明显比另一侧深灰色,因此不太对称。coolwarm的灰度范围很小,打印出来的图像会更均匀,会丢失很多细节。请注意,重叠的、带标签的等高线有助于区分颜色图的一侧和另一侧,因为一旦打印到灰度,就不能使用颜色。许多定性和其他颜色贴图,如口音、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, 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)
  • Perceptually Uniform Sequential colormaps
  • Sequential colormaps
  • Sequential (2) colormaps
  • Diverging colormaps
  • Cyclic colormaps
  • Qualitative colormaps
  • Miscellaneous colormaps

色觉缺陷

有很多关于色盲的信息( e.g.[colorblindness]) . 此外,还有一些工具可以将图像转换成不同类型的色觉缺陷。

最常见的色觉缺陷包括区分红色和绿色。因此,避免同时使用红色和绿色的彩色地图可以避免一般情况下的许多问题。