使用Matplotlib繪製3D圖形
本文是Matplotlib的第二篇文章,會講解如何通過Matplotlib繪製3D圖形。關於Matplotlib的第一篇文章,請看這裡:Python繪相簿Matplotlib入門教程。
測試環境
由於這是一個Python語言的軟體包,因此需要你的機器上首先安裝好Python語言的環境。關於這一點,請自行在網路上搜尋獲取方法。
關於如何安裝Matplotlib請參見這裡:Matplotlib Installing。
本文中的原始碼和測試資料可以在這裡獲取:Github: matplotlib_tutorial
本文的程式碼示例會用到其他一些Python庫。建議讀者先對其有一定的熟悉,我的部落格中也有一些相關文章。
本文的程式碼在如下環境中測試:
- Apple OS X 10.13
- Python 3.6.2
- matplotlib 2.2.3
- numpy 1.14.1
準備
繪製3D圖形的時候我們通常都會包含下面這個程式碼片段,這裡我們先對其進行說明。
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
這4行程式碼說明如下:
- 第一行自然不必說,就是匯入
matplotlib.pyplot
。 - 第二行
from mpl_toolkits.mplot3d import Axes3D
是匯入Axes3D類。我們後面在繪製3D圖形的時候,相應的函式都位於這個介面上。 -
fig = plt.figure()
是獲取到當前figure物件。 -
ax = fig.gca(projection=`3d`)
這一行是比較關鍵的。fig.gca
是獲取圖中的當前極軸。如果不存在,或者不是極軸,則將建立相應的軸,然後返回。此時得到的ax
物件的型別是Axes3D
的子類,這個物件將是繪製3D圖形的入口。
Colormap
繪製圖形的時候,常常會需要對圖形著色。Matplotlib中內建了很多的Colormap來簡化這個工作,具體可以看這裡:Choosing Colormaps。
下面是一些Colormap示例:
通過指定相應的名稱我們就可以直接使用這裡的Colormap了。
線形圖
Axes3D.plot 函式用來繪製線形圖。
首先我們來看最簡單的圖形 – 線形圖。
由於這是三維空間中的線,所以需要若干個(x, y, z)
座標的值。
下面這段程式碼生成了一條三維空間中的直線。
# line.py
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
x = np.linspace(-10, 10, 1000)
y = np.linspace(-10, 10, 1000)
z = np.add(x, y)
ax.plot(x, y, z)
plt.show()
從這段程式碼可以看出,這條線的x和y軸的範圍都是[-10, 10]。我們共計取樣了1000個點。
需要注意的是,由於線是通過點來描繪的,每一個點都由[x,y,z]三個座標值來確定,因此這裡x,y,z
三個陣列的元素數量應該是一樣多的。
z軸取值為np.add(x, y)
。請注意,np.add
是元素級(element-wise)的運算:它是將x和y兩個陣列的元素逐個相加,所以得到的結果仍然是包含了1000個元素的陣列。
這段程式碼得到的結果如下:
散點圖
Axes3D.scatter 函式用來繪製散點圖。
下面我們再來看一下散點圖。
和線形圖類似,它也是展示若干個(x, y, z)
座標的值。區別在於,這裡僅僅是一些點,沒有通過線連在一起。
下面是一段程式碼示例:
# scatter.py
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
count = 100
range = 100
xs = np.random.rand(count) * range
ys = np.random.rand(count) * range
zs = np.random.rand(count) * range
ax.scatter(xs, ys, zs, s=zs, c=zs)
ax.set_xlabel(`X Label`)
ax.set_ylabel(`Y Label`)
ax.set_zlabel(`Z Label`)
plt.show()
這段程式碼中,我們還設定了三個座標軸的Label。
另外,所有點的x,y,z軸都是隨機的。範圍都在100以內。並且,我們在顯示這些點的時候,根據z值的大小設定了點的顏色和尺寸以示區分。
最終我們得到的圖形如下所示:
線框圖
Axes3D.plot_wireframe 函式用來繪製線框圖。
線框圖要比前面的圖形要複雜一些。
線框圖展示的是一個曲面的框架結構,由於是一個面,因此它在x,y兩個座標的整個面上都應該有所取值。
前面兩種圖形的x,y軸的值都是一維的陣列,而對於線框圖來說,其x,y軸的取值應該是一個二維的矩陣。
例如,我們設定x的範圍是[1, 3]之間,y的範圍是[11, 15]之間。並且,每一個整數座標取一個點,那麼如下的所有點上都會對應一個z值:
$$
egin{pmatrix}
[1,11], [2,11], [3,11] \
[1,12], [2,12], [3,12] \
[1,13], [2,13], [3,13] \
[1,14], [2,14], [3,14] \
[1,15], [2,15], [3,15]
end{pmatrix}
$$
而對於描述x,y軸的兩個陣列來說,它們各自應該是下面這樣的矩陣:
$$
egin{pmatrix}
1, 2, 3 \
1, 2, 3 \
1, 2, 3 \
1, 2, 3 \
1, 2, 3
end{pmatrix}
$$
$$
egin{pmatrix}
11, 11, 11 \
12, 12, 12 \
13, 13, 13 \
14, 14, 14 \
15, 15, 15
end{pmatrix}
$$
這裡的兩個矩陣其實是互相由對方資料的數量而確定尺寸的。
numpy中的meshgrid
函式剛好可以幫我們完成這個功能,下面是一段程式碼示例:
# meshgrid_demo.py
import numpy as np
x = np.arange(1, 4)
y = np.arange(11, 16)
print(x)
print(y)
X, Y = np.meshgrid(x, y)
print(X)
print(Y)
請仔細觀察一下它的輸出:
[1 2 3]
[11 12 13 14 15]
[[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]]
[[11 11 11]
[12 12 12]
[13 13 13]
[14 14 14]
[15 15 15]]
有了這個基礎之後,我們就可以以此來產生我們需要的線框圖了。
假設我們要展示的函式如下:
$$
z = -x^3 + y^4 , -10 lt x,y lt 10
$$
我們可以通過下面這段程式碼來生成這個函式的圖形:
# wireframe.py
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.add(-np.power(X, 3), np.power(Y, 4))
surf = ax.plot_wireframe(X, Y, Z)
plt.show()
請注意,這段程式碼中關於
np
的函式都是元素級(element-wise)的運算。請讀者思考一下,
np.power(X, 3)
與X**3
的含義分別是什麼。
這段程式碼所得到的圖形如下所示:
曲面圖
Axes3D.plot_surface 函式用來繪製曲面圖。
曲面圖和線框圖類似,它們都是描述三維空間中的曲面的。區別在於:曲面圖中的面是著色的。
下面這段程式碼繪製出了下面這個函式的圖形:
$$
z = -x^3 + y^2, -10 lt x, y lt 10
$$
# surface.py
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.add(-np.power(X, 3), np.power(Y, 2))
surf = ax.plot_surface(X, Y, Z, cmap=cm.gist_rainbow)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
這段程式碼整體應該都不難理解,只有兩個地方需要說明一下:
- 這裡通過
cmap=cm.gist_rainbow
指定了曲面的顏色。更多的Colormap請到查閱這裡:Choosing Colormaps。 - 通過
fig.colorbar(surf, shrink=0.5, aspect=5)
新增了一個色彩條。shrink
指定了色彩條與圖形高度的比例,aspect
指定了色彩條本身的長寬比。
這段程式碼得到的圖形如下所示:
等高線
Axes3D.contour 函式用來繪製等高線。
等高線顧名思義,就是描述高度相等的線。等高線通常伴隨主體圖形一起出現,輔助我們觀察主體圖形的一些特性。
有了前面的基礎,繪製等高線也就很容易了。
下面這段程式碼繪製出了以下這個函式的線框圖以及等高線:
$$
Z = -x^4 + Y^4, -10 lt x, y lt 10
$$
# contour.py
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.add(-np.power(X, 4), np.power(Y, 4))
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.plot_wireframe(X, Y, Z, alpha=0.1)
ax.contour(X, Y, Z, cmap=cm.Accent, linewidths=2)
plt.show()
為了便於觀察等高線,我們將主體圖形的線框圖透明度設為0.1,然後將等高線的粗度設定為2。
上面這段程式碼得到的圖形如下所示:
這個圖形比較複雜,單從一個角度不太容易看清楚其完整結構,文末我們會講解怎麼製作一副動態圖來展示圖形的全貌。
柱狀圖
Axes3D.bar 函式用來繪製柱狀圖。
柱狀圖也是很常用的圖。
下面這段程式碼展示了這樣一種場景:在一副圖中,對比一個城市四年期間每個月的降水量。
在這幅圖中,每一年的12個月是一組柱狀圖。四年的資料進行了前後的對比展示。
程式碼如下:
# bar.py
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
np.random.seed(59)
month = np.arange(1, 12)
years = [2016, 2017, 2018, 2019]
def get_color(value_array):
color = []
for v in value_array:
if (v < 50):
color.append(`y`)
elif (v < 100):
color.append(`g`)
elif (v < 150):
color.append(`b`)
elif (v < 200):
color.append(`c`)
elif (v < 250):
color.append(`m`)
else:
color.append(`r`)
return color
for year, c in zip(years, [`b`,`c`,`r`,`m`]):
value = np.random.rand(len(month)) * 300
ax.bar(month, value, year, zdir=`y`, color=get_color(value), alpha=0.7)
for i in np.arange(0, 12):
ax.bar
ax.set_xlabel(`Month`)
ax.set_xticks(np.arange(1, 13))
ax.set_ylabel(`Year`)
ax.set_yticks(np.arange(2016, 2020))
ax.set_zlabel(`Precipitation`)
plt.show()
在這段程式碼中,我們通過隨機數生成了每個月的降水量。並且根據降水量的程度設定了條柱的顏色以示區分。
我們最終得到的圖形如下所示:
多邊形
Axes3D.add_collection3d 函式用來向圖形中新增3D集合物件。
對於某些資料(例如降水量)來說,我們也可能希望通過多邊形來了解其每個點的走勢。
下面這段程式碼通過多邊形的形式展示了和上面柱狀圖一樣的資料。
# poly.py
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection=`3d`)
np.random.seed(59)
month = np.arange(0, 13)
years = [2016, 2017, 2018, 2019]
precipitation = []
for year in years:
value = np.random.rand(len(month)) * 300
value[0], value[-1] = 0, 0
precipitation.append(list(zip(month, value)))
poly = PolyCollection(precipitation, facecolors=[`b`,`c`,`r`,`m`])
poly.set_alpha(0.7)
ax.add_collection3d(poly, zs=years, zdir=`y`)
ax.set_xlabel(`Month`)
ax.set_xlim3d(0, 12)
ax.set_ylabel(`Year`)
ax.set_ylim3d(2015, 2020)
ax.set_zlabel(`Precipitation`)
ax.set_zlim3d(0, 300)
plt.show()
Axes3D.add_collection3d 函式除了支援PolyCollection
,還支援LineCollection
和PatchCollection
。這一點,讀者可以自行研究一下。
上面這段程式碼得到的圖形如下:
製作動圖
很多時候,我們可能需要製作一張動畫圖來展示圖形的全貌,下面我們就來看一下如何做到。
生成不同角度的圖形
為了製作動圖,我們需要先有製作動圖的圖片素材。
下面我們就以前面等高線那個函式生成的複雜圖形為例,來看看如何生成一個關於這個圖形不同角度的動圖。
相關程式碼如下:
# surface_files.py
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 8))
ax = fig.gca(projection=`3d`)
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.add(-np.power(X, 4), np.power(Y, 4))
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.plot_surface(X, Y, Z, cmap=cm.hsv)
for angle in range(95, 180, 3):
ax.set_zlabel("Angle: " + str(angle))
ax.view_init(30, angle)
filename = "./" + str(angle) + ".png"
plt.savefig(filename)
print("Save " + filename + " finish")
這段程式碼其實並不複雜,與前面的區別主要就是在於程式碼最後的for
迴圈。
在這個for
迴圈中,我們選取了從95到180這個範圍的角度,每隔3做一次取樣,每次取樣做如下的事情:
- 通過
set_zlabel
設定了當前旋轉角度的Label - 通過
ax.view_init(30, angle)
設定圖形的視角 - 根據當前角度生成一個單獨的檔名稱
- 通過
plt.savefig(filename)
儲存檔案 - 列印日誌
這段程式碼執行完成之後,我們就會得到一系列的png檔案。下面我們就通過這些png檔案來生成動圖。
使用ImageMagick
這裡通過一個免費的跨平臺工具ImageMagick來製作動圖。該工具支援 Linux,Windows,Mac OS X,iOS和Android等各個平臺。
首先,我們到這裡進行下載:Download ImageMagick。
請根據你的平臺選擇下載哪個版本。
由於我是Mac使用者,所以直接通過下面的命令就可以安裝ImageMagick。
brew install ImageMagick
安裝好之後,命令列就會有convert
工具。通過這個工具就可以生成動圖了。
相關命令如下:
convert -delay 50 *.png animated.gif
當然,你可以研究一下這個命令的其他引數和功能。這裡就不贅述了。
我們最終得到的動圖看起來像下面這個樣子:
結束語
能夠繪製3D圖形將是一項非常有用的技能。因為在今後的機器學習過程中,我們常常會將資料以圖形的形式展示出來,以便我們觀察和了解。
由於篇幅所限,本文只介紹了一些最基本的用法,但實際上Matplotlib所支援的功能遠不止這些,因此建議讀者朋友們以此為基礎繼續進行更多的探索。
參考資料與推薦讀物
- Matplotlib官方網站
- mplot3d tutorial
- 3D – The Python Graph Gallery
- Three-Dimensional Plotting in Matplotlib
相關文章
- matplotlib繪製圖形
- 利用 Matplotlib 繪製資料圖形(一)
- 利用 Matplotlib 繪製資料圖形(二)
- 如何使用Python和Plotly繪製3D圖形Python3D
- Python Matplotlib繪製條形圖的全過程Python
- 使用css繪製圖形CSS
- 使用python matplotlib實現動圖繪製Python
- 08【matplotlib】06matplotlib繪製多次圖形和不同圖形的差異介紹和總結
- Matplotlib 繪製折線圖
- 【matplotlib基礎】--3D圖形3D
- 繪製圖形
- Matplotlib直方圖繪製技巧直方圖
- Python matplotlib繪製散點圖Python
- java:繪製圖形Java
- Python Matplotlib繪製氣溫圖表Python
- css繪製特殊圖形CSS
- Python 利用pandas和matplotlib繪製餅圖Python
- matplotlib的直方圖繪製(筆記)直方圖筆記
- Matplotlib呼叫imshow()函式繪製熱圖函式
- ## matplotlib.pyplot庫的知識點之bar函式——繪製條形圖函式
- Python 利用pandas 和 matplotlib繪製柱狀圖Python
- [Python] Matplotlib 圖表的繪製和美化技巧Python
- 分段函式圖形繪製函式
- CAD有趣圖形的繪製
- Shader 繪製基礎圖形
- [1]Python 中用 matplotlib 繪製熱點圖(heat map)Python
- Excalidraw:繪製圖形的新利器
- MATLAB圖形繪製練習(一)Matlab
- Matplotlib繪圖基礎繪圖
- Matplotlib 詳細繪圖繪圖
- python繪圖之matplotlibPython繪圖
- 如何使用 css 繪製心形CSS
- CSS3繪製圖形圖案效果CSSS3
- 使用joinjs繪製流程圖(五)-流程圖繪製JS流程圖
- Python例項:僅繪製圖例而不繪製實際的圖形Python
- Python 利用pandas和matplotlib繪製柱狀折線圖Python
- 小提琴圖的繪製方法:Python matplotlib實現Python
- Python基本圖形繪製--模組1:turtle庫的使用Python