2.2 颜色系统与色彩映射


文档摘要

2.2 颜色系统与色彩映射 本节摘要:颜色是 Matplotlib 里最容易"让图变专业"、也最容易"一用就翻车"的部分。本节先讲清五种写颜色的方式——RGB、RGBA、十六进制、灰度、命名颜色,再讲 colormap(色彩映射)如何把数值翻译成颜色,以及顺序、发散、定性、循环四类的适用场景,顺带说清颜色条与归一化如何让颜色和数值严格对应。学完你能按数据类型选对配色,避开红绿混淆、感知不均这些坑。

2.2 颜色系统与色彩映射

本节摘要:颜色是 Matplotlib 里最容易"让图变专业"、也最容易"一用就翻车"的部分。本节先讲清五种写颜色的方式——RGB、RGBA、十六进制、灰度、命名颜色,再讲 colormap(色彩映射)如何把数值翻译成颜色,以及顺序、发散、定性、循环四类的适用场景,顺带说清颜色条与归一化如何让颜色和数值严格对应。学完你能按数据类型选对配色,避开红绿混淆、感知不均这些坑。

上手前先明确

阅读完本节,你应当能够:

  1. 用 RGB、RGBA、十六进制、灰度、命名颜色五种方式在代码里指定颜色
  2. 解释 colormap 的含义,以及它和"直接指定颜色"的区别
  3. 区分顺序、发散、定性、循环四类 colormap,并说出各自适合的数据
  4. 按数据类型(连续、正负、类别、循环)挑选合适的 colormap
  5. ListedColormapLinearSegmentedColormap 做简单的自定义配色

一、颜色不是装饰,是编码

先纠正一个普遍误解:在数据可视化里,颜色首先不是用来"好看"的,而是用来编码信息的。柱状图里每种柱子的颜色,可能在标记不同的类别;热力图里从蓝到红的渐变,可能在表示数值从低到高;散点图里点的深浅,可能在表示密度。颜色一旦用错,读者会读出错误的信息,或者干脆读不出信息。

但颜色又确实影响美观。一张图里大红配大绿、饱和度拉满,信息再准确也让人不想看。所以我们既要懂"颜色怎么指定",也要懂"颜色怎么选"——前者是语法,后者是审美加逻辑。这两件事搞混,是最常见的起点错误:以为学会了五种写法就懂配色了,其实那只是刚会写字的阶段,离把图配得又准又好看还差得远。

Matplotlib 在这两件事上都给了充分的空间:指定颜色有五种写法,选颜色有一套预定义的 colormap 体系和自定义接口。下面我们分开讲。

二、五种写颜色的方式

同一个"红色",Matplotlib 至少有五种表达。它们没有优劣之分,只是适用场景不同。我按"从精确到省事"的顺序过一遍。

RGB 元组是最底层的写法,用三个 0 到 1 的浮点数表示红、绿、蓝的强度:(1, 0, 0) 是纯红,(0, 0, 1) 是纯蓝,(0.5, 0.5, 0.5) 是灰。注意这里是 0 到 1,不是 0 到 255,很多从网页设计转过来的人会在这里栽跟头。RGBA 就是 RGB 再加一个 Alpha 通道表示透明度,(1, 0, 0, 0.5) 是半透明红,Alpha 为 0 时完全透明。

十六进制是网页风格的写法,以 # 开头:#FF0000 是纯红,#3399CC 是种浅蓝,八位的 #FF000080 末尾两位表示透明度。如果你手头有设计稿,上面标注的颜色多半是十六进制,直接照抄即可。

灰度值是单个浮点数或字符串,0 是黑、1 是白,'0.5' 是中间灰。打印或灰度显示场景常用,也能做出克制、耐看的单色图。

命名颜色最省事,直接写 'red''skyblue''coral'。Matplotlib 内置了 CSS 和 XKCD 两套命名颜色,数量上百个,日常够用。它的缺点是颜色名不精确——你写 'blue' 得到的蓝,未必是你心里那个蓝。

import matplotlib.pyplot as plt x = [1, 2, 3, 4] plt.plot(x, [2, 4, 1, 3], color=(1, 0, 0)) # RGB 元组 plt.plot(x, [3, 1, 4, 2], color='#008000') # 十六进制 plt.plot(x, [1, 2, 3, 4], color='0.6') # 灰度 plt.plot(x, [4, 2, 3, 1], color='skyblue') # 命名颜色 plt.scatter([1.5, 2.5], [2.5, 1.5], color=(0, 0, 1, 0.5)) # RGBA 半透明蓝

选哪种写法,看你要不要"精确可控"。要精确复现某个颜色,用 RGB 或十六进制;图省事、颜色要求不高,用命名颜色。灰度值我多用在"要打印出来"或"想要单色高级感"的场合。

三、colormap:把数值翻译成颜色

五种写法解决的是"给单个元素指定颜色",但热力图、等高线、散点着色这类场景,颜色要随数据值连续变化——一个数据值对应一个颜色。这靠的就不是写死一个颜色,而是 colormap(色彩映射)。

colormap 本质上是一个"翻译器":输入一个 0 到 1 的数值,输出一个颜色。数值小,映射到 colormap 一端的颜色;数值大,映射到另一端;中间值落在中间的过渡色上。cmap='viridis' 就是把数值翻译成"深紫到亮黄"的渐变色带。理解了这个"翻译器"的比喻,你就明白为什么 colormap 的选择会直接决定读者看到的数据模式——翻译错了,趋势就失真了。

Matplotlib 内置的 colormap 分四类,这是本节的重点。顺序型从浅到深连续变化,适合"从低到高、从小到大"这种有方向的连续数据,代表有 viridismagmaplasmaBlues发散型两端是两种对立的颜色、中间是浅色,适合"围绕一个中心值向两侧偏离"的数据,比如有正有负、有涨有跌,代表有 coolwarmRdBuseismic定性型是一组差异明显的离散颜色,适合类别数据,比如 tab10Set1Paired循环型首尾相接,适合角度、时间、相位这类转一圈又回到原点的数据,比如 twilighthsv

图 2.2 colormap 三类对比

图 2.2 colormap 三类对比

选 colormap 的黄金法则是:先问数据有没有"方向"和"中心"。有方向、单向递增,用顺序型;有方向、但围绕中心正负发散,用发散型;没有顺序只有类别,用定性型;数据是循环的,用循环型。这个判断做对了,配色就成功了一大半。

类型 代表 数据特征 典型例子
顺序型 viridis、magma、Blues 从低到高单向连续 温度、销量、浓度
发散型 coolwarm、RdBu、seismic 围绕中心正负偏离 涨跌幅、相关系数
定性型 tab10、Set1、Paired 无序类别 不同产品、不同组别
循环型 twilight、hsv 首尾相接循环 角度、时间、相位

四、为什么默认是 viridis,而不是彩虹色

这里值得停下来讲一个工程判断:Matplotlib 把默认 colormap 定为 viridis,不是随便选的。老式的"彩虹色"(从红到紫穿过整个光谱)虽然看着热闹,却有严重的感知问题——它的亮度并不随数据值单调变化,中间有一段又亮又突兀,会误导读者以为那里数据有突变。更重要的是,红绿段对色盲人群不友好,这部分差异在红绿色盲眼里直接消失。

viridis 这类新式 colormap 解决的就是这两个问题:亮度单调递增,数据从低到高时视觉上也稳定地从深到亮;且对色盲相对友好。所以顺序数据我基本无脑用 viridis,只有在正负发散时才换成 coolwarm,在纯类别时才换 tab10。除非有品牌色或特殊语义要求,别轻易回到彩虹色。

还有个细节:当类别只有两三个时,与其套一个定性 colormap,不如直接给每个类别写一个具体颜色,比如一个用 '#1f77b4'、一个用 '#ff7f0e'。这样每个颜色都有明确的语义绑定,读者一看图例就知道谁是谁,比从 colormap 里自动取色更可控,也更好在正文里用文字描述。colormap 真正发力的地方,是类别多、或数值连续、需要自动过渡的场景。

五、自定义 colormap

预置方案不够用时,Matplotlib 允许自定义。两条路径对应两种需求:要离散的颜色分段,用 ListedColormap;要连续的渐变过渡,用 LinearSegmentedColormap

from matplotlib.colors import ListedColormap, LinearSegmentedColormap # 离散:四个颜色对应四类 discrete = ListedColormap(['lightcoral', 'skyblue', 'lightgreen', 'gold']) # 连续:定义各通道在 0、0.5、1 三个位置的颜色,中间自动线性插值 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.5, 0.0, 1.0), (1.0, 1.0, 1.0)], 'blue': [(0.0, 1.0, 1.0), (0.5, 0.0, 0.0), (1.0, 0.0, 0.0)], } continuous = LinearSegmentedColormap('my_cmap', cdict)

ListedColormap 就是把一个颜色列表直接当成映射表,数据落进哪个区间就用哪个颜色,干净利落。LinearSegmentedColormap 复杂一点,它要你分别给出红、绿、蓝三个通道在若干锚点上的值,锚点之间线性插值——本质是在"手工拼一条渐变色带"。我的经验是:离散分类用前者,想做"从品牌色浅到深"这类连续渐变用后者。

和 colormap 一起出现、但常被当成一回事的,还有颜色条归一化,这里顺带说清。颜色条(colorbar)是把 colormap 的渐变显示成一根带刻度的竖条,充当"颜色对照表",告诉读者这个颜色对应哪个数值,热力图和等高线图几乎必带。它不是手画的,plt.colorbar() 一行就加上。归一化(Normalize)则决定数据值如何映射到 colormap 的 0 到 1 区间:默认是线性映射,但当数据呈数量级差异(比如从 1 到 10000)时,对数归一化能让颜色分布更合理,否则小数值全挤在一端、颜色根本拉不开。

颜色条还有一个关键性质:它和主图共享同一套归一化,所以颜色条上的数值范围和主图数据严格对应,这是它可信的来源。多子图里各带颜色条时,用 fig.colorbar 并指定对应的 ax,让每个颜色条贴对位置。把它们串起来理解就是:colormap 负责"翻译",colorbar 负责"给出对照表",归一化负责"决定翻译规则",三者配合才是一条完整的数值到颜色链路。

⚠️ 常见坑:RGB 元组的值域是 0 到 1,不是 0 到 255。写成 (255, 0, 0) 会被截断成纯红,颜色看着"对"其实是错的,透明度或混合时就露馅。另一个坑是给连续数据用了定性 colormap,颜色跳变会制造出根本不存在的"断层"。
💡 关键直觉:把 colormap 当成"数值的翻译官"。翻译官选得不好,数据的意思就传歪了。所以选 colormap 前先问一句:这个数据是"从低到高"还是"有正有负"还是"只是不同类"?

六、常被问到的几个细节

RGB 的 0 到 1 和网页的 0 到 255,到底怎么回事

Matplotlib 里 RGB 元组用的是归一化的 0 到 1,而网页、设计稿里习惯用 0 到 255。换算很简单:把 255 制除以 255 就是 Matplotlib 要的值。(255, 0, 0) 这种写法会被截断成纯红,颜色看起来"对",其实已经踩了边界,做透明度和混合时就露馅。不确定的话,宁可用十六进制,它和设计稿是同一种语言。

给连续数据用了定性 colormap,会出什么问题

定性 colormap 是一组彼此割裂的离散颜色,没有"从低到高"的渐变。把它套在连续数据上,会在颜色跳变处制造出根本不存在的数据断层,读者会以为那里有突变。反过来,把顺序型 colormap 用在类别数据上,相邻类别又可能糊在一起分不清。所以选 colormap 的第一问永远是:数据有没有方向。

怎么照顾色盲读者

避开红绿这对最容易混淆的颜色组合。viridiscividisplasma 这类新式 colormap 在设计时就考虑了色盲友好,顺序数据直接选它们最省心。如果非要表达正负对立,coolwarm 的红蓝对比在色盲下也相对可辨。真要严格,就用色盲模拟工具检查一遍成图。

想用某个 colormap,但方向反了怎么办

在 colormap 名字后加 _r 即可反转,比如 cmap='viridis_r' 就是把"深到亮"翻成"亮到深"。这在你需要"高值浅、低值深"的语义时很常用,不用去重新定义一个映射。

命名颜色从哪查

Matplotlib 内置了 CSS 和 XKCD 两套命名颜色,加起来上百个。想要精确的色名,可以查 matplotlib.colors.CSS4_COLORSmatplotlib.colors.XKCD_COLORS 这两个字典。不过我的经验是,日常写作直接用几个记得住的色名,精确复现再换十六进制或 RGB。

要点串联

  • 颜色是编码,不只是装饰:它标记类别、表达数值,选错会误导读者。
  • 五种写法:RGB、RGBA、十六进制、灰度、命名颜色,精确度从高到低。
  • RGB 值域是 0 到 1:别和网页的 0 到 255 搞混。
  • colormap 是翻译器:输入数值,输出颜色,viridis 是默认的稳妥选择。
  • 四类 colormap:顺序看趋势、发散看偏离、定性看类别、循环看周期。
  • 优先感知均匀、色盲友好的方案viridiscividis 等,避开老式彩虹色。
  • 自定义两条路:离散用 ListedColormap,连续用 LinearSegmentedColormap

颜色定好了,图的"神"就有了。但一张图要能拿出手,还得统一外观、打磨细节——这正是第 2.3 节"样式与美化"要干的事。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U