matplotlib接口——显示与隐式的区别(explicit vs implicit)
我很肯定,不少人在学习或使用matplotlib绘图时,总是看到不同的调用方法,如果恰恰与自己的使用习惯相反的话,也会一时不知所措。
希望看完本片内容后,你会有所收获,对不同的编码方式都能融会贯通
Matplotlib有两大接口,也是两种不同的使用风格
- 轴区(Axes)接口,也就是显式接口,直接在图形(Figure)或轴区(Axes)对象上调用方法来创建艺术器(Artist),一步步地完成可视化过程。这又叫做“面向对象”接口(object-oriented interface)。
- pyplot接口,即隐式接口,会始终追踪到最新创建的图形(Figure)和轴区(Axes),通过猜测用户的想法而将艺术器(Artist)添加到目标对象上。
另外,pandas和xarray等库可以使用内置的plot函数来绘图,使用方法:data.plot()。
这些接口的差别很微妙,特别是网络上的一些代码片段,或者是在某些例子中混合使用多个接口
原生的Matplotlib接口
Axes显式接口
Axes接口是Matplotlib的实现方式。在此基础上用户可以完成多项自定义配置和微调。
本接口会实例化Figure类为一个对象(fig),调用subplots函数在该对象上创建一个或多个axes(轴区,ax),最后在axes上调用绘图函数(plot)。
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.subplots()
ax.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])
plt.show()
将其称为显式接口就是因为每个对象都是直接调用的,并用于创建下一个对象。保持对对象的调用是非常灵活的,我们可以在对象创建后、绘制前对其随意配置。
pyplot隐式接口
pyplot模块隐藏了Axes大部分的绘图函数,替代用户完成例如上面展示的Figure和Axes创建的操作。
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])
plt.show()
本接口在执行交互工作或简单的脚本时很方便。使用gcf和gca可以分别调用当前Figure和Axes。pyplot会储存一个由Figures组成的列表,每个Figures又储存了该图形上所有Axes组成的列表。
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1) # 前面的1,2表示创建1行有2个axes的figure,最后的1表示调用第1个axes,隐藏了调用axes
plt.plot([1, 2, 3], [0, 0.5, 0.2])
plt.subplot(1, 2, 2) # 前面的1,2表示创建1行有2个axes的figure,最后的1表示调用第2个axes,隐藏了调用axes
plt.plot([3, 2, 1], [0, 0.5, 0.2])
plt.show()
下面的代码与上方实现同样的效果:
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1) # 前面的1,2表示创建1行有2个axes的figure,最后的1表示调用第1个axes
ax = plt.gca() # 通过gca()调用当前的axes,即第1个
ax.plot([1, 2, 3], [0, 0.5, 0.2])
plt.subplot(1, 2, 2) # 前面的1,2表示创建1行有2个axes的figure,最后的1表示调用第2个axes
ax = plt.gca() # 通过gca()调用当前的axes,即第2个
ax.plot([3, 2, 1], [0, 0.5, 0.2])
plt.show()
显式接口是这么实现的:
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 2)
axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
axs[1].plot([3, 2, 1], [0, 0.5, 0.2])
plt.show()
为什么要有显式接口
当你不得不回溯到先前的Axes进行操作却不能通过plt.gca()调用时,你会怎么做呢?一个简单的办法就是用相同的参数再次调用subplot()函数。这样显得太野蛮了,你也可以通过Figures的Axes列表来定位,但很可能会出错,因为colorbar也是一种轴区(Axes)。最理想的是你给创建的每个Axes保存具柄,但何不一开始就创建好所有的Axes,又何必再回头呢?
下面用代码展示下前文提到的使用plt.pyplot函数再次调用:
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1)
plt.plot([1, 2, 3], [0, 0.5, 0.2])
plt.subplot(1, 2, 2)
plt.plot([3, 2, 1], [0, 0.5, 0.2])
plt.suptitle('Implicit Interface: re-call subplot')
for i in range(1, 3):
plt.subplot(1, 2, i)
plt.xlabel('Boo')
plt.show()
再看看添加句柄的代码实现:
import matplotlib.pyplot as plt
axs = []
ax = plt.subplot(1, 2, 1)
axs += [ax]
plt.plot([1, 2, 3], [0, 0.5, 0.2])
ax = plt.subplot(1, 2, 2)
axs += [ax]
plt.plot([3, 2, 1], [0, 0.5, 0.2])
plt.suptitle('Implicit Interface: save handles')
for i in range(2):
plt.sca(axs[i])
plt.xlabel('Boo')
plt.show()
最后,在起始阶段就调用显式接口:
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 2)
axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
axs[1].plot([3, 2, 1], [0, 0.5, 0.2])
fig.suptitle('Explicit Interface')
for i in range(2):
axs[i].set_xlabel('Boo')
plt.show()
第三方库的“Data对象”接口
一些第三方库可以用它们的data对象绘图,比如pandas的data.plot()。
为了便于演示,这样的数据可能仅仅储存了x和y数据,然后调用plot函数
import matplotlib.pyplot as plt
# supplied by downstream library:
class DataContainer:
def __init__(self, x, y):
"""
Proper docstring here!
"""
self._x = x
self._y = y
def plot(self, ax=None, **kwargs):
if ax is None:
ax = plt.gca()
ax.plot(self._x, self._y, **kwargs)
ax.set_title('Plotted from DataClass!')
return ax
# what the user usually calls:
data = DataContainer([0, 1, 2, 3], [0, 0.2, 0.5, 0.3])
data.plot()
可以看到,第三方库把累活都干了,然后让Data类型有了绘图(plot)功能。
许多第三方库的plot函数都接受ax参数。这就让用户有了更多调整的机会。
总结
平心而论,理解Axes显式接口受益无穷,它不但使用起来很灵活,还是其他接口的底层。我们应该学会使用显式接口并对底层对象进行操作。