在后台回复【阅读书籍】
即可获取python相关电子书~
Hi,我是山月。
今天来给大家介绍下Matplotlib里的颜色部分。
01
指定颜色
Matplotlib可以通过以下格式来指定颜色:
一个RGB或RGBA(红,绿,蓝,alpha)元组,元组内元素是在封闭区间[0,1]内的浮点数(如:(0.1,0.2,0.5)或(0.1,0.2,0.5,0.3));
十六进制的 RGB 或 RGBA 字符串(如:“#0f0f0f”或“#0f0f0f80”,不区分大小写);
一个简写的十六进制 RGB 或 RGBA 字符串,把每个字符复制即可获得完整的十六进制 RGB 或 RGBA 字符串,(如:“#abc”相当于“#aabbcc”;“#abcd”相当于“#aabbccdd”;不区分大小写);
封闭区间[0,1]内的浮点数字符串,包括灰度(如'0.5');
{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'} 之一,它们是蓝色、绿色、红色、青色、品红、黄色、黑色和白色的单个字符速记符号。
X11/CSS4 颜色名称(不区分大小写);
xkcd 颜色名称,前缀为“xkcd:”(例如,“xkcd:sky blue”;不区分大小写)。
'T10' 分类调色板中的一种 Tableau 颜色(默认颜色循环):{'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'}(不区分大小写);
“CN”颜色规范,即'C'后面跟着一个数字,它是默认属性循环的索引(matplotlib.rcParams[' axis .prop_cycle']);索引将在呈现时发生,如果循环不包含颜色,则默认为黑色。
"Red", "Green"和"Blue"是这些颜色的强度,它们的组合跨越了色彩空间。
“Alpha”的行为方式取决于 Artist 的 zorder,高zorder Artists 绘制在低 Artists 之上,“Alpha”决定低Artists是否被高Artists覆盖。
如果一个像素的旧 RGB 是 RGBold,而要添加的 Artist 像素的 RGB 是带有alpha 的 RGBnew。
则该像素的 RGB 更新为:RGB = RGBOld * (1 - Alpha) + RGBnew * Alpha。
Alpha 为 1 表示旧颜色完全被新 Artist 覆盖,Alpha 为 0 表示 Artist 的像素是透明的。
1、“CN”颜色选择
一旦创建artist,“CN”颜色就会转换为 RGBA。如:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
th = np.linspace(0, 2*np.pi, 128)
def demo(sty):
mpl.style.use(sty)
fig, ax = plt.subplots(figsize=(3, 3))
ax.set_title('style: {!r}'.format(sty), color='C0')
ax.plot(th, np.cos(th), 'C1', label='C1')
ax.plot(th, np.sin(th), 'C2', label='C2')
ax.legend()
demo('default')
demo('seaborn')
plt.show()
效果:
标题使用第一种颜色,然后使用每种样式的第二种和第三种颜色进行绘图。
2、xkcd 和 X11/CSS4
xkcd 颜色名称详情可看:https://xkcd.com/color/rgb/;
X11/CSS4 和 xkcd 之间有 95 个名称冲突,其中只有3个名称对应的十六进制值是相同的。
如,'blue'映射到'#0000FF',而'xkcd:blue'映射到'#0343DF'。
也正是由于这些名称冲突存在,所以所有的xkcd颜色名称都有前缀'xkcd:'。
名称冲突如下所示,其中十六进制值一致的颜色名称以粗体显示:
import matplotlib.pyplot as plt
import matplotlib._color_data as mcd
import matplotlib.patches as mpatch
overlap = {name for name in mcd.CSS4_COLORS
if "xkcd:" + name in mcd.XKCD_COLORS}
fig = plt.figure(figsize=[4.8, 16])
ax = fig.add_axes([0, 0, 1, 1])
for j, n in enumerate(sorted(overlap, reverse=True)):
weight = None
cn = mcd.CSS4_COLORS[n]
xkcd = mcd.XKCD_COLORS["xkcd:" + n].upper()
if cn == xkcd:
weight = 'bold'
r1 = mpatch.Rectangle((0, j), 1, 1, color=cn)
r2 = mpatch.Rectangle((1, j), 1, 1, color=xkcd)
txt = ax.text(2, j+.5, ' ' + n, va='center', fontsize=10,
weight=weight)
ax.add_patch(r1)
ax.add_patch(r2)
ax.axhline(j, color='k')
ax.text(.5, j + 1.5, 'X11', ha='center', va='center')
ax.text(1.5, j + 1.5, 'xkcd', ha='center', va='center')
ax.set_xlim(0, 3)
ax.set_ylim(0, j + 2)
ax.axis('off')
plt.show()
效果:
ps:为什么说有95个名称冲突,但是只有49个颜色名称?
因为除了3个一样的十六进制之外,其它46个名称都对应了2个十六进制,所以46*2+3=95。
02
自定义颜色条
颜色条需要一个“可映射”(matplotlib.cm.ScalarMappable)对象(通常是图像)来指示要使用的色图和标准。
如果想创建没有附加图像的颜色条,可以改为使用没有关联数据的 ScalarMappable。
1、基本连续颜色条
现在我们来创建一个带有刻度和标签的基本连续颜色条。
colorbar调用的参数包括ScalarMapable(使用norm和cmap参数构造)、绘制colorbar的axes以及colorbar的方向。
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.cm.cool
norm = mpl.colors.Normalize(vmin=5, vmax=10)
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
cax=ax, orientation='horizontal', label='Some Units')
plt.show()
效果:
2、离散间隔颜色条
第二个示例演示了如何使用ListedColormap,它从一组列出的颜色生成一个色图。
colors.BoundaryNorm根据离散间隔和扩展端生成色图索引,以显示“over”和“under”值颜色。
Over 和 under 用于显示标准化[0,1]范围之外的数据。在这里,我们将颜色作为灰色阴影传递给编码 0-1 范围内浮点数的字符串。
如果使用 ListedColormap,则 bounds 数组的长度必须比颜色列表的长度大 1。边界必须是单调递增的。
这一次,除了之前的参数外,我们还要向colorbar传递更多的参数。
要在colorbar上显示超出范围的值,我们必须使用extend关键字参数。要使用extend,必须指定两个额外的边界。
最后,间距参数确保间隔按比例显示在colorbar上。
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
cmap.set_over('0.25')
cmap.set_under('0.75')
bounds = [1, 2, 4, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
cax=ax,
boundaries=[0] + bounds + [13],
extend='both',
ticks=bounds,
spacing='proportional',
orientation='horizontal',
label='Discrete intervals, some other units',
)
plt.show()
效果:
3、具有自定义扩展长度的彩条
在这里,我们说明了自定义长度颜色条扩展的使用,用于具有离散间隔的颜色条。
要使每个扩展的长度与内部颜色的长度相同,得使用 extendfrac='auto'。
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.colors.ListedColormap(['royalblue', 'cyan',
'yellow', 'orange'])
cmap.set_over('red')
cmap.set_under('blue')
bounds = [-1.0, -0.5, 0.0, 0.5, 1.0]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
cax=ax,
boundaries=[-10] + bounds + [10],
extend='both',
extendfrac='auto',
ticks=bounds,
spacing='uniform',
orientation='horizontal',
label='Custom extension lengths, some other units',
)
plt.show()
效果:
03
创建色图
Matplotlib 有许多可通过 matplotlib.cm.get_cmap 访问的内置色图。还有一些外部库,如 palettable,它们有许多额外的色图。
然而,我们经常想在 Matplotlib 中创建或操作色图,这可以使用 ListedColormap 或 LinearSegmentedColormap 类来完成。
从外部看,两个色图类都将 0 到 1 之间的值映射到一堆颜色。但是,有一些细微的差别,其中一些如下所示。
在手动创建或操作色图之前,让我们先看看如何从现有的色图类中获取色图及其颜色。
1、获取色图并访问它们的值
首先,获取一个已命名的色图,其中大部分在 Matplotlib 中的可选色图中列出,可以使用 matplotlib.cm.get_cmap 完成,它会返回一个色图对象。
第二个参数给出了用于定义色图的颜色列表的大小,我们使用一个适度的值 8:
from matplotlib import cm
viridis = cm.get_cmap('viridis', 8)
print(viridis) # <matplotlib.colors.ListedColormap object at 0x0000024BACC8AFD0>
对象 viridis 是一个可调用对象,当传递一个介于 0 和 1 之间的浮点数时,它会从色图中返回一个 RGBA 值:
print(viridis(0.56)) # (0.122312, 0.633153, 0.530398, 1.0)
1)ListedColormap
ListedColormap 将它们的颜色值存储在 .colors 属性中,因此可以使用 colors 属性直接访问组成色图的颜色列表。
也可以通过调用viridis来间接访问它,该viridis的值数组与colormap的长度相匹配。
请注意,返回的列表是 RGBA Nx4 数组的形式,其中 N 是色图的长度。
import numpy as np
from matplotlib import cm
viridis = cm.get_cmap('viridis', 8)
print('viridis.colors', viridis.colors)
print('viridis(range(8))', viridis(range(8)))
print('viridis(np.linspace(0, 1, 8))', viridis(np.linspace(0, 1, 8)))
'''
viridis.colors [[0.267004 0.004874 0.329415 1. ]
[0.275191 0.194905 0.496005 1. ]
[0.212395 0.359683 0.55171 1. ]
[0.153364 0.497 0.557724 1. ]
[0.122312 0.633153 0.530398 1. ]
[0.288921 0.758394 0.428426 1. ]
[0.626579 0.854645 0.223353 1. ]
[0.993248 0.906157 0.143936 1. ]]
viridis(range(8)) [[0.267004 0.004874 0.329415 1. ]
[0.275191 0.194905 0.496005 1. ]
[0.212395 0.359683 0.55171 1. ]
[0.153364 0.497 0.557724 1. ]
[0.122312 0.633153 0.530398 1. ]
[0.288921 0.758394 0.428426 1. ]
[0.626579 0.854645 0.223353 1. ]
[0.993248 0.906157 0.143936 1. ]]
viridis(np.linspace(0, 1, 8)) [[0.267004 0.004874 0.329415 1. ]
[0.275191 0.194905 0.496005 1. ]
[0.212395 0.359683 0.55171 1. ]
[0.153364 0.497 0.557724 1. ]
[0.122312 0.633153 0.530398 1. ]
[0.288921 0.758394 0.428426 1. ]
[0.626579 0.854645 0.223353 1. ]
[0.993248 0.906157 0.143936 1. ]]
'''
色图是一个查找表,因此“过采样 oversampling”色图会返回最近邻插值(注意下面列表中的重复颜色)。
print('viridis(np.linspace(0, 1, 12))', viridis(np.linspace(0, 1, 12)))
'''
viridis(np.linspace(0, 1, 12)) [[0.267004 0.004874 0.329415 1. ]
[0.267004 0.004874 0.329415 1. ]
[0.275191 0.194905 0.496005 1. ]
[0.212395 0.359683 0.55171 1. ]
[0.212395 0.359683 0.55171 1. ]
[0.153364 0.497 0.557724 1. ]
[0.122312 0.633153 0.530398 1. ]
[0.288921 0.758394 0.428426 1. ]
[0.288921 0.758394 0.428426 1. ]
[0.626579 0.854645 0.223353 1. ]
[0.993248 0.906157 0.143936 1. ]
[0.993248 0.906157 0.143936 1. ]]
'''
2)LinearSegmentedColormap
LinearSegmentedColormap 没有 .colors 属性。但是,仍然可以使用整数数组或 0 到 1 之间的浮点数组调用色图。
import numpy as np
from matplotlib import cm
copper = cm.get_cmap('copper', 8)
print(copper) # <matplotlib.colors.LinearSegmentedColormap object at 0x0000020C937BD668>
print('copper(range(8))', copper(range(8)))
print('copper(np.linspace(0, 1, 8))', copper(np.linspace(0, 1, 8)))
'''
copper(range(8)) [[0. 0. 0. 1. ]
[0.17647055 0.1116 0.07107143 1. ]
[0.35294109 0.2232 0.14214286 1. ]
[0.52941164 0.3348 0.21321429 1. ]
[0.70588219 0.4464 0.28428571 1. ]
[0.88235273 0.558 0.35535714 1. ]
[1. 0.6696 0.42642857 1. ]
[1. 0.7812 0.4975 1. ]]
copper(np.linspace(0, 1, 8)) [[0. 0. 0. 1. ]
[0.17647055 0.1116 0.07107143 1. ]
[0.35294109 0.2232 0.14214286 1. ]
[0.52941164 0.3348 0.21321429 1. ]
[0.70588219 0.4464 0.28428571 1. ]
[0.88235273 0.558 0.35535714 1. ]
[1. 0.6696 0.42642857 1. ]
[1. 0.7812 0.4975 1. ]]
'''
2、创建列出的色图
创建色图得本质是上面的逆操作,我们向 ListedColormap 提供颜色规范的列表或数组以创建新的色图。
在这里我们会定义一个辅助函数:plot_examples,该函数将多个色图之一作为输入,创建一些随机数据,并将色图应用于该数据集的图像图。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
cmap = ListedColormap(["darkorange", "gold", "lawngreen", "lightseagreen"])
plot_examples([cmap])
效果:
事实上,该列表可能包含任何有效的 matplotlib 颜色规范。
Nx4 numpy 数组对创建自定义色图特别有用,因为我们可以在这样的数组上执行各种 numpy 操作,因此从现有色图制作新色图变得非常简单。
比如我们想要将 256 长度的“viridis”色图得前 25 个条目设为粉红色:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from matplotlib import cm
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
viridis = cm.get_cmap('viridis', 256)
newcolors = viridis(np.linspace(0, 1, 256))
pink = np.array([248/256, 24/256, 148/256, 1])
newcolors[:25, :] = pink
newcmp = ListedColormap(newcolors)
plot_examples([viridis, newcmp])
效果:
我们可以轻松地减小色图的动态范围;比如我们选择色图的中值0.5。
但是,我们需要从更大的色图插值,否则新的色图将具有重复值。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from matplotlib import cm
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
viridis = cm.get_cmap('viridis', 256)
viridisBig = cm.get_cmap('viridis', 512)
newcmp = ListedColormap(viridisBig(np.linspace(0.25, 0.75, 256)))
plot_examples([viridis, newcmp])
效果:
我们可以轻松地连接两个色图:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from matplotlib import cm
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
viridis = cm.get_cmap('viridis', 256)
top = cm.get_cmap('Oranges_r', 128)
bottom = cm.get_cmap('Blues', 128)
newcolors = np.vstack((top(np.linspace(0, 1, 128)),
bottom(np.linspace(0, 1, 128))))
newcmp = ListedColormap(newcolors, name='OrangeBlue')
plot_examples([viridis, newcmp])
效果:
当我们不需要从一个已命名的色图开始时,我们只需要创建 Nx4 数组传递给 ListedColormap。
创建一个从棕色 (RGB: 90,40,40) 到白色 (RGB: 255,255,255) 的色图。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from matplotlib import cm
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
viridis = cm.get_cmap('viridis', 256)
N = 256
vals = np.ones((N, 4))
vals[:, 0] = np.linspace(90/256, 1, N)
vals[:, 1] = np.linspace(40/256, 1, N)
vals[:, 2] = np.linspace(40/256, 1, N)
newcmp = ListedColormap(vals)
plot_examples([viridis, newcmp])
效果:
3、创建线性分段色图
LinearSegmentedColormap使用在RGB(A)值之间插值的锚点来指定色图。
指定的这些色图的格式允许在锚点处出现不连续。
每个锚点被指定为 [x[i] yleft[i] yright[i]] 形式的矩阵中的一行,其中 x[i] 是锚点,yleft[i] 和 yright[i] 是锚点两侧的颜色值。
如果没有不连续点,则 yleft[i]=yright[i]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
cdict = {'red': [[0.0, 0.0, 0.0],
[0.5, 1.0, 1.0],
[1.0, 1.0, 1.0]],
'green': [[0.0, 0.0, 0.0],
[0.25, 0.0, 0.0],
[0.75, 1.0, 1.0],
[1.0, 1.0, 1.0]],
'blue': [[0.0, 0.0, 0.0],
[0.5, 0.0, 0.0],
[1.0, 1.0, 1.0]]}
def plot_linearmap(cdict):
newcmp = LinearSegmentedColormap('testCmap', segmentdata=cdict, N=256)
rgba = newcmp(np.linspace(0, 1, 256))
fig, ax = plt.subplots(figsize=(4, 3), constrained_layout=True)
col = ['r', 'g', 'b']
for xx in [0.25, 0.5, 0.75]:
ax.axvline(xx, color='0.7', linestyle='--')
for i in range(3):
ax.plot(np.arange(256)/256, rgba[:, i], color=col[i])
ax.set_xlabel('index')
ax.set_ylabel('RGB')
plt.show()
plot_linearmap(cdict)
效果:
为了在锚点处产生不连续性,第三列与第二列不同。“red”、“green”、“blue”和可选的“alpha”中的每一个的矩阵设置为:
cdict['red'] = [...
[x[i] yleft[i] yright[i]],
[x[i+1] yleft[i+1] yright[i+1]],
...]
对于在 x[i] 和 x[i+1] 之间传递给色图的值,插值在 yright[i] 和 yleft[i+1] 之间。
在下面的示例中,在 0.5 处有一个红色的不连续性。0 和 0.5 之间的插值从 0.3 变为 1,而 0.5 和 1 之间的插值从 0.9 变为 1。
注意 red[0, 1] 和 red[2, 2] 对于插值都是多余的,因为 red[0 , 1] 是 0 左边的值,red[2, 2] 是 1.0 右边的值。
cdict['red'] = [[0.0, 0.0, 0.3],
[0.5, 1.0, 0.9],
[1.0, 1.0, 1.0]]
plot_linearmap(cdict)
效果:
1)直接从列表创建分段色图
上述是一种非常通用的方法,但无可否认,实施起来有点麻烦。
对于一些基本情况,使用 LinearSegmentedColormap.from_list 可能更容易。
它会从提供的颜色列表中创建一个具有相等间距的分段色图。
colors = ["darkorange", "gold", "lawngreen", "lightseagreen"]
cmap1 = LinearSegmentedColormap.from_list("mycmap", colors)
如果需要,可以将色图的节点指定为 0 到 1 之间的数字。
例如可以让带红色的部分在色图中占据更多空间。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
def plot_examples(colormaps):
"""
辅助函数,可以用关联的色图绘制数据
"""
np.random.seed(19680801)
data = np.random.randn(30, 30)
n = len(colormaps)
fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
constrained_layout=True, squeeze=False)
for [ax, cmap] in zip(axs.flat, colormaps):
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
fig.colorbar(psm, ax=ax)
plt.show()
colors = ["darkorange", "gold", "lawngreen", "lightseagreen"]
cmap1 = LinearSegmentedColormap.from_list("mycmap", colors)
nodes = [0.0, 0.4, 0.8, 1.0]
cmap2 = LinearSegmentedColormap.from_list("mycmap", list(zip(nodes, colors)))
plot_examples([cmap1, cmap2])
效果:
04
色图规范化
默认情况下,可以使用色图的对象将色图中的颜色从数据值vmin线性映射到vmax。如:
pcm = ax.pcolormesh(x, y, Z, vmin=-1., vmax=1., cmap='RdBu_r')
将 Z 中的数据从 -1 线性映射到 +1,因此 Z=0 将在色图 RdBu_r 的中心给出颜色(在这种情况下为白色)。
Matplotlib 会分两步进行此映射,首先将输入数据标准化为 [0, 1],然后映射到色图中的索引。
规范化是在 matplotlib.colors() 模块中定义的类,默认的线性归一化是 matplotlib.colors.Normalize()。
将数据映射到颜色的Artists传递参数 vmin 和 vmax 来构造一个 matplotlib.colors.Normalize() 实例,然后调用它:
import matplotlib as mpl
norm = mpl.colors.Normalize(vmin=-1.,vmax=1.)
print(norm(0.)) # 0.5
但是,有时以非线性方式将数据映射到色图也是很有用的。
1、对数
最常见的转换之一是通过取其对数(以 10 为底)来绘制数据,这种转换对于显示不同比例的变化很有用。
可以使用 colors.LogNorm 通过 log10 来将数据规范化。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-(X)**2 - (Y)**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
Z = Z1 + 50 * Z2
fig, ax = plt.subplots(2, 1)
pcm = ax[0].pcolor(X, Y, Z,
norm=colors.LogNorm(vmin=Z.min(), vmax=Z.max()),
cmap='PuBu_r')
fig.colorbar(pcm, ax=ax[0], extend='max')
pcm = ax[1].pcolor(X, Y, Z, cmap='PuBu_r')
fig.colorbar(pcm, ax=ax[1], extend='max')
plt.show()
效果:
2、对称对数
虽然有时会出现正数和负数,但我们仍然希望将对数比例应用于两者。在这种情况下,负数也会按对数缩放,但是映射到更小的数字;
例如,如果 vmin=-vmax,那么它们的负数从 0 映射到 0.5,正数从 0.5 映射到 1。
由于接近零的值的对数趋于无穷大,因此需要线性映射零附近的小范围。
参数 linthresh 允许用户指定此范围的大小(-linthresh,linthresh)。色图中此范围的大小由 linscale(默认1.0) 设置。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
fig, ax = plt.subplots(2, 1)
pcm = ax[0].pcolormesh(X, Y, Z,
norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,
vmin=-1.0, vmax=1.0, base=10),
cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[0], extend='both')
pcm = ax[1].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z))
fig.colorbar(pcm, ax=ax[1], extend='both')
plt.show()
效果:
3、幂律
有时将颜色重新映射到幂律关系(即y=xγ,其中 γ 是幂)是有用的。为此,我们可以使用colors.PowerNorm。
它会将 gamma 作为参数(gamma == 1.0 只会产生默认的线性归一化):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
N = 100
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
Z1 = (1 + np.sin(Y * 10.)) * X**(2.)
fig, ax = plt.subplots(2, 1)
pcm = ax[0].pcolormesh(X, Y, Z1, norm=colors.PowerNorm(gamma=0.5),
cmap='PuBu_r')
fig.colorbar(pcm, ax=ax[0], extend='max')
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='PuBu_r')
fig.colorbar(pcm, ax=ax[1], extend='max')
plt.show()
效果:
4、离散边界
Matplotlib 附带的另一个规范化是 colors.BoundaryNorm。
除了 vmin 和 vmax 之外,它还需要映射数据的边界作为参数。然后颜色在这些边界之间线性分布。如:
import numpy as np
import matplotlib.colors as colors
bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=4)
print(norm([-0.2,-0.15,-0.02, 0.3, 0.8, 0.99])) # [0 0 1 2 3 3]
注意:与其他规范不同,此规范返回从 0 到 ncolors-1 的值。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
fig, ax = plt.subplots(3, 1, figsize=(8, 8))
ax = ax.flatten()
# 均匀边界会产生类似于轮廓的效果
bounds = np.linspace(-1, 1, 10)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[0].pcolormesh(X, Y, Z,
norm=norm,
cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical')
# 不均匀边界改变了颜色映射:
bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
pcm = ax[2].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z))
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
plt.show()
效果:
5、TwoSlopeNorm
有时我们希望在中心点的两侧有不同的色图,并且我们希望这两个色图具有不同的线性比例。
我们以地形图来作为示例:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cbook as cbook
filename = cbook.get_sample_data('topobathy.npz', asfileobj=False)
with np.load(filename) as dem:
topo = dem['topo']
longitude = dem['longitude']
latitude = dem['latitude']
fig, ax = plt.subplots()
# 制作一张彩色地图,清晰地描绘陆地和海洋,长度相同(256+256)
colors_undersea = plt.cm.terrain(np.linspace(0, 0.17, 256))
colors_land = plt.cm.terrain(np.linspace(0.25, 1, 256))
all_colors = np.vstack((colors_undersea, colors_land))
terrain_map = colors.LinearSegmentedColormap.from_list('terrain_map',
all_colors)
# 制定标准:注意中心偏移,以便土地具有更大的动态范围:
divnorm = colors.TwoSlopeNorm(vmin=-500., vcenter=0, vmax=4000)
pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=divnorm,
cmap=terrain_map,)
# 简单的地理绘图,设置纵横比,因为经线之间的距离取决于纬度。
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
fig.colorbar(pcm, shrink=0.6)
plt.show()
效果:
6、自定义规范
上面描述的 TwoSlopeNorm 为自定义规范提供了一个有用的示例。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cbook as cbook
filename = cbook.get_sample_data('topobathy.npz', asfileobj=False)
with np.load(filename) as dem:
topo = dem['topo']
longitude = dem['longitude']
latitude = dem['latitude']
fig, ax = plt.subplots()
colors_undersea = plt.cm.terrain(np.linspace(0, 0.17, 256))
colors_land = plt.cm.terrain(np.linspace(0.25, 1, 256))
all_colors = np.vstack((colors_undersea, colors_land))
terrain_map = colors.LinearSegmentedColormap.from_list('terrain_map',
all_colors)
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
self.vcenter = vcenter
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y))
midnorm = MidpointNormalize(vmin=-500., vcenter=0, vmax=4000)
pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=midnorm,
cmap=terrain_map)
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
fig.colorbar(pcm, shrink=0.6, extend='both')
plt.show()
效果:
05
选择色图
Matplotlib 有许多可通过 matplotlib.cm.get_cmap 访问的内置色图,还有像 [palettable] 和 [colorcet] 这样的外部库,它们有许多额外的色图。
在这里,我们简要讨论如何在众多选项之间进行选择。
1、概述
选择一个好色图是因为想在 3D 颜色空间中为数据集找到一个好的表示。
任何给定数据集的最佳色图取决于许多因素,包括:
是表示形式数据还是度量数据([Ware])
对数据集的了解(比如,是否存在其他值偏离的临界值?)
正在绘制的参数是否有一个直观的配色方案
在这个领域是否有一个观众可能期待的标准
对于许多应用程序,感知一致的色图是最佳选择 --- 数据中的相等步长被视为颜色空间中的相等步长。
研究人员发现,当数据发生变化时,人类大脑对亮度参数变化的感知要比诸如色调变化等要好得多。因此,具有亮度单调递增的色图将更好地被查看者解读。
[colorcet]是一个很好的感知统一色图的例子。
颜色可以以各种方式在 3D 空间中表示,一种表示颜色的方法是使用 CIELAB。
在 CIELAB 中,色彩空间可以通过亮度(L∗);red-green(a*);和yellow-blue(b∗)表示。
然后可以使用参数亮度来了解更多关于 matplotlib 色图将如何被观众感知的信息。
2、色图类别
色图通常根据功能分为几个类别:
Sequential: 颜色的亮度和饱和度逐渐变化,通常使用单一色调;应该用于表示有顺序的信息。
Diverging: 在不饱和色中遇到的两种不同颜色的亮度和可能的饱和度的变化,当所绘制的信息具有一个关键中值(例如地形或数据偏离零附近时)时可以使用。
Cyclic: 两种不同颜色在中间相交并以不饱和颜色开始/结束的亮度变化;应该用于在端点处环绕的值,例如相位角、风向或一天中的时间。
Qualitative: 常为杂色;应该用来表示没有顺序或关系的信息。
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
from colorspacious import cspace_converter # 提前用pip安装好 colorspacious
from collections import OrderedDict
cmaps = OrderedDict()
1)Sequential
对于Sequential图,亮度值通过色图单调增加。
色图中的一些 L∗ 值从 0 到 100(二进制和其他灰度),而其他一些从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']
显示每个色图的范围:
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
cmaps = OrderedDict()
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']
'''
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)
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()
效果:
2)Sequential2
Sequential2 图中的许多L∗ 值是单调递增的,但有些在 L∗ 空间中会出现平稳甚至上下波动。
其他(afmhot、copper、gist_heat 和 hot)在L∗ 函数中有扭结。在色图的平稳或扭结区域中表示的数据将导致对数据的带状感知。
cmaps['Sequential (2)'] = [
'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
'hot', 'afmhot', 'gist_heat', 'copper']
色图范围:
3)Diverging
对于Diverging图,我们希望L∗ 值单调递增到最大值,该值应接近L∗=100,然后单调递减 L∗值。我们会在色图的两端寻找近似相等的最小L∗ 值。
按这些指标衡量,BrBG 和 RdBu 是不错的选择。虽然coolwarm 也是一个不错的选择,但它不涵盖广泛的 L∗ 值。
cmaps['Diverging'] = [
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']
色图范围:
4)Cyclic
对于Cyclic图,我们希望以相同的颜色开始和结束,并在中间遇到一个对称的中心点。
常用的 HSV 色图包含在这组色图中,尽管它与中心点不对称。
cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv']
色图范围:
5)Qualitative
L∗ 值会在整个色图中到处移动,并且不是单调递增的。
cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']
色图范围:
6)Miscellaneous
Miscellaneous色图具有创建它们的特定用途。
cmaps['Miscellaneous'] = [
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']
色图范围:
3、色图亮度
matplotlib 色图的亮度值:
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
from matplotlib import cm
from colorspacious import cspace_converter
import matplotlib as mpl
cmaps = OrderedDict()
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']
cmaps['Sequential (2)'] = [
'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
'hot', 'afmhot', 'gist_heat', 'copper']
cmaps['Diverging'] = [
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']
cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv']
cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']
cmaps['Miscellaneous'] = [
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']
mpl.rcParams.update({'font.size': 12})
_DSUBS = {'Perceptually Uniform Sequential': 5, 'Sequential': 6,
'Sequential (2)': 6, 'Diverging': 6, 'Cyclic': 3,
'Qualitative': 4, 'Miscellaneous': 6}
# 色图之间的间距
_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}
# 通过颜色映射进行分步索引
x = np.linspace(0.0, 1.0, 100)
# 绘制
for cmap_category, cmap_list in cmaps.items():
#进行子图绘制,以便色图有足够的空间。默认值为每个子图上有6个色图。
dsub = _DSUBS.get(cmap_category, 6)
nsubplots = int(np.ceil(len(cmap_list) / dsub))
# squeeze=False 以类似方式处理单个子批次的情况
fig, axes = plt.subplots(nrows=nsubplots, squeeze=False,
figsize=(7, 2.6*nsubplots))
for i, ax in enumerate(axes.flat):
locs = [] # 文本标签的位置
for j, cmap in enumerate(cmap_list[i*dsub:(i+1)*dsub]):
# 获取colormap的RGB值,并在CAM02-UCS颜色空间中转换色图。lab[0, :, 0]是亮度
rgb = cm.get_cmap(cmap)(x)[np.newaxis, :, :3]
lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb)
# 为每个类别单独绘制色图的亮度值。
if cmap_category == 'Sequential':
y_ = lab[0, ::-1, 0]
c_ = x[::-1]
else:
y_ = lab[0, :, 0]
c_ = x
dc = _DC.get(cmap_category, 1.4) # cmaps 水平间距
ax.scatter(x + j*dc, y_, c=c_, cmap=cmap, s=300, linewidths=0.0)
# 存储色图标签位置
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)
# 设置轴的限制:
# * 第1个子图用作x轴极限的参考
# * 亮度值从0到100 (y轴限制)
ax.set_xlim(axes[0, 0].get_xlim())
ax.set_ylim(0.0, 100.0)
# 为色图设置标签
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()
效果(每次只显示一个图形,删除后会按顺序显示剩余的):
4、灰度转换
注意将色图的灰度转换,因为它们可能会在黑白打印机上打印。
转换为灰度有许多不同的方式:
较好的方法使用像素rgb值的线性组合,但根据对颜色强度的感知进行加权。
转换为灰度的非线性方法是使用像素的L∗ 值。
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
from matplotlib import cm
from colorspacious import cspace_converter
import matplotlib as mpl
cmaps = OrderedDict()
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']
cmaps['Sequential (2)'] = [
'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
'hot', 'afmhot', 'gist_heat', 'copper']
cmaps['Diverging'] = [
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']
cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv']
cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']
cmaps['Miscellaneous'] = [
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']
mpl.rcParams.update({'font.size': 14})
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):
# 获取子图的RGB值。
rgb = cm.get_cmap(plt.get_cmap(name))(x)[np.newaxis, :, :3]
# 在CAM02-UCS颜色空间中获取色图,我们需要的是亮度。
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)
# 关闭图
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)
效果:
好啦,今天的内容就到这~
END
您的“点赞”、“在看”和 “分享”是我们产出的动力。