Matplotlib 应用程序接口 (APIs)#

Matplotlib 有两个主要的应用程序接口,或者说是使用库的风格:

  • 一个显式的 "Axes" 接口,它使用 Figure 或 Axes 对象上的方法来创建其他的 Artist,并逐步构建可视化.这也被称为"面向对象"的接口.

  • 一个隐式的 "pyplot" 接口,它会跟踪最近创建的 Figure 和 Axes,并将 Artist 添加到它认为用户想要的对象上.

此外,一些下游库(如 pandasxarray)直接在其数据类上提供了一个 plot 方法,以便用户可以调用 data.plot() .

这些接口之间的区别可能有点令人困惑,特别是在网络上看到使用其中一个或有时在同一个示例中使用多个接口的代码片段时. 在这里,我们试图指出"pyplot"和下游接口如何与显式的"Axes"接口相关联,以帮助用户更好地浏览库.

原生 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])

(Source code, png)

我们称之为"显式"接口,因为每个对象都被显式引用,并用于创建下一个对象. 保持对对象的引用非常灵活,并允许我们在创建对象之后但在显示对象之前对其进行自定义.

隐式的 "pyplot" 接口#

pyplot 模块 shadow 了大多数 Axes 绘图方法,以提供与上述等效的功能,其中 Figure 和 Axes 的创建是为用户完成的:

import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])

(Source code, png)

这可能很方便,尤其是在进行交互式工作或编写简单脚本时. 可以使用 gcf 检索对当前 Figure 的引用,并使用 gca 检索对当前 Axes 的引用. pyplot 模块保留一个 Figures 列表,每个 Figure 都保留一个 Axes 列表,以便用户可以执行以下操作:

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])

(Source code, png)

等同于:

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])

(Source code, png)

在显式接口中,这将是:

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])

(Source code, png)

在 Axes 接口和 pyplot 接口之间转换#

您可能会在现有代码中找到这两种接口,不幸的是,有时甚至是混合使用.本节介绍两种接口中特定操作的模式,以及如何从一种接口转换为另一种接口.

  • 对于这两种接口,创建图形的方式是相同的:使用相应的 pyplot 函数 plt.figure() , plt.subplots() , plt.subplot_mosaic() .对于 Axes 接口,您通常将创建的 Figure(以及可能的 Axes)存储在变量中以供以后使用.使用 pyplot 接口时,通常不存储这些值.例如:

    • Axes: fig, ax = plt.subplots()

    • pyplot: plt.subplots()

  • "绘图" 函数,即添加数据的函数,在 Axes 和 pyplot 上的名称相同,并且参数也相同.例如:

    • Axes: ax.plot(x, y)

    • pyplot: plt.plot(x, y)

  • 在 pyplot 中,检索属性的函数与属性名称相同,而在 Axes 上则以 get_ 为前缀.例如:

    • Axes: label = ax.get_xlabel()

    • pyplot: label = plt.xlabel()

  • 在 pyplot 中,设置属性的函数与属性名称相同,而在 Axes 上则以 set_ 为前缀.例如:

    • Axes: ax.set_xlabel("time")

    • pyplot: plt.xlabel("time")

下面是一个简短的示例摘要,以并排比较的方式再次呈现:

操作

Axes 接口

pyplot 接口

创建图形

fig, ax = plt.subplots()

plt.subplots()

绘制数据

ax.plot(x, y)

plt.plot(x, y)

获取属性

label = ax.get_xlabel()

label = plt.xlabel()

设置属性

ax.set_xlabel("time")

plt.xlabel("time")

为什么要显式?#

如果您必须回溯并操作一个未被 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')

(Source code, png)

第二种方法是保存一个句柄:

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')

(Source code, png)

但是,推荐的方法是从一开始就显式:

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')

(Source code, png)

第三方库 "数据对象" 接口#

一些第三方库选择为他们的数据对象实现绘图,例如,在 pandas , xarray 和其他第三方库中,可以看到 data.plot() .出于说明目的,下游库可以实现一个简单的数据容器,该容器将 xy 数据存储在一起,然后实现一个 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()

(Source code, png)

因此,库可以对用户隐藏所有繁琐的细节,并且可以根据数据类型制作适当的可视化,通常具有良好的标签,颜色映射选择和其他便利的功能.

然而,在上面,我们可能不喜欢库提供的标题.值得庆幸的是,它们将 Axesplot() 方法传递回给我们,并且理解了显式的 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, png)

附录:"pylab" 接口#

还有一种非常不鼓励使用的接口,那就是基本上执行 from matplotlib.pylab import * .这会将 matplotlib.pyplot , numpy , numpy.fft , numpy.linalgnumpy.random 中的所有函数以及一些其他函数导入到全局命名空间中.

这种模式在现代 python 中被认为是不好的做法,因为它会使全局命名空间变得混乱.更严重的是,在 pylab 的情况下,这将覆盖一些内置函数(例如,内置的 sum 将被 numpy.sum 替换),这可能导致意外行为.