8.15 Matplotlib 中的三維繪圖
原文:Three-Dimensional Plotting in Matplotlib
譯者:飛龍
本節是《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 中創建和展示各種各樣的三維對象和圖案。