在 Python 中使用 matplotlib 进行 3D 绘图

用鼠标旋转 3D 绘图

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from scipy.stats import multivariate_normal
X = np.linspace(-5,5,50)
Y = np.linspace(-5,5,50)
X, Y = np.meshgrid(X,Y)
X_mean = 0; Y_mean = 0
X_var = 5; Y_var = 8
pos = np.empty(X.shape+(2,))
pos[:,:,0]=X
pos[:,:,1]=Y
rv = multivariate_normal([X_mean, Y_mean],[[X_var, 0], [0, Y_var]])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, rv.pdf(pos), cmap="plasma")
plt.show()

绘制 3D 连续线

现在我们知道如何在 3D 中绘制单个点,我们可以类似地绘制一条穿过 3D 坐标列表的连续线。

我们将使用 plot()方法并传递 3 个数组,每个数组一个用于线上点的 x、y 和 z 坐标。

import numpy as np
x = np.linspace(−4*np.pi,4*np.pi,50)
y = np.linspace(−4*np.pi,4*np.pi,50)
z = x**2 + y**2
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(x,y,z)
plt.show()

我们正在为 50 个点生成 x、y 和 z 坐标。

x 和 y 坐标是使用 np.linspace生成的,以在 -4π 和 +4π 之间生成 50 个均匀分布的点。
z 坐标只是相应的 x 和 y 坐标的平方和。

自定义 3D 绘图

让我们在 3D 空间中绘制一个散点图,看看我们如何根据我们的喜好以不同的方式自定义其外观。
我们将使用 NumPy 随机种子,因此我们可以生成与教程相同的随机数。

np.random.seed(42)
xs = np.random.random(100)*10+20
ys = np.random.random(100)*5+7
zs = np.random.random(100)*15+50
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs,ys,zs)
plt.show()

添加标题

我们将调用轴对象的set_title方法为绘图添加标题。

ax.set_title("Atom velocity distribution")
plt.show()

添加轴标签

我们可以通过在轴对象上调用 set_xlabel 、 set_ylabel 和 set_zlabel 方法为 3D 图中的每个轴设置标签。

ax.set_xlabel("Atomic mass (dalton)")
ax.set_ylabel("Atomic radius (pm)")
ax.set_zlabel("Atomic velocity (x10⁶ m/s)")
plt.show()

修改标记

正如我们在前面的例子中看到的,默认情况下,每个点的标记是一个固定大小的蓝色实心圆圈。

我们可以改变标记的外观,使它们更具表现力。

让我们从改变标记的颜色和样式开始

ax.scatter(xs,ys,zs, marker="x", c="red")
plt.show()

我们使用了参数 markerc来改变各个点的样式和颜色

修改轴限制和刻度

默认情况下,根据输入值设置轴上值的范围和间隔。

然而,我们可以将它们更改为我们想要的值。

让我们创建另一个表示一组新数据点的散点图,然后修改其轴范围和间隔。

np.random.seed(42)
ages = np.random.randint(low = 8, high = 30, size=35)
heights = np.random.randint(130, 195, 35)
weights = np.random.randint(30, 160, 35)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs = heights, ys = weights, zs = ages)
ax.set_title("Age-wise body weight-height distribution")
ax.set_xlabel("Height (cm)")
ax.set_ylabel("Weight (kg)")
ax.set_zlabel("Age (years)")
plt.show()

我们在 3 个轴上绘制了 3 个变量的数据,即身高、体重和年龄。

如我们所见,X、Y 和 Z 轴上的限制已根据输入数据自动分配。

让我们通过调用 set_xlimset_ylimset_zlim方法来修改每个轴的最小和最大限制。

ax.set_xlim(100,200)
ax.set_ylim(20,160)
ax.set_zlim(5,35)
plt.show()

我们还可以修改每个轴的单个刻度。

让我们将X 轴刻度更新为 [100,125,150,175,200]

ax.set_xticks([100,125,150,175,200])
plt.show()

同样,我们可以使用 set_yticksset_zticks方法更新 Y 和 Z 刻度。

ax.set_yticks([20,55,90,125,160])
ax.set_zticks([5,15,25,35])
plt.show()

改变图的大小

如果我们希望我们的绘图比默认大小更大或者更小,我们可以在使用 plt.figure方法的 figsize参数初始化图形时轻松设置绘图的大小,
或者我们可以通过调用图形对象上的“set_size_inches”方法来更新现有绘图的大小。

在这两种方法中,我们必须以英寸为单位指定绘图的宽度和高度。

我们将散点图的大小更改为 6×6 英寸。

fig.set_size_inches(6, 6)
plt.show()

关闭/打开网格线

到目前为止,我们绘制的所有图都默认带有网格线。

ax.grid(False)
plt.show()

根据类设置 3D 绘图颜色

让我们假设散点图所代表的个体被进一步分为两个或者更多类别。

我们可以通过用不同颜色绘制每个类别的个体来表示这些信息。

np.random.seed(42)
ages = np.random.randint(low = 8, high = 30, size=35)
heights = np.random.randint(130, 195, 35)
weights = np.random.randint(30, 160, 35)
gender_labels = np.random.choice([0, 1], 35) #0 for male, 1 for female
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs = heights, ys = weights, zs = ages, c=gender_labels)
ax.set_title("Age-wise body weight-height distribution")
ax.set_xlabel("Height (cm)")
ax.set_ylabel("Weight (kg)")
ax.set_zlabel("Age (years)")
plt.show()

但是我们怎么知道哪种颜色对应哪个类别呢?

我们可以添加一个'colorbar'来解决这个问题。

scat_plot = ax.scatter(xs = heights, ys = weights, zs = ages, c=gender_labels)
cb = plt.colorbar(scat_plot, pad=0.2)
cb.set_ticks([0,1])
cb.set_ticklabels(["Male", "Female"])
plt.show()

放置图例

通常我们有超过 1 组数据要绘制在同一个图上。

在这种情况下,我们必须为每个图分配标签并在图中添加图例以区分不同的图。

labels = ["Florida", "Georgia", "California"]
for l in labels:
    ages = np.random.randint(low = 8, high = 20, size=20)
    heights = np.random.randint(130, 195, 20)
    weights = np.random.randint(30, 160, 20)
    ax.scatter(xs = heights, ys = weights, zs = ages, label=l)
ax.set_title("Age-wise body weight-height distribution")
ax.set_xlabel("Height (cm)")
ax.set_ylabel("Weight (kg)")
ax.set_zlabel("Age (years)")
ax.legend(loc="best")
plt.show()

不同大小的绘图标记

在我们目前看到的散点图中,所有点标记的大小都是恒定的。

我们可以通过将自定义值传递给散点图的参数 s来改变标记的大小。

np.random.seed(42)
ages = np.random.randint(low = 8, high = 30, size=35)
heights = np.random.randint(130, 195, 35)
weights = np.random.randint(30, 160, 35)
bmi = weights/((heights*0.01)**2)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs = heights, ys = weights, zs = ages, s=bmi*5 )
ax.set_title("Age-wise body weight-height distribution")
ax.set_xlabel("Height (cm)")
ax.set_ylabel("Weight (kg)")
ax.set_zlabel("Age (years)")
plt.show()

将 Python 3D 绘图输出到 HTML

如果我们想将 3D 绘图图形嵌入到 HTML 页面中,而无需先将其保存为图像文件,
我们可以通过将图形编码为“base64”,然后将其插入 HTML img标签中的正确位置来实现

import base64
from io import BytesIO
np.random.seed(42)
xs = np.random.random(100)*10+20
ys = np.random.random(100)*5+7
zs = np.random.random(100)*15+50
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs,ys,zs)
#encode the figure
temp = BytesIO()
fig.savefig(temp, format="png")
fig_encode_bs64 = base64.b64encode(temp.getvalue()).decode('utf-8')
html_string = """
<h2>This is a test html</h2>
<img src = 'data:image/png;base64,{}'/>
""".format(fig_encode_bs64)

我们现在可以将这个 HTML 代码字符串写入到一个 html 文件,然后我们可以在浏览器中查看

with open("test.html", "w") as f:
    f.write(html_string)

在 3D 空间中绘制单个点

第 1 步:导入库

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

第 2 步:创建图形和轴

fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(111, projection='3d')

在这里,我们首先创建一个大小为 4 英寸 X 4 英寸的图形。

然后我们通过调用 add_subplot方法并将值 '3d' 指定给 projection参数来创建一个 3-D 轴对象。

我们将使用这个轴对象 'ax' 向图中添加任何绘图。

第 3 步:绘制点

创建轴对象后,我们可以使用它在 3D 空间中创建我们想要的任何类型的绘图。

要绘制单个点,我们将使用 scatter()方法,并传递该点的三个坐标。

fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(2,3,4) # plot the point (2,3,4) on the figure
plt.show()

在 (2,3,4) 处绘制了一个点(蓝色)。

绘制高斯分布

我们还可以使用多元正态分布在 3D 空间中绘制高斯分布。

我们必须定义变量 X 和 Y,并一起绘制它们的概率分布。

from scipy.stats import multivariate_normal
X = np.linspace(-5,5,50)
Y = np.linspace(-5,5,50)
X, Y = np.meshgrid(X,Y)
X_mean = 0; Y_mean = 0
X_var = 5; Y_var = 8
pos = np.empty(X.shape+(2,))
pos[:,:,0]=X
pos[:,:,1]=Y
rv = multivariate_normal([X_mean, Y_mean],[[X_var, 0], [0, Y_var]])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, rv.pdf(pos), cmap="plasma")
plt.show()

绘制两个不同的 3D 分布

借助 fig.add_subplot 方法,我们可以将两个不同的 3D 绘图添加到同一个图形中。

#data generation for 1st plot
np.random.seed(42)
xs = np.random.random(100)*10+20
ys = np.random.random(100)*5+7
zs = np.random.random(100)*15+50
#data generation for 2nd plot
np.random.seed(42)
ages = np.random.randint(low = 8, high = 30, size=35)
heights = np.random.randint(130, 195, 35)
weights = np.random.randint(30, 160, 35)
fig = plt.figure(figsize=(8,4))
#First plot
ax = fig.add_subplot(121, projection='3d')
ax.scatter(xs,ys,zs, marker="x", c="red")
ax.set_title("Atom velocity distribution")
ax.set_xlabel("Atomic mass (dalton)")
ax.set_ylabel("Atomic radius (pm)")
ax.set_zlabel("Atomic velocity (x10⁶ m/s)")
#Second plot
ax = fig.add_subplot(122, projection='3d')
ax.scatter(xs = heights, ys = weights, zs = ages)
ax.set_title("Age-wise body weight-height distribution")
ax.set_xlabel("Height (cm)")
ax.set_ylabel("Weight (kg)")
ax.set_zlabel("Age (years)")
plt.show()

绘制 3D 多边形

我们还可以在 Python 中绘制具有 3 维顶点的多边形。

from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [1, 0, 3, 4]
y = [0, 5, 5, 1]
z = [1, 3, 4, 0]
vertices = [list(zip(x,y,z))]
poly = Poly3DCollection(vertices, alpha=0.8)
ax.add_collection3d(poly)
ax.set_xlim(0,5)
ax.set_ylim(0,5)
ax.set_zlim(0,5)
日期:2020-07-15 11:16:26 来源:oir作者:oir