Python Matplotlib 画图实战:中文、刻度与论文出图
2026/9/17 13:40:51 网站建设 项目流程

简介:面向Python初学者与需要快速上手数据可视化的开发者,这份速查资料以单份PDF文档呈现,适合在软件开发中制作图表、撰写分析报告时随手翻阅。文档围绕绘图模块的基本用法展开:从最简单的折线绘制、坐标轴标签设置与图形显示,到用简写字符串指定点形与颜色、限定坐标轴取值范围;再借助数组运算在同一张图上叠加多条曲线,并通过属性设置函数统一调整颜色与线宽;最后用子图命令划分多面板布局,同时演示图中标注文字、绘制直方图、添加网格等常见做法,便于读者对照练习。全包仅1个PDF文件,约217KB,体量轻便,适合离线保存与打印。目前已有155人学习浏览,可作为入门阶段查阅常用接口、巩固绘图思路的参考清单。

1. 图纸有了,代码还没写:matplotlib 画图解决的其实是“改得动”

同事丢过来一份《pythonmatplotlib画图[参照].pdf》,里面是几张样例图:一条趋势折线、一组散点、一张带双纵轴的对比图。要求三天后交同样的图,数据换成新一版。手绘一次不难,难的是数据一动就得从头再来,这正是 Excel 和桌面画图软件最别扭的地方——图是“死”的,改一次数据就重排一次版式。matplotlib 把图变成一段可复现的 python 代码:换掉输入数组,坐标轴范围、图例、字体、线宽全部跟着走,同一份脚本批量生成几十张论文画图或数学建模画图都不成问题。学习曲线集中在两处,一是 matplotlib 的对象模型,二是中文、刻度、布局这些看起来不起眼但天天卡人的细节。下面按“能跑起来、画得对、改得动、出得去”的顺序,把常用做法和参数挨个抠一遍。

2. python + matplotlib 画图的最小可复现路径

2.1 python 安装、matplotlib 安装与编辑器解释器选择

画不出图的人,九成不是代码写错了,而是装了两个 python:pip 装进 A,编辑器跑的是 B。先确认当前解释器身份,再决定往哪装。用虚拟环境把依赖锁在项目里,是后面所有步骤的前提。

# 确认当前解释器版本与路径 python --version python -c "import sys; print(sys.executable)" # 建虚拟环境并激活 python -m venv .venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate # 升级 pip,再装画图三件套 python -m pip install --upgrade pip python -m pip install matplotlib pandas numpy

python -m venv .venv建出隔离目录,避免污染系统解释器;激活后坚持用python -m pip而不是裸pip,这样安装目标一定是当前解释器。国内网络拉包慢,在 pip 命令后追加镜像源参数即可,不必改全局配置。装完立刻验证:

python -c "import matplotlib; print(matplotlib.__version__, matplotlib.get_backend())"

后端的名字会打出来,这个信息在第 2.2 节会用到。编辑器侧,VSCode 装好 python 扩展后按Ctrl+Shift+P选 “Python: Select Interpreter”,指向.venv里的解释器;PyCharm 在 Settings → Project → Python Interpreter 里添加已存在的虚拟环境。vscode python 环境配置和 pycharm 配置 python 环境踩的坑几乎一样:右下角显示的解释器和你终端里激活的不是同一个。pandas numpy matplotlib scikit-learn这一套通常一起装,做 python 数据分析与可视化时省得来回补依赖。

2.2 后端(backend)与 plt.show() 不弹窗的关系

脚本没报错,窗口却没出来;或者本地好好的,扔到服务器上就报一串 TclError。这是后端在工作。后端决定 matplotlib 把图画到哪儿去:画到屏幕窗口、画到内存,还是画进 notebook 单元格。

import matplotlib # 先看一眼当前用的是什么后端 print(matplotlib.get_backend()) # 无图形界面的环境(服务器、CI、Docker)只出文件不弹窗 # 注意:必须在 import pyplot 之前调用 matplotlib.use("Agg") import matplotlib.pyplot as plt

matplotlib.use()一旦 pyplot 被导入后再调用,部分后端已经初始化,切换会失效甚至直接抛错,所以它必须写在最前面。用 Agg 之后plt.show()不会有任何反应,出图只能靠savefig(),这不是 bug。

后端典型场景show() 行为
TkAgg本地桌面,标准 python 安装弹出窗口
QtAgg本地桌面,装了 Qt 绑定弹出窗口,交互更顺
Agg服务器 / 批量出图不弹窗,只能存文件
inline 后端Jupyter Notebook直接显示在单元格下方

Jupyter 里不用手动切,单元格首行写%matplotlib inline即可,想要可缩放、带坐标读数的图就换成%matplotlib widget,前提是装了 ipympl。

2.3 折线图与散点图的最小可运行代码

把两张最常见的图放一起写,顺便交代几个必然会用到的参数。

import numpy as np import matplotlib.pyplot as plt # 生成横坐标与两条曲线 x = np.linspace(0, 2 * np.pi, 200) y = np.sin(x) # figsize 单位是英寸,dpi 决定屏幕像素密度 fig, ax = plt.subplots(figsize=(6.4, 4.0), dpi=120) ax.plot(x, y, label="sin(x)", color="#2b6cb0", linewidth=1.8) # 每隔 20 个点采一个散点,zorder 保证散点压在线条上方 ax.scatter(x[::20], y[::20], s=18, color="#e53e3e", zorder=3) ax.set_xlabel("x / rad") ax.set_ylabel("amplitude") ax.set_title("sin(x) 采样示意") ax.legend(loc="upper right") fig.savefig("sin.png", bbox_inches="tight") plt.show()

figsize=(6.4, 4.0)是画布宽高,单位英寸,乘上dpi就是最终像素;linewidth控制线宽,论文里通常 1.5~2.0 之间;s是散点面积(不是直径),几十的量级就比较克制;zorder决定绘制层级,数值越大越靠上;bbox_inches="tight"在保存时裁掉画布四周多余白边,这一步几乎每次都要加,否则导出的图边缘会留一大圈空白。

2.4 pyplot 状态机与面向对象写法的取舍

入门教程大多从plt.plot()讲起,正式项目里更常见fig, ax = plt.subplots()。两者不是新旧关系,而是适用场景不同。

写法典型 API适用场景风险
pyplot 状态机plt.plot/plt.title一次性脚本、快速验证多子图或多图叠加时状态互相干扰
面向对象fig, ax = plt.subplots()子图、函数封装、批量出图代码行数略多

判断标准很简单:这段画图逻辑要不要被包进函数、要不要循环调用、要不要多个子图。只要沾上一条,就用面向对象写法。ax上的方法是显式的,fig负责整体布局和保存,ax负责一个坐标系里的所有元素,边界清晰,排查问题也快。

提示:函数里画图时,把figax都返回出去,让调用方决定savefig还是show。函数内部直接plt.show()的代码,在服务器上跑必炸。

3. 标题、坐标轴名称与图例:一张 matplotlib 图的可读性配置

3.1 set_title / set_xlabel / set_ylabel 的常用参数

图能不能看懂,一半靠数据,一半靠标注。标题和轴标签有几个参数几乎每次都要调。

fig, ax = plt.subplots(figsize=(7, 4.2), dpi=120) ax.plot(t, temp, label="室内") ax.set_title("一天内的温度变化", fontsize=15, pad=12, fontweight="bold") ax.set_xlabel("时间 t / h", fontsize=12, labelpad=6) ax.set_ylabel("温度 T / ℃", fontsize=12, labelpad=6)

fontsize会覆盖 rcParams 里的全局字号,只对这一个元素生效;pad是标题到绘图区上边缘的距离,单位是点,图很挤的时候把它调小比压缩画布更划算;labelpad是轴标签到刻度文字的距离,中文上下结构的字容易被刻度压住,加 4~8 个点通常就干净了。set_ylabel还支持loc="top",把纵轴标签挪到轴顶端,这是很多期刊的排版习惯。

多子图场景下,ax.set_title只管自己那一格。整张图的总标题要用fig.suptitle("..."),它属于 figure 级别,配合fig.subplots_adjust(top=0.85)给它腾出空间,否则总标题会和子图标题叠在一起。

3.2 legend 的位置、列数与框线

图例空着、图例挡住数据、图例跑到画布外面,是三个高频抱怨,原因都指向同一处:图例只认带label的元素。

ax.legend( title="传感器", # 图例组标题 loc="upper left", # 九宫格锚点 ncol=2, # 横向排列两列 frameon=False, # 去掉外框,论文排版常用 fontsize=10, handlelength=1.8, # 图例中线段样例的长度 )

locupper left/lower right这类九宫格关键词时,图例会贴着对应角。九宫格位置都不满意,就用bbox_to_anchor=(x, y)配合loc精确定位:坐标默认是坐标轴的归一化值(0 到 1),加bbox_transform=fig.transFigure就切成整张画布的归一化坐标。图例太长时用ncol拆列,比调字号更有效。

注意:调用legend()之后才给线条补label,图例不会更新。正确顺序是先plot(..., label="xx"),再legend()

3.3 用 rcParams 统一管理字号、网格与刻度

一个项目里几十张图,靠每张单独设字号迟早不一致。把公共样式提到 rcParams 里,一处改动全图生效。

import matplotlib.pyplot as plt BASE = { "font.size": 12, "axes.titlesize": 14, "axes.labelsize": 12, "xtick.labelsize": 11, "ytick.labelsize": 11, "legend.fontsize": 11, "axes.grid": True, "grid.linestyle": ":", "grid.alpha": 0.4, "figure.dpi": 120, } plt.rcParams.update(BASE)

rcParams是一份全局字典,update只影响之后新建的 figure 和 axes,已经创建的对象不会追溯生效,所以这段代码要放在所有画图逻辑之前。只希望在局部换风格,用rc_context包一段,退出后自动还原,不会污染后面的图:

with plt.rc_context({"axes.grid": False, "font.size": 14}): fig, ax = plt.subplots() ax.plot(x, y) # 这一张无网格、字号 14,出了 with 就恢复原样

3.4 常用配置项速查

rcParams 键含义建议值
font.size全局默认字号10~12
axes.titlesize子图标题字号比全局大 2 号
axes.labelsize轴标签字号与全局一致
axes.grid默认是否画网格开启并配grid.alpha
axes.unicode_minus是否使用 Unicode 负号中文环境设False
figure.dpi屏幕显示分辨率100~150
savefig.dpi保存分辨率论文 300 以上

网格线要克制,alpha在 0.3~0.5 之间既起参考作用又不抢数据。双纵轴图里两条轴线颜色和刻度颜色最好跟着曲线走,避免看错归属。

4. plt 画图显示中文、横坐标太密集与对数坐标轴的三类硬骨头

4.1 plt 画图显示中文问题的定位与修复

现象很有辨识度:所有中文变成一排方框,同时终端刷出UserWarning: Glyph xxx missing from current font。根因是默认字体 DejaVu Sans 不含中文字形,matplotlib 找不到就画方块。第一步先看系统里到底有哪些可用字体:

from matplotlib import font_manager # 列出所有字体名,过滤出可能带中文的 names = sorted({f.name for f in font_manager.fontManager.ttflist}) print([n for n in names if any(k in n for k in ("Hei", "YaHei", "Song", "Noto", "CJK", "PingFang"))])

拿到名字后写进候选列表,matplotlib 会按顺序取第一个存在的:

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

font.sans-serif是列表而非单值,跨平台脚本把 Windows、macOS、Linux 上常见的名字都列上,哪台机器跑都能命中一个。SimHei 在 Windows 自带,macOS 用 PingFang SC 或 Arial Unicode MS,Linux 桌面环境一般不预装中文字体,需要额外装 Noto Sans CJK,或者手动把字体文件注册进去:

font_manager.fontManager.addfont("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc") plt.rcParams["font.family"] = "Noto Sans CJK SC"

axes.unicode_minus = False这行别漏。字体里的 Unicode 减号 U+2212 经常缺失,负刻度会变成方框,设成False后改用 ASCII 连字符渲染,能显示但视觉上略短,属于可接受折中。

提示:改了font.sans-serif仍不生效,检查是否在设置之前就import pyplot并创建了 figure,以及字体缓存是否过期。删掉~/.cache/matplotlib下的字体缓存文件重跑一次通常就好。

4.2 python 画图横坐标太密集的四种收口手法

时间序列画出来最容易出这个问题:横坐标全是01-01 00:00:00这种长标签,叠成一团黑。核心思路是减少刻度数量或缩短标签长度,两条路可以一起走。

import matplotlib.dates as mdates # 主刻度每 3 天一个,次刻度每天一个 ax.xaxis.set_major_locator(mdates.DayLocator(interval=3)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%m-%d")) ax.xaxis.set_minor_locator(mdates.DayLocator(interval=1)) # 自动旋转并右对齐,一行顶替手写 rotation 加对齐 fig.autofmt_xdate(rotation=45, ha="right")

日期轴用DayLocator/MonthLocator/HourLocator这类专用定位器,比统一用数值间隔靠谱,跨月跨年不会错位。格式器DateFormatter管显示,%m-%d比默认的完整时间戳短得多。数值轴则换成本地定位器:

from matplotlib.ticker import MultipleLocator, MaxNLocator ax.xaxis.set_major_locator(MultipleLocator(10)) # 固定每 10 一个刻度 ax.xaxis.set_major_locator(MaxNLocator(nbins=6)) # 最多 6 个,自动挑整数 ax.tick_params(axis="x", labelsize=9, rotation=30) # 兜底:缩字号 + 旋转

MultipleLocator(10)是硬性间隔,适合量纲规整的数据;MaxNLocator(nbins=6)让 matplotlib 自己挑整数刻度,标签长度不一时更协调。两者都装在同一根轴上会互相覆盖,后设的生效,别同时用。旋转标签必须配对齐方式,只写rotation=45不改水平对齐,标签尾部会顶到坐标轴上,ha="right"才是完整做法。

注意:日期格式化的前提是 x 轴数据本身是datetime64Timestamp,字符串数组不会被自动解析成时间轴,pd.to_datetime()要先转一遍。

4.3 matplotlib 对数坐标轴与次刻度网格

跨几个数量级的数据必须上对数轴,这里默认行为有几个点容易被忽略。

fig, ax = plt.subplots(figsize=(6.5, 4.0)) ax.plot(freq, gain, marker="o", ms=3) ax.set_xscale("log") ax.set_yscale("log") ax.set_xlim(1e-1, 1e4) ax.grid(True, which="both", linestyle=":", alpha=0.35) # 主次刻度都画网格 ax.tick_params(which="minor", length=2, labelsize=9)

set_xscale("log")默认底数 10,需要底数 2 就写成set_xscale("log", base=2)。横纵都取对数等价于快捷函数loglog(),只对 x 取对数用semilogx(),只对 y 用semilogy()——这三个函数在科学计算可视化 matplotlib 模块里出现频率极高。gridwhich参数默认只作用于主刻度,加both才会把密集的次刻度网格也画出来,视觉参考价值正在这些次刻度的位置。次刻度的标签默认不显示,tick_params(which="minor")里给labelsize就能打开。

数据里含 0 或负值时,对数轴会直接丢弃这些点,不报错也不提示,只是曲线上少一段。处理办法是按业务含义过滤掉无效值,或者整体加一个远小于最小正数的偏移量再取对数。这条规则在双对数坐标下尤其要盯一眼,图不会有任何异常提示。

4.4 现象与排查对照表

现象常见原因处理
中文变方框字体不含中文字形设置font.sans-serif候选列表
负号变方框字体缺 U+2212axes.unicode_minus = False
横轴标签重叠刻度间隔太小Locator 调间隔 + 旋转右对齐
对数轴少一段曲线数据含 0 或负值过滤、加偏移或改回线性轴
保存的图一片空白用了 Agg 仍等 show只调savefig,检查路径与文件名
图例是空框线条没设labelplot(..., label="xx")后再legend()

5. 论文画图与数据分析出图的进阶参数

5.1 样式表切换与自建 mplstyle

想要整体换风格不必逐项设 rcParams,样式表一步到位:

import matplotlib.pyplot as plt print(sorted(plt.style.available)[:12]) # 看内置样式名 plt.style.use("seaborn-v0_8-whitegrid") # 全局生效 with plt.style.context("ggplot"): # 只在 with 内生效 fig, ax = plt.subplots() ax.plot(x, y)

style.userc_context是同一套机制的不同入口,前者整段脚本生效,后者限定作用域。投稿用的版式建议写成.mplstyle文件,plt.style.use("./paper.mplstyle")直接加载,内容就是一行一个键: 值,可以进版本库,团队里谁跑都一样。

5.2 GridSpec 布局与共享坐标轴

不规则版式用add_gridspecsubplots(2,2)灵活得多,横跨、比例不等都能表达:

fig = plt.figure(figsize=(7, 5), constrained_layout=True) gs = fig.add_gridspec(2, 2, height_ratios=[2, 1]) ax1 = fig.add_subplot(gs[0, :]) # 上排横跨两列 ax2 = fig.add_subplot(gs[1, 0]) ax3 = fig.add_subplot(gs[1, 1], sharex=ax2) # 与 ax2 共享 x 轴刻度 ax1.plot(t, y1) ax2.scatter(t, y2, s=6) ax3.scatter(t, y3, s=6) fig.suptitle("多子图出图", fontsize=14)

sharex让两块子图共用同一根横轴,缩放联动、刻度只显示在下方那一块,比手工对齐set_xlim稳得多。constrained_layout=True会自动避让标题和标签,比tight_layout()更适应带 colorbar 或 suptitle 的复杂版式,两者不要同时开。

5.3 保存出图:dpi、bbox_inches 与矢量格式

plt.rcParams["pdf.fonttype"] = 42 # PDF 内嵌 TrueType 字体子集 fig.savefig("fig1.png", dpi=300, bbox_inches="tight", facecolor="white") fig.savefig("fig1.pdf", bbox_inches="tight")
参数作用建议取值
dpi位图分辨率论文 300 起,屏幕 100~150
bbox_inches="tight"裁掉四周多余白边保存时常态开启
facecolor背景色截图混排用白色,PPT 叠加可transparent=True
format输出格式png 位图,pdf / svg 矢量
pdf.fonttypePDF 字体嵌入方式设 42,避免期刊端替换字体

位图和矢量的选择看用途:屏幕展示、PPT 用 png,投稿和打印用 pdf 或 svg,放大不糊且体积小。矢量格式的字体这一步最容易被忽略,不改pdf.fonttype时部分期刊的预检工具会报字体缺失或嵌入方式不合规,改成 42 让字体以 TrueType 子集嵌进文件,跨机器打开样式不走样;换成 svg 输出时则要确认文字是按文本写入还是已转成路径,前者能在 Illustrator 里继续编辑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询