Matplotlib 应用程序接口 (API)#
Matplotlib 有两个主要的应用程序接口,或使用该库的样式
使用 Figure 或 Axes 对象上的方法创建其他 Artist 并逐步构建可视化的显式“Axes”接口。这也称为“面向对象”接口。
隐式“pyplot”接口,它跟踪创建的最后一个 Figure 和 Axes,并将 Artist 添加到它认为用户想要的对象中。
此外,许多下游库(如 pandas
和 xarray)在其数据类上直接实现了一个 plot
方法,以便用户可以调用 data.plot()
。
这些接口之间的差异可能有点令人困惑,尤其是考虑到网络上使用一个或另一个接口,有时甚至在同一示例中使用多个接口的代码段。在此,我们尝试指出“pyplot”和下游接口如何与显式“Axes”接口相关,以帮助用户更好地浏览该库。
原生 Matplotlib 接口#
显式“Axes”接口#
Matplotlib 的实现方式就是“Axes”接口,许多自定义和微调最终都会在此级别完成。
此接口通过实例化 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])
我们称之为“显式”接口,因为每个对象都显式引用并用于生成下一个对象。保留对对象的引用非常灵活,并允许我们在创建对象后但在显示对象之前自定义对象。
隐式“pyplot”接口#
模块 pyplot
隐藏了大部分 Axes
绘图方法,以提供上述等效项,其中用户无需创建图形和轴
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])
这很方便,尤其是在进行交互式工作或编写简单脚本时。可以使用 gcf
检索当前图形的引用,可以使用 gca
检索当前轴的引用。模块 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])
等效于
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1)
ax = plt.gca()
ax.plot([1, 2, 3], [0, 0.5, 0.2])
plt.subplot(1, 2, 2)
ax = plt.gca()
ax.plot([3, 2, 1], [0, 0.5, 0.2])
在显式界面中,这将是
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.gca()
引用的旧坐标轴,会发生什么情况?一种简单的方法是再次使用相同参数调用 subplot
。但是,这很快就会变得不优雅。您还可以检查 Figure 对象并获取其 Axes 对象列表,但是,这可能会产生误导(颜色条也是 Axes!)。最好的解决方案可能是保存对您创建的每个 Axes 的句柄,但如果您这样做,为什么不直接在开始时创建所有 Axes 对象呢?
第一种方法是再次调用 plt.subplot
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')
第二种方法是保存一个句柄
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')
但是,推荐的方法是从一开始就明确
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')
第三方库“数据对象”接口#
一些第三方库选择为其数据对象实现绘图,例如,data.plot()
,在 pandas
、xarray 和其他第三方库中可以看到。出于说明目的,下游库可能会实现一个简单的容器,其中存储了 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()
因此,库可以向用户隐藏所有繁琐细节,并且可以根据数据类型制作适当的可视化,通常带有良好的标签、配色图选择和其他便捷功能。
然而,在上述情况下,我们可能不喜欢库提供的标题。值得庆幸的是,它们将 plot()
方法中的 Axes 传递给我们,并且了解显式 Axes 接口,我们可以调用:ax.set_title('My preferred title')
来自定义标题。
许多库还允许其 plot
方法接受一个可选的 ax 参数。这允许我们将可视化放置在我们放置并可能自定义的 Axes 中。
摘要#
总体而言,了解显式的“Axes”接口非常有用,因为它是最灵活的,并且是其他接口的基础。用户通常可以弄清楚如何切换到显式接口并在基础对象上操作。虽然显式接口的设置可能有点冗长,但复杂的绘图通常最终比尝试使用隐式“pyplot”接口更简单。
注意
人们有时会感到困惑,因为我们为这两个接口都导入了 pyplot
。目前,pyplot
模块实现了“pyplot”接口,但它还提供了顶层的 Figure 和 Axes 创建方法,并最终启动图形用户界面(如果正在使用)。因此,无论选择哪个接口,pyplot
仍然是必需的。
同样地,由合作伙伴库提供的声明性接口使用“Axes”接口可访问的对象,并且通常将这些对象作为参数接受或从方法中传递回来。通常,必须使用显式的“Axes”接口来执行默认可视化的任何自定义,或将数据解包到 NumPy 数组中并直接传递到 Matplotlib。
附录:“Axes”接口与数据结构#
大多数 Axes
方法允许通过将数据对象传递给方法并指定参数作为字符串来进行另一种 API 寻址
import matplotlib.pyplot as plt
data = {'xdat': [0, 1, 2, 3], 'ydat': [0, 0.2, 0.4, 0.1]}
fig, ax = plt.subplots(figsize=(2, 2))
ax.plot('xdat', 'ydat', data=data)
(Source code
, 2x.png
, png
)
附录:“pylab”接口#
还有一个强烈不建议使用的接口,那就是基本上执行 from matplotlib.pylab import *
。这会将所有函数从 matplotlib.pyplot
、numpy
、numpy.fft
、numpy.linalg
和 numpy.random
导入到全局命名空间中,以及一些其他函数。
这种模式在现代 Python 中被认为是不良做法,因为它会使全局命名空间变得杂乱无章。更严重的是,在 pylab
的情况下,这会覆盖一些内置函数(例如,内置 sum
将被 numpy.sum
替换),这可能会导致意外的行为。