Excel中,颜色是通过界面展示及按钮选择确定的,在python代码环境下,自然是没有这种条件的,那么,matplotlib中颜色是如何构建与使用的呢?
matplotlib提供了强大的颜色接口,供使用者定制颜色、使用颜色、使用色板,主要包括以下几个部分:
1、 matplotlib.colors 接口中定义的颜色空间,每一个颜色通过字典的形式进行保存,字典名为颜色名称,字典值为颜色对应的RGB元组或十六进制字符串。例如颜色名称可以为 darkred,字典值为 #8B0000。凡是颜色命名空间中定义了的颜色,均可以通过颜色名字直接引用。
2、 命名空间解决了颜色怎么称呼的问题,但并没有建立好颜色与数据的映射关系,使用者往往希望序列(或数据)按照指定的颜色规则自动赋值,比如,我们希望较小的值被赋值为蓝色,中间大小的值被赋值为白色,较大的值被赋值为黑色。但我们不可能为每一个数值一一指定颜色(1000个不唯一数据指定1000种颜色的难度可想而知)。为此,开发者提出了色板colormap,通过plt.cm接口实现。主要通过两种原理:一种是指定有限的N种颜色,循环使用;另一种是将颜色映射到一个线性空间,将最大和最小的数值与颜色空间的两端做对应,中间的数据采取差值的方法计算与之相对应的颜色。
本文将对上述部分做详细介绍。
本文的运行环境为 jupyter notebook
python版本为3.7
本文所用到的库包括
%matplotlib inline
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib as mpl
颜色命名空间
mpl.colors模块下可以看到,matplotlib定制了5个大的颜色空间,有matplotlib默认采用的BASE_COLORS,包含了可视化软件TableAu颜色命名空间的TABLEAU_COLORS等,各个颜色空间,命名的颜色个数如下。
mpl.colors
print(
'mpl.colors.BASE_COLORS nums:', len(mpl.colors.BASE_COLORS), '\n',
'mpl.colors.TABLEAU_COLORS nums:', len(mpl.colors.TABLEAU_COLORS), '\n',
'mpl.colors.cnames nums:', len(mpl.colors.cnames), '\n',
'mpl.colors.CSS4_COLORS nums:', len(mpl.colors.CSS4_COLORS), '\n',
'mpl.colors.XKCD_COLORS nums:', len(mpl.colors.XKCD_COLORS))
颜色命名空间 | 颜色个数 |
---|---|
mpl.colors.BASE_COLORS | 8 |
mpl.colors.TABLEAU_COLORS | 10 |
mpl.colors.cnames | 148 |
mpl.colors.CSS4_COLORS | 148 |
mpl.colors.XKCD_COLORS | 949 |
sns.color_palette可以对包含N个颜色的列表进行可视化,以下对主要命名空间的颜色字典和颜色进行了展示和可视化。
mpl.colors.BASE_COLORS
colors = mpl.colors.BASE_COLORS
print(colors)
pal = [color for name, color in colors.items()]
pal = sns.color_palette(palette=pal)
sns.palplot(pal)
输出
{'b': (0, 0, 1), 'g': (0, 0.5, 0), 'r': (1, 0, 0), 'c': (0, 0.75, 0.75), 'm': (0.75, 0, 0.75), 'y': (0.75, 0.75, 0), 'k': (0, 0, 0), 'w': (1, 1, 1)}
mpl.colors.TABLEAU_COLORS
colors = mpl.colors.TABLEAU_COLORS
print(colors)
pal = [color for name, color in colors.items()]
pal = sns.color_palette(palette=pal)
sns.palplot(pal)
输出
OrderedDict([('tab:blue', '#1f77b4'), ('tab:orange', '#ff7f0e'), ('tab:green', '#2ca02c'), ('tab:red', '#d62728'), ('tab:purple', '#9467bd'), ('tab:brown', '#8c564b'), ('tab:pink', '#e377c2'), ('tab:gray', '#7f7f7f'), ('tab:olive', '#bcbd22'), ('tab:cyan', '#17becf')])
mpl.colors.cnames
colors = mpl.colors.cnames
print(list(colors.items())[:10])
pal = [color for name, color in colors.items()][:20]
pal = sns.color_palette(palette=pal)
sns.palplot(pal)
输出
[('aliceblue', '#F0F8FF'), ('antiquewhite', '#FAEBD7'), ('aqua', '#00FFFF'), ('aquamarine', '#7FFFD4'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'), ('bisque', '#FFE4C4'), ('black', '#000000'), ('blanchedalmond', '#FFEBCD'), ('blue', '#0000FF')]
mpl.colors.CSS4_COLORS
colors = mpl.colors.CSS4_COLORS
print(list(colors.items())[:10])
pal = [color for name, color in colors.items()][:20]
pal = sns.color_palette(palette=pal)
sns.palplot(pal)
输出
[('aliceblue', '#F0F8FF'), ('antiquewhite', '#FAEBD7'), ('aqua', '#00FFFF'), ('aquamarine', '#7FFFD4'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'), ('bisque', '#FFE4C4'), ('black', '#000000'), ('blanchedalmond', '#FFEBCD'), ('blue', '#0000FF')]
mpl.colors.XKCD_COLORS
colors = mpl.colors.XKCD_COLORS
print(list(colors.items())[:10])
pal = [color for name, color in colors.items()][:20]
pal = sns.color_palette(palette=pal)
sns.palplot(pal)
输出
[('xkcd:cloudy blue', '#acc2d9'), ('xkcd:dark pastel green', '#56ae57'), ('xkcd:dust', '#b2996e'), ('xkcd:electric lime', '#a8ff04'), ('xkcd:fresh green', '#69d84f'), ('xkcd:light eggplant', '#894585'), ('xkcd:nasty green', '#70b23f'), ('xkcd:really light blue', '#d4ffff'), ('xkcd:tea', '#65ab7c'), ('xkcd:warm purple', '#952e8f')]
从以上案例可以看到,一些命名空间的颜色是通过十六进制字符串形式表示的,另一些是通过0-1的长度为3的元组形式表示的。这两种有什么区别呢?
本例展示了他们的数学原理,你会看到他们的本质并没有什么不同。
RGB元组与十六进制字符
def color16_to_rgb(color16):
# 分别获得FF,63,47三个十六进制数字
R16, G16, B16 = color16[1:3], color16[3:5], color16[5:7]
# 将十六进制转成十进制
R10, G10, B10 = int(R16, 16), int(G16, 16), int(B16, 16)
# 将十进制转成归一化
R, G, B = R10/255.0, G10/255.0, B10/255.0
return (R, G, B), (R10, G10, B10)
color16_to_rgb('#FF6347')
输出
((1.0, 0.38823529411764707, 0.2784313725490196), (255, 99, 71))
函数color16_to_rgb解析:先将十六进制字符串 # 符号后的6个字符分裂为3个,每两个代表一个十六进制数字,通过int(num,16)可实现将其转换为十进制。
从返回值来看,’#FF6347’被转换成十进制元组——(255, 99, 71)。事实上,电脑显示带颜色的图片就是通过三通道的数组(宽×高×3)实现的,电脑屏幕在不同分辨率下被划分为M×N的小格,每个小格包含3个数字,分别代表红色通道(R),绿色通道(G),蓝色通道(B)的明暗,0表示最暗,255表示最亮。3通道排列组合一共将产生256×256×256=16777216种颜色组合,这便是理论上总的颜色个数。
matplotlib并没有保存十进制RGB元组,保存的是十进制元组归一化后的元组,即十进制元组/255.0后的浮点数。
十六进制和RGB元组都能表示颜色,不同的是前者是离散的,后者是连续的。在使用过程中,可充分利用离散数据的唯一性和连续数据的连续性进行相对应的配色。
可通过matplotlib自带的mpl.colors.to_rgb 接口实现了上述功能,如下图可知,两种方式输出的归一化后元组完全相同。
mpl.colors.to_rgb('#FF6347')
输出
(1.0, 0.38823529411764707, 0.2784313725490196)
以下代码展示了’#FF6347’的显色效果(左上角子图)和分通道的显色效果。
(R, G, B), (R10, G10, B10)=color16_to_rgb('#FF6347')
img=np.zeros(shape=[15,15,3],dtype=np.int) # 均为0的3通道数组
imgR,imgG,imgB=img.copy(),img.copy(),img.copy() # 复制为3个,后续分别用来给每个通道赋值
fig,axs=plt.subplots(2,2,figsize=(8,8))
axs=axs.ravel() # 将axs拉平成一维列表
# 去掉所有子图坐标系
for ax in axs:
ax.axis('off')
# R,G,B channels —— ax0
img[:,:,0]=R10
img[:,:,1]=G10
img[:,:,2]=B10
axs[0].imshow(img)
axs[0].set_title('img R,G,B channels\nvalues: %d,%d,%d '% (R10,G10,B10),fontsize=20)
# R channel —— ax1
imgR[:,:,0]=R10
axs[1].imshow(imgR)
axs[1].set_title('img R channel\nvalue: %d'%R10,fontsize=20)
# G channel —— ax2
imgG[:,:,1]=G10
axs[2].imshow(imgG)
axs[2].set_title('img G channel\nvalue: %d'%G10,fontsize=20)
# B channel —— ax3
imgB[:,:,2]=B10
axs[3].imshow(imgB)
axs[3].set_title('img B channel\nvalue: %d'%B10,fontsize=20)
可视化CSS4_COLORS
以下代码可视化地展示了CSS4_COLORS命名空间各颜色的名称及显色效果。根据显色效果,使用者可根据十六进制字符串或颜色名称进行相应调用。
fig, axs = plt.subplots(37, 8, figsize=(12, 20))
axs = axs.ravel() # 将axs拉平成一维列表
colors = mpl.colors.CSS4_COLORS # 字典,包含148个元素
for i, (name, color) in enumerate(colors.items()):
ax_color = axs[2*i] # 偶数子图填充颜色并打印颜色十六进制字符
ax_name = axs[2*i+1] # 奇数子图打印颜色名称
# 去除x,y轴标签
for ax in [ax_color, ax_name]:
ax.set_xticks([])
ax.set_yticks([])
ax_color.set(facecolor=color) # 颜色子图填充该颜色
# 将十六进制颜色字符打印至子图ax_color
ax_color.text(0.5, 0.5, color, va='center', ha='center',
fontsize=12, transform=ax_color.transAxes)
# 将颜色名称字符打印至子图ax_name
ax_name.text(0.5, 0.5, name, va='center', ha='center',
fontsize=12, transform=ax_name.transAxes)
plt.tight_layout(pad=0)
plt.subplots_adjust(wspace=0,hspace=0) # 子图水平与垂直方向间距均调成0
绘制N条渐变线
利用RGB元组的数据连续性,可以很快的绘制线性变化的颜色序列。本图绘制了颜色在R通道上从150-255线性均匀的变化过程。
N = 6
x = np.linspace(-4, 4, 100)
ys = [i+np.sin(x) for i in range(N)]
cs = np.linspace(150, 255, N)
for i, y in enumerate(ys):
plt.plot(x, y, c=(cs[i]/255.0, 0, 0), lw=10, label='R-%d' % int(cs[i]))
plt.legend(bbox_to_anchor=(1.02, 0.8), fontsize=15, frameon=False)
# plt.xticks([])
# plt.yticks([])
不难看出元组表示的颜色更适合连续数据的映射,十六进制表示的颜色更适合精确命名颜色。
自定义颜色空间
mpl.colors.ListedColormap
ListedColormap定制的是离散型色板,颜色会根据色板列表循环。
本例在r,g,b三个通道上,线性地生成了20个数据,并通过列表推导式,生成了包含此20个颜色的列表。并将颜色与数据y的大小进行了映射。
从图中可以看出,y值比较小的时候,颜色为翠绿色,当y值比较大的时候,颜色为亮紫色。
N=20
colors=[(r,g,b) for (r,g,b) in zip(np.linspace(0,1,N),np.linspace(1,0,N),np.linspace(0,1,N))]
my_cmap=mpl.colors.ListedColormap(colors)
plt.cm.register_cmap(name='mycmp',cmap=my_cmap)
x=np.linspace(-np.pi,np.pi,N)
y=np.sin(x)
plt.scatter(x,y,s=200,c=y,cmap=plt.cm.get_cmap('mycmp'))
输出定制色板的前五种颜色,如下:
plt.cm.get_cmap('mycmp').colors[:5]
输出
[(0.0, 1.0, 0.0),
(0.05263157894736842, 0.9473684210526316, 0.05263157894736842),
(0.10526315789473684, 0.8947368421052632, 0.10526315789473684),
(0.15789473684210525, 0.8421052631578947, 0.15789473684210525),
(0.21052631578947367, 0.7894736842105263, 0.21052631578947367)]
mpl.colors.LinearSegmentedColormap.from_list
LinearSegmentedColormap定制的是一个色板算法。其原理是通过线性的给出3个颜色点,其余颜色通过插值的方式获得。
本例将色板与y值大小进行了数据映射,从图中可以看出y较小时为黑色,较大时为白色,中间为蓝色,与我们传入的startcolor、midcolor、endcolor实现了对应。
startcolor=(0.0,0.0,0.0) # 全白
midcolor=(0.0,0.0,1.0) # 全蓝
endcolor=(1.0,1.0,1.0) # 全黑
my_cmap1=mpl.colors.LinearSegmentedColormap.from_list(name='mycmp1',colors=[startcolor,midcolor,endcolor])
plt.cm.register_cmap(name='mycmp1',cmap=my_cmap1)
x=np.linspace(-np.pi,np.pi,N)
y=np.sin(x)
plt.scatter(x,y,s=200,c=y,cmap=plt.cm.get_cmap('mycmp1'))
颜色地图colormap
可通过plt.cm直接调用已在matplotlib中定制好的色板。
以下两个案例分别将y、x数据与色板进行了数据映射,效果如下。
plt.cm
N=40
np.random.seed(123)
x=np.random.randn(N)
y=np.random.randn(N)
s=np.linspace(100,1000,N)
fig=plt.figure()
ax=fig.add_subplot(111)
mappable=ax.scatter(x,y,c=y,s=s,alpha=0.6,cmap=plt.cm.RdBu)
# 添加颜色条
cb=plt.colorbar(mappable=mappable,ax=ax)
N=40
np.random.seed(123)
x=np.random.randn(N)
y=np.random.randn(N)
s=np.linspace(100,1000,N)
fig=plt.figure(figsize=(6,5))
ax=fig.add_subplot(111)
mappable=ax.scatter(x,y,c=x,s=s,alpha=0.6,cmap=plt.cm.RdBu)
# 添加水平方向颜色条
cb=plt.colorbar(mappable=mappable,ax=ax,orientation='horizontal')
plt.cm — listed_cmap
plt.cm包含很多默认色板,如下:
cmap_all='''Accent = <matplotlib.colors.ListedColormap object>
Accent_r = <matplotlib.colors.ListedColormap object>
Blues = <matplotlib.colors.LinearSegmentedColormap object>
………………
hot = <matplotlib.colors.LinearSegmentedColormap object>
hot_r = <matplotlib.colors.LinearSegmentedColormap object>
hsv = <matplotlib.colors.LinearSegmentedColormap object>
hsv_r = <matplotlib.colors.LinearSegmentedColormap object>
inferno = <matplotlib.colors.ListedColormap object>
inferno_r = <matplotlib.colors.ListedColormap object>
jet = <matplotlib.colors.LinearSegmentedColormap object>
…………………
winter = <matplotlib.colors.LinearSegmentedColormap object>
winter_r = <matplotlib.colors.LinearSegmentedColormap object>'''
提取信息
def get_cmap_names(string):
import re
match=re.match(pattern=r'(.*) = <matplotlib.colors.(.*) object>',string=string)
return match.group(1),match.group(2)
cmap_dict={}
for cmap_ in cmap_all.split('\n'):
cmap_name,cmap_cat=get_cmap_names(cmap_)
cmap_dict[cmap_name]=cmap_cat
listed_cmap_list=[k for k,v in cmap_dict.items() if v=='ListedColormap']
listed_Seg_cmap_list=[k for k,v in cmap_dict.items() if v=='LinearSegmentedColormap']
cmap_dict,listed_cmap_list,listed_Seg_cmap_list
len(listed_cmap_list),len(listed_Seg_cmap_list)
plt.cm共包含38个ListedColormap色板和126个LinearSegmentedColormap
(38, 126)
可视化listed_cmap
将38个ListedColormap色板可视化,如下:
from matplotlib.gridspec import GridSpec
nrows = len(listed_cmap_list)
gs = GridSpec(nrows=nrows, ncols=2, width_ratios=[
6, 15], left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
fig = plt.figure(figsize=(8, 12))
plt.xticks([])
plt.yticks([])
x = np.linspace(0, 1, 20)
y = x.reshape(-1, 1)
xv, yv = np.meshgrid(x, y)
for i, listed_map in enumerate(listed_cmap_list):
# 奇数子图显示colormap名称
ax = fig.add_subplot(gs[i, 0], frameon=True)
ax.text(0.5, 0.5, listed_map, fontdict={
'size': 17}, ha='center', va='center', transform=ax.transAxes)
ax.set_xticks([])
ax.set_yticks([])
# 偶数子图绘制colormap渐变颜色
ax = fig.add_subplot(gs[i, 1], frameon=True)
ax.imshow(xv, cmap=plt.cm.get_cmap(listed_map), aspect='auto' # auto 自动调节以充满整个ax
, extent=(0, 1, 0, 1)) # 通过增加extent,从而充满整个ax坐标轴
ax.set_xticks([])
ax.set_yticks([])
可视化listed_Seg_cmap
将126个LinearSegmentedColormap色板可视化,如下:
nrows = len(listed_Seg_cmap_list)
gs = GridSpec(nrows=nrows, ncols=2, width_ratios=[
6, 15], left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
fig = plt.figure(figsize=(12, 35))
plt.xticks([])
plt.yticks([])
x = np.linspace(0, 1, 20)
y = x.reshape(-1, 1)
xv, yv = np.meshgrid(x, y)
for i, listed_map in enumerate(listed_Seg_cmap_list):
index = i
ax = fig.add_subplot(gs[index, 0], frameon=True)
ax.set_xticks([])
ax.set_yticks([])
ax.text(0.5, 0.5, listed_map, fontdict={
'size': 17}, ha='center', va='center', transform=ax.transAxes)
ax = fig.add_subplot(gs[index, 1], frameon=True)
ax.set_xticks([])
ax.set_yticks([])
ax.imshow(xv, cmap=plt.cm.get_cmap(listed_map), aspect='auto' # auto 自动调节以充满整个ax
, extent=(0, 1, 0, 1)) # 通过增加extent,从而充满整个ax坐标轴
色板 palette
seaborn是在matplolib基础上封装的高级可视化库,其色板主要通过palette参数设置
色板命名空间
seaborn默认6种色板命名空间:deep,muted, pastel, bright, dark, colorblind
各色板颜色变化顺序如下:
pal = sns.color_palette(palette='deep')
sns.palplot(pal)
pal = sns.color_palette(palette='muted')
sns.palplot(pal)
pal = sns.color_palette(palette='pastel')
sns.palplot(pal)
pal = sns.color_palette(palette='bright')
sns.palplot(pal)
pal = sns.color_palette(palette='dark')
sns.palplot(pal)
pal = sns.color_palette(palette='colorblind')
sns.palplot(pal)
定制色板
有多种方式定制seaborn的palette。
基于plt.cm
pal=pal_from_cmap=sns.color_palette('hot')
sns.palplot(pal)
基于颜色列表
colors=[(r,g,b) for r,g,b in zip(np.linspace(0,1,5),np.linspace(0.2,0.7,5),np.linspace(0,1,5))]
pal=pal_from_colors=sns.color_palette(colors)
sns.palplot(pal)
基于HLS色彩空间生成的离散型色盘
pal=sns.hls_palette(n_colors=10, h=0.5, l=0.6, s=0.3)
sns.palplot(pal)
基于球形空间
# 基于球形空间生成的线性色盘
pal=sns.cubehelix_palette(n_colors=10,
start=1,
rot=0.4,
gamma=1.0,
hue=0.8,
light=0.85,
dark=0.15,)
sns.palplot(pal)
基于色系
pal=sns.dark_palette(color='red',n_colors=10,)
sns.palplot(pal)
pal=sns.light_palette(color='red',n_colors=10,)
sns.palplot(pal)
色板最直接、最明显的应用绘图对象包括:散点图、面积图、填充图、等高线图、等高面图、热力图等。前三个已经本系列第二部分做了详细介绍,以下对剩下几个绘图对象做案例介绍,细节处请详细阅读代码注释:
等高线
等高线
N = 10
x = np.linspace(-10, 10, N)
y = np.linspace(-10, 10, N)
X, Y = np.meshgrid(x, y) # return X,Y, shape 100*100,100*100
# 这里的X,Y可以这么理解,相当于把x轴上的点,和y轴上的点投影到了 2D平面
# 针对2D平面的每一个坐标点,计算Z的值。 Z shape 100*100
Z = np.power(X/0.8, 2)+np.power(Y/0.6, 2)
fig = plt.figure()
ax = fig.add_subplot(111)
# levels 显示指定的Z值 比如 x=0,y=-10,此时 Z=100
CS = ax.contour(X, Y, Z, levels=[20, 40, 70,
100, 150], cmap=plt.cm.RdBu, linewidths=5)
ax.clabel(CS, fontsize=16, fmt='%.0f')
cb = plt.colorbar(mappable=CS)
等高面
N = 1000
x = np.linspace(0, 10, N)
y = np.linspace(-10, 10, N)
X, Y = np.meshgrid(x, y) # return X,Y, shape 100*100,100*100
# 这里的X,Y可以这么理解,相当于把x轴上的点,和y轴上的点投影到了 2D平面
Z = np.power(X, 2)-np.power(Y, 2) # 针对2D平面的每一个坐标点,计算Z的值。 Z shape 100*100
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(-1, 11)
ax.set_ylim(-12, 12)
CS = ax.contourf(X, Y, Z # ,levels=[-100,-50,50,100]
, cmap=plt.cm.Blues
) # levels 显示指定的Z值 比如 x=0,y=-10,此时 Z=100
ax.clabel(CS, fontsize=16, fmt='%.0f', inline=False, colors='black')
cb = plt.colorbar(mappable=CS)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.plot(x, x, c=(0.75, 0, 0), lw=4, ls='--')
热力图
grid_kws = {"height_ratios": (0.9, 0.1)} # 热力图与颜色条高度比值为 0.9:0.1
fig, (ax, cbar_ax) = plt.subplots(
2, 1, gridspec_kw=grid_kws) # 2行1列,第一行为热力图,第二行为颜色条
data = np.random.randn(64).reshape(8, -1)
sns.heatmap(data,
ax=ax,
cbar_ax=cbar_ax,
cbar=True,
cbar_kws={"orientation": "horizontal"}) # 将颜色条方向调为水平方向
总结
颜色命名空间 mpl.colors 总结
命名空间 | 类型 | 键 | 值 | 颜色个数 |
---|---|---|---|---|
BASE_COLORS | 字典 | 颜色名称 | RGB元组 | 8 |
TABLEAU_COLORS | 字典 | 颜色名称 | 十六进制 | 10 |
cnames | 字典 | 颜色名称 | 十六进制 | 148 |
CSS4_COLORS | 字典 | 颜色名称 | 十六进制 | 148 |
XKCD_COLORS | 字典 | 颜色名称 | 十六进制 | 949 |
plt.cm 总结
颜色板 | 类型 | 值 | 色板个数 | 创建/引用举例 |
---|---|---|---|---|
ListedColormap | 字典 | RGB元组 | 38 | plt.cm.Accent |
LinearSegmentedColormap | 字典 | RGB元组 | 126 | plt.cm.hot |
自定义 ListedColormap | 列表 | RGB元组 | 任意多 | mpl.colors.ListedColormap(colors) plt.cm.get_cmap(‘mycmap’) |
自定义 LinearSegmentedColormap | 列表 | 3个RGB元组数学运算得到 (start,mid,end) | 任意多 | mpl.colors.LinearSegmentedColormap.from_list(name,colors) plt.cm.get_cmap(‘mycmap’) |
希望对你有所帮助和启发!