Finished graph

生成数据点#

为了熟悉使用matplotlib进行绘图的基础知识,让我们尝试绘制自由落体物体随时间变化的位移以及每个时间步长的速度。

如果你曾经学习过物理,你就会知道这是一个牛顿运动方程的经典案例,其中

$$ v = a \times t $$

$$ S = 0.5 \times a \times t^{2} $$

我们将假设初始速度为零。

import numpy as np

time = np.arange(0.0, 10.0, 0.2)
velocity = np.zeros_like(time, dtype=float)
distance = np.zeros_like(time, dtype=float)

我们知道,在自由落体下,所有物体都以恒定的加速度$$g = 9.8~m/s^2$$运动。

g = 9.8  # m/s^2

velocity = g * time
distance = 0.5 * g * np.power(time, 2)

以上代码为我们提供了两个填充了距离和速度数据点的numpy数组。

Pyplot 与面向对象接口#

使用matplotlib时,我们有两种方法

  1. pyplot接口/函数式接口。
  2. 面向对象接口 (OO)。

Pyplot 接口#

matplotlib表面上是为了模仿MATLAB生成绘图的方法,称为pyplot。所有pyplot命令都会更改并修改同一图形。这是一个基于状态的接口,其中状态(即图形)通过各种函数调用(即修改图形的方法)来保留。此接口允许我们快速轻松地生成绘图。该接口的基于状态的特性允许我们根据需要添加元素和/或修改绘图。

此接口在语法和方法上与MATLAB有很多相似之处。例如,如果我们想绘制一条蓝线,其中每个数据点都用圆圈标记,我们可以使用字符串'bo-'

import matplotlib.pyplot as plt

plt.figure(figsize=(9, 7), dpi=100)
plt.plot(time, distance, "bo-")
plt.xlabel("Time")
plt.ylabel("Distance")
plt.legend(["Distance"])
plt.grid(True)

该图显示了自由落体物体每秒钟经过的距离。

Fig. 1.1

图 1.1 每秒钟行驶的距离在增加,这是由于重力加速度导致速度增加的直接结果。
plt.figure(figsize=(9, 7), dpi=100)
plt.plot(time, velocity, "go-")
plt.xlabel("Time")
plt.ylabel("Velocity")
plt.legend(["Velocity"])
plt.grid(True)

下图显示了速度是如何增加的。

Fig. 1.2

图 1.2 由于“恒定”加速度,速度以固定的步长增加。

让我们尝试看看当我们在同一图中绘制距离和速度时会得到什么样的图。

plt.figure(figsize=(9, 7), dpi=100)
plt.plot(time, velocity, "g-")
plt.plot(time, distance, "b-")
plt.ylabel("Distance and Velocity")
plt.xlabel("Time")
plt.legend(["Distance", "Velocity"])
plt.grid(True)

png

在这里,我们遇到了一些明显且严重的问题。我们可以看到,由于这两个量共享相同的轴但具有非常不同的数量级,因此图形看起来不成比例。我们需要做的是将这两个量分离到两个不同的轴上。这就是绘制图的第二种方法发挥作用的地方。

此外,当我们需要制作多个绘图或需要制作需要大量自定义的复杂绘图时,pyplot方法实际上并不能很好地扩展。但是,matplotlib在内部有一个面向对象的接口,可以同样轻松地访问,从而允许重用对象。

面向对象接口#

使用OO接口时,了解matplotlib如何构建其绘图会有所帮助。我们作为输出看到的最终绘图是一个“Figure”对象。Figure对象是构成图形图像的所有其他元素的顶级容器。这些“其他”元素称为Artists。可以将Figure对象视为画布,不同的艺术家在上面绘制以创建最终的图形图像。此Figure可以包含任意数量的各种艺术家。

png

关于图形解剖需要注意的事项是

  1. 所有以蓝色标记的项目都是ArtistsArtists基本上是在图形上渲染的所有元素。这可能包括文本、补丁(如箭头和形状)等。因此,以下所有FigureAxesAxis对象也是Artists。
  2. 我们在图形中看到的每个绘图都是一个Axes对象。Axes对象保存我们将要显示的实际数据。它还将包含X轴和Y轴标签、标题。每个Axes对象将包含两个或多个Axis对象。
  3. Axis对象设置数据限制。它还包含刻度和刻度标签。刻度是我们在轴上看到的标记。

理解FigureArtistAxesAxis的这种层次结构非常重要,因为它在我们在matplotlib中制作动画的方式中起着至关重要的作用。

现在我们了解了绘图的生成方式,我们可以轻松解决之前遇到的问题。为了使速度和距离图更有意义,我们需要将每个数据项绘制到一个单独的轴上,并使用不同的比例。因此,我们将需要一个父Figure对象和两个Axes对象。

fig, ax1 = plt.subplots()

ax1.set_ylabel("distance (m)")
ax1.set_xlabel("time")
ax1.plot(time, distance, "blue")

ax2 = ax1.twinx()  # create another y-axis sharing a common x-axis

ax2.set_ylabel("velocity (m/s)")
ax2.set_xlabel("time")
ax2.plot(time, velocity, "green")

fig.set_size_inches(7, 5)
fig.set_dpi(100)

plt.show()

png

此图仍然不是很直观。我们应该添加一个网格和一个图例。也许,我们还可以将轴标签和刻度标签的颜色更改为线条的颜色。

但是,当我们尝试打开网格时,会发生一些非常奇怪的事情,你可以在此处的单元格 8 中看到。网格线与两个Y轴上的刻度标签未对齐。我们可以看到matplotlib自己计算的刻度值不适合我们的需求,因此我们将不得不自己计算它们。

fig, ax1 = plt.subplots()

ax1.set_ylabel("distance (m)", color="blue")
ax1.set_xlabel("time")
ax1.plot(time, distance, "blue")
ax1.set_yticks(np.linspace(*ax1.get_ybound(), 10))
ax1.tick_params(axis="y", labelcolor="blue")
ax1.xaxis.grid()
ax1.yaxis.grid()

ax2 = ax1.twinx()  # create another y-axis sharing a common x-axis

ax2.set_ylabel("velocity (m/s)", color="green")
ax2.set_xlabel("time")

ax2.tick_params(axis="y", labelcolor="green")
ax2.plot(time, velocity, "green")
ax2.set_yticks(np.linspace(*ax2.get_ybound(), 10))

fig.set_size_inches(7, 5)
fig.set_dpi(100)
fig.legend(["Distance", "Velocity"])
plt.show()

命令ax1.set_yticks(np.linspace(*ax1.get_ybound(), 10))为我们计算刻度值。让我们分解一下以了解发生了什么

  1. np.linspace命令将在指定的上下限之间创建一组n个分区。
  2. 方法ax1.get_ybound()返回一个列表,其中包含该特定轴(在本例中为Y轴)的最大和最小限制。
  3. 在python中,运算符*listtuple之前时充当解包运算符。因此,它会将列表[1, 2, 3, 4]转换为单独的值1, 2, 3, 4。这是一个非常强大的功能。
  4. 因此,我们要求np.linspace方法将最大和最小刻度值之间的间隔分成10个相等的部分。
  5. 我们将此数组提供给set_yticks方法。

对第二个轴重复相同的过程。

png

结论#

在本部分中,我们介绍了一些matplotlib绘图的基础知识,涵盖了如何制作绘图的基本两种方法。在下一部分中,我们将介绍如何制作简单的动画。如果您喜欢这篇博文的內容,或者有任何建议或意见,请给我发送电子邮件或推特或在IRC上联系我。现在,你会发现我在Freenode上的#matplotlib频道闲逛。谢谢!

后续思考#

这篇文章是我在我的个人博客上正在进行的一系列文章的一部分。本系列基本上将介绍如何使用python的matplotlib库来制作动画。matplotlib有一个优秀的文档,您可以在其中找到有关我在本文中使用的每种方法的详细文档。此外,我将以jupyter notebook的形式发布本系列的每一部分,可以在此处找到。

本系列将包含三篇文章,涵盖

  1. 第1部分 - 如何使用matplotlib制作绘图。
  2. 第2部分 - 使用FuncAnimation制作基本动画。
  3. 第3部分 - 优化以使动画更快(blitting)。

我想说几句关于本系列方法论的话

  1. 每一部分的帖子末尾都将有一系列参考文献,主要指向文档的相关页面以及其他人撰写的有益博客。这是最重要的部分。您越早习惯阅读文档,就越好。
  2. 此处编写的代码旨在向您展示如何将所有内容组合在一起。我将尽最大努力描述我的实现的细微差别以及我学到的微小教训。

参考文献#

  1. Python 生成器(YouTube)
  2. Matplotlib:其面向对象接口简介
  3. 绘图的生命周期
  4. 基本概念