用鼠标旋转 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()
我们使用了参数 marker
和 c
来改变各个点的样式和颜色
修改轴限制和刻度
默认情况下,根据输入值设置轴上值的范围和间隔。
然而,我们可以将它们更改为我们想要的值。
让我们创建另一个表示一组新数据点的散点图,然后修改其轴范围和间隔。
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_xlim
、set_ylim
和 set_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_yticks
和 set_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)