ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pandas画图设置全攻略:从中文方块到高清出图的配置模板

Pandas画图设置全攻略:从中文方块到高清出图的配置模板

数据分析做到最后一步,图表出不来或者出来丑得没法看,这事儿我见得太多了。很多人用Pandas把数据清洗完、聚合完,一到df.plot()就卡住——要么报错说没有matplotlib,要么画出来一堆线挤在一起分不清谁是谁,要么中文全变成小方块。Pandas数据的画图设置这个事,说大不大,说小也不小,它恰好卡在“数据算完了”和“图能给人看”之间那道坎上。我这些年带过不少做数据分析的同事,发现一个规律:凡是能把Pandas画图设置玩明白的人,出图效率至少快三倍,因为他们不用每次画完图再导到别的工具里返工。这篇内容就是把我自己踩过的坑、调过的参数、攒下来的配置模板一次性摊开讲清楚,适合已经会用Pandas做基本数据处理、但一画图就头疼的读者,也适合想把出图流程标准化的老手参考。

1. 先搞清楚Pandas画图到底在画什么

1.1 Pandas画图的本质是matplotlib的语法糖

很多人以为Pandas自带一个画图引擎,其实不是。df.plot()底层调用的就是matplotlib的Axes对象,Pandas只是帮你把DataFrame的列自动映射成x轴、y轴、图例标签这些东西。理解这一点特别关键,因为它决定了你所有的“画图设置”最终都要落到matplotlib的参数体系里去。

我举个例子你就明白了。你写df.plot(kind='line'),Pandas内部做的事情大致是:检查索引是不是默认的RangeIndex,如果是就用索引当x轴;把每一列数值列当成一条线;自动生成图例;然后把这些信息打包传给matplotlib的plot()方法。所以你在df.plot()里能传的参数,大部分是matplotlib参数的透传,少部分是Pandas自己封装的快捷方式。

这个认知带来的直接好处是:当你遇到Pandas文档里没写的设置需求时,不用死磕Pandas文档,直接去查matplotlib对应的参数就行。比如你想设置线宽,Pandas的linewidth参数背后就是matplotlib的linewidth;你想改图例位置,df.plot()里没有直接暴露这个参数,但你可以拿到返回的Axes对象再调ax.legend(loc='upper left')。我见过太多人卡在“Pandas文档里找不到这个参数”上,其实答案一直在matplotlib那边。

提示:df.plot()的返回值是一个matplotlib的Axes对象(多子图时是Axes数组),拿到它之后你可以继续用matplotlib的所有方法做精细调整,这是Pandas画图设置的核心工作流。

1.2 为什么默认出图总是“能用但不好看”

Pandas的默认画图设置走的是“最小惊讶原则”——它假设你只是想快速看一眼数据长什么样,所以默认样式非常朴素:白底、细线、小字体、图例挤在角落。这套默认值在Jupyter里快速探索时够用,但一旦你要把图放进报告、PPT或者给别人看,就完全不够了。

默认不好看的具体原因有几个。第一,默认画布尺寸是6.4×4.8英寸,在现在的显示器上偏小,线条和文字都显得局促。第二,默认DPI是100,导出图片时分辨率不够,放大就糊。第三,默认字体是英文字体,中文标签直接显示成方块。第四,默认配色是matplotlib的经典色环,几条线颜色接近,色盲用户根本分不清。第五,默认没有网格线,读数值全靠目测。

这些问题单独看都不致命,但叠在一起就是“这图没法用”。我的做法是建一套全局配置模板,一次性把这些默认值全部覆盖掉,后面所有图自动继承。这套模板我用了好几年,基本覆盖了日常90%的出图场景,下面会详细拆解每个配置项为什么这么设。

1.3 画图设置该在哪一层做:全局、局部还是单图

Pandas画图的设置可以分三个层次来做,选错层次会导致配置混乱或者重复劳动。全局层是通过pd.options.plotting和matplotlib的rcParams设置的,影响当前会话所有图;局部层是在单次df.plot()调用里传参数,只影响这一张图;单图层是拿到Axes对象后做后处理,适合精细调整。

我的经验是:字体、画布尺寸、DPI、配色方案这类“每个图都要用”的设置放全局层;标题、坐标轴标签、图例位置这类“每张图不一样”的设置放局部层;刻度密度、标注位置、特殊参考线这类“需要看具体情况”的放单图层。三层各司其职,既不重复也不遗漏。下面几个章节会按这个逻辑展开,先讲全局配置怎么搭,再讲局部参数怎么调,最后讲后处理技巧。

2. 全局配置:一次设置,后面所有图都受益

2.1 中文字体配置的完整方案与踩坑记录

中文显示成方块是Pandas画图最高频的问题,没有之一。根因是matplotlib默认字体族里没有中文字体,遇到中文字符就回退到默认字体,而默认字体没有对应字形,于是显示成方块。解决办法是显式指定一个系统里存在的中文字体。

配置代码大致是这样:

import matplotlib.pyplot as plt import matplotlib as mpl mpl.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC', 'Noto Sans CJK SC'] mpl.rcParams['axes.unicode_minus'] = False

这里有几个细节值得说。第一,font.sans-serif是一个字体列表,matplotlib会按顺序找第一个系统里存在的字体,所以你可以把不同操作系统常见的中文字体都列进去,这样配置模板跨平台也能用。第二,axes.unicode_minus必须设成False,否则负号会显示成方块——因为中文字体里的负号字形和matplotlib期望的不一样,这个坑我踩过不止一次,坐标轴有负数的时候特别明显。第三,字体名称必须和系统里注册的名称完全一致,写错了不会报错,只会静默回退到默认字体,然后你又看到方块了。

不同系统常见的中文字体名称我整理了一下:

操作系统常见中文字体名称备注
WindowsSimHei、Microsoft YaHei、SimSun黑体、微软雅黑、宋体
macOSPingFang SC、Heiti SC、STHeiti苹方、黑体
LinuxNoto Sans CJK SC、WenQuanYi Micro Hei需提前安装

如果你不确定系统里有哪些字体,可以用matplotlib.font_manager模块查询,或者直接跑一段代码列出所有可用字体名,然后挑一个中文字体填进去。我一般会在配置模板里写一个字体探测函数,自动从候选列表里选第一个可用的,这样模板拿到任何机器上都能直接跑。

注意:字体配置必须在画图之前执行,而且如果用了plt.style.use()切换样式,某些样式会覆盖字体设置,所以字体配置要放在样式设置之后。

2.2 画布尺寸与DPI:导出清晰图的关键参数

画布尺寸和DPI这两个参数决定了导出图片的物理大小和清晰度,很多人只调其中一个,结果要么图太小要么糊。先说结论:画布尺寸按最终用途定,DPI按输出介质定。

画布尺寸用figure.figsize设置,单位是英寸。常见的参考值:放进文档或网页的图,宽度6到8英寸够用;做PPT全屏展示的图,宽度10到12英寸;做海报或打印的图,宽度12英寸以上。高度一般按宽高比来,折线图常用16:9或4:3,柱状图可以矮一点。

DPI用figure.dpi设置,含义是每英寸多少像素。屏幕显示72到100就够,导出PNG放文档里150起步,打印或高清展示300起步。这里有个容易混淆的点:figure.dpi影响的是显示时的渲染精度,而savefig.dpi影响的是保存文件时的精度,两个要分开设。我一般把figure.dpi设成100保证交互时流畅,把savefig.dpi设成300保证导出清晰。

mpl.rcParams['figure.figsize'] = (10, 6) mpl.rcParams['figure.dpi'] = 100 mpl.rcParams['savefig.dpi'] = 300 mpl.rcParams['savefig.bbox'] = 'tight'

savefig.bbox设成tight是个小技巧,它会在保存时自动裁掉图周围的空白边距,避免导出图有一大圈白边。这个参数不影响显示,只影响保存,建议默认打开。

算一下就很清楚:10英寸宽、300 DPI的图,导出后是3000像素宽,放进A4纸打印绰绰有余;如果DPI只有100,同样10英寸宽只有1000像素,打印出来就是糊的。所以别嫌300 DPI文件大,该清晰的时候就得清晰。

2.3 配色方案:让多条线一眼分得清

默认配色在多线场景下辨识度不够,尤其是超过5条线的时候。Pandas画图可以通过colormap参数指定配色方案,也可以通过matplotlib的axes.prop_cycle设置全局色环。

我的建议是:分类数据用定性配色,连续数据用渐变配色。定性配色推荐tab10、Set2、Paired这几套,颜色区分度高且对色盲友好;渐变配色推荐viridis、plasma、cividis,这几套在灰度打印下也能保持层次。不要用jet,它的亮度变化不单调,容易误导读者对数值大小的判断。

mpl.rcParams['axes.prop_cycle'] = mpl.cycler(color=[ '#4C72B0', '#DD8452', '#55A868', '#C44E52', '#8172B3', '#937860', '#DA8BC3', '#8C8C8C' ])

这段配置设了一个8色的全局色环,颜色取自常见的科研配色方案,饱和度和明度都经过调校,打印和屏幕显示都舒服。8色基本覆盖日常多线场景,超过8条线建议考虑分面或者换图表类型,硬塞进一张图里谁也看不清。

另外提一句透明度。多条线重叠时,适当降低透明度(alpha参数,0.6到0.8之间)能让重叠区域显现出来,避免后面的线完全盖住前面的线。这个设置在df.plot()里直接传alpha=0.7就行。

2.4 网格、边框与背景:决定图“干净”还是“杂乱”

默认出图没有网格线,读数值全靠目测,加上网格线能显著提升可读性。但网格线也不能乱加,加多了图会显得杂乱。我的配置是:只开y轴主网格,线型用虚线,颜色用浅灰,透明度调低。

mpl.rcParams['axes.grid'] = True mpl.rcParams['grid.linestyle'] = '--' mpl.rcParams['grid.color'] = '#CCCCCC' mpl.rcParams['grid.alpha'] = 0.6 mpl.rcParams['axes.grid.axis'] = 'y'

只开y轴网格是因为大多数图读的是y值,x轴网格反而干扰视线。如果你的图是横向柱状图,那就改成只开x轴网格。线型用虚线是为了和实线的数据线区分开,颜色用浅灰是为了不抢数据线的视觉权重。

边框方面,默认四边都有边框,其实上边框和右边框没什么信息量,去掉之后图更清爽。用ax.spines可以精细控制:

ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False)

背景色默认是白色,一般不用改。如果要做深色主题的图,把axes.facecolor和figure.facecolor一起改,注意同时调整文字和网格颜色,否则对比度不够。

3. 局部参数:单张图的精细控制

3.1 df.plot()常用参数速查与选择逻辑

df.plot()的参数不少,但日常高频使用的就那么十几个。我把它们按用途分成几组,配上选择逻辑,方便你按需查用。

参数作用常用取值选择逻辑
kind图表类型line、bar、barh、hist、box、scatter、pie看数据关系:趋势用line,对比用bar,分布用hist/box,相关用scatter
x / y指定轴字段列名字符串或列表默认用索引当x,显式指定更可控
figsize画布尺寸元组,如(10,6)覆盖全局设置,单图特殊需求时用
title标题字符串每张图都该有,放局部层
xlabel / ylabel轴标签字符串字段名不直观时必加
legend图例True/False/‘reverse’多列时开,单列时关
grid网格True/False覆盖全局设置时用
style线型样式列表或字典需要区分线型时用
alpha透明度0到1重叠多时调低
rotx轴标签旋转角度整数标签长时设45或90
colormap配色方案配色名分类多时换配色
subplots是否分面True/False多列且量纲不同时开
layout子图布局元组配合subplots用
sharex / sharey共享轴True/False分面时按需开

这张表建议存下来,画图时对着查。重点说几个容易用错的。kind参数决定图表类型,选错了图就传达错了信息——比如用折线图画分类对比,读者会误以为x轴有连续关系。x和y不指定时Pandas用索引当x、所有数值列当y,数据量大时容易画出几十条线,一定要显式指定。rot参数在x轴标签是长字符串时特别有用,不旋转的话标签会重叠成一团。

3.2 多列数据画在一张图还是分开画

DataFrame有多列数值时,df.plot()默认把所有列画在同一张图上,共用y轴。这在各列量纲相近时没问题,但量纲差异大时就会出问题——比如一列是百分比(0到1),一列是金额(几万),画在一起百分比那条线就贴在x轴上成一条直线了。

判断标准很简单:各列量纲相近就画一张图,量纲差异大就分开画。量纲相近的例子:多个产品的月度销量、多个指标的同比增速。量纲差异大的例子:价格和销量、温度和降雨量。

分开画用subplots=True参数:

df.plot(subplots=True, layout=(2, 2), figsize=(12, 8), sharex=True)

layout指定子图的行列数,sharex=True让所有子图共用x轴,避免重复显示刻度标签。注意subplots=True时df.plot()返回的是Axes数组,后续调整要遍历数组操作,不能直接当单个Axes用。

还有一种折中方案是双y轴,适合两列量纲不同但需要对照看趋势的场景。Pandas本身不直接支持双y轴,需要拿到Axes后用twinx()创建第二个y轴,这个操作稍微绕一点,但用熟了也快。

3.3 图例、标题、轴标签的排版技巧

图例、标题、轴标签是图的“文字部分”,排版不好会显得很业余。图例最常见的问题是位置挡住数据线,解决办法是用loc参数指定位置,或者放到图外。Pandas的df.plot()里图例位置通过ax.legend(loc=...)设置,常用取值有upper left、upper right、lower left、lower right、best。best会让matplotlib自动找空白位置,但有时候找的位置并不理想,我一般手动指定。

图例放到图外用bbox_to_anchor参数:

ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1))

这行代码把图例放到图的右侧外面,配合savefig.bbox='tight'保存时不会裁掉。图例列数多时可以用ncol参数排成多列,避免图例竖着拉太长。

标题分总标题和子图标题。总标题用fig.suptitle(),子图标题用ax.set_title()。标题字号比正文大一两号,加粗,位置居中。轴标签用ax.set_xlabel()和ax.set_ylabel(),字号比标题小一号,不加粗。这里有个细节:轴标签不要和刻度标签挤在一起,用labelpad参数调间距。

提示:标题和轴标签里出现中文时,确保字体配置已经生效,否则又是方块。另外标题不要太长,超过一行就换行或者精简,长标题在窄图上会溢出。

3.4 刻度与数值格式:让数字读起来不费劲

刻度设置包括刻度位置、刻度标签格式、刻度密度三块。刻度位置一般不用手动调,matplotlib自动选的通常合理,但数据范围特殊时可能需要干预。刻度标签格式在数值很大或很小时需要处理,比如金额显示成“1.2万”比“12000”好读,百分比显示成“35%”比“0.35”直观。

数值格式用FuncFormatter自定义:

from matplotlib.ticker import FuncFormatter def format_wan(x, pos): if abs(x) >= 10000: return f'{x/10000:.1f}万' return f'{x:.0f}' ax.yaxis.set_major_formatter(FuncFormatter(format_wan))

这段代码把y轴数值超过一万的显示成“X.X万”,否则显示整数。类似的可以写百分比格式化、千分位格式化。格式化函数要注意处理负数和零,别写出除零错误或者负号丢失。

刻度密度方面,刻度太密标签会重叠,太疏读不出精确值。经验值是x轴刻度控制在5到10个,y轴控制在5到8个。用MaxNLocator可以限制最大刻度数:

from matplotlib.ticker import MaxNLocator ax.xaxis.set_major_locator(MaxNLocator(8))

日期型x轴是另一个高频场景,Pandas画时间序列时x轴刻度经常挤成一团。解决办法是用pd.plotting.register_matplotlib_converters()注册转换器(新版Pandas自动注册),然后用fig.autofmt_xdate()自动旋转日期标签,或者手动用mdates模块设置日期格式。

4. 完整实操流程:从数据到成图

4.1 环境准备与配置模板搭建

先把环境理清楚。核心依赖就三个:pandas、matplotlib、numpy。如果要做统计图可能还需要scipy,但画图本身不需要。安装命令:

pip install pandas matplotlib numpy

版本方面,Pandas 1.5以上、matplotlib 3.5以上比较稳,新版本的画图接口更统一,老版本有些参数行为不一致。装完之后验证一下:

import pandas as pd import matplotlib print(pd.__version__, matplotlib.__version__)

接下来搭配置模板。我的习惯是建一个plot_config.py文件,把全局配置封装成一个函数,每个分析脚本开头调一次:

import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib import font_manager def setup_plot_style(): # 字体探测 candidates = ['SimHei', 'Microsoft YaHei', 'PingFang SC', 'Noto Sans CJK SC', 'WenQuanYi Micro Hei'] available = {f.name for f in font_manager.fontManager.ttflist} chosen = [f for f in candidates if f in available] if chosen: mpl.rcParams['font.sans-serif'] = chosen + ['DejaVu Sans'] mpl.rcParams['axes.unicode_minus'] = False # 画布与分辨率 mpl.rcParams['figure.figsize'] = (10, 6) mpl.rcParams['figure.dpi'] = 100 mpl.rcParams['savefig.dpi'] = 300 mpl.rcParams['savefig.bbox'] = 'tight' # 配色 mpl.rcParams['axes.prop_cycle'] = mpl.cycler(color=[ '#4C72B0', '#DD8452', '#55A868', '#C44E52', '#8172B3', '#937860', '#DA8BC3', '#8C8C8C' ]) # 网格与边框 mpl.rcParams['axes.grid'] = True mpl.rcParams['grid.linestyle'] = '--' mpl.rcParams['grid.color'] = '#CCCCCC' mpl.rcParams['grid.alpha'] = 0.6 mpl.rcParams['axes.grid.axis'] = 'y' mpl.rcParams['axes.spines.top'] = False mpl.rcParams['axes.spines.right'] = False # 字号 mpl.rcParams['font.size'] = 11 mpl.rcParams['axes.titlesize'] = 14 mpl.rcParams['axes.labelsize'] = 12 mpl.rcParams['legend.fontsize'] = 10

这个模板的每个配置项前面都讲过理由,这里不重复。重点说字体探测那段:它从候选列表里筛出系统实际安装的字体,按优先级排序,这样同一份模板在Windows、macOS、Linux上都能用,不用改代码。DejaVu Sans作为兜底放在最后,保证至少有个英文字体能显示。

4.2 一个完整案例:多指标趋势图从零到成品

拿一个模拟场景走一遍完整流程。假设有一份某产品连续12个月的运营数据,包含访问量、注册数、付费数、客单价四个指标,要画一张趋势图放进月度报告。

先造模拟数据:

import numpy as np import pandas as pd np.random.seed(42) months = pd.date_range('2024-01-01', periods=12, freq='MS') df = pd.DataFrame({ '访问量': np.random.randint(8000, 15000, 12), '注册数': np.random.randint(500, 1200, 12), '付费数': np.random.randint(50, 200, 12), '客单价': np.random.uniform(80, 150, 12).round(2) }, index=months)

数据有了,先分析量纲:访问量是万级,注册数是千级,付费数是百级,客单价是百级。访问量和付费数差了两个数量级,画在一张图上付费数会贴底。所以决定分面画,用subplots=True。

setup_plot_style() axes = df.plot( subplots=True, layout=(2, 2), figsize=(12, 8), sharex=True, title='某产品月度运营指标趋势', legend=False, rot=45 )

跑完这一步,图的基本骨架就有了。但还有几个问题要处理:总标题位置偏上、子图标题和总标题重复、y轴数值格式没处理、x轴日期标签可能重叠。逐个解决:

fig = axes[0, 0].get_figure() fig.suptitle('某产品月度运营指标趋势', fontsize=16, y=1.02) for ax in axes.flat: ax.set_title(ax.get_title(), fontsize=12) ax.set_xlabel('') axes[0, 0].yaxis.set_major_formatter(FuncFormatter(format_wan)) axes[0, 1].yaxis.set_major_formatter(FuncFormatter(format_wan)) axes[1, 0].yaxis.set_major_formatter(FuncFormatter(format_wan)) axes[1, 1].yaxis.set_major_formatter(FuncFormatter(lambda x, p: f'{x:.0f}元')) fig.autofmt_xdate(rotation=45)

这里fig.autofmt_xdate()自动处理日期标签旋转和对齐,比手动设rot更省事。数值格式化按子图分别设,访问量和注册数用“万”格式,客单价加“元”单位。最后保存:

fig.savefig('monthly_report.png', dpi=300, bbox_inches='tight')

整个流程从造数据到出图不到30行代码,核心工作量在配置模板和格式化函数上,这两块是一次性投入,后面每张图都省事。

4.3 保存与导出:格式选择与参数配置

保存图的格式选择经常被忽略,其实不同格式适用场景差别很大。PNG适合屏幕显示和文档嵌入,无损但文件大;JPG适合照片类图,有损但文件小,不过线条图用JPG会有压缩伪影,不推荐;SVG是矢量格式,放大不失真,适合网页和需要后期编辑的场景;PDF也是矢量,适合打印和正式文档。

fig.savefig('chart.png', dpi=300, bbox_inches='tight', facecolor='white') fig.savefig('chart.svg', bbox_inches='tight', facecolor='white')

facecolor='white'这个参数容易被忽略,不设的话保存的图背景可能是透明的,放进深色背景的文档里会很难看。bbox_inches='tight'前面说过,裁掉白边。矢量格式不需要设DPI,设了也没用。

还有一个细节:保存要在plt.show()之前执行,因为show()之后某些后端会清空画布,再保存就是空白图。这个坑在脚本里跑的时候特别容易踩,Jupyter里因为交互模式的关系反而不明显。

5. 常见问题排查与避坑实录

5.1 中文方块、负号方块、字体警告三连

中文相关的问题占了Pandas画图问题的半壁江山,我把最常见的三种整理成速查表:

现象根因解决
中文显示成方块默认字体无中文字形配置font.sans-serif为中文字体列表
负号显示成方块中文字体负号字形不匹配设axes.unicode_minus=False
启动时字体警告配置的字体系统里不存在用font_manager查询实际可用字体
部分中文正常部分方块字体缺字或回退混乱换覆盖更全的字体,如Noto Sans CJK

字体警告特别烦人,每次import都刷一屏。根因是font.sans-serif列表里有系统不存在的字体,matplotlib找不到就警告。解决办法是用前面那个字体探测函数,只把实际存在的字体填进去。如果警告来自第三方库的默认配置,可以在脚本开头用warnings.filterwarnings过滤掉特定警告,但这是治标,治本还是把字体配对。

还有一个隐蔽的坑:某些样式文件(.mplstyle)里写死了字体设置,plt.style.use()之后会覆盖你的字体配置。如果你发现字体配置明明写了却不生效,检查一下是不是后面又调了style.use()。顺序应该是先style.use()再设字体。

5.2 图例遮挡、标签重叠、刻度拥挤的应急处理

图例遮挡数据线是最常见的排版问题。应急处理有三招:换位置(loc参数)、放图外(bbox_to_anchor)、缩小图例(fontsize调小、ncol排多列)。长期方案是画图前就规划好图例位置,数据线密集的区域别放图例。

x轴标签重叠在分类多或者标签长的时候必现。处理优先级:先试旋转(rot=45或rot=90),旋转还不行就缩短标签(截断或换简称),再不行就换横向柱状图(kind='barh'),标签变成y轴就不挤了。日期标签用fig.autofmt_xdate()自动处理,比手动调省心。

刻度拥挤分两种情况:刻度太多和刻度标签太长。刻度太多用MaxNLocator限制数量;标签太长用格式化函数缩写,比如把“2024年1月”缩成“24-01”。这里要注意缩写不能损失关键信息,缩写后的标签读者得能看懂。

注意:旋转标签之后记得调plt.tight_layout()或者保存时用bbox_inches='tight',否则旋转后的标签可能被裁掉。tight_layout()在子图场景下有时会失效,bbox_inches='tight'更可靠。

5.3 大数据量画图卡顿的优化思路

数据量到十万行以上时,df.plot()会明显变慢,几十万行时可能卡死。根因是matplotlib要把每个数据点都渲染出来,点数太多渲染开销爆炸。优化思路分三层。

第一层是降采样。趋势图不需要每个点都画,按时间窗口聚合或者等间隔抽样,几千个点足够看出趋势。Pandas的resample()按时间聚合,或者df.iloc[::n]等间隔抽样,都是常用手段。

第二层是换图表类型。几十万点的散点图用scatter会卡,换成hexbin(六边形分箱图)或者二维直方图,渲染快且更能看出密度分布。折线图数据点多时考虑用alpha降低透明度,视觉上也能缓解线条糊成一团的问题。

第三层是渲染后端。交互式后端(如notebook里的inline)渲染慢,导出图时用非交互后端(Agg)快很多。在脚本开头设matplotlib.use('Agg'),只保存不显示的场景能快好几倍。

import matplotlib matplotlib.use('Agg')

这行要放在import pyplot之前,顺序错了不生效。Agg后端不支持plt.show(),只适合纯保存场景,交互分析时别用。

5.4 我踩过的五个坑和对应解法

第一个坑:df.plot()画完直接plt.savefig(),保存出来是空白。原因是某些后端在画布切换时清空了内容。解法是拿到fig对象再保存,fig = ax.get_figure()然后fig.savefig()。

第二个坑:设置了figsize但保存出来尺寸不对。原因是savefig的bbox_inches='tight'会裁边,实际尺寸比设定的小一圈。如果对尺寸有精确要求,保存时别用tight,或者保存后检查实际像素尺寸。

第三个坑:多子图时tight_layout()报警告说布局无法满足。原因是子图标题或标签太长,空间不够。解法是调大figsize、缩小字号,或者改用constrained_layout。

第四个坑:时间序列x轴刻度显示成数字而不是日期。原因是Pandas的日期转换器没注册。新版Pandas自动注册,老版本需要手动调pd.plotting.register_matplotlib_converters()。

第五个坑:颜色循环在多次画图后错乱。原因是axes.prop_cycle是全局状态,某些操作会重置它。解法是每次画图前重新应用配置,或者用plt.rcdefaults()重置后再配置,保证状态干净。

这些坑的共同点是:不报错,但结果不对。所以画完图一定要肉眼检查一遍,别只看代码跑通了就完事。我现在的习惯是每张图保存后打开看一眼,确认字体、颜色、布局都正常再往下走,这个习惯帮我省了无数次返工。

6. 把画图设置沉淀成可复用的工作流

配置模板搭好之后,剩下的就是把它用顺。我的做法是把setup_plot_style()和几个常用的格式化函数、保存函数打包成一个内部小模块,每个分析项目开头import一次,后面画图只关注数据本身,样式的事交给模块处理。这样做的直接好处是同一个项目里所有图风格统一,不会这张图字体大那张图字体小。

模块里我还会放几个预设的画图函数,比如plot_trend(df, title)封装趋势图的常用配置,plot_compare(df, title)封装对比图的配置。调用的时候一行代码出图,需要微调时再传参数覆盖。这套东西用熟了之后,出图从“每次调半天”变成“一行代码加偶尔微调”,效率提升非常明显。

最后分享一个小心得:画图设置这件事,前期多花半小时把模板搭好,后面每个项目省下的时间是以小时计的。而且模板是可以迭代的,遇到新的需求就往里加配置,用久了就成了一套贴合自己习惯的画图体系。我现在这套模板已经迭代了十几版,从最初的字体加尺寸,到后来的配色、格式化、子图布局,每次踩坑就补一条,现在基本能做到“配置一次,出图无忧”。你要是刚开始搭,别追求一步到位,先把字体和尺寸这两个最影响观感的配好,其他的遇到再加,慢慢就攒起来了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进