Python 绘图笔记
matplotlib 介绍
matplotlib 大致是类似 matlab 风格的 2D 绘图库(3D
的功能比较弱,还是基于 2D 引擎勉强实现的),同时提供了两套 api:
- 一个是面向过程的,主要调用
matplotlib.pyplot的函数; - 一个是面向对象的,主要调用
matplotlib的两个子类:matplotlib.figure.Figure和matplotlib.axes.Axes,使用它们的方法进行细节操作。
面向过程的 api
适合简易使用的场景,但是不容易弄清原理;为了更复杂的绘图要求,这里主要采用面向对象的
api。关于绘图的呈现方式,在 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分辨率(默认的 dpi 为 100,通常范围为 100-300。dpi 值越大,则图像越清晰,文件越大。可以设置为dpi=300)facecolor背景颜色edgecolor边框颜色frameon是否显示边框
添加 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
还可以附加一些细节参数,例如颜色,曲线样式,图例等,下面依次介绍。
颜色
几个常用的颜色及其缩写如下:
- 蓝色 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)) |

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

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

绘图标记
常用的点的标记如下:
- 点
'.' - 像素点
',' - 实心圆
'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)) |

注:使用 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 之后,图例的文字支持简单的 latex。
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

网格线
函数原型为
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.set_xlim(-1,2) |
例如
1 | fig, ax = plt.subplots(figsize=(4, 4)) |

坐标轴指定刻度
可以指定一条轴上使用哪些刻度,使用 ax.set_xticks
方法传入一个数组。
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

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

坐标轴刻度朝向
如下的语句可以使得刻度朝内侧("in"),还支持两个选项:"out"
朝外(默认),"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)) |

添加文字
可以使用 ax.text 向指定的 Axes 对象添加文字,位置是在
Axes 的 xy 轴坐标,文字可以有很多细节的处理,这里略过。
1 | def text(self, x, y, s, fontdict=None, **kwargs): |
例如
1 | fig, ax = plt.subplots(figsize=(4, 3)) |

解决中文乱码
使用下面的两行语句可以支持中文字体显示,否则默认情况下中文字体可能显示异常,负号可能显示为方块。
1 | matplotlib.rcParams['font.sans-serif']=['SimHei'] |
动画
这里记录一下 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
统计图
下面简单列举几个目前不常用的统计图绘制的示例代码,关于统计图的绘制还可以参考第三方库 seaborn。
直方图
函数原型为 1
2def ax.hist(x, bins=None, range=None, density=False, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, *, data=None, **kwargs):
...
其中的主要选项包括:
x,要统计的数据,array or sequence of arraysbins,决定数据 x 如何被分割- 如果 bins 是整数 n,则数据 x 分割为 n 个等宽部分
- 如果 bins 是一个序列,则定义 bin 的边缘,包括第一个 bin 的左边缘和最后一个 bin 的右边缘,这样 bin 可能不等距
- 如果 bins 是字符串,则支持分割策略,'auto', 'fd', 'doane', 'scott', 'stone', 'rice', 'sturges', or 'sqrt'
density,是否绘制概率密度,bool,default: Falseweights,与 x 形状相同的权重数组,array or sequence of arrays,default: Nonecumulative,是否累计,bool,default: Falsebottom,每个条形的基底部高度,必须与 bin 相匹配,default: Nonehisttype,直方图的类型,{'bar', 'barstacked', 'step', 'stepfilled'},default: 'bar'align,对齐方式,{'left', 'mid', 'right'}, default: 'mid'orientation,直方图的方向,{'vertical', 'horizontal'}, default: 'vertical'rwidthfloat,条的相对宽度,float,default: None
例如 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24np.random.seed(0)
x = 4 + np.random.normal(0, 1, 2000)
fig, axes = plt.subplots(2, 2, figsize=(9, 6))
axes[0, 0].hist(x, bins=8, linewidth=0.5, edgecolor="white")
axes[0, 0].set_title("bins=8")
axes[0, 1].hist(x, bins=np.arange(0, 8, 0.5), linewidth=0.5, edgecolor="white")
axes[0, 1].set_title("bins=np.arange(0,8,0.5)")
axes[1, 0].hist(
x, bins=np.arange(0, 8, 0.5), linewidth=0.5, edgecolor="white", density=True
)
axes[1, 0].set_title("density=True")
axes[1, 1].hist(
x, bins=np.arange(0, 8, 0.5), linewidth=0.5, edgecolor="white", cumulative=True
)
axes[1, 1].set_title("cumulative=True")
for i in np.arange(2):
for j in np.arange(2):
axes[i, j].set(xlim=[0, 8])

饼图
直接用例子吧,很少用饼图: 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50fig, axes = plt.subplots(2, 2, figsize=(8, 8))
labels = ["大型", "中型", "小型", "微型"]
sizes = [46, 253, 321, 66]
axes[0, 0].pie(
sizes,
explode=(0, 0, 0, 0),
labels=labels,
colors=["red", "yellowgreen", "lightskyblue", "yellow"],
autopct="%3.2f%%",
shadow=False,
startangle=90,
pctdistance=0.6,
)
axes[0, 0].legend()
axes[0, 1].pie(
sizes,
explode=(0.1, 0.05, 0.05, 0.05),
labels=labels,
colors=["red", "yellowgreen", "lightskyblue", "yellow"],
autopct="%3.2f%%",
shadow=False,
startangle=90,
pctdistance=0.6,
)
axes[1, 0].pie(
sizes,
explode=(0.1, 0.05, 0.05, 0.05),
labels=labels,
colors=["red", "yellowgreen", "lightskyblue", "yellow"],
autopct="%3.2f%%",
shadow=True,
startangle=90,
pctdistance=0.6,
)
axes[1, 1].pie(
sizes,
explode=(0.1, 0.05, 0.05, 0.05),
labels=labels,
colors=["red", "yellowgreen", "lightskyblue", "yellow"],
autopct="%3.2f%%",
shadow=True,
startangle=90,
pctdistance=0.7,
)

等值线图
最简单的例子如下: 1
2
3
4
5
6
7
8
9
10
11
12
13xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + 2*Y**2)
fig,ax=plt.subplots()
cp = ax.contourf(X, Y, Z)
fig.colorbar(cp)
ax.set_title('Filled Contours Plot')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_aspect('equal')
包括如下几个部分:
- 生成
(X,Y,Z)网格数据 - 绘制等值线图并添加 colorbar
- 补充细节

自定义样式
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
是一个全局生效的键值对字典实例,用于保存和配置绘图参数,优先级高于之前加载的样式。
也可以通过
plt.rcParams访问。
可以直接对字典进行修改,例如 1
2matplotlib.rcParams['font.size'] = 14
matplotlib.rcParams['lines.linewidth'] = 2
也可以用下面的接口设置某一类参数 1
matplotlib.rc('lines', linewidth=2, 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])