Python Matplotlib 绘图笔记
Matplotlib 介绍
matplotlib 是模仿 MATLAB 风格的 Python 2D 绘图库(3D
的功能比较弱,还是基于 2D 引擎勉强实现的),同时提供了两套 api:
- 一个是面向过程的,主要调用
matplotlib.pyplot的函数; - 一个是面向对象的,主要调用
matplotlib的两个子类:matplotlib.figure.Figure和matplotlib.axes.Axes,使用它们的方法进行细节操作。
面向过程的 api 适合简易使用的场景,但是不容易弄清原理;为了更复杂的绘图要求,这里主要采用面向对象的 api。 (MATLAB 绘图也是一样的问题,而且主要都在使用面向过程的接口)
关于绘图的呈现方式,在 Jupyter 中很可能会自动绘图而不需要
plt.show(),这与魔法指令 %matplotlib inline
有关。
在这一系列笔记中,需要使用如下模块: 1
2
3import numpy as np
import matplotlib
import matplotlib.pyplot as plt
由于 matplotlib
的内容太多太杂,这里只会介绍最容易理解的,最本质的面向对象的接口,然后作为辅助会介绍其它等价的,或者更方便的调用方式。但是
matplotlib
的面向对象其实有很多层,在每一层都可能为同一个功能提供了等价的接口,这里为了简化笔记,主要考虑
Figure 和 Axes 层面提供的接口。
由于个人目前不需要使用各种统计图,因此这里不作讨论。
简单例子
面向过程风格的绘图例子如下,主要是 plt 的函数调用:
1
2
3
4
5plt.plot(
np.linspace(0, 5, 100),
(lambda x: np.cos(2 * np.pi * x) * np.exp(-x))(np.linspace(0, 5, 100)),
)
plt.title("eg-1-1")

面向对象风格的绘图例子如下,主要是画布和子图的方法调用:
1
2
3
4
5
6fig, ax = plt.subplots()
ax.plot(
np.linspace(0, 5, 100),
(lambda x: -np.cos(2 * np.pi * x) * np.exp(-x))(np.linspace(0, 5, 100)),
)
ax.set_title("eg-1-2")

基本概念
对于一个图像,matplotlib 将其分成了两层主要的概念:
Figure层,可以理解为整个画布,控制整体的尺寸,标题等;Axes层,可以理解为一个子图或者子图的数组,这里的子图通常有两个坐标轴,以及具体数据图像和细节组成,因此名称为Axes。一个Figure对象可以包括多个子图,达到多个子图并列放置的效果。
这两个概念非常难以翻译,因此下面主要使用英文。
一个 Figure 上主要有如下的元素:
Title标题,这里存在Figure标题和Axes标题的区分Legend图例x Axis, y Axis坐标轴xlabel, ylabel坐标轴标签Grid网格Line线条Markers标记Major tick, Minor tick主刻度,副刻度Major tick label主刻度标签Spine边框线
各个元素如下图所示

创建 Figure 和 Axes
创建 Figure
创建 Figure 对象的函数原型如下 1
2
3
4
5def figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True)
...
fig = plt.figure()
fig = plt.figure(figsize=(4,3))
常用参数的含义:
figsize尺寸,依次为横向宽度和纵向高度,单位为英寸,例如figsize=(5,2.7)dpi分辨率facecolor背景颜色edgecolor边框颜色frameon是否显示边框
关于 dpi 选项,需要一些额外的说明:
- 默认的 dpi 为 100,通常范围为 100-300。dpi 值越大,则图像越清晰,文件越大。
- 在交互式显示时,通常不建议设置过大的
dpi,因为这会导致显示的图像过大,在保存时指定高的 dpi 即可,例如
dpi=300。
添加 Axes
Figure 上必须有至少一个 Axes 对象,才能进行绘图,绘图的数据终归是要基于 Axes 的坐标轴体系来完成。
在Figure上添加一个 Axes 对象可以使用下面的语句 1
2fig = plt.figure()
ax = fig.add_subplot(111)
多个 Axes 在同一个 Figure 上是按照矩阵格式排列布局的,例如 3 行 2
列一共 6 个位置,当然并不要求填满所有位置。 可以使用如下方式,依次添加
Axes 对象,需要给出整体的行列数以及自身的序号(从 1 开始)
1
2
3
4
5
6
7
8fig = plt.figure()
ax1 = fig.add_subplot(3,2,1)
ax2 = fig.add_subplot(3,2,2)
ax3 = fig.add_subplot(3,2,3)
ax4 = fig.add_subplot(3,2,4)
ax5 = fig.add_subplot(3,2,5)
ax6 = fig.add_subplot(3,2,6)
得到的空给框架如下(这里的刻度尺都是动态的,会随着填入数据的范围而调整)

对于上面函数中小的个位数索引(即总位置不超过 9
个),可以直接拼成一个三位数参数,例如 1
2
3
4ax1 = fig.add_subplot(3,2,1)
# i.e.
ax1 = fig.add_subplot(321)
注意:如果反复添加 Axes,它们可能会相互重叠显示,显得非常混乱。
一次性创建 Figure 并添加 Axes
对于多行多列的矩阵布局,先创建 Figure,然后一个个添加 Axes
非常繁琐,可以使用pyplot 提供的 plt.subplots
接口一次性完成。
例如,创建一个Figure并添加一个Axes 1
2
3fig, ax = plt.subplots()
type(ax) # matplotlib.axes._axes.Axes
例如,创建一个Figure并添加多个Axes 1
2
3
4fig, axes = plt.subplots(3,2)
type(axes) # numpy.ndarray
print(axes.shape) # (3,2)
通过访问这个数组的分量来实现对每一个 Axes 的修改。
需要注意的是,还有一个名称非常相似的接口
plt.subplot,它是面向过程绘图的接口,含义是为当前的 Figure 添加一个 Axes。
绘制曲线
使用 Axes 的 plot 方法绘制曲线图,假设
x,y,x2,y2
都是一维的数组,那么可以使用下面几种方式绘制曲线:
ax.plot(y):一条散点连成的曲线,横坐标取从0开始的整数,纵坐标取自yax.plot(x,y):一条散点连成的曲线,横坐标取自x,纵坐标取自yax.plot(x,y,x2,y2):两条曲线,第一条横坐标取自x,纵坐标取自y,第二条横坐标取自x2,纵坐标取自y2
还可以附加一些细节参数,例如颜色,曲线样式,图例等,下面依次介绍。
颜色 color
几个常用的颜色及其缩写如下:
- 蓝色 blue-b
- 绿色 green-g
- 红色 red-r
- 白色 white-w
- 黑色 black-k(因为b已经被blue占用)
- 金黄色 yellow-y
- 蓝绿色(青色)cyan-c
- 品红色 magenta-m
可以使用关键字参数设置固定的颜色 color='red'
或者使用十六进制的颜色编码如
color='#32CD32'。支持如下的简写:
- 将常用颜色使用简写,例如
color='r' - 将关键字
color简写为c,例如c='r'
例如:(这里白色的线和背景重了看不出来)
1 | fig, ax = plt.subplots(figsize=(7, 5)) |

曲线样式 linestyle
支持的曲线样式如下:
- 实线(默认):solid,简写
- - 短虚线(点虚线):dotted,简写
: - 长虚线(破折线):dashed,简写
-- - 长短交错虚线(点划线):dashdot,简写
-. - 不画线(什么也没有):None,简写
""或者''
可以使用关键字参数 linestyle='dotted'
设置曲线样式。支持如下的简写:
- 将曲线样式使用简写,例如
linestyle=':' - 将关键字
linestyle简写为ls,例如ls='-.'
例如
1 | fig, ax = plt.subplots(figsize=(7, 5)) |

还支持调整线的宽度(linewidth,简称
lw),线宽的默认值是 1.5(MATLAB 的线宽默认是
0.5),例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

设置线宽是高频使用的选项,无论是 matplotlib 还是 MATLAB 绘图,因为前者的默认线宽 1.5 略粗,后者的默认线宽 0.5 略细。
绘图标记 marker
常用的点的标记如下:
- 点
'.' - 像素点
',' - 实心圆
'o'(小写字母 o) - 星号
'*' - 乘号
'x' - 加号
'+' - 菱形
'D',瘦菱形'd' - 正方形
's' - 三角形
'^','<','v','>'(不同的四个朝向)
可以使用关键字参数 marker='*' 设置绘图标记。
例如:(这里像素点根本看不清)
1 | fig, ax = plt.subplots(figsize=(7, 5)) |

关于绘图标记,我们还可以指定标记的大小(markersize 简写
ms),内部填充颜色(markerfacecolor 简写
mfc),边框填充颜色(markeredgecolor 简写
mec),例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

MATLAB 默认是不会填充标记的内部,呈现的是空心的标记,但是 matplotlib 的默认行为是填充,可以通过
markerfacecolor="none"选项阻止填充。
如果数据很多,不需要在每一个数据点绘制标记,否则标记会堆叠在一起,掩盖数据,可以通过
markevery 选项控制,例如
1 | fig, ax = plt.subplots() |
markevery
选项如果提供的是一个值,代表间隔,如果提供两个数组成的元组,则第一个元素为起始的偏移量,第二个元素为间隔,可以用来错开多条曲线的标记。
使用 linestyle='',marker='o'
可以达到散点图的效果,并且可能比专门的散点图 plt.scatter
更好,例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

fmt 参数
对于曲线常用的标记、样式、颜色三种设置,可以把相应的简写合并为一个字符串参数,这里每一个部分都是可选的,顺序也是任意的。
1 | '[marker][line][color]' |
例如:
o:r圆点,点虚线,红色go--绿色,破折线,绿色
示例如下
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

图像细节
标题和轴标签
支持两层标题:Figure 可以使用 suptitle 设置标题,Axes
可以使用 set_title 设置标题。
例如
1 | fig, axes = plt.subplots(1,2,figsize=(7, 3)) |

可以给每一个坐标轴都可以加上轴标签:
1 | ax.set_xlabel("x") |
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

对于标题和轴标签这类的文本选项,有很多额外的选项:
- 位置:例如
loc="left",对于标题和 x 轴包括"left","center","right"选项,对于 y 轴包括"top","center","bottom"选项,默认都是居中 - 颜色:例如
color='r' - 字体:例如
fontfamily = 'sans-serif',注意默认不支持中文显示。 - 字体大小:例如
fontsize=18 - 字体样式:例如
fontstyle='oblique'
图例
对每一条曲线加上 label,然后使用
Axes.legend() 显示图例,注意 legend()
调用必须在所有 label 之后。
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

可以对图例进行细节调整,包括加上单独的标题,移除边框,指定位置等。(当然这些细节也可以通过 GUI 界面进行调整)
例如 1
2
3
4
5
6
7
8
9
10fig, ax = plt.subplots(figsize=(4, 3))
x = np.linspace(0, 2 * np.pi, 100)
ax.plot(x, np.sin(x), label="sin(x)")
ax.plot(x, np.cos(x), label="cos(x)")
ax.legend(title="Legends", loc="best", frameon=False, fontsize=10)
ax.set_xlabel("xlabel", loc="right")
ax.set_ylabel("ylabel", loc="top")

matplotlib 图例的默认样式与 MATLAB 有很多差异,例如图例边框的形状和颜色,图例背景透明度等。
网格线
函数原型为
1 | Axes.grid(visible=None, which='major', axis='both', **kwargs) |
其中主要参数:
visible可见性which绘制主刻度还是辅助刻度的网格线,支持选项'major','minor','both',默认'major'只绘制主刻度的网格线axis绘制从 x 轴或 y 轴发出的网格线,支持选项'both','x','y',默认'both'
可以简单地使用 ax.grid()
开启网格线,还可以指定网格线更复杂的细节(样式,宽度,颜色,透明度
alpha 等)。
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

轴的单位长度比
可以手动设置 x 轴和 y 轴的单位长度的比例,默认是
auto,也就是会根据作图需要自动调整,可以改成相等或者某个固定的值。
1 | ax.set_aspect('auto') |
例如
1 | fig, ax = plt.subplots(figsize=(4, 4)) |

边框隐藏
默认情况下作图会绘制四个边框,有时我们需要去掉某些边框,有两种方案:
- 设置边框透明度为 0
- 设置边框颜色为空
例如:
1 | fig, ax = plt.subplots(figsize=(4, 4)) |

坐标轴范围
通常 Axes 的坐标轴范围会根据数据范围自动生成,并且相对数据本身范围有一定的冗余,可以使用下面的代码使得数据轴范围更紧。
1 | ax.autoscale(tight=True) |
某些数据图需要紧贴的效果,某些数据图则需要冗余,需要根据情况自行选择。
我们也可以手动指定坐标轴的范围(数据如果不在范围内就不会显示)
1 | ax.set_xlim(-1,2) |
例如
1 | fig, ax = plt.subplots(figsize=(4, 4)) |

坐标轴刻度
默认坐标轴会显示主级和次级的坐标,但是有时次级坐标会显得太密集,可以手动关闭。
例如基于 plt 的写法如下 1
2
3
4from matplotlib.ticker import NullLocator
plt.gca().xaxis.set_minor_locator(NullLocator())
plt.gca().yaxis.set_minor_locator(NullLocator())
基于面向对象的写法如下 1
2ax.xaxis.set_minor_locator(NullLocator())
ax.yaxis.set_minor_locator(NullLocator())
可以指定一条轴上使用哪些刻度,使用 ax.set_xticks
方法传入一个数组。
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

我们可以在指定刻度的基础上,将刻度的数值换成另外的文字,此时使用
ax.set_xticklabels 方法传入一个数组。例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

坐标轴刻度朝向
关于坐标轴的刻度朝向,matplotlib 默认刻度朝外(但是 MATLAB
默认刻度朝内),可以用下面的命令调整当前绘图窗口的刻度朝向
1
2
3
4
5
6
7
8ax.tick_params(axis='x', direction='in')
ax.tick_params(axis='y', direction='in')
ax.tick_params(axis='x', direction='out')
ax.tick_params(axis='y', direction='out')
ax.tick_params(axis='x', direction='inout')
ax.tick_params(axis='y', direction='inout')
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

坐标轴比例
默认的坐标轴比例是线性的,但是我们可以指定对数,双对数等,使用
ax.set_yscale('log') 方法可以设置 y 轴为对数刻度,x
轴也同理。
例如
1 | fig, axes = plt.subplots(1, 2, figsize=(7, 3)) |

批量设置 Axes 属性
前文中对 Axes 使用了各种 set_xxx
方法,实际可以打包为一个字典进行统一设置,例如
1 | ax.set( |
字体设置
设置字体为 Times New Roman 1
matplotlib.rcParams['font.sans-serif'] = 'Times New Roman'
让 text 支持 LaTeX(默认为 false) 1
matplotlib.rcParams['text.usetex'] = True
如果使用的 LaTeX 命令不被支持,需要加入额外的 LaTeX 宏包,例如
1
matplotlib.rcParams["text.latex.preamble"] = r"\usepackage{amsmath,amssymb,amsfonts}"
修改字体以支持中文 1
matplotlib.rcParams['font.sans-serif']=['SimHei']
如果负号被显示为方块,可以修改下面的配置 1
matplotlib.rcParams['axes.unicode_minus'] = False
显然这里要求系统中已经安装 LaTeX 或者对应字体,否则无法使用。
添加文字
可以使用 ax.text 向指定的 Axes 对象添加文字,位置是在
Axes 的 xy 轴坐标,文字可以有很多细节处理,这里略过。
1 | def text(self, x, y, s, fontdict=None, **kwargs): |
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

局部放大图
局部放大图的添加更加复杂,需要引入一个额外的axes,下面提供一个示例

1 | import numpy as np |
图像保存
对于面向过程的接口,使用 plt.savefig
方法可以保存当前图像;对于面向对象的接口,使用 fig.savefig
方法可以保存对应图像。
最基本的用法只需要提供文件名即可,会根据文件名后缀自动选择保存格式,例如
.png、.pdf、.svg
等。(如果不含后缀,会自动加上 .png 后缀)
1 | plt.savefig("sine_curve.png") |
当然也可以添加一些参数,例如 1
2
3plt.savefig("sine_curve.png", dpi=300, bbox_inches='tight', transparent=False)
fig.savefig("sine_curve.png", dpi=300, bbox_inches='tight', transparent=False)
常见的可选参数及其含义如下:
dpi:分辨率,例如dpi=300transparent:是否透明,例如transparent=true设置透明背景,默认为falsebbox_inches:边界框控制,包括tight/standard/ None 三种选择,可以使用bbox_inches='tight'裁剪外部的空白部分pad_inches:边框内间距,通常与bbox_inches搭配使用,控制图像的边框留白细节facecolor:背景色,影响绘图方框之外部分的颜色,例如facecolor='grey'
动画
这里记录一下 Python 的 matplotlib 的动画效果,注意我们需要分别考虑如下三种运行环境,它们对于动画的支持是不一样的:
- 浏览器启动 Jupyter Notebook 或 Jupyter Lab
- 直接运行 Python 脚本
- VSCode 启动 Jupyter(目前仍然不支持动画)
例如浏览器启动 Jupyter 可能需要相关的插件(例如 ipympl 插件),并且需要魔法命令
1 | %matplotlib notebook |
在启动内核后,至少需要执行这个魔法命令一次,否则无法播放动画,两个同时播放的动画甚至还会相互影响。
在直接运行 Python 脚本时,Jupyter 的魔法指令是语法错误,对于动画而言,直接运行 Python 脚本反而比 Jupyter 更方便。
matplotlib 绘制动画主要包括两种方法:FuncAnimation 和 ArtistAnimation,下面的代码都需要导入如下的库
1 | import numpy as np |
FuncAnimation
FuncAnimation 的原理是首先绘制第一帧图像,然后逐帧调用更新函数进行修改,达到动画效果。
官方文档的示例如下
1 | %matplotlib notebook |

这里的参数含义依次为:
fig=fig:指定第一帧图像func=update:指定更新函数,调用更新函数时会传递当前帧数 frame,额外的参数传递可以通过偏函数实现,返回值类型必须为元组,例如
1 | def update(frame, art, *, y=None): |
frames=40:一共绘制 40 帧,这里还可以传递迭代器,事实上直接使用数字类似于frames=range(40)interval=30:两帧之间的间隔时间,单位毫秒,默认值为 200
还有一些可能需要的参数:参考官方文档
repeat: 是否重复,默认为 Truerepeat_delay: 重复时的延时init_func: 初始化函数,可以用于在第一帧之前清空图像fargs: 传递给更新函数的额外参数,但是更建议用偏函数封装blit:优化绘图效率,进行块状更新,默认为 False
例如
1 | %matplotlib notebook |

ArtistAnimation
和前面的基于第一帧不断进行更新不同,ArtistAnimation 的原理是预先产生每一帧图像组成的列表,然后逐帧播放。
1 | %matplotlib notebook |
注意这里不接受frames参数,也可以换成 FuncAnimation
等价实现
1 | %matplotlib notebook |

注意:
- 如果要提高输出动画的质量,首先需要完善绘图的细节,例如增加点数,然后在保存时可以指定 dpi,默认的 dpi 为 100,通常范围为 100-300。dpi 值越大,则图像越清晰,文件越大。
- 在保存动画文件时,可能会报警告
MovieWriter ffmpeg unavailable; using Pillow instead.,这是因为没有找到ffmpeg,手动下载即可:conda install ffmpeg
自定义绘图样式
Matplotlib 允许从下面两个层次进行绘图样式的定制:
matplotlib.style模块matplotlib.rcParams字典
前者适合整套方案的切换,后者则适合精细控制单个参数,后者的优先级更高。
相比于在每一次绘图时进行的参数微调,这里讨论的两种方式都会产生全局性的影响。
matplotlib.style
1 | import matplotlib.style |
除了导入子模块,其实也可以通过
plt.style直接访问。
下面介绍几个常用的功能。
列出可用的所有样式名称:(这是一个列表) 1
matplotlib.style.available
输出示例:
1 | ['classic', 'bmh', 'ggplot', 'dark_background', 'Solarize_Light2', ...] |
加载一个或多个样式表(theme),会覆盖当前全局参数,例如
1 | matplotlib.style.use('ggplot') |
上面的样式修改是全局的,并且是持续性的。通常更建议使用
with
语句创建一个单独的上下文,在其中修改样式,退出后自动恢复原状。例如
1
2with matplotlib.style.context('dark_background'):
plt.plot([1, 2, 3])
这里的样式实际是一个 name.mplstyle
文件,内置样式通常存放在如下目录中 1
site-packages\matplotlib\mpl-data\stylelib
.mplstyle 文件实质就是一个 INI 格式的键值对文件,例如
dark_background.mplstyle 的完整内容如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# Set black background default line colors to white.
lines.color: white
patch.edgecolor: white
text.color: white
axes.facecolor: black
axes.edgecolor: white
axes.labelcolor: white
axes.prop_cycle: cycler('color', ['8dd3c7', 'feffb3', 'bfbbd9', 'fa8174', '81b1d2', 'fdb462', 'b3de69', 'bc82bd', 'ccebc4', 'ffed6f'])
xtick.color: white
ytick.color: white
grid.color: white
figure.facecolor: black
figure.edgecolor: black
### Boxplots
boxplot.boxprops.color: white
boxplot.capprops.color: white
boxplot.flierprops.color: white
boxplot.flierprops.markeredgecolor: white
boxplot.whiskerprops.color: white
我们可以自行提供一个样式文件,然后手动导入,例如 1
2with matplotlib.style.context('./my_style.mplstyle')
plt.plot([1, 2, 3])
也可以通过传入一个字典进行样式修改,例如 1
2
3custom_style = {'lines.linewidth': 3, 'axes.titlesize': 14}
with matplotlib.style.context(custom_style):
plt.plot([1, 2, 3])
可以考虑使用第三方库 SciencePlots,它提供了科研绘图风格的一些样式文件。
matplotlib.rcParams
matplotlib.rcParams
是一个全局生效的键值对字典实例,用于保存和配置绘图参数,优先级高于之前加载的样式。
下面的代码中的
matplotlib.rcParams都可以替换为plt.rcParams,后者的使用更加方便。
可以直接对字典进行修改,例如 1
2matplotlib.rcParams['font.size'] = 14
matplotlib.rcParams['lines.linewidth'] = 1
也可以用下面的接口设置某一类参数 1
matplotlib.rc('lines', linewidth=1, linestyle='--')
效果等价于 1
2matplotlib.rcParams['lines.linewidth'] = 2
matplotlib.rcParams['lines.linestyle'] = '--'
这里的修改都是持续生效的,可以用下面的函数将其恢复为默认值。
1
matplotlib.rcdefaults()
与 matplotlib.style.context 类似,更建议使用
matplotlib.rc_context
创建一个临时的上下文,此时可以传入改动的参数字典 1
2with matplotlib.rc_context({'font.size': 18, 'axes.titlesize': 20}):
plt.plot([1, 2, 3])
常见配置选项
下面是一些经常进行全局修改的绘图选项。
设置衬线字体和支持 LaTeX 1
2
3text.usetex: True
font.family: serif
font.serif: Times New Roman
调整字号(这里统一设置为 14
是为了省事,实际绘图时根据需要,可以把特定文字调大或调小)
1
2
3
4
5
6font.size: 14
axes.labelsize: 14
axes.titlesize: 14
xtick.labelsize: 14
ytick.labelsize: 14
legend.fontsize: 14
调整线宽,轴线宽和 marker 大小 1
2lines.linewidth: 1.0
axes.linewidth: 1.0
调整 marker 大小和样式(默认实心,这里将其设置为空心)
1
2
3lines.markersize: 5
lines.markerfacecolor: none
lines.markeredgecolor: auto
设置坐标轴刻度朝内(默认朝外) 1
2xtick.direction: in
ytick.direction: in
调整图例样式 1
2
3legend.facecolor: white
legend.edgecolor: black
legend.framealpha: 1.0