
生成数据点#
为了熟悉使用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
时,我们有两种方法
pyplot
接口/函数式接口。- 面向对象接口 (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)
该图显示了自由落体物体每秒钟经过的距离。
plt.figure(figsize=(9, 7), dpi=100)
plt.plot(time, velocity, "go-")
plt.xlabel("Time")
plt.ylabel("Velocity")
plt.legend(["Velocity"])
plt.grid(True)
下图显示了速度是如何增加的。
让我们尝试看看当我们在同一图中绘制距离和速度时会得到什么样的图。
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)
在这里,我们遇到了一些明显且严重的问题。我们可以看到,由于这两个量共享相同的轴但具有非常不同的数量级,因此图形看起来不成比例。我们需要做的是将这两个量分离到两个不同的轴上。这就是绘制图的第二种方法发挥作用的地方。
此外,当我们需要制作多个绘图或需要制作需要大量自定义的复杂绘图时,pyplot
方法实际上并不能很好地扩展。但是,matplotlib
在内部有一个面向对象的接口,可以同样轻松地访问,从而允许重用对象。
面向对象接口#
使用OO接口时,了解matplotlib
如何构建其绘图会有所帮助。我们作为输出看到的最终绘图是一个“Figure”对象。Figure
对象是构成图形图像的所有其他元素的顶级容器。这些“其他”元素称为Artists
。可以将Figure
对象视为画布,不同的艺术家在上面绘制以创建最终的图形图像。此Figure
可以包含任意数量的各种艺术家。
关于图形解剖需要注意的事项是
- 所有以蓝色标记的项目都是
Artists
。Artists
基本上是在图形上渲染的所有元素。这可能包括文本、补丁(如箭头和形状)等。因此,以下所有Figure
、Axes
和Axis
对象也是Artists。 - 我们在图形中看到的每个绘图都是一个
Axes
对象。Axes
对象保存我们将要显示的实际数据。它还将包含X轴和Y轴标签、标题。每个Axes
对象将包含两个或多个Axis
对象。 Axis
对象设置数据限制。它还包含刻度和刻度标签。刻度
是我们在轴上看到的标记。
理解Figure
、Artist
、Axes
和Axis
的这种层次结构非常重要,因为它在我们在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()
此图仍然不是很直观。我们应该添加一个网格和一个图例。也许,我们还可以将轴标签和刻度标签的颜色更改为线条的颜色。
但是,当我们尝试打开网格时,会发生一些非常奇怪的事情,你可以在此处的单元格 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))
为我们计算刻度值。让我们分解一下以了解发生了什么
np.linspace
命令将在指定的上下限之间创建一组n
个分区。- 方法
ax1.get_ybound()
返回一个列表,其中包含该特定轴(在本例中为Y轴)的最大和最小限制。 - 在python中,运算符
*
在list
或tuple
之前时充当解包运算符。因此,它会将列表[1, 2, 3, 4]
转换为单独的值1, 2, 3, 4
。这是一个非常强大的功能。 - 因此,我们要求
np.linspace
方法将最大和最小刻度值之间的间隔分成10个相等的部分。 - 我们将此数组提供给
set_yticks
方法。
对第二个轴重复相同的过程。
结论#
在本部分中,我们介绍了一些matplotlib
绘图的基础知识,涵盖了如何制作绘图的基本两种方法。在下一部分中,我们将介绍如何制作简单的动画。如果您喜欢这篇博文的內容,或者有任何建议或意见,请给我发送电子邮件或推特或在IRC上联系我。现在,你会发现我在Freenode上的#matplotlib频道闲逛。谢谢!
后续思考#
这篇文章是我在我的个人博客上正在进行的一系列文章的一部分。本系列基本上将介绍如何使用python的matplotlib
库来制作动画。matplotlib
有一个优秀的文档,您可以在其中找到有关我在本文中使用的每种方法的详细文档。此外,我将以jupyter notebook的形式发布本系列的每一部分,可以在此处找到。
本系列将包含三篇文章,涵盖
- 第1部分 - 如何使用
matplotlib
制作绘图。 - 第2部分 - 使用
FuncAnimation
制作基本动画。 - 第3部分 - 优化以使动画更快(blitting)。
我想说几句关于本系列方法论的话
- 每一部分的帖子末尾都将有一系列参考文献,主要指向文档的相关页面以及其他人撰写的有益博客。这是最重要的部分。您越早习惯阅读文档,就越好。
- 此处编写的代码旨在向您展示如何将所有内容组合在一起。我将尽最大努力描述我的实现的细微差别以及我学到的微小教训。