使用Matplotlib繪製3D圖形
本文是Matplotlib的第二篇文章,會講解如何通過Matplotlib繪製3D圖形。關於Matplotlib的第一篇文章,請看這裡: ofollow,noindex" target="_blank">Python繪相簿Matplotlib入門教程 。
測試環境
由於這是一個Python語言的軟體包,因此需要你的機器上首先安裝好Python語言的環境。關於這一點,請自行在網路上搜索獲取方法。
關於如何安裝Matplotlib請參見這裡: Matplotlib Installing 。
筆者推薦大家通過 pip 或者 anaconde 的方式進行安裝。
本文中的原始碼和測試資料可以在這裡獲取: 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值:
而對於描述x,y軸的兩個陣列來說,它們各自應該是下面這樣的矩陣:
這裡的兩個矩陣其實是互相由對方資料的數量而確定尺寸的。
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]]
有了這個基礎之後,我們就可以以此來產生我們需要的線框圖了。
假設我們要展示的函式如下:
我們可以通過下面這段程式碼來生成這個函式的圖形:
# 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 函式用來繪製曲面圖。
曲面圖和線框圖類似,它們都是描述三維空間中的曲面的。區別在於:曲面圖中的面是著色的。
下面這段程式碼繪製出了下面這個函式的圖形:
# 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 函式用來繪製等高線。
等高線顧名思義,就是描述高度相等的線。等高線通常伴隨主體圖形一起出現,輔助我們觀察主體圖形的一些特性。
有了前面的基礎,繪製等高線也就很容易了。
下面這段程式碼繪製出了以下這個函式的線框圖以及等高線:
# 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 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所支援的功能遠不止這些,因此建議讀者朋友們以此為基礎繼續進行更多的探索。