8.15 Matplotlib 中的三维绘图
原文:Three-Dimensional Plotting in Matplotlib
译者:飞龙
协议:CC BY-NC-SA 4.0
本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。
Matplotlib 最初设计时只考虑了二维绘图。在 1.0 版本发布时,一些三维绘图工具构建在 Matplotlib 的二维显示之上,结果是一组方便(但是有限)的三维数据可视化工具。通过导入mplot3d
工具包来启用三维绘图,它包含在主要的 Matplotlib 安装中:
from mpl_toolkits import mplot3d
导入子模块后,可以通过将关键字projection ='3d'
传递给任何普通轴域创建例程来创建三维轴域:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection='3d')
启用此三维轴后,我们现在可以绘制各种三维绘图。三维绘图通过交互式查看图形,而非静态地在笔记本中查看图形而获益;回想一下,要使用交互式图形,运行此代码时可以使用%matplotlib notebook
而不是%matplotlib inline
。
三维的点和线
最基本的三维图是根据(x, y, z)
三元组创建的散点图的线或集合。与前面讨论的更常见的二维图类比,这些可以使用ax.plot3D
和ax.scatter3D
函数创建。
这些调用签名几乎与它们的二维对应的签名相同,所以对于控制输出的更多信息,你可以参考“简单的折线图”和“简单的散点图”。在这里,我们将绘制一个三角螺旋线,并且在线条附近随机绘制一些点:
ax = plt.axes(projection='3d')
# 三维线条的数据
zline = np.linspace(0, 15, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, 'gray')
# 三维散点的数据
zdata = 15 * np.random.random(100)
xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='Greens');
请注意,默认情况下,散点会调整其透明度,以便在页面上给出深度感。虽然在静态图像中有时难以看到三维效果,但是交互式视图可以产生点的布局的一些很好的直觉。
三维等高线图
类似于我们在“密度和等高线图”中探索的等高线图,mplot3d
包含使用相同输入创建三维浮雕图的工具。像二维ax.contour
图一样,ax.contour3D
要求所有输入数据都是二维规则网格的形式,带有每个点求得的Z
数据。这里我们将展示三维正弦函数的三维等高线图:
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 50, cmap='binary')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z');
有时默认的视角不是最佳的,在这种情况下我们可以使用view_init
方法来设置俯仰角和方位角。 在下面的示例中,我们将使用 60 度的俯仰角(即,在 x-y 平面上方 60 度)和 35 度的方位角(即绕 z 轴逆时针旋转 35 度):
ax.view_init(60, 35)
fig
再次注意,当使用 Matplotlib 的交互式后端之一时,通过单击和拖动可以交互式地完成这种类型的旋转。
线框和曲面图
处理网格化数据的另外两种类型的三维图是线框和曲面图。它们接受值的网格,并将其投影到指定的三维表面上,并且可以使得到的三维形式非常容易可视化。以下是使用线框图的示例:
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_wireframe(X, Y, Z, color='black')
ax.set_title('wireframe');
曲面图类似于线框图,但线框的每个面都是填充多边形。将颜色表添加到填充多边形,有助于感知可视化的表面拓扑:
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='viridis', edgecolor='none')
ax.set_title('surface');
请注意,虽然曲面图的值的网格需要是二维的,但它不必是直线的。下面是一个创建部分极坐标网格的示例,与surface3D
图形一起使用时,可以为我们提供我们正在可视化的函数的切面:
r = np.linspace(0, 6, 20)
theta = np.linspace(-0.9 * np.pi, 0.8 * np.pi, 40)
r, theta = np.meshgrid(r, theta)
X = r * np.sin(theta)
Y = r * np.cos(theta)
Z = f(X, Y)
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='viridis', edgecolor='none');
表面的三角剖分
对于某些应用,上述例程所需的均匀采样网格过于严格且不方便。在这些情况下,基于三角剖分的图形可能非常有用。如果我们不从笛卡尔坐标或极坐标网格中均匀抽取,而是随机抽取一组的话,会如何呢?
theta = 2 * np.pi * np.random.random(1000)
r = 6 * np.random.random(1000)
x = np.ravel(r * np.sin(theta))
y = np.ravel(r * np.cos(theta))
z = f(x, y)
我们可以创建点的散点图,来了解我们从中采样的表面:
ax = plt.axes(projection='3d')
ax.scatter(x, y, z, c=z, cmap='viridis', linewidth=0.5);
这留下了许多不足之处。在这种情况下帮助我们的函数是ax.plot_trisurf
,它通过首先找到在相邻点之间形成的一组三角形来创建表面(请记住,这里x
,y
和z
是一维数组):
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z,
cmap='viridis', edgecolor='none');
结果当然不像用网格绘制时那样干净,但这种三角剖分的灵活性,允许一些非常有趣的三维图。例如,实际上可以使用它绘制三维莫比乌斯条带,我们将在下面看到。
示例:可视化莫比乌斯带
莫比乌斯条带类似于旋转 90 度而拼接的纸条。在拓扑上,它非常有趣,因为外观只有一面!在这里,我们将使用 Matplotlib 的三维工具来可视化这样的对象。
创建莫比乌斯带的关键是考虑它的参数化:它是一个二维条带,所以我们需要两个内在维度。 让我们称它们为θ
,其范围从0
到2π
,并且w
的范围从-1
到1
,跨越条带的宽度:
theta = np.linspace(0, 2 * np.pi, 30)
w = np.linspace(-0.25, 0.25, 8)
w, theta = np.meshgrid(w, theta)
现在根据这个参数化,我们必须确定嵌入条带的(x, y, z)
位置。
考虑到这一点,我们可能会发现有两个发生的旋转:一个是环绕其中心的位置(我们称之为θ
),而另一个是条带绕其轴的扭曲(我会称其为φ
)。 对于莫比乌斯条带,我们必须让条带在完整循环期间产生半个扭曲,或者Δφ = Δθ/2
。
phi = 0.5 * theta
现在我们使用三角函数的记忆来推导三维嵌入。我们将定义r
,每个点距离中心的距离,并使用它来查找嵌入的(x, y, z)
坐标:
# x-y 平面中的半径
r = 1 + w * np.cos(phi)
x = np.ravel(r * np.cos(theta))
y = np.ravel(r * np.sin(theta))
z = np.ravel(w * np.sin(phi))
最后,为了绘制对象,我们必须确保三角剖分是正确的。 执行此操作的最佳方法是,在底层参数化中定义三角剖分,然后让 Matplotlib 将此三角剖分投影到莫比乌斯条带的三维空间中。这可以通过以下方式完成:
# 在底层参数化中进行三角剖分
from matplotlib.tri import Triangulation
tri = Triangulation(np.ravel(w), np.ravel(theta))
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles,
cmap='viridis', linewidths=0.2);
ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1);
结合所有这些技巧,可以在 Matplotlib 中创建和展示各种各样的三维对象和图案。