matplotlib 介绍

matplotlib 大致是类似 matlab 风格的 2D 绘图库(3D 的功能比较弱,还是基于 2D 引擎勉强实现的),同时提供了两套 api:

  • 一个是面向过程的,主要调用 matplotlib.pyplot 的函数;
  • 一个是面向对象的,主要调用 matplotlib 的两个子类:matplotlib.figure.Figurematplotlib.axes.Axes ,使用它们的方法进行细节操作。

面向过程的 api 适合简易使用的场景,但是不容易弄清原理;为了更复杂的绘图要求,这里主要采用面向对象的 api。关于绘图的呈现方式,在 Jupyter 中很可能会自动绘图而不需要 plt.show(),这与魔法指令 %matplotlib inline 有关。

在这一系列笔记中,需要使用如下模块:

1
2
3
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

由于 matplotlib 的内容太多太杂,这里只会介绍最容易理解的,最本质的面向对象的接口,然后作为辅助会介绍其它等价的,或者更方便的调用方式。但是 matplotlib 的面向对象其实有很多层,在每一层都可能为同一个功能提供了等价的接口,这里为了简化笔记,主要考虑 FigureAxes 层面提供的接口。

简单例子

面向过程风格的绘图例子如下,主要是 plt 的函数调用:

1
2
3
4
5
plt.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
6
fig, 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
5
def 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
2
fig = plt.figure()
ax = fig.add_subplot(111)

多个 Axes 在同一个 Figure 上是按照矩阵格式排列布局的,例如 3 行 2 列一共 6 个位置,当然并不要求填满所有位置。 可以使用如下方式,依次添加 Axes 对象,需要给出整体的行列数以及自身的序号(从 1 开始)

1
2
3
4
5
6
7
8
fig = 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
4
ax1 = 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
3
fig, ax = plt.subplots()

type(ax) # matplotlib.axes._axes.Axes

例如,创建一个Figure并添加多个Axes

1
2
3
4
fig, axes = plt.subplots(3,2)

type(axes) # numpy.ndarray
print(axes.shape) # (3,2)

通过访问这个数组的分量来实现对每一个 Axes 的修改。

需要注意的是,还有一个名称非常相似的接口 plt.subplot,它是面向过程绘图的接口,含义是为当前的 Figure 添加一个 Axes。

绘制曲线

使用 Axesplot 方法绘制曲线图,假设 x,y,x2,y2 都是一维的数组,那么可以使用下面几种方式绘制曲线:

  • ax.plot(y):一条散点连成的曲线,横坐标取从0开始的整数,纵坐标取自y
  • ax.plot(x,y):一条散点连成的曲线,横坐标取自x,纵坐标取自y
  • ax.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
2
3
4
5
6
7
8
9
fig, ax = plt.subplots(figsize=(7, 5))

data = 10 + 0.4 * np.random.randn(9, 100)
colors = ["blue", "green", "red", "white", "black", "yellow", "cyan", "m",'#32CD32']

for i, item in enumerate(colors):
ax.plot(data[i] - i, color=item, label=item)

ax.legend()

曲线样式

支持的曲线样式如下:

  • 实线(默认):solid,简写 -
  • 短虚线(点虚线):dotted,简写 :
  • 长虚线(破折线):dashed,简写 --
  • 长短交错虚线(点划线):dashdot,简写 -.
  • 不画线(什么也没有):None,简写 "" 或者 ''

可以使用关键字参数 linestyle='dotted' 设置曲线样式。支持如下的简写:

  • 将曲线样式使用简写,例如 linestyle=':'
  • 将关键字 linestyle 简写为 ls,例如 ls='-.'

例如

1
2
3
4
5
6
7
8
9
10
11
fig, ax = plt.subplots(figsize=(7, 5))


style = ["solid", "dotted", "dashed", "dashdot", "None" ]

data = 10 + 0.4 * np.random.randn(len(style), 100)

for i, item in enumerate(style):
ax.plot(data[i] - i,linestyle=item,label=item)

ax.legend()

还支持调整线的宽度(linewidth,简称 lw),线宽的默认值是 1.5,例如

1
2
3
4
5
6
fig, ax = plt.subplots(figsize=(4, 3))

data = np.sin(np.linspace(0, 2 * np.pi, 100))

ax.plot(data,lw=5)
ax.plot(data+0.5)

绘图标记

常用的点的标记如下:

  • '.'
  • 像素点 ','
  • 实心圆 'o' 字母 o
  • 星号 '*'
  • 乘号 'x'
  • 加号 '+'
  • 菱形 'D',瘦菱形 'd'
  • 正方形 's'
  • 三角形 '^','<','v','>'(不同的四个朝向)

可以使用关键字参数 marker='*' 设置绘图标记。

例如:(这里像素点根本看不清)

1
2
3
4
5
6
7
8
9
10
11
fig, ax = plt.subplots(figsize=(7, 5))

markers = [".", ",", "o", "*", "x", "D", "d", "s", "^"]
data = np.sin(np.linspace(0, 2 * np.pi, 10 * len(markers)))

ax.plot(data)

for i, item in enumerate(markers):
ax.plot(i * 10, data[i * 10], label=item, marker=item)

ax.legend()

关于绘图标记,我们还可以指定标记的大小(markersize 简写 ms),内部填充颜色(markerfacecolor 简写 mfc),边框填充颜色(markeredgecolor 简写 mec),例如

1
2
3
4
5
fig, ax = plt.subplots(figsize=(4, 3))

data = np.random.randn(7)

ax.plot(data, marker = 'o', ms = 10, mec = 'r')

注:使用 linestyle='',marker='o' 可以达到散点图的效果,并且可能比专门的散点图 plt.scatter 更好,例如

1
2
3
4
5
fig, ax = plt.subplots(figsize=(4, 3))

data = np.random.randn(7)

ax.plot(data, ls='',marker = 'o', ms = 5)

fmt 参数

对于曲线常用的标记、样式、颜色三种设置,可以把相应的简写合并为一个字符串参数,这里每一个部分都是可选的,顺序也是任意的。

1
'[marker][line][color]'

例如:

  • o:r 圆点,点虚线,红色
  • go-- 绿色,破折线,绿色

示例如下

1
2
3
4
fig, ax = plt.subplots(figsize=(4, 3))

x = np.linspace(0, 2 * np.pi, 20)
ax.plot(x,np.sin(x),'o:r')

图像细节

标题和轴标签

支持两层标题:Figure 可以使用 suptitle 设置标题,Axes 可以使用 set_title 设置标题。

例如

1
2
3
4
5
6
7
8
9
10
fig, axes = plt.subplots(1,2,figsize=(7, 3))

x = np.linspace(0, 2 * np.pi, 100)

axes[0].plot(x,np.sin(x))
axes[1].plot(x,np.cos(x))

axes[0].set_title("sin(x)")
axes[1].set_title("cos(x)")
fig.suptitle("fig-title")

可以给每一个坐标轴都可以加上轴标签:

1
2
ax.set_xlabel("x")
ax.set_ylabel("y")

例如

1
2
3
4
5
6
7
8
9
10
fig, ax = plt.subplots(figsize=(4, 3))

x = np.linspace(0, 2 * np.pi, 100)

ax.plot(x,np.sin(x))

ax.set_xlabel("x")
ax.set_ylabel("y")

ax.set_title("sin(x)")

对于标题和轴标签这类的文本选项,有很多额外的选项:

  • 位置:例如 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
2
3
4
5
6
7
8
9
10
fig, 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()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

网格线

函数原型为

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
2
3
4
5
6
7
8
9
10
11
12
fig, 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.grid(ls=':', lw=1, color='r',alpha=0.3)

ax.legend()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

轴的单位长度比

可以手动设置 x 轴和 y 轴的单位长度的比例,默认是 auto,也就是会根据作图需要自动调整,可以改成相等或者某个固定的值。

1
2
3
4
5
ax.set_aspect('auto')

ax.set_aspect('equal')

ax.set_aspect(2)

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fig, ax = plt.subplots(figsize=(4, 4))

x = np.linspace(-1,1,100)

ax.plot(x,x,label='$x$')
ax.plot(x,x**2,label='$x^2$')
ax.plot(x,x**3,label='$x^3$')

ax.grid(ls=':', lw=1, color='r',alpha=0.3)

ax.legend()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

ax.set_aspect('equal')

边框隐藏

默认情况下作图会绘制四个边框,有时我们需要去掉某些边框,有两种方案:

  • 设置边框透明度为 0
  • 设置边框颜色为空

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
fig, ax = plt.subplots(figsize=(4, 4))

x = np.linspace(0,1,100)
ax.plot(x,x**2,label='$x^2$')

ax.legend()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

ax.spines['right'].set_alpha(0)
ax.spines['top'].set_color('none')

ax.set_aspect('equal')

坐标轴范围

通常 Axes 的坐标轴具体范围根据数据而来,但是我们也可以指定坐标轴的范围(数据如果不在范围内就不会显示)

1
2
ax.set_xlim(-1,2)
ax.set_ylim(0,3)

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig, ax = plt.subplots(figsize=(4, 4))

x = np.linspace(-1,1,100)
ax.plot(x,x**2,label='$x^2$')
ax.plot(x,x+1,label='$x+1$')

ax.legend()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

ax.spines['right'].set_alpha(0)
ax.spines['top'].set_color('none')

ax.set_aspect('equal')

ax.set_xlim(-1,2)
ax.set_ylim(0,3)

坐标轴指定刻度

可以指定一条轴上使用哪些刻度,使用 ax.set_xticks 方法传入一个数组。

例如

1
2
3
4
5
6
7
8
9
10
11
12
fig, 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()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

ax.set_xticks([0,np.pi,np.pi*2])

坐标轴自定义刻度

我们可以在指定刻度的基础上,将刻度的数值换成另外的文字,使用 ax.set_xticklabels 方法传入一个数组。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
fig, 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()
ax.set_xlabel("xlabel",loc='right')
ax.set_ylabel("ylabel",loc='top')

ax.set_xticks([0,np.pi,np.pi*2])
ax.set_xticklabels(['0','$\pi/2$','$\pi$'])

坐标轴刻度朝向

如下的语句可以使得刻度朝内侧("in"),还支持两个选项:"out" 朝外(默认),"inout" 内外均有。

1
2
3
4
5
6
7
8
fig, ax = plt.subplots(figsize=(4, 3))

x = np.linspace(0, 3, 100)
y = np.exp(x)

ax.tick_params(axis='x', direction='in')
ax.tick_params(axis='y', direction='in')
ax.plot(x,y)

坐标轴比例

默认的坐标轴比例是线性的,但是我们可以指定对数,双对数等,使用 ax.set_yscale('log') 方法可以设置 y 轴为对数刻度,x 轴也同理。

例如

1
2
3
4
5
6
7
8
9
10
11
12
fig, axes = plt.subplots(1, 2, figsize=(7, 3))

x = np.linspace(0, 3, 100)
y = np.exp(x)

axes[0].plot(x,y)
axes[1].plot(x,y)

axes[0].set_title("linear")
axes[1].set_title("log")

axes[1].set_yscale("log")

添加文字

可以使用 ax.text 向指定的 Axes 对象添加文字,位置是在 Axes 的 xy 轴坐标,文字可以有很多细节的处理,这里略过。

1
2
def text(self, x, y, s, fontdict=None, **kwargs):
...

例如

1
2
3
4
5
6
7
8
9
fig, ax = plt.subplots(figsize=(4, 3))

x = np.linspace(0, 3, 100)
y = np.exp(x)

ax.plot(x,y)

ax.plot(1,np.e,'o:r')
ax.text(1+0.2,np.e-0.2,"(1,e)")

解决中文乱码

使用下面的两行语句可以支持中文字体显示,否则默认情况下中文字体可能显示异常,负号可能显示为方块。

1
2
matplotlib.rcParams['font.sans-serif']=['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False

动画

这里记录一下 Python 的 matplotlib 的动画效果,注意我们需要分别考虑如下三种运行环境,它们对于动画的支持是不一样的:

  • 浏览器启动 Jupyter Notebook 或 Jupyter Lab
  • 直接运行 Python 脚本
  • VSCode 启动 Jupyter(目前仍然不支持动画)

例如浏览器启动 Jupyter 可能需要相关的插件(例如 ipympl 插件),并且需要魔法命令

1
%matplotlib notebook

在启动内核后,至少需要执行这个魔法命令一次,否则无法播放动画,两个同时播放的动画甚至还会相互影响。

在直接运行 Python 脚本时,Jupyter 的魔法指令是语法错误,对于动画而言,直接运行 Python 脚本反而比 Jupyter 更方便。

matplotlib 绘制动画主要包括两种方法:FuncAnimation 和 ArtistAnimation,下面的代码都需要导入如下的库

1
2
3
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

FuncAnimation

FuncAnimation 的原理是首先绘制第一帧图像,然后逐帧调用更新函数进行修改,达到动画效果。

官方文档的示例如下

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
%matplotlib notebook
fig, ax = plt.subplots()
t = np.linspace(0, 3, 40)
g = -9.81
v0 = 12
z = g * t**2 / 2 + v0 * t
v02 = 5
z2 = g * t**2 / 2 + v02 * t

scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0]
ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]')
ax.legend()

def update(frame):
# for each frame, update the data stored on each artist.
x = t[:frame]
y = z[:frame]
# update the scatter plot:
data = np.stack([x, y]).T
scat.set_offsets(data)
# update the line plot:
line2.set_xdata(t[:frame])
line2.set_ydata(z2[:frame])
return (scat, line2)


ani = animation.FuncAnimation(fig=fig, func=update, frames=40, interval=30)
ani.save('demo.gif')
plt.show()

这里的参数含义依次为:

  • fig=fig:指定第一帧图像
  • func=update:指定更新函数,调用更新函数时会传递当前帧数 frame,额外的参数传递可以通过偏函数实现,返回值类型必须为元组,例如
1
2
3
4
def update(frame, art, *, y=None):
...

ani = animation.FuncAnimation(fig=fig, update=partial(update, art=ln, y='foo'))
  • frames=40:一共绘制 40 帧,这里还可以传递迭代器,事实上直接使用数字类似于frames=range(40)
  • interval=30:两帧之间的间隔时间,单位毫秒,默认值为 200

还有一些可能需要的参数:参考官方文档

  • repeat: 是否重复,默认为 True
  • repeat_delay: 重复时的延时
  • init_func: 初始化函数,可以用于在第一帧之前清空图像
  • fargs: 传递给更新函数的额外参数,但是更建议用偏函数封装
  • blit:优化绘图效率,进行块状更新,默认为 False

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%matplotlib notebook
w = 0.1
Lambda = 4
k = 2 * np.pi / Lambda

def update(t, w, k):
x = np.linspace(0, 10, 100)
y = np.cos(w * t - k * x)
line.set_data((x, y))
ax.set_xlim(0, 10)
ax.set_ylim(-1, 1)
return line

fig, ax = plt.subplots()
(line,) = ax.plot([], [], lw=2)
ani = animation.FuncAnimation(fig=fig, func=update, frames=100, fargs=(w, k))
ani.save('demo2.gif')
plt.show()

ArtistAnimation

和前面的基于第一帧不断进行更新不同,ArtistAnimation 的原理是预先产生每一帧图像组成的列表,然后逐帧播放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%matplotlib notebook
fig, ax = plt.subplots()

def f(x, y):
return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 300).reshape(-1, 1)

img_list = []
for i in range(100):
x += np.pi / 15.
y += np.pi / 20.
im = plt.imshow(f(x, y), animated=True)
img_list.append([im])

ani = animation.ArtistAnimation(fig, img_list, interval=200, blit=True)
plt.show()

注意这里不接受frames参数,也可以换成 FuncAnimation 等价实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%matplotlib notebook
fig, ax = plt.subplots()

def f(x, y):
return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 300).reshape(-1, 1)

im = plt.imshow(f(x, y), animated=True)

def update(*args):
global x, y
x += np.pi / 15.
y += np.pi / 20.
im.set_array(f(x, y))
return im,

ani = animation.FuncAnimation(fig=fig, func=update, interval=200, frames=100, blit=True)
ani.save("demo3.gif",dpi=300)
plt.show()

注意:

  • 如果要提高输出动画的质量,首先需要完善绘图的细节,例如增加点数,然后在保存时可以指定 dpi,默认的 dpi 为 100,通常范围为 100-300。dpi 值越大,则图像越清晰,文件越大。
  • 在保存动画文件时,可能会报警告MovieWriter ffmpeg unavailable; using Pillow instead.,这是因为没有找到ffmpeg,手动下载即可:conda install ffmpeg

统计图

下面简单列举几个目前不常用的统计图绘制的示例代码,关于统计图的绘制还可以参考第三方库 seaborn。

直方图

函数原型为

1
2
def 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 arrays
  • bins,决定数据 x 如何被分割
    • 如果 bins 是整数 n,则数据 x 分割为 n 个等宽部分
    • 如果 bins 是一个序列,则定义 bin 的边缘,包括第一个 bin 的左边缘和最后一个 bin 的右边缘,这样 bin 可能不等距
    • 如果 bins 是字符串,则支持分割策略,'auto', 'fd', 'doane', 'scott', 'stone', 'rice', 'sturges', or 'sqrt'
  • density,是否绘制概率密度,bool,default: False
  • weights,与 x 形状相同的权重数组,array or sequence of arrays,default: None
  • cumulative,是否累计,bool,default: False
  • bottom,每个条形的基底部高度,必须与 bin 相匹配,default: None
  • histtype,直方图的类型,{'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
24
np.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
50
fig, 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
13
xlist = 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
2
matplotlib.style.use('ggplot')
matplotlib.style.use(['dark_background', 'seaborn-v0_8'])

上面的样式修改是全局的,并且是持续性的。通常更建议使用 with 语句创建一个单独的上下文,在其中修改样式,退出后自动恢复原状。例如

1
2
with 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
2
with matplotlib.style.context('./my_style.mplstyle')
plt.plot([1, 2, 3])

也可以通过传入一个字典进行样式修改,例如

1
2
3
custom_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
2
matplotlib.rcParams['font.size'] = 14
matplotlib.rcParams['lines.linewidth'] = 2

也可以用下面的接口设置某一类参数

1
matplotlib.rc('lines', linewidth=2, linestyle='--')

效果等价于

1
2
matplotlib.rcParams['lines.linewidth'] = 2
matplotlib.rcParams['lines.linestyle'] = '--'

这里的修改都是持续生效的,可以用下面的函数将其恢复为默认值。

1
matplotlib.rcdefaults()

matplotlib.style.context 类似,更建议使用 matplotlib.rc_context 创建一个临时的上下文,此时可以传入改动的参数字典

1
2
with matplotlib.rc_context({'font.size': 18, 'axes.titlesize': 20}):
plt.plot([1, 2, 3])