1. 为什么颜色和风格值得单独拎出来讲
很多人学Seaborn的路径都差不多:先跑通scatterplot、lineplot、boxplot这几个基础图,觉得API挺简单,然后就开始往业务里塞。结果图是出来了,但要么颜色丑得没法放进汇报材料,要么同一份报告里五张图风格各异,看起来像五个人做的。更尴尬的是,明明数据结论很清晰,图一摆出来,别人第一眼注意到的却是“这个配色怎么这么刺眼”。
这就是我把颜色和风格单独拿出来写一篇的原因。Seaborn真正区别于Matplotlib的地方,不只是统计图类型丰富,而是它把“视觉决策”这件事系统化了。它内置了几套经过调校的调色板和主题风格,让你不用从零去调RGB,也能做出能直接放进文档、汇报、论文里的图。
这篇内容适合两类人:一类是已经会用Seaborn画基础图,但图总是“差口气”的;另一类是接手了别人的可视化代码,想搞清楚set_theme、color_palette、palette参数到底在干什么的。我会把颜色体系、风格体系、二者如何配合、以及实际项目里怎么选,按我自己的使用顺序讲一遍。
先给一个整体认知:Seaborn的视觉控制分两条线,一条是颜色(color),管的是数据映射成什么颜色;另一条是风格(style),管的是背景、网格、边框、字体这些与数据无关的装饰。两条线是正交的,可以独立设置,也可以一起设置。理解这一点,后面所有参数都不会乱。
2. Seaborn颜色体系的核心逻辑
2.1 三类调色板:定性、顺序、发散
Seaborn把调色板分成三大类,这个分类不是它自己发明的,而是数据可视化领域的通用分类,但Seaborn把它做成了可以直接调用的函数。
**定性调色板(Qualitative)**用于分类数据,类别之间没有大小顺序。比如不同产品线、不同地区、不同实验组。这类调色板的核心要求是:颜色之间区分度要高,但亮度、饱和度要接近,不能出现某个颜色特别抢眼的情况。Seaborn里典型的是deep、muted、pastel、bright、dark、colorblind这几套。
**顺序调色板(Sequential)**用于有大小顺序的数据,比如温度、销量、密度。颜色从浅到深或者从深到浅,亮度单调变化。Seaborn里以rocket、mako、flare、crest为代表,这几个是后来加入的,比早期直接借用Matplotlib的viridis、Blues更有辨识度。
**发散调色板(Diverging)**用于有中心点的数据,比如增长率(正负)、温差(高于/低于基准)、相关系数(-1到1)。颜色从中心向两端发散,中心通常是浅色或中性色。Seaborn里典型的是vlag、icefire。
我自己的判断标准很简单:先问数据有没有顺序,再问有没有中心点。两个都没有,用定性;有顺序没中心,用顺序;有顺序有中心,用发散。这个判断顺序不能反,反了就会出现“用彩虹色表示温度”这种经典错误。
2.2 调色板的调用方式与返回值
Seaborn里获取调色板最常用的函数是sns.color_palette()。它返回的是一个列表,里面是RGB元组。这个返回值可以直接传给Matplotlib的color参数,也可以传给Seaborn绘图函数的palette参数。
import seaborn as sns import matplotlib.pyplot as plt # 获取默认调色板 palette = sns.color_palette() print(palette) # 输出类似:[(0.12, 0.47, 0.71), (1.0, 0.5, 0.05), ...] # 获取指定调色板,指定颜色数量 palette = sns.color_palette("deep", 5) print(len(palette)) # 5这里有个细节值得说:当你指定的颜色数量超过调色板本身的数量时,Seaborn会自动循环或插值。比如deep本身是10个颜色,你要12个,它会循环前两个。但如果你要的是顺序调色板,比如rocket,它会从调色板里均匀采样,而不是循环。这个行为差异很重要,因为定性调色板循环会导致两个类别颜色相同,而顺序调色板采样不会。
注意:如果你的分类数量超过调色板容量,优先换一个容量更大的调色板,或者手动指定颜色列表,不要依赖自动循环。两个类别颜色一样,图就废了。
2.3 自定义调色板的三种方式
内置调色板不够用的时候,有三种自定义方式,我按使用频率排序。
第一种是基于现有调色板微调。比如你觉得deep不错,但想让第一个颜色更饱和一点,可以用snsset或者直接改列表。
base = sns.color_palette("deep", 5) custom = [(r, g, b) for r, g, b in base] custom[0] = (0.9, 0.2, 0.2) # 把第一个改成红色 sns.palplot(custom)第二种是用blend_palette混合。这个函数可以生成从一种颜色到另一种颜色的渐变,适合做顺序调色板。
# 从浅蓝到深蓝,生成7个颜色 palette = sns.blend_palette(["#B0D0E8", "#08306B"], 7) sns.palplot(palette)第三种是用dark_palette或light_palette基于单色生成。这两个函数以一个种子色为基础,生成从深到浅或从浅到深的顺序调色板。
# 基于一个绿色生成顺序调色板 palette = sns.dark_palette("#2E8B57", 6) sns.palplot(palette)我个人的经验是,项目里最常用的其实是第二种和第三种。因为业务方经常给一个品牌色,要求图表围绕这个色系做,这时候dark_palette和blend_palette就非常顺手。第一种微调反而用得少,因为微调容易调出“说不上哪里怪”的颜色。
3. 风格体系:不只是好不好看的问题
3.1 五种内置风格的实际差异
Seaborn内置了五种风格:white、dark、whitegrid、darkgrid、ticks。很多人只知道darkgrid是默认的,但不知道这五种风格到底差在哪,什么时候该换。
我按自己的使用场景给它们排个序:
| 风格 | 背景 | 网格 | 适用场景 | 我的使用频率 |
|---|---|---|---|---|
whitegrid | 白色 | 有 | 汇报、文档、需要打印 | 最高 |
darkgrid | 灰色 | 有 | 探索性分析、Jupyter里看 | 高 |
white | 白色 | 无 | 论文、极简风格 | 中 |
ticks | 白色 | 无,但有刻度线 | 需要精确读数的图 | 中 |
dark | 灰色 | 无 | 深色主题演示 | 低 |
whitegrid和darkgrid的区别不只是背景色。darkgrid的灰色背景在屏幕上看着舒服,但打印出来会变成一片灰,很费墨,而且如果图要放进Word文档,灰色背景和白色页面之间会有明显的边界,看起来不专业。所以只要图是要放进文档或打印的,我一律用whitegrid。
white和ticks的区别在于刻度线。ticks会在坐标轴外侧加短刻度线,适合需要精确读数的场景,比如工程图纸、实验数据。white则完全干净,适合追求极简的场合。
3.2 set_style与set_theme的区别
这是很多人容易混淆的地方。set_style()只控制风格,不影响调色板;set_theme()同时控制风格、调色板、字体、上下文等多个维度。
# 只改风格,颜色不变 sns.set_style("whitegrid") # 同时改风格和调色板 sns.set_theme(style="whitegrid", palette="deep")我现在的习惯是统一用set_theme,因为一个项目里风格和颜色通常是一起定的,分开设置容易漏。而且set_theme还支持context参数,可以一键调整字体大小和线条粗细,适配不同输出场景。
# 适配论文场景:小字体、细线条 sns.set_theme(style="white", palette="muted", context="paper") # 适配汇报场景:大字体、粗线条 sns.set_theme(style="whitegrid", palette="deep", context="talk")context有四个预设:paper、notebook、talk、poster。字号和线条粗细依次递增。这个功能在需要同时输出论文版和汇报版图表时特别省事,不用手动调fontsize和linewidth。
3.3 风格覆盖与局部调整
set_theme是全局设置,但有时候你只想让某一张图用不同风格。这时候可以用axes_style上下文管理器。
with sns.axes_style("white"): plt.figure(figsize=(8, 5)) sns.scatterplot(data=df, x="x", y="y") plt.show() # 退出上下文后,全局风格不变这个用法在写函数或类的时候特别有用。比如你封装了一个绘图函数,希望它不管外部全局风格是什么,都输出统一风格,就可以在函数内部用axes_style包一层。
提示:
axes_style只影响风格,不影响调色板。如果连调色板也要临时改,用plotting_context配合,或者直接在绘图函数里指定palette参数。
4. 颜色与风格的配合实战
4.1 从业务需求反推视觉方案
我接过一个需求,要在一张图里展示六个地区的季度销售趋势,同时用颜色深浅表示同比增长率。这个需求里其实混了两层信息:地区是分类,增长率是连续值。如果只用一种颜色映射,必然有一层信息丢失。
我的处理方式是:用定性调色板区分地区,用顺序调色板映射增长率,但两者不放在同一张图里。具体做法是画两张图,一张用定性色画趋势线,一张用顺序色画增长率热力图。这样每张图只承担一个视觉任务,读者不会混淆。
如果非要放在一张图里,可以用hue映射地区,用size或style映射增长率,但这样可读性会下降。我的原则是:一张图最多承担两个视觉维度,超过两个就拆图。
4.2 调色板与数据类型的匹配检查
在正式画图之前,我习惯先做一次匹配检查。这个检查很简单,就是问三个问题:
- 这个变量是分类还是连续?
- 如果是连续,有没有自然中心点?
- 类别数量是多少?
根据答案选调色板类型,再根据类别数量选具体调色板。比如:
- 分类,5个类别:
deep、muted、colorblind都可以,colorblind在需要兼顾色觉障碍读者时优先。 - 连续,无中心点,范围0到100:
rocket、mako、viridis都可以,rocket在深色背景上表现更好。 - 连续,有中心点,范围-1到1:
vlag、icefire,中心点用浅色,两端用对比色。
这个检查花不了一分钟,但能避免90%的配色返工。
4.3 让图表在黑白打印下依然可读
很多汇报材料最终会被打印成黑白,这时候颜色区分就失效了。我的应对策略是颜色加形状双重编码。
sns.scatterplot( data=df, x="x", y="y", hue="category", style="category", palette="deep", s=100 )这样即使打印成黑白,不同类别也能通过点形状区分。如果是折线图,可以用dashes参数让不同线型不同。
sns.lineplot( data=df, x="x", y="y", hue="category", style="category", palette="deep", dashes=True )这个技巧我从一次惨痛经历里学来的:当时做了二十页彩色图表,结果会议室打印机只有黑白,所有图看起来都一样,汇报效果大打折扣。从那以后,只要图有可能被打印,我一定加形状或线型编码。
5. 常见问题与排查技巧实录
5.1 颜色不生效的几种原因
这是被问得最多的问题:“我设置了palette,但图还是默认颜色。”原因通常有这几个:
第一种,palette参数名写错或位置不对。Seaborn不同绘图函数的参数名不完全一致,scatterplot用palette,boxplot也用palette,但heatmap用的是cmap。写错了不会报错,只是被忽略。
第二种,hue没有设置。palette只有在有hue映射时才生效。如果你只画单色图,palette不起作用,要用color参数。
第三种,全局主题覆盖了局部设置。如果你在set_theme之后又调用了set_palette,后者会覆盖前者。检查一下代码顺序。
第四种,Matplotlib的rcParams被其他代码改了。这种情况在Jupyter里常见,因为notebook是顺序执行的,前面的代码可能改了全局配置。重启内核或者显式重置可以解决。
5.2 调色板数量不够用的处理
当类别数超过调色板容量时,有三个处理方式,我按推荐顺序排:
- 换容量更大的调色板。
hls可以生成任意数量的颜色,但颜色质量参差不齐。husl更好,但需要额外安装。 - 手动指定颜色列表。从多个调色板里挑颜色拼起来,或者用在线工具生成。
- 合并小类别。如果类别太多,本身就应该考虑合并,而不是硬塞颜色。
我一般优先考虑第三种,因为类别太多本身就是数据问题,不是可视化问题。把占比小于5%的类别合并成“其他”,图会清晰很多。
5.3 风格设置后字体乱码的处理
设置风格后中文字体变成方框,这是Matplotlib的经典问题。Seaborn的风格设置会重置字体配置,导致中文字体失效。解决方法是在set_theme之后重新指定中文字体。
sns.set_theme(style="whitegrid", palette="deep") plt.rcParams["font.sans-serif"] = ["SimHei"] # 指定中文字体 plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题注意axes.unicode_minus这一行,不设置的话负号也会变成方框。这个坑我踩过不止一次,每次换环境都要重新设一遍。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 颜色不生效 | hue未设置 | 设置hue或改用color参数 |
| 颜色不生效 | 参数名错误 | 检查是palette还是cmap |
| 颜色循环重复 | 类别数超过调色板容量 | 换大容量调色板或合并类别 |
| 中文字体变方框 | 风格设置重置了字体 | 重新指定font.sans-serif |
| 负号变方框 | unicode_minus未设置 | 设置axes.unicode_minus=False |
| 打印后背景灰 | 用了darkgrid | 改用whitegrid或white |
| 图在文档里太小 | context未调整 | 用context="talk"或"poster" |
6. 我个人的调色与风格工作流
6.1 项目开始时的视觉规范设定
每个项目开始,我会先花十分钟定一个视觉规范,写在一个单独的配置文件或notebook单元格里。这个规范包括:主调色板、辅助调色板、风格、上下文、字体。之后所有图都从这个规范出发,保证一致性。
# visual_config.py import seaborn as sns import matplotlib.pyplot as plt PRIMARY_PALETTE = "deep" SEQUENTIAL_PALETTE = "rocket" DIVERGING_PALETTE = "vlag" STYLE = "whitegrid" CONTEXT = "notebook" def setup(): sns.set_theme(style=STYLE, palette=PRIMARY_PALETTE, context=CONTEXT) plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False这个文件在项目里被反复导入,所有绘图函数都调用setup()。这样即使换了环境,只要这个文件在,图就是一致的。
6.2 快速预览调色板的技巧
选调色板的时候,我习惯先用palplot快速看一眼,而不是直接画图。palplot会把调色板画成色块,一眼就能看出颜色分布是否均匀、有没有特别刺眼的颜色。
sns.palplot(sns.color_palette("deep", 8)) sns.palplot(sns.color_palette("rocket", 8)) sns.palplot(sns.color_palette("vlag", 8)) plt.show()这个习惯帮我省了很多时间。有时候调色板名字看着不错,但palplot一看就发现某个颜色太跳,直接换掉,不用等到画完图才发现。
6.3 从现有图表反推调色板
接手别人代码的时候,经常遇到“图很好看但不知道用了什么调色板”的情况。这时候可以用palplot配合肉眼比对,或者直接看代码里的palette参数。如果代码里没有显式设置,那就是用了全局默认,查一下set_theme或set_palette的调用即可。
还有一种情况是图是从图片里截的,没有代码。这时候可以用取色工具取几个主要颜色,然后用blend_palette或手动构造一个近似调色板。虽然不能完全一致,但风格上能接近。
7. 颜色风格之外,还需要注意什么
7.1 颜色不是越多越好
我见过很多图,用了十几种颜色,结果读者根本分不清哪个是哪个。颜色数量应该和类别数量匹配,但类别数量本身应该受控。如果类别超过8个,就应该考虑用分面(FacetGrid)或者小多图(small multiples),而不是硬塞颜色。
分面的好处是每个子图只用一种颜色,读者注意力集中在数据形状上,而不是颜色区分上。Seaborn的FacetGrid和relplot的col、row参数可以很方便地实现分面。
7.2 颜色要与语义一致
如果数据里有“增长/下降”“盈利/亏损”这种语义,颜色应该和语义一致。增长用绿色或蓝色,下降用红色或橙色。如果反过来,读者会产生认知冲突。
Seaborn的发散调色板vlag和icefire默认就是这种语义:一端暖色一端冷色,中心浅色。用这两个调色板的时候,注意数据的正负方向要和调色板方向一致。如果反了,可以用_r后缀反转,比如vlag_r。
7.3 风格要服务于内容
风格不是越花哨越好。darkgrid在探索阶段好用,因为网格帮助读数;但到了汇报阶段,whitegrid或white更合适,因为背景干净,数据突出。ticks适合需要精确读数的工程图,但不适合展示趋势。
我的原则是:探索阶段用darkgrid,汇报阶段用whitegrid,论文阶段用white或ticks。这个对应关系不是绝对的,但能覆盖大部分场景。
7.4 保存图表时的参数设置
最后一步是保存。savefig有几个参数直接影响输出质量:
plt.savefig( "figure.png", dpi=300, # 分辨率,打印用300,屏幕用150 bbox_inches="tight", # 去掉多余白边 facecolor="white", # 背景色,避免透明背景打印变黑 edgecolor="none" )bbox_inches="tight"特别重要,不加的话图周围会有一圈白边,放进文档里很难对齐。facecolor="white"在用了darkgrid风格时尤其重要,不然保存的图背景是灰的,但透明区域在Word里可能变成黑色。
注意:如果图要放进LaTeX文档,建议保存为PDF格式,矢量图放大不失真。
savefig("figure.pdf", bbox_inches="tight")即可。
8. 一个完整的颜色风格配置示例
把前面讲的串起来,给一个完整的配置示例。这个示例可以直接复制到项目里用。
import seaborn as sns import matplotlib.pyplot as plt import pandas as pd import numpy as np # 1. 全局配置 sns.set_theme( style="whitegrid", palette="deep", context="notebook", font="SimHei" ) plt.rcParams["axes.unicode_minus"] = False # 2. 准备数据 np.random.seed(42) df = pd.DataFrame({ "month": list(range(1, 13)) * 3, "sales": np.random.randint(100, 500, 36), "region": ["华东"] * 12 + ["华南"] * 12 + ["华北"] * 12 }) # 3. 定性调色板:分类数据 plt.figure(figsize=(10, 5)) sns.lineplot( data=df, x="month", y="sales", hue="region", style="region", palette="deep", dashes=False, markers=True, linewidth=2 ) plt.title("各地区月度销售趋势") plt.savefig("trend.png", dpi=300, bbox_inches="tight", facecolor="white") plt.show() # 4. 顺序调色板:连续数据 plt.figure(figsize=(8, 6)) pivot = df.pivot(index="region", columns="month", values="sales") sns.heatmap( pivot, cmap="rocket", annot=True, fmt="d", linewidths=0.5 ) plt.title("地区月度销售热力图") plt.savefig("heatmap.png", dpi=300, bbox_inches="tight", facecolor="white") plt.show() # 5. 发散调色板:有中心点的数据 df["growth"] = df["sales"] - 300 # 以300为基准 plt.figure(figsize=(10, 5)) sns.barplot( data=df, x="month", y="growth", hue="region", palette="vlag", dodge=True ) plt.axhline(0, color="black", linewidth=0.8) plt.title("各地区月度增长(基准300)") plt.savefig("growth.png", dpi=300, bbox_inches="tight", facecolor="white") plt.show()这个示例里,三张图分别用了定性、顺序、发散三种调色板,风格统一为whitegrid,字体统一为黑体,保存参数一致。这样出来的图放在同一份报告里,视觉上是一套的。
9. 几个容易被忽略的细节
9.1 调色板的透明度控制
Seaborn的绘图函数大多支持alpha参数,控制透明度。透明度在重叠点很多的时候特别有用,可以让读者看到密度分布。但透明度也会让颜色变淡,如果调色板本身颜色就浅,加透明度后可能看不清。
我的经验是:定性调色板加透明度要谨慎,顺序调色板加透明度效果通常不错。因为顺序调色板本身有明暗变化,透明度不会破坏这种变化,反而能增强层次感。
9.2 颜色与图例的对应关系
图例是读者理解颜色的关键。Seaborn默认会把hue的图例放在合适的位置,但有时候会挡住数据。可以用plt.legend()手动调整位置。
plt.legend( bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0 )这个设置把图例放到图外面右侧,适合类别较多的情况。如果类别少,放图内也可以,但要注意不要挡住数据点。
9.3 深色背景下的调色板选择
如果演示环境是深色背景(比如某些会议室的投影),whitegrid风格会显得刺眼。这时候可以用dark风格配合亮色调色板。
sns.set_theme(style="dark", palette="bright")但要注意,深色背景下的图保存成图片后,放进白色文档里会很突兀。所以深色风格只适合现场演示,不适合文档输出。如果既要现场演示又要文档输出,建议做两个版本。
9.4 调色板的色觉障碍友好性
colorblind调色板是专门为色觉障碍读者设计的,颜色区分度高,红绿色盲也能分辨。如果图的受众不确定,或者需要满足无障碍要求,优先用colorblind。
sns.set_theme(style="whitegrid", palette="colorblind")这个调色板的颜色比deep稍微暗一些,但区分度更好。我现在的默认选择就是colorblind,除非有特殊需求才换。
10. 从颜色风格延伸到整体视觉一致性
颜色和风格只是视觉一致性的一部分。真正要做到“一套图看起来是一套”,还需要注意坐标轴范围、标题位置、图例样式、标注格式这些细节。但这些细节太多太碎,不适合在一篇里全讲完。
我的做法是把视觉规范写成一个函数库,每个项目复用。函数库里封装了常用的绘图函数,每个函数内部已经设好了颜色、风格、字体、保存参数。这样调用的时候只需要传数据,不用每次重复设置。
# viz_lib.py def plot_trend(df, x, y, hue, title, filename): sns.set_theme(style="whitegrid", palette="colorblind", context="notebook") plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False plt.figure(figsize=(10, 5)) sns.lineplot(data=df, x=x, y=y, hue=hue, style=hue, markers=True) plt.title(title) plt.savefig(filename, dpi=300, bbox_inches="tight", facecolor="white") plt.close()这个函数库随着项目积累会越来越丰富,后面做新项目的时候,直接调用就行,不用从零开始。这也是我从“每次画图都重新调”到“一次配置到处复用”的转变过程。
颜色和风格这件事,说大不大,说小不小。它不会影响数据结论的正确性,但会影响结论的传达效率。一张配色混乱的图,读者要花额外精力去理解;一张配色清晰的图,读者一眼就能抓住重点。这个差别在单张图上不明显,但在几十张图的报告里,就是专业和业余的分界线。