Matplotlib 作为 Python 界相当知名的绘图库,其功能非常之多。为了防止自己哪天忘了,特将网上的零散教程收集起来,方便自查。
Table Of Contents
Matplotlib基本概念图
绘图前的设置
rcParam 设置
- 中文支持和 Unicode 负号问题
这个问题对于图里同时有中英文字体的场景来说非常重要。目前我的解决方案是把字体设置为 “STSong”(如果没有这个字体的话去网上下载一个)。省去了设置matplotlib.rcParams[‘axes.unicode_minus’]=False
,同时显示效果还不错。
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "STSong"
- Ticker 朝里
plt.rcParams['xtick.direction'] = "in"
plt.rcParams['ytick.direction'] = "in"
- 显示子 ticker
plt.rcParams["xtick.minor.visible"] = True
plt.rcParams["ytick.minor.visible"] = True
Latex 字体问题
- 选用 stix 字体使其样式与常规公式一致
plt.rcParams["mathtext.fontset"] = "stix"
-
如果觉得
\frac{}{}
输出的分式太小,可以用\dfrac{}{}
。详见 Matplotlib 官方 Demo -
Python 对字符串的解析问题,这一问题应该在高版本python中会得到解决。比如
>>> A = "\frac{1}{2}" # '\f' 被当成换页符了
>>> A
'\x0crac{1}{2}'
>>> print(A)
rac{{1}}{{2}}
>>> A = "\\frac{1}{2}" # 将 '\f' 换为 '\\f'
>>> print(A)
\frac{1}{2}
>>> A = "\\frac{{{}}}{{{}}}".format(1, 2) # 用三引号实现想要的latex输出
>>> print(A)
\frac{1}{2}
基本绘图
以不同单位设置 Figure 的大小
这里总结一下官方文档的说法。
首先,matplotlib 默认的 Figure 的大小是以 inch 计算的,默认的 dpi=100
。如果想将图片以厘米输出,可以将长度乘上从厘米到 inch 的转换系数,就如官方给出的下面代码(效果图):
import matplotlib.pyplot as plt
text_kwargs = dict(ha='center', va='center', fontsize=28, color='C1')
cm = 1/2.54 # 厘米到 inch 的转换系数
plt.subplots(figsize=(15*cm, 5*cm))
plt.text(0.5, 0.5, '15cm x 5cm', **text_kwargs)
plt.show()
如果想要指定像素输出的话,其实只需要在定义 Figure 时修改 figsize
和 dpi
两个选项即可。比如:输出一张 800x600
像素的图,且 dpi=200
,则
fig = plt.figure(figsize=(4, 3), dpi=200) # 图像的像素大小为 figsize 乘以 dpi
多列/行子图布局绘图
plt.title()
会为当前正在绘制的子图添加标题,等价于plt.gca().set_title()
。如果需要为整张图添加一个总标题,使用plt.suptitle()
,或者像这样。
fig, ax = plt.subplots(figsize=(2, 2), facecolor='lightskyblue', layout='constrained')
fig.suptitle('Figure') # 设置总标题
ax.set_title('Axes', loc='left', fontstyle='oblique', fontsize='medium') # 为Axes设置标题
2. 如果只需要等大的子图,使用 plt.subplots()
,或者采用如下的OOP写法:
fig = plt.figure()
# >> 添加2行3列,指向第一个子图的 `Axes`
fig.add_subplot(2, 3, 1)
# >> 只添加1行2列,并把所有子图 `Axes` 的列表都作为返回值
ax1, ax2 = fig.subplots(1, 2)
- 如果需要不等大的子图,使用
GridSpec
划分空间。Figure
对象也支持add_gridspec
方法。
如果需要将GridSpec
划分出的网格做出进一步细分,则需要使用GridSpecFromSubplotSpec
函数,或者对GridSpec
下标对应的SubplotSpec
对象使用subgridspec
方法。
下面是参照 Matplotlib 的 Examples 写的示例代码,并给出中文注释和效果图。
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.gridspec import (
GridSpec, GridSpecFromSubplotSpec
)
def format_axes(fig: Figure) -> None:
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center")
ax.tick_params(labelbottom=False, labelleft=False)
# 创建 Figure 对象
fig: Figure = plt.figure()
# 将 Figure 分成 1 行 2 列
gs0 = GridSpec(1, 2, figure=fig)
# 将左侧(即 gs0[0])分为 3 行 3 列
gs00 = GridSpecFromSubplotSpec(3, 3, subplot_spec=gs0[0])
# 分配左侧子区域
ax1 = fig.add_subplot(gs00[:2, :])
ax2 = fig.add_subplot(gs00[2, 0])
ax3 = fig.add_subplot(gs00[2, 1:])
# 将右侧(即 gs0[1])分为 3 行 3 列
# 与使用 `GridSpecFromSubplotSpec` 函数效果一致,但更为方便
gs01 = gs0[1].subgridspec(3, 3)
# 分配右侧子区域
ax4 = fig.add_subplot(gs01[:, :2])
ax5 = fig.add_subplot(gs01[:2, 2])
ax6 = fig.add_subplot(gs01[2, 2])
plt.suptitle("GridSpec Inside GridSpec")
format_axes(fig)
plt.show()
绘制水平条形图
关于绘制条形图的细节,可以参考CSDN用户 超爱太阳雨 的帖子。
fig, ax = plt.subplots(
figsize=(8, 4)
)
MLUs_label = ["CPU", "GPU"]
MLUs_data = [30.27, 320.94]
# 绘制条形图对象
MLUs_barh = ax.barh(
range(len(MLUs_label)), MLUs_data,
color='#6699CC'
)
# 这里在每个条形图对象旁边把数值标注出来
for rect in MLUs_barh:
ax.text(
rect.get_width(), rect.get_y() + rect.get_height()/2, # 文本位置
f"{w:.2f}", # 文本格式
ha='left',va='center' # 文本左对齐 + 居中对齐
)
# 把 y 轴的 tick 设置为 MLUs_label
ax.set_yticks(range(len(MLUs_label)))
ax.set_yticklabels(MLUs_label)
# 设置 x 轴
ax.set_xlim([0, 400])
ax.set_xlabel("Million lattices per second")
# 隐藏右侧和顶部边框
ax.spines["right"].set_color("none")
ax.spines["top"].set_color("none")
设置label和ticker
ticker设置
如果需要调整 ticker 的设置,可以使用 plt.tick_param()
或者 Axes
对象 ax
的 ax.tick_param()
进行设置。
如果需要具体配置 ticker 的格式,则可以使用 matplotlib.ticker
的 ticker API 进行设置。
进阶绘图
局部放大图
上图来自知乎专栏 【Matplotlib】 局部放大图 。绘图思路为:
- 在父坐标系中画图
- 使用
mpl_toolkits.axes_grid1.inset_locator.inset_axes
插入子图。随后在子图中画对应内容,控制显示区间。【inset_axes
也是Axes
对象中的一个方法】 - 使用
mpl_toolkits.axes_grid1.inset_locator.mark_inset
画出父坐标系与子坐标系的连接线。
图例组合
可参照此网页的做法
import numpy as np
import matplotlib.pyplot as plt
x=np.arange(0,10,1)
y=x**2
fig, ax = plt.subplots(1,1)
blue_dot = ax.scatter(x[:5],y[:5], s=100, color='b')
red_dot = ax.scatter(x[5:],y[5:], s=200, color='r')
black_cross = ax.scatter(x[5:],y[5:], s=400, marker='+', color='k')
# 将红色圆与黑色十字交叉组合为一个图例
lgnd = ax.legend(
[blue_dot, (red_dot, black_cross)],
["Blue Circle", "Red Circle and Black Cross"]
)
导出视频
注: matplotlib 的导出视频功能需要本机安装有
ffmpeg
之类的视频编码器,而matplotlib 在找不到 ffpmeg 时会使用 Pillow 生成视频。如果 matplotlib 找不到ffmpeg
的位置,可以在 rcParams 中添加
matplotlib.rcParams["animation.ffmpeg_path"] = "在此用字符串写 ffmpeg 的绝对位置"
关于使用matplotlib制作动画,可参考 Animations using Matplotlib 这一官方教程。需要提醒的是 matplotlib.animation.FuncAnimation
在 3.7 版本中被弃用。