【Python】科研论文绘图实操干货汇总,11类Matplotlib图表,含代码
作者丨數據派THU
來源丨DataScience
編輯丨極市平臺
導讀
Matplotlib 是一個 Python 的 2D繪圖庫,它以各種硬拷貝格式和跨平臺的交互式環境生成出版質量級別的圖形。通過 Matplotlib,開發者可以僅需要幾行代碼,便可以生成繪圖,直方圖,功率譜,條形圖,錯誤圖,散點圖等。
以下內容來自Github,為《PythonDataScienceHandbook[1]》(Python 數據科學手冊[2])第四章Matplotlib介紹部分。全部內容都在以下環境演示通過:
numpy:1.18.5
pandas:1.0.5
matplotlib:3.2.1
1.簡單的折線圖
對于圖表來說,最簡單的莫過于作出一個單一函數??的圖像。本節中我們首先來介紹創建這種類型圖表。本節和后續小節中,我們都會使用下面的代碼將我們需要的包載入到 notebook 中:
%matplotlib?inline import?matplotlib.pyplot?as?plt plt.style.use('seaborn-whitegrid') import?numpy?as?np對于所有的 Matplotlib 圖表來說,我們都需要從創建圖形和維度開始。圖形和維度可以使用下面代碼進行最簡形式的創建:
fig?=?plt.figure() ax?=?plt.axes()在 Matplotlib 中,圖形(類plt.Figure的一個實例)可以被認為是一個包括所有維度、圖像、文本和標簽對象的容器。維度(類plt.Axes的一個實例)就是你上面看到的圖像,一個有邊界的格子包括刻度和標簽,最終還有我們畫在上面的圖表元素。在本書中,我們會使用變量名fig來指代圖形對象,以及變量名ax來指代維度變量。
一旦我們創建了維度,我們可以使用ax.plot方法將數據繪制在圖表上。下面是一個簡單的正弦函數圖形:
fig?=?plt.figure() ax?=?plt.axes()x?=?np.linspace(0,?10,?1000) ax.plot(x,?np.sin(x));同樣的,我們可以使用 pylab 接口(MATLAB 風格的接口)幫我們在后臺自動創建這兩個對象:
plt.plot(x,?np.sin(x));如果我們需要在同一幅圖形中繪制多根線條,只需要多次調用plot函數即可:
plt.plot(x,?np.sin(x)) plt.plot(x,?np.cos(x));這就是在 Matplotlib 中繪制簡單函數圖像的所有接口了。下面我們深入了解一下控制坐標軸和線條外觀的細節。
調整折線圖:線條顏色和風格
你可能第一個想到需要進行調整的部分就是線條的顏色和風格。plt.plot()函數接受額外的參數可以用來指定它們。通過指定color關鍵字參數可以調整顏色,這個字符串類型參數基本上能用來代表任何你能想到的顏色??梢酝ㄟ^多種方式指定顏色參數:
所有 HTML 顏色名稱可以在這里[3]找到。
plt.plot(x,?np.sin(x?-?0),?color='blue')????????#?通過顏色名稱指定 plt.plot(x,?np.sin(x?-?1),?color='g')???????????#?通過顏色簡寫名稱指定(rgbcmyk) plt.plot(x,?np.sin(x?-?2),?color='0.75')????????#?介于0-1之間的灰階值 plt.plot(x,?np.sin(x?-?3),?color='#FFDD44')?????#?16進制的RRGGBB值 plt.plot(x,?np.sin(x?-?4),?color=(1.0,0.2,0.3))?#?RGB元組的顏色值,每個值介于0-1 plt.plot(x,?np.sin(x?-?5),?color='chartreuse');?#?能支持所有HTML顏色名稱值
如果沒有指定顏色,Matplotlib 會在一組默認顏色值中循環使用來繪制每一條線條。
類似的,通過linestyle關鍵字參數可以指定線條的風格:
plt.plot(x,?x?+?0,?linestyle='solid') plt.plot(x,?x?+?1,?linestyle='dashed') plt.plot(x,?x?+?2,?linestyle='dashdot') plt.plot(x,?x?+?3,?linestyle='dotted');#?還可以用形象的符號代表線條風格 plt.plot(x,?x?+?4,?linestyle='-')??#?實線 plt.plot(x,?x?+?5,?linestyle='--')?#?虛線 plt.plot(x,?x?+?6,?linestyle='-.')?#?長短點虛線 plt.plot(x,?x?+?7,?linestyle=':');??#?點線如果你喜歡更簡潔的代碼,這些linestyle和color參數能夠合并成一個非關鍵字參數,傳遞給plt.plot()函數:
plt.plot(x,?x?+?0,?'-g')??#?綠色實線 plt.plot(x,?x?+?1,?'--c')?#?天青色虛線 plt.plot(x,?x?+?2,?'-.k')?#?黑色長短點虛線 plt.plot(x,?x?+?3,?':r');??#?紅色點線上面的單字母顏色碼是 RGB 顏色系統以及 CMYK 顏色系統的縮寫,被廣泛應用在數字化圖像的顏色系統中。
還有很多其他的關鍵字參數可以對折線圖的外觀進行精細調整;可以通過在 IPython 中使用幫助工具查看plt.plot()函數的文檔來獲得更多細節內容。
調整折線圖:坐標軸范圍
Matplotlib 會自動選擇非常合適的坐標軸范圍來繪制你的圖像,但是有些情況下你也需要自己進行相關調整。使用plt.xlim()和plt.ylim()函數可以調整坐標軸的范圍:
plt.plot(x,?np.sin(x))plt.xlim(-1,?11) plt.ylim(-1.5,?1.5);如果某些情況下你希望將坐標軸反向,你可以通過上面的函數實現,將參數順序顛倒即可:
plt.plot(x,?np.sin(x))plt.xlim(10,?0) plt.ylim(1.2,?-1.2);相關的函數還有plt.axis()(注意:這不是plt.axes()函數,函數名稱是 i 而不是 e)。這個函數可以在一個函數調用中就完成 x 軸和 y 軸范圍的設置,傳遞一個[xmin, xmax, ymin, ymax]的列表參數即可:
plt.plot(x,?np.sin(x)) plt.axis([-1,?11,?-1.5,?1.5]);當然plt.axis()函數不僅能設置范圍,還能像下面代碼一樣將坐標軸壓縮到剛好足夠繪制折線圖像的大小:
plt.plot(x,?np.sin(x)) plt.axis('tight');還可以通過設置'equal'參數設置x軸與y軸使用相同的長度單位:
plt.plot(x,?np.sin(x)) plt.axis('equal');更多關于設置 axis 屬性的內容請查閱plt.axis函數的文檔字符串。
折線圖標簽
本節最后介紹一下在折線圖上繪制標簽:標題、坐標軸標簽和簡單的圖例。
標題和坐標軸標簽是最簡單的這類標簽,Matplotlib 提供了函數用來方便的設置它們:
plt.plot(x,?np.sin(x)) plt.title("A?Sine?Curve") plt.xlabel("x") plt.ylabel("sin(x)");這些標簽的位置、大小和風格可以通過上面函數的可選參數進行設置。參閱 Matplotlib 在線文檔和這些函數的文檔字符串可以獲得更多的信息。
當一幅圖中繪制了多條折線時,如果能夠繪制一個線條對應的圖例能讓圖表更加清晰。Matplotlib 也內建了函數來快速創建圖例。估計你也猜到了,通過plt.legend()函數可以實現這個需求。雖然有很多種正確的方法來指定圖例,作者認為最簡單的方法是通過在繪制每條線條時指定對應的label關鍵字參數來使用這個函數:
plt.plot(x,?np.sin(x),?'-g',?label='sin(x)') plt.plot(x,?np.cos(x),?':b',?label='cos(x)') plt.axis('equal')plt.legend();上圖可見,plt.legend()函數繪制的圖例線條與圖中的折線無論風格和顏色都保持一致。查閱plt.legend文檔字符串可以獲得更多相關信息;我們在[自定義圖表圖例]一節中也會討論更高級的圖例應用。
額外內容:Matplotlib 的坑
雖然大多數的plt函數都可以直接轉換為ax的方法進行調用(例如plt.plot()?→ ax.plot(),plt.legend()?→ ax.legend()等),但是并不是所有的命令都能應用這種情況。特別是用于設置極值、標簽和標題的函數都有一定的改變。下表列出了將 MATLAB 風格的函數轉換為面向對象的方法的區別:
plt.xlabel()?→?ax.set_xlabel()
plt.ylabel()?→?ax.set_ylabel()
plt.xlim()?→?ax.set_xlim()
plt.ylim()?→?ax.set_ylim()
plt.title()?→?ax.set_title()
在面向對象接口中,與其逐個調用上面的方法來設置屬性,更常見的使用ax.set()方法來一次性設置所有的屬性:
ax?=?plt.axes() ax.plot(x,?np.sin(x)) ax.set(xlim=(0,?10),?ylim=(-2,?2),xlabel='x',?ylabel='sin(x)',title='A?Simple?Plot');2.簡單散點圖
另一種常用的圖表類型是簡單散點圖,它是折線圖的近親。不像折線圖,圖中的點連接起來組成連線,散點圖中的點都是獨立分布的點狀、圓圈或其他形狀。本節開始我們也是首先將需要用到的圖表工具和函數導入到 notebook 中:
%matplotlib?inline import?matplotlib.pyplot?as?plt plt.style.use('seaborn-whitegrid') import?numpy?as?np使用?plt.plot?繪制散點圖
在上一節中,我們介紹了plt.plot/ax.plot方法繪制折線圖。這兩個方法也可以同樣用來繪制散點圖:
x?=?np.linspace(0,?10,?30) y?=?np.sin(x)plt.plot(x,?y,?'o',?color='black');傳遞給函數的第三個參數是使用一個字符代表的圖表繪制點的類型。就像你可以使用'-'或'--'來控制線條的風格那樣,點的類型風格也可以使用短字符串代碼來表示。所有可用的符號可以通過plt.plot文檔或 Matplotlib 在線文檔進行查閱。大多數的代碼都是非常直觀的,我們使用下面的例子可以展示那些最通用的符號:
rng?=?np.random.RandomState(0) for?marker?in?['o',?'.',?',',?'x',?'+',?'v',?'^',?'<',?'>',?'s',?'d']:plt.plot(rng.rand(5),?rng.rand(5),?marker,label="marker='{0}'".format(marker)) plt.legend(numpoints=1) plt.xlim(0,?1.8);而且這些符號代碼可以和線條、顏色代碼一起使用,這會在折線圖的基礎上繪制出散點:
plt.plot(x,?y,?'-ok');plt.plot還有很多額外的關鍵字參數用來指定廣泛的線條和點的屬性:
plt.plot(x,?y,?'-p',?color='gray',markersize=15,?linewidth=4,markerfacecolor='white',markeredgecolor='gray',markeredgewidth=2) plt.ylim(-1.2,?1.2);plt.plot函數的這種靈活性提供了很多的可視化選擇。查閱plt.plot幫助文檔獲得完整的選項說明。
使用plt.scatter繪制散點圖
第二種更強大的繪制散點圖的方法是使用plt.scatter函數,它的使用方法和plt.plot類似:
plt.scatter(x,?y,?marker='o');plt.scatter和plt.plot的主要區別在于,plt.scatter可以針對每個點設置不同屬性(大小、填充顏色、邊緣顏色等),還可以通過數據集合對這些屬性進行設置。
讓我們通過一個隨機值數據集繪制不同顏色和大小的散點圖來說明。為了更好的查看重疊的結果,我們還使用了alpha關鍵字參數對點的透明度進行了調整:
rng?=?np.random.RandomState(0) x?=?rng.randn(100) y?=?rng.randn(100) colors?=?rng.rand(100) sizes?=?1000?*?rng.rand(100)plt.scatter(x,?y,?c=colors,?s=sizes,?alpha=0.3,cmap='viridis') plt.colorbar();??#?顯示顏色對比條注意圖表右邊有一個顏色對比條(這里通過colormap()函數輸出),圖表中的點大小的單位是像素。使用這種方法,散點的顏色和大小都能用來展示數據信息,在希望展示多個維度數據集合的情況下很直觀。
例如,當我們使用 Scikit-learn 中的鳶尾花數據集,里面的每個樣本都是三種鳶尾花中的其中一種,并帶有仔細測量的花瓣和花萼的尺寸數據:
from?sklearn.datasets?import?load_iris iris?=?load_iris() features?=?iris.data.Tplt.scatter(features[0],?features[1],?alpha=0.2,s=100*features[3],?c=iris.target,?cmap='viridis') plt.xlabel(iris.feature_names[0]) plt.ylabel(iris.feature_names[1]);我們可以從上圖中看出,可以通過散點圖同時展示該數據集的四個不同維度:圖中的(x, y)位置代表每個樣本的花萼的長度和寬度,散點的大小代表每個樣本的花瓣的寬度,而散點的顏色代表一種特定的鳶尾花類型。如上圖的多種顏色和多種屬性的散點圖對于我們分析和展示數據集時都非常有幫助。
plot 和 scatter 對比:性能提醒
除了上面說的plt.plot和plt.scatter對于每個散點不同屬性的支持不同之外,還有別的因素影響對這兩個函數的選擇嗎?對于小的數據集來說,兩者并無差別,當數據集增長到幾千個點時,plt.plot會明顯比plt.scatter的性能要高。造成這個差異的原因是plt.scatter支持每個點使用不同的大小和顏色,因此渲染每個點時需要完成更多額外的工作。而plt.plot來說,每個點都是簡單的復制另一個點產生,因此對于整個數據集來說,確定每個點的展示屬性的工作僅需要進行一次即可。對于很大的數據集來說,這個差異會導致兩者性能的巨大區別,因此,對于大數據集應該優先使用plt.plot函數。
3.誤差可視化
對于任何的科學測量來說,精確計算誤差與精確報告測量值基本上同等重要。例如,設想我正在使用一些天文物理學觀測值來估算哈勃常數,即本地觀測的宇宙膨脹系數。我從一些文獻中知道這個值大概是 71 (km/s)/Mpc,而我測量得到的值是 74 (km/s)/Mpc,。這兩個值是否一致?在僅給定這些數據的情況下,這個問題的答案是,無法回答。
Mpc(百萬秒差距)參見秒差距[4]
如果我們將信息增加一些,給出不確定性:最新的文獻表示哈勃常數的值大約是 71 ?2.5 (km/s)/Mpc,我的測量值是 74??5 (km/s)/Mpc。這兩個值是一致的嗎?這就是一個可以準確回答的問題了。
在數據和結果的可視化中,有效地展示這些誤差能使你的圖表涵蓋和提供更加完整的信息。
基礎誤差條
調用一個 Matplotlib 函數就能創建一個基礎的誤差條:
%matplotlib?inline import?matplotlib.pyplot?as?plt plt.style.use('seaborn-whitegrid') import?numpy?as?np x?=?np.linspace(0,?10,?50) dy?=?0.8 y?=?np.sin(x)?+?dy?*?np.random.randn(50)plt.errorbar(x,?y,?yerr=dy,?fmt='.k');這里的fmt參數是用來控制線條和點風格的代碼,與plt.plot有著相同的語法,參見[簡單的折線圖]和[簡單的散點圖]。
除了上面的基本參數,errorbar函數還有很多參數可以用來精細調節圖表輸出。使用這些參數你可以很容易的個性化調整誤差條的樣式。作者發現通常將誤差線條顏色調整為淺色會更加清晰,特別是在數據點比較密集的情況下:
plt.errorbar(x,?y,?yerr=dy,?fmt='o',?color='black',ecolor='lightgray',?elinewidth=3,?capsize=0);除了上面介紹的參數,你還可以指定水平方向的誤差條(xerr),單邊誤差條和其他很多的參數。參閱plt.errorbar的幫助文檔獲得更多信息。
連續誤差
在某些情況下可能需要對連續值展示誤差條。雖然 Matplotlib 沒有內建的函數能直接完成這個任務,但是你可以通過簡單將plt.plot和plt.fill_between函數結合起來達到目標。
這里我們會采用簡單的高斯過程回歸方法,Scikit-Learn 提供了 API。這個方法非常適合在非參數化的函數中獲得連續誤差。我們在這里不會詳細介紹高斯過程回歸,僅僅聚焦在如何繪制連續誤差本身:
譯者注:新版的 sklearn 修改了高斯過程回歸實現方法,下面代碼做了相應修改。
from?sklearn.gaussian_process?import?GaussianProcessRegressor#?定義模型和一些符合模型的點 model?=?lambda?x:?x?*?np.sin(x) xdata?=?np.array([1,?3,?5,?6,?8]) ydata?=?model(xdata)#?計算高斯過程回歸,使其符合?fit?數據點 gp?=?GaussianProcessRegressor() gp.fit(xdata[:,?np.newaxis],?ydata)xfit?=?np.linspace(0,?10,?1000) yfit,?std?=?gp.predict(xfit[:,?np.newaxis],?return_std=True) dyfit?=?2?*?std??#?兩倍sigma?~?95%?確定區域我們現在有了xfit、yfit和dyfit,作為對我們數據的連續擬合值以及誤差限。當然我們也可以像上面一樣使用plt.errorbar繪制誤差條,但是事實上我們不希望在圖標上繪制 1000 個點的誤差條。于是我們可以使用plt.fill_between函數在誤差限區域內填充一道淺色的誤差帶來展示連續誤差:
#?可視化結果 plt.plot(xdata,?ydata,?'or') plt.plot(xfit,?yfit,?'-',?color='gray')plt.fill_between(xfit,?yfit?-?dyfit,?yfit?+?dyfit,color='gray',?alpha=0.2) plt.xlim(0,?10);注意上面我們調用fill_between函數:我們傳遞了的參數包括 x 值,y 值的低限,然后是 y 值的高限,結果是圖表中介于低限和高限之間的區域會被填充。
上圖為我們提供了一個非常直觀的高斯過程回歸展示:在觀測點的附近,模型會被限制在一個很小的區域內,反映了這些數據的誤差比較小。在遠離觀測點的區域,模型開始發散,反映了這時的數據誤差比較大。
如果需要獲得plt.fill_between(以及類似的plt.fill函數)更多參數的信息,請查閱函數的幫助文檔或 Matplotlib 在線文檔。
4.密度和輪廓圖
有些情況下,我們需要在二維圖表中使用輪廓或顏色區域來展示三維的數據(可以設想等高線地圖或溫度分布圖)。Matplotlib 提供了三個有用的函數來處理這項任務:plt.contour繪制輪廓圖,plt.contourf來繪制填充區域顏色的圖表以及plt.imshow來展示圖像。本節會介紹幾個使用它們的例子。當然我們還是首先從將需要使用的包導入 notebook 和初始化工作開始:
%matplotlib?inline import?matplotlib.pyplot?as?plt plt.style.use('seaborn-white') import?numpy?as?np三維可視化函數
我們首先使用一個簡單的函數??繪制一個輪廓圖來進行說明,我們用來作為數組廣播運算的例子:
def?f(x,?y):return?np.sin(x)?**?10?+?np.cos(10?+?y?*?x)?*?np.cos(x)輪廓圖可以使用plt.contour函數進行創建。它接收三個參數:x參數代表三維網格的平面橫軸坐標,y參數代表三維網格的平面縱軸坐標,而z參數代表三維網格的高度坐標。最容易用來準備這種網格數據的是np.meshgrid函數,可以將兩個一維的數組構造成一個二維的網格:
x?=?np.linspace(0,?5,?50) y?=?np.linspace(0,?5,?40)X,?Y?=?np.meshgrid(x,?y) Z?=?f(X,?Y)下面我們可以繪制標準的輪廓線圖表:
plt.contour(X,?Y,?Z,?colors='black');圖中值得注意的是,當使用單色繪制輪廓圖時,虛線代表的是負數的數值,而實線代表的是正數。而輪廓線可以通過指定cmap參數來設置線條的色圖。下例中展示了使用色圖且繪制了更多的輪廓線的例子,會在整個數據范圍區域內等距分布有 20 條輪廓線:
plt.contour(X,?Y,?Z,?20,?cmap='RdGy');上例中我們選擇了RdGy(Red-Gray的縮寫)色圖,這對于聚集的數據來說是一個不錯的選擇。Matplotlib 有大量的顏色圖可供使用,你可以通過在 IPython 中對plt.cm模塊使用 TAB 自動補全方法就可以看到:
plt.cm.<TAB>上面的圖看起來比第一幅圖好多了,但是線條之間的空隙還是有點讓人混淆。我們可以將上面的圖改為填充輪廓圖來解決這個問題,使用plt.contourf()函數(注意函數名最后有個 f,代表填充 fill),這個函數的語法基本上與plt.contour()保持一致。
并且我們加上了plt.colorbar()函數,這個函數會在圖表邊上創建一個顏色圖例用以展示顏色所表示的數值區域:
plt.contourf(X,?Y,?Z,?20,?cmap='RdGy') plt.colorbar();有了圖例,很容易可以看出黑色區域代表著“峰”,而紅色區域代表這“谷”。
上圖有一個缺點,那就是圖中顏色的階梯是離散的而不是連續的,這通常不是我們想要的。我們可以通過設置很高的輪廓線數量來改善,但是這會導致繪制圖表的性能降低:Matplotlib 必須在每個顏色階梯上繪制一條新的輪廓多邊形。更好的辦法是使用plt.imshow()函數,它會將一個二維的網格圖表轉換為一張圖像。
下面的例子展示了該方法:
plt.imshow(Z,?extent=[0,?5,?0,?5],?origin='lower',cmap='RdGy') plt.colorbar() plt.axis(aspect='image'); C:\Users\gdc\Anaconda3\lib\site-packages\ipykernel_launcher.py:4: MatplotlibDeprecationWarning: Passing unsupported keyword arguments to axis() will raise a TypeError in 3.3.after removing the cwd from sys.path.然而,在使用imshow()的時候也有一些坑:
plt.imshow()不接受 x 和 y 網格值作為參數,因此你需要手動指定extent參數[xmin, xmax, ymin, ymax]來設置圖表的數據范圍。
plt.imshow()使用的是默認的圖像坐標,即左上角坐標點是原點,而不是通常圖表的左下角坐標點。這可以通過設置origin參數來設置。
plt.imshow()會自動根據輸入數據調整坐標軸的比例;這可以通過參數來設置,例如,plt.axis(aspect='image')能讓 x 和 y 軸的單位一致。
最后,有時可能需要將輪廓圖和圖像結合起來。例如,下例中我們使用了半透明的背景圖像(通過alpha參數設置透明度),然后在背景圖層之上繪制了輪廓圖,并帶有每個輪廓的數值標簽(使用plt.clabel()函數繪制標簽):
contours?=?plt.contour(X,?Y,?Z,?3,?colors='black') plt.clabel(contours,?inline=True,?fontsize=8)plt.imshow(Z,?extent=[0,?5,?0,?5],?origin='lower',cmap='RdGy',?alpha=0.5) plt.colorbar();通過組合使用plt.contour、plt.contourf和plt.imshow這三個函數,基本可以滿足我們繪制所有這種在二維圖標上的三維數據的需求。需要了解更多函數的參數信息,參考它們的文檔字符串。如果你對于使用三維圖表展示這種數據感興趣,參見[在 matplotlib 中創建三維圖表]。
5.直方圖,分桶和密度
一個簡單的直方圖可以是我們開始理解數據集的第一步。前面我們看到了 Matplotlib 的直方圖函數,我們可以用一行代碼繪制基礎的直方圖,當然首先需要將需要用的包導入 notebook:
%matplotlib?inline import?numpy?as?np import?matplotlib.pyplot?as?plt plt.style.use('seaborn-white')data?=?np.random.randn(1000) plt.hist(data);hist()函數有很多的參數可以用來調整運算和展示;下面又一個更加個性化的直方圖展示:
譯者注:normed 參數已經過時,此處對代碼進行了相應修改,使用了替代的 density 參數。
plt.hist(data,?bins=30,?density=True,?alpha=0.5,histtype='stepfilled',?color='steelblue',edgecolor='none');plt.hist文檔中有更多關于個性化參數的信息。作者發現聯合使用histtype='stepfilled'和alpha參數設置透明度在對不同分布的數據集進行比較展示時很有用:
x1?=?np.random.normal(0,?0.8,?1000) x2?=?np.random.normal(-2,?1,?1000) x3?=?np.random.normal(3,?2,?1000)kwargs?=?dict(histtype='stepfilled',?alpha=0.3,?density=True,?bins=40)plt.hist(x1,?**kwargs) plt.hist(x2,?**kwargs) plt.hist(x3,?**kwargs);如果你只是需要計算直方圖的數值(即每個桶的數據點數量)而不是展示圖像,np.histogram()函數可以完成這個目標:
counts,?bin_edges?=?np.histogram(data,?bins=5) print(counts) [ 49 273 471 183 24]二維直方圖和分桶
正如前面我們可以在一維上使用數值對應的直線劃分桶一樣,我們也可以在二維上使用數據對應的點來劃分桶。本節我們介紹幾種實現的方法。首先定義數據集,從多元高斯分布中獲得x和y數組:
mean?=?[0,?0] cov?=?[[1,?1],?[1,?2]] x,?y?=?np.random.multivariate_normal(mean,?cov,?10000).Tplt.hist2d:二維直方圖
繪制二維直方圖最直接的方法是使用 Matplotlib 的plt.hist2d函數:
plt.hist2d(x,?y,?bins=30,?cmap='Blues') cb?=?plt.colorbar() cb.set_label('counts?in?bin')類似plt.hist,plt.hist2d有許多額外的參數來調整分桶計算和圖表展示,可以通過文檔了解更多信息。而且,plt.hist有np.histogram,plt.hist2d也有其對應的函數np.histogram2d。如下例:
counts,?xedges,?yedges?=?np.histogram2d(x,?y,?bins=30)如果要獲得更高維度的分桶結果,參見np.histogramd函數文檔。
plt.hexbin:六角形分桶
剛才的二維分桶是沿著坐標軸將每個桶分為正方形。另一個很自然的分桶形狀就是正六邊形。對于這個需求,Matplotlib 提供了plt.hexbin函數,它也是在二維平面上分桶展示,不過每個桶(即圖表上的每個數據格)將會是六邊形:
plt.hexbin(x,?y,?gridsize=30,?cmap='Blues') cb?=?plt.colorbar(label='count?in?bin')plt.hexbin有許多有趣的參數,包括能對每個點設置權重和將每個桶的輸出數據結果改為任意的 NumPy 聚合結果(帶權重的平均值,帶權重的標準差等)。
核密度估計
另外一個常用來統計多維數據密度的工具是核密度估計(KDE)。目前我們只需要知道 KDE 被認為是一種可以用來填補數據的空隙并補充上平滑變化數據的方法就足夠了??焖俸秃唵蔚?KDE 算法已經在scipy.stats模塊中有了成熟的實現。下面我們就一個簡單的例子來說明如何使用 KDE 和繪制相應的二維直方圖:
from?scipy.stats?import?gaussian_kde#?產生和處理數據,初始化KDE data?=?np.vstack([x,?y]) kde?=?gaussian_kde(data)#?在通用的網格中計算得到Z的值 xgrid?=?np.linspace(-3.5,?3.5,?40) ygrid?=?np.linspace(-6,?6,?40) Xgrid,?Ygrid?=?np.meshgrid(xgrid,?ygrid) Z?=?kde.evaluate(np.vstack([Xgrid.ravel(),?Ygrid.ravel()]))#?將圖表繪制成一張圖像 plt.imshow(Z.reshape(Xgrid.shape),origin='lower',?aspect='auto',extent=[-3.5,?3.5,?-6,?6],cmap='Blues') cb?=?plt.colorbar() cb.set_label("density")KDE 有著光滑的長度,可以在細節和光滑度中有效的進行調節(一個例子是方差偏差權衡)。這方面有大量的文獻介紹:高斯核密度估計gaussian_kde使用了經驗法則來尋找輸入數據附近的優化光滑長度值。
其他的 KDE 實現也可以在 SciPy 中找到,每一種都有它的優點和缺點;參見sklearn.neighbors.KernelDensity和statsmodels.nonparametric.kernel_density.KDEMultivariate。要繪制基于 KDE 進行可視化的圖表,Matplotlib 寫出的代碼會比較冗長。
6.自定義圖標圖例
圖例可以為可視化賦予實際含義,為不同的圖標元素附上明確說明。我們前面看到了一些簡單的圖例創建例子;本小節中我們來介紹一下在 Matplotlib 中自定義圖例的位置和進行美化的方法。
可以使用plt.legend()函數來創建最簡單的圖例,這個函數能自動創建任何帶有標簽屬性的圖表元素的圖例:
import?matplotlib.pyplot?as?plt plt.style.use('classic') %matplotlib?inline import?numpy?as?np x?=?np.linspace(0,?10,?1000) fig,?ax?=?plt.subplots() ax.plot(x,?np.sin(x),?'-b',?label='Sine') ax.plot(x,?np.cos(x),?'--r',?label='Cosine') ax.axis('equal') leg?=?ax.legend();但除此之外還有很多能自定義圖例的方法。例如,我們可以指定圖例位置并且去除邊框:
ax.legend(loc='upper?left',?frameon=False) fig我們可以使用ncol屬性設置圖例中每行的列數:
ax.legend(frameon=False,?loc='lower?center',?ncol=2) fig還可以使用圓角方框(fancybox)或者增加陰影,設置方框的透明度(alpha 值)或修改文字的邊距:
ax.legend(fancybox=True,?framealpha=1,?shadow=True,?borderpad=1) fig要獲取更多 legend 函數的可用選項信息,請參考plt.legend的文檔字符串。
選擇設置圖例的元素
正如我們前面例子所示,繪制的圖例默認包括所有帶標簽的元素。如果這不是想要的效果,我們可以調整哪些元素和標簽會出現在圖例當中,這可以通過設置 plot 函數或方法返回的對象實現。plt.plot函數能夠同時產生多條折線,然后將這些線條的實例列表返回。將其中的部分實例傳遞到plt.legend()函數就能設置哪些線條會出現在圖例中,再通過一個標簽的列表指定圖例的名稱:
y?=?np.sin(x[:,?np.newaxis]?+?np.pi?*?np.arange(0,?2,?0.5)) lines?=?plt.plot(x,?y)#?lines是一個線條實例的列表 plt.legend(lines[:2],?['first',?'second']);作者更加傾向于使用第一種方式,因為更加清晰。通過將標簽應用在圖表元素上,然后繪制到圖例中:
plt.plot(x,?y[:,?0],?label='first') plt.plot(x,?y[:,?1],?label='second') plt.plot(x,?y[:,?2:]) plt.legend(framealpha=1,?frameon=True);請注意默認情況下,legend 會忽略所有不帶標簽的元素。
散點大小的圖例
某些情況下默認的圖例不足以滿足特定的可視化需求。例如,你在使用散點的大小來標記數據的某個特征,然后希望創建一個相應的圖例。下面的例子是加州城市人口的散點圖,我們使用散點的大小表現該城市的面積,散點的顏色來表現城市的人口數量(自然對數值)。我們希望使用一個圖例來指明散點尺寸的比例,同時用一個顏色條來說明人口數量,我們可以通過自定義繪制一些標簽數據來實現尺寸圖例:
譯者注:新版 Matplotlib 已經取消 aspect 參數,此處改為使用新的'scaled'參數調用 axis 函數。
import?pandas?as?pd cities?=?pd.read_csv(r'D:\python\Github學習材料\Python數據科學手冊\data\california_cities.csv')#?提取我們感興趣的數據 lat,?lon?=?cities['latd'],?cities['longd'] population,?area?=?cities['population_total'],?cities['area_total_km2']#?繪制散點圖,使用尺寸代表面積,顏色代表人口,不帶標簽 plt.scatter(lon,?lat,?label=None,c=np.log10(population),?cmap='viridis',s=area,?linewidth=0,?alpha=0.5) plt.axis('scaled') plt.xlabel('longitude') plt.ylabel('latitude') plt.colorbar(label='log$_{10}$(population)') plt.clim(3,?7)#?下面我們創建圖例: #?使用空列表繪制圖例中的散點,使用不同面積和標簽,帶透明度 for?area?in?[100,?300,?500]:plt.scatter([],?[],?c='k',?alpha=0.3,?s=area,label=str(area)?+?'?km$^2$') plt.legend(scatterpoints=1,?frameon=False,?labelspacing=1,?title='City?Area')plt.title('California?Cities:?Area?and?Population');之前的圖例都關聯著圖表上的一些對象,因此如果我們需要展示圖例的話我們首先需要繪制圖表元素。在上例中,我們需要的圖例對象(灰色圓圈)不在圖表上,因此我們采用繪制空列表的方式將它們仿造在圖表上(實際上圖上沒有點),但是還是需要注意,只有那些帶標簽的元素才會出現在圖例中。
通過繪制空列表,我們創建了三個帶標簽的對象,然后就可以出現在圖例當中,這個圖例就能表示出有關城市面積的相關信息。這個策略在很多復雜可視化圖表構建過程中都被用到。
最后我們注意到這個圖表實際上是一個地理位置圖表,如果我們能在上面繪制州界線或其他地圖相關的元素的話,會更加清晰。Matplotlib 提供了一個 Basemap 額外工具集來實現這個目標。
多重圖例
有時候我們可能需要在同一個圖表維度中設計多個圖例。不幸的是,Matplotlib 并沒有提供很簡單的方式實現:通過標準的legend接口,只能在整張圖表上創建一個圖例。如果你試圖使用plt.legend()或ax.legend()創建第二個圖例,那么第二條語句創建的圖例會覆蓋第一條語句創建的。我們只能通過從底層開始來創建一個新的圖例 artist 這種方法來解決這個問題,然后使用ax.add_artist()的底層方法手動將第二個作者加到圖表上:
fig,?ax?=?plt.subplots()lines?=?[] styles?=?['-',?'--',?'-.',?':'] x?=?np.linspace(0,?10,?1000)for?i?in?range(4):lines?+=?ax.plot(x,?np.sin(x?-?i?*?np.pi?/?2),styles[i],?color='black') ax.axis('equal')#?指定第一個圖例的線條和標簽 ax.legend(lines[:2],?['line?A',?'line?B'],loc='upper?right',?frameon=False)#?手動創建第二個圖例,并將作者添加到圖表中 from?matplotlib.legend?import?Legend leg?=?Legend(ax,?lines[2:],?['line?C',?'line?D'],loc='lower?right',?frameon=False) ax.add_artist(leg);上例展示了用來組成任何 Matplotlib 圖表的底層 artist 對象的簡單說明。如果你去查看ax.legend()的源代碼(你可以通過 IPython 的ax.legend幫助工具做到),你可以看到這個方法包含了用來構建合適Legend的 artist 對象的邏輯,構建的對象被保存在legend_屬性當中,當繪制時被添加到圖表上進行展示。
7.個性化顏色條
圖例可以將離散的點標示為離散的標簽。對于建立在不同顏色之上的連續的值(點線面)來說,標注了的顏色條是非常方便的工具。Matplotlib 的顏色條是獨立于圖表之外的一個類似于比色卡的圖形,用來展示圖表中不同顏色的數值含義。因為本書是使用黑白打印的,本節內容中的所有帶色彩的圖都可以在(https://github.com/wangyingsm/Python-Data-Science-Handbook)中找到。我們還是首先導入本節需要的包和模塊:
import?matplotlib.pyplot?as?plt plt.style.use('classic') %matplotlib?inline import?numpy?as?np通過plt.colorbar函數可以創建最簡單的顏色條,在本節中我們會多次看到:
x?=?np.linspace(0,?10,?1000) I?=?np.sin(x)?*?np.cos(x[:,?np.newaxis])plt.imshow(I) plt.colorbar();我們下面來討論如何個性化顏色條以及在不同的場合高效的使用它們。
自定義顏色條
顏色條可以通過cmap參數指定使用的色譜系統(或叫色圖):
plt.imshow(I,?cmap='gray');所有可用的色圖都可以在plt.cm模塊中找到;在 IPython 中使用 Tab 自動補全功能能列出所有的色圖列表:
plt.cm.<TAB>但是知道在哪里選擇色圖只是第一步,更重要的是在各種選項中選出合適的色圖。這個選擇比你預料的要微妙的多。
選擇色圖
在可視化方案中選擇顏色完整的介紹說明超出了本書的范圍,如果你對這個課題和相關內容有興趣,可以參考文章["繪制更漂亮圖表的 10 個簡單規則"](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1003833)。Matplotlib 的在線文檔也有一章關于色圖選擇的有趣討論[5]。
通常來說,你應該注意以下三種不同類型的色圖:
序列色圖:這類型的色譜只包括一個連續序列的色系(例如binary或viridis)。
分化色圖:這類型的色譜包括兩種獨立的色系,這兩種顏色有著非常大的對比度(例如RdBu或PuOr)。
定性色圖:這類型的色圖混合了非特定連續序列的顏色(例如rainbow或jet)。
jet色圖,在 Matplotlib 2.0 版本之前都是默認的色圖,是定性色圖的一個例子。jet作為默認色圖的位置其實有點尷尬,因為定性圖通常都不是對定量數據進行展示的好選擇。原因是定性圖通常都不能在范圍增加時提供亮度的均勻增長。
我們可以通過將jet顏色條轉換為黑白來看到這點:
from?matplotlib.colors?import?LinearSegmentedColormapdef?grayscale_cmap(cmap):"""返回給定色圖的灰度版本"""cmap?=?plt.cm.get_cmap(cmap)?#?使用名稱獲取色圖對象colors?=?cmap(np.arange(cmap.N))?#?將色圖對象轉為RGBA矩陣,形狀為N×4#?將RGBA顏色轉換為灰度#?參考?http://alienryderflex.com/hsp.htmlRGB_weight?=?[0.299,?0.587,?0.114]?#?RGB三色的權重值luminance?=?np.sqrt(np.dot(colors[:,?:3]?**?2,?RGB_weight))?#?RGB平方值和權重的點積開平方根colors[:,?:3]?=?luminance[:,?np.newaxis]?#?得到灰度值矩陣#?返回相應的灰度值色圖return?LinearSegmentedColormap.from_list(cmap.name?+?"_gray",?colors,?cmap.N)def?view_colormap(cmap):"""將色圖對應的灰度版本繪制出來"""cmap?=?plt.cm.get_cmap(cmap)colors?=?cmap(np.arange(cmap.N))cmap?=?grayscale_cmap(cmap)grayscale?=?cmap(np.arange(cmap.N))fig,?ax?=?plt.subplots(2,?figsize=(6,?2),subplot_kw=dict(xticks=[],?yticks=[]))ax[0].imshow([colors],?extent=[0,?10,?0,?1])ax[1].imshow([grayscale],?extent=[0,?10,?0,?1]) view_colormap('jet')注意一下上面的灰度圖中亮條紋的位置。即使在上述彩色圖中,也出現了這種不規則的亮條紋,這會導致眼睛被區域中亮條紋所吸引,這很可能造成閱讀者被不重要的數據集部分干擾了。更好的選擇是使用類似viridis這樣的色圖(Matplotlib 2.0 后默認色圖),它們被設計為有著均勻的亮度變化。因此它們無論是在彩色圖中還是在灰度圖中都有著同樣的亮度變化:
view_colormap('viridis')如果你更喜歡彩虹方案,另一個好的選擇是使用cubehelix色圖:
view_colormap('cubehelix')對于其他的情況,例如某種正負分布的數據集,雙色顏色條如RdBu(Red-Blue)會很常用。然而正如你從下面例子看到的,如果將雙色顏色條轉化為灰度的話,正負或兩級的信息就會丟失:
view_colormap('RdBu')后面我們會看到更多使用這些色圖的例子。
Matplotlib 中有大量可用的色圖;要看到它們的列表,你可以使用 IPython 來探索plt.cm模塊。要在 Python 中更加正規的使用顏色,你可以查看 Seaborn 庫的工具和文檔。
顏色限制和擴展
Matplotlib 允許你對顏色條進行大量的自定義。顏色條本身就是一個plt.Axes對象,因此所有軸和刻度定制的技巧都可以應用在上面。顏色條也有著一些有趣的自定義行為:例如,我們可以縮小顏色的范圍并且通過設置extend參數將超出范圍之外的數值展示為頂部和底部的三角箭頭形狀。這對于展示一些受到噪聲干擾的數據時非常方便:
#?在I數組中人為生成不超過1%的噪聲 speckles?=?(np.random.random(I.shape)?<?0.01) I[speckles]?=?np.random.normal(0,?3,?np.count_nonzero(speckles))plt.figure(figsize=(10,?3.5)) #?不考慮去除噪聲時的顏色分布 plt.subplot(1,?2,?1) plt.imshow(I,?cmap='RdBu') plt.colorbar() #?設置去除噪聲時的顏色分布 plt.subplot(1,?2,?2) plt.imshow(I,?cmap='RdBu') plt.colorbar(extend='both') plt.clim(-1,?1);注意到在左邊的圖表中,默認的顏色閾值是包括了噪聲的,因此整體的條紋形狀都被噪聲數據沖刷淡化了。而右邊的圖表,我們手動設置了顏色的閾值,并在繪制顏色條是加上了extend參數來表示超出閾值的數據。對于我們的數據來說,右圖比左圖要好的多。
離散顏色條
色圖默認是連續的,但是在某些情況下你可能需要展示離散值。最簡單的方法是使用plt.cm.get_cmap()函數,在傳遞某個色圖名稱的同時,還額外傳遞一個顏色分桶的數量值參數給該函數:
plt.imshow(I,?cmap=plt.cm.get_cmap('Blues',?6)) plt.colorbar() plt.clim(-1,?1);離散色圖的使用方式和其他色圖沒有任何區別。
例子:手寫數字
最后我們來看一個很有實用價值的例子,讓我們實現對一些手寫數字圖像數據的可視化分析。這個數據包含在 Sciki-Learn 中,以供包含有將近 2,000 張??大小的不同筆跡的手寫數字縮略圖。
首先,我們下載這個數據集,然后使用plt.imshow()將其中部分數據展示出來:
#?讀取數字0-5的手寫圖像,然后使用Matplotlib展示頭64張縮略圖 from?sklearn.datasets?import?load_digits digits?=?load_digits(n_class=6)fig,?ax?=?plt.subplots(8,?8,?figsize=(6,?6)) for?i,?axi?in?enumerate(ax.flat):axi.imshow(digits.images[i],?cmap='binary')axi.set(xticks=[],?yticks=[])因為每個數字都是使用 64 個像素點渲染出來的,我們可以認為每個數字是一個 64 維空間中的點:每個維度代表這其中一個像素的灰度值。但是要在圖表中將這么高維度空間的聯系可視化出來是非常困難的。有一種做法是使用降維技術,比方說使用流形學習來減少數據的維度然而不會丟失數據中有效的信息。
我們來看一下將這些手寫數字圖像數據映射到二維流形學習當中:
#?使用Isomap將手寫數字圖像映射到二維流形學習中 from?sklearn.manifold?import?Isomap iso?=?Isomap(n_components=2) projection?=?iso.fit_transform(digits.data)我們使用離散顏色條來展示結果,設置ticks和clim來進一步美化結果的顏色條:
#?繪制圖表結果 plt.scatter(projection[:,?0],?projection[:,?1],?lw=0.1,c=digits.target,?cmap=plt.cm.get_cmap('cubehelix',?6)) plt.colorbar(ticks=range(6),?label='digit?value') plt.clim(-0.5,?5.5)我們從流形學習中的映射中可以觀察到一些有趣現象:例如,圖表中 5 和 3 有一些重疊的部分,這表示一些手寫體中 5 和 3 是比較難以辨別的,因此對于自動識別算法來說這是比較容易混淆的部分。而 0 和 1,它們在圖表中距離很遠,這表示兩者比較容易辨別,不太可能造成混淆。這個圖表分析與我們的直覺一致,因為 5 和 3 顯然比 0 和 1 看起來更加接近。
8.多個子圖表
在一些情況中,如果能將不同的數據圖表并列展示,對于我們進行數據分析和比較會很有幫助。Matplotlib 提供了子圖表的概念來實現這一點:單個圖表中可以包括一組小的 axes 用來展示多個子圖表。這些子圖表可以是插圖,網格狀分布或其他更復雜的布局。在本節中我們會介紹 Matplotlib 中用來構建子圖表的四個函數。
%matplotlib?inline import?matplotlib.pyplot?as?plt plt.style.use('seaborn-white') import?numpy?as?npplt.axes:手動構建子圖表
構建 axes 作為子圖表的最基礎方法就是使用plt.axes函數。正如我們前面已經看到,默認情況下,這個函數夠創建一個標準的 axes 對象填滿整個圖表區域。plt.axes函數也可以接收一個可選的列表參數用來指定在 axes 在整個圖表中的坐標點位置。列表中有四個數值分別為[left, bottom, width, height](取值都是 0-1),代表著子圖表的左邊、底部、寬度、高度在整個圖表中左邊、底部、寬度、高度所占的比例值。
例如,我們可以在距離左邊和底部 65%的位置,以插圖的形式放置一個寬度和高度都是 20%子圖表,上述數值應該為[0.65, 0.65, 0.2, 0.2]:
ax1?=?plt.axes()??#?標準圖表 ax2?=?plt.axes([0.65,?0.65,?0.2,?0.2])?#子圖表與上述等價的面向對象接口的語法是fig.add_axes()。我們使用這個方法來創建兩個垂直堆疊的子圖表:
fig?=?plt.figure()?#?獲得figure對象 ax1?=?fig.add_axes([0.1,?0.5,?0.8,?0.4],xticklabels=[],?ylim=(-1.2,?1.2))?#?左邊10%?底部50%?寬80%?高40% ax2?=?fig.add_axes([0.1,?0.1,?0.8,?0.4],ylim=(-1.2,?1.2))?#?左邊10%?底部10%?寬80%?高40%x?=?np.linspace(0,?10) ax1.plot(np.sin(x)) ax2.plot(np.cos(x));這樣我們就有兩個子圖表(上面的子圖表沒有 x 軸刻度),這兩個子圖表正好吻合:上面圖表的底部是整個圖表高度 50%位置,而下面圖表的頂部也是整個圖表的 50%位置(0.1+0.4)。
plt.subplot:簡單網格的子圖表
將子圖表的行與列對齊是一個很常見的需求,因此 Matplotlib 提供了一些簡單的函數來實現它們。這些函數當中最底層的是plt.subplot(),它會在網格中創建一個子圖表。函數接受三個整數參數,網格行數,網格列數以及該網格子圖表的序號(從左上角向右下角遞增):
for?i?in?range(1,?7):plt.subplot(2,?3,?i)plt.text(0.5,?0.5,?str((2,?3,?i)),fontsize=18,?ha='center')plt.subplots_adjust函數用來調整這些子圖表之間的距離。下面的代碼使用了與plt.subplot()等價的面向對象接口方法fig.add_subplot():
fig?=?plt.figure() fig.subplots_adjust(hspace=0.4,?wspace=0.4) for?i?in?range(1,?7):ax?=?fig.add_subplot(2,?3,?i)ax.text(0.5,?0.5,?str((2,?3,?i)),fontsize=18,?ha='center')上例中我們指定了plt.subplots_adjust函數的hspace和wspace參數,它們代表這沿著高度和寬度方向子圖表之間的距離,單位是子圖表的大小(在本例中,距離是子圖表寬度和高度的 40%)。
plt.subplots:一句代碼設置所有網格子圖表
上面的方法在我們需要創建大量的子圖表網格時會變得非常冗長乏味,特別是如果我們需要將內部圖表 x 軸和 y 軸標簽隱藏的情況下。因此,plt.subplots在這種情況下是一個合適的工具(注意末尾有個 s)。這個函數會一次性創建所有的網格子圖表,而不是單個網格,并將它們存儲在一個 NumPy 數組中返回。參數是行數和列數,還有兩個可選的關鍵字參數sharex和sharey,可以讓你指定不同子圖表之間的關聯。
下面我們來創建一個??網格的子圖表,其中每一行的子圖表共享它們的 y 軸,而每一列的子圖表共享它們的 x 軸:
fig,?ax?=?plt.subplots(2,?3,?sharex='col',?sharey='row')注意上面我們設置了sharex和sharey之后,內部子圖表的 x 軸和 y 軸的標簽就自動被去掉了。返回值中 ax 是一個 NumPy 數組,里面含有每一個子圖表的實例,你可以使用 NumPy 索引的語法很簡單的獲得它們:
#?axes是一個2×3的數組,可以通過[row,?col]進行索引訪問 for?i?in?range(2):for?j?in?range(3):ax[i,?j].text(0.5,?0.5,?str((i,?j)),fontsize=18,?ha='center') fig并且相對于plt.subplot,plt.subplots()更復合 Python 從 0 開始進行索引的習慣。
plt.GridSpec:更復雜的排列
當你需要子圖表在網格中占據多行或多列時,plt.GridSpec()正是你所需要的。plt.GridSpec()對象并不自己創建圖表;它只是一個可以被傳遞給plt.subplot()的參數。例如,一個兩行三列并帶有指定的寬度高度間隔的 gridspec 可以如下創建:
grid?=?plt.GridSpec(2,?3,?wspace=0.4,?hspace=0.3)使用這個對象我們可以指定子圖表的位置和占據的網格,僅需要使用熟悉的 Python 切片語法即可:
plt.subplot(grid[0,?0]) plt.subplot(grid[0,?1:]) plt.subplot(grid[1,?:2]) plt.subplot(grid[1,?2]);這種靈活的網格對齊控制方式有著廣泛的應用。作者經常在需要創建多個直方圖的聯合圖表中使用這種方法,如下例:
#?構建二維正態分布數據 mean?=?[0,?0] cov?=?[[1,?1],?[1,?2]] x,?y?=?np.random.multivariate_normal(mean,?cov,?3000).T#?使用GridSpec創建網格并加入子圖表 fig?=?plt.figure(figsize=(6,?6)) grid?=?plt.GridSpec(4,?4,?hspace=0.2,?wspace=0.2) main_ax?=?fig.add_subplot(grid[:-1,?1:]) y_hist?=?fig.add_subplot(grid[:-1,?0],?xticklabels=[],?sharey=main_ax) x_hist?=?fig.add_subplot(grid[-1,?1:],?yticklabels=[],?sharex=main_ax)#?在主圖表中繪制散點圖 main_ax.plot(x,?y,?'ok',?markersize=3,?alpha=0.2)#?分別在x軸和y軸方向繪制直方圖 x_hist.hist(x,?40,?histtype='stepfilled',orientation='vertical',?color='gray') x_hist.invert_yaxis()?#?x軸方向(右下)直方圖倒轉y軸方向y_hist.hist(y,?40,?histtype='stepfilled',orientation='horizontal',?color='gray') y_hist.invert_xaxis()?#?y軸方向(左上)直方圖倒轉x軸方向這種沿著數據各自方向分布并繪制相應圖表的需求是很通用的,因此在 Seaborn 包中它們有專門的 API 來實現。
9.文本和標注
創建一個優秀的可視化圖表的關鍵在于引導讀者,讓他們能理解圖表所講述的故事。在一些情況下,這個故事可以通過純圖像的方式表達,不需要額外添加文字,但是在另外一些情況中,圖表需要文字的提示和標簽才能將故事講好。也許標注最基本的類型就是圖表的標簽和標題,但是其中的選項參數卻有很多。讓我們在本節中使用一些數據來創建可視化圖表并標注這些圖表來表達這些有趣的信息。首先還是需要將要用到的模塊和包導入 notebook:
%matplotlib?inline import?matplotlib.pyplot?as?plt import?matplotlib?as?mpl plt.style.use('seaborn-whitegrid') import?numpy?as?np import?pandas?as?pd例子:節假日對美國出生率的影響
本例中的數據可以在 https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv 下載。
我們先按照前面的方式進行同樣的數據清洗程序,然后以圖表展示這個結果:
births?=?pd.read_csv(r'D:\python\Github學習材料\Python數據科學手冊\data\births.csv')quartiles?=?np.percentile(births['births'],?[25,?50,?75]) mu,?sig?=?quartiles[1],?0.74?*?(quartiles[2]?-?quartiles[0]) births?=?births.query('(births?>?@mu?-?5?*?@sig)?&?(births?<?@mu?+?5?*?@sig)')births['day']?=?births['day'].astype(int)births.index?=?pd.to_datetime(10000?*?births.year?+100?*?births.month?+births.day,?format='%Y%m%d') births_by_date?=?births.pivot_table('births',[births.index.month,?births.index.day]) births_by_date.index?=?[pd.datetime(2012,?month,?day)for?(month,?day)?in?births_by_date.index] C:\Users\gdc\Anaconda3\lib\site-packages\ipykernel_launcher.py:15: FutureWarning: The pandas.datetime class is deprecated and will be removed from pandas in a future version. Import from datetime module instead.from ipykernel import kernelapp as app fig,?ax?=?plt.subplots(figsize=(12,?4)) births_by_date.plot(ax=ax);當我們繪制了這樣的圖表來表達數據時,如果我們能對一些圖表的特性作出標注來,這對吸引讀者的注意力通常是非常有幫助的。這可以通過調用plt.text或ax.text函數來實現,它們可以在某個特定的 x,y 軸位置輸出一段文字:
fig,?ax?=?plt.subplots(figsize=(12,?4)) births_by_date.plot(ax=ax)#?在折線的特殊位置標注文字 style?=?dict(size=10,?color='gray')ax.text('2012-1-1',?3950,?"New?Year's?Day",?**style) ax.text('2012-7-4',?4250,?"Independence?Day",?ha='center',?**style) ax.text('2012-9-4',?4850,?"Labor?Day",?ha='center',?**style) ax.text('2012-10-31',?4600,?"Halloween",?ha='right',?**style) ax.text('2012-11-25',?4450,?"Thanksgiving",?ha='center',?**style) ax.text('2012-12-25',?3850,?"Christmas?",?ha='right',?**style)#?設置標題和y軸標簽 ax.set(title='USA?births?by?day?of?year?(1969-1988)',ylabel='average?daily?births')#?設置x軸標簽月份居中 ax.xaxis.set_major_locator(mpl.dates.MonthLocator()) ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15)) ax.xaxis.set_major_formatter(plt.NullFormatter()) ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'));ax.text方法接收 x 位置、y 位置、一個字符串和額外可選的關鍵字參數可以用來設置顏色、大小、樣式、對齊等文本格式。上面我們使用了ha='right'和ha='center',這里的ha是*hirizonal alignment(水平對齊)*的縮寫。要查閱更多的可用參數,請查看plt.text()和mpl.text.Text()的文檔字符串內容。
轉換和文本位置
在剛才的例子中,我們將文字標注根據數據位置進行了定位。有些時候我們需要將文字標注獨立于數據位置而根據圖表位置進行定位。Matplotlib 通過轉換完成這項工作。
任何的圖形顯示框架都需要在坐標系統之間進行轉換的機制。例如,一個數據點位于??被轉換為圖表中的某個位置,進而轉換為屏幕上顯示的像素。這樣的坐標轉換在數學上都相對來說比較直接,而且 Matplotlib 提供了一系列的工具實現了轉換(這些工具可以在matplotlib.transforms模塊中找到)。
一般來說,用戶很少需要關注這些轉換的細節,但是當考慮將文本在圖表上展示時,這些知識卻比較有用。在這種情況中,下面三種定義好的轉換是比較有用的:
ax.transData:與數據坐標相關的轉換
ax.tranAxes:與 Axes 尺寸相關的轉換(單位是 axes 的寬和高)
ax.tranFigure:與 figure 尺寸相關的轉換(單位是 figure 的寬和高)
下面我們來看看使用這些轉換將文字寫在圖表中不同位置的例子:
fig,?ax?=?plt.subplots(facecolor='lightgray') ax.axis([0,?10,?0,?10])#?transform=ax.transData是默認的,這里寫出來是為了明確對比 ax.text(1,?5,?".?Data:?(1,?5)",?transform=ax.transData) ax.text(0.5,?0.1,?".?Axes:?(0.5,?0.1)",?transform=ax.transAxes) ax.text(0.2,?0.2,?".?Figure:?(0.2,?0.2)",?transform=fig.transFigure);注意默認情況下,文字是在指定坐標位置靠左對齊的:這里每個字符串開始的"."的位置就是每種轉換的坐標位置。
transData坐標給定的是通常使用的 x 和 y 軸坐標位置。transAxes坐標給定的是從 axes 左下角開始算起(白色區域)的坐標位置,使用的是寬度和長度的占比。transFigure坐標類似,給定的是從 figure 左下角開始算起(灰色區域)的坐標位置,使用的也是寬度和長度的占比。
因此如果我們改變了軸的最大長度,只有transData坐標會收到影響,其他兩個還是保持在相同位置:
ax.set_xlim(0,?2) ax.set_ylim(-6,?6) fig這個變化可以通過動態改變軸的最大長度看的更加清楚:如果你在 notebook 執行這段代碼,你可以將%matplotlib inline改為%matplotlib notebook,然后使用圖表的菜單來交互式的改變圖表。
箭頭和標注
除了刻度標簽和文字標簽,另一種常用的標注是箭頭。
在 Matplotlib 中繪制箭頭通常比你想象的難得多。雖然有plt.arrow()函數,作者不建議使用它:這個函數繪制的箭頭是一個 SVG 對象,因此在圖表使用不同的比例的情況會產生問題,結果通常不能讓用戶滿意。因此,作者建議使用plt.annotate()函數。這個函數會繪制一些文字以及一個箭頭,并且箭頭可以非常靈活的進行配置。
下面我們提供一些參數來使用annotate函數:
%matplotlib?inlinefig,?ax?=?plt.subplots()x?=?np.linspace(0,?20,?1000) ax.plot(x,?np.cos(x)) ax.axis('equal')ax.annotate('local?maximum',?xy=(6.28,?1),?xytext=(10,?4),arrowprops=dict(facecolor='black',?shrink=0.05))ax.annotate('local?minimum',?xy=(5?*?np.pi,?-1),?xytext=(2,?-6),arrowprops=dict(arrowstyle="->",connectionstyle="angle3,angleA=0,angleB=-90"));箭頭的樣式是使用箭頭屬性字典值進行控制的,里面有很多可用的參數。這些參數在 Matplotlib 的在線文檔中已經有了很詳細的說明,因此在這里就不將這部分內容重復介紹一遍了。我們在前面出生率圖上再使用一些參數進行更多的說明:
fig,?ax?=?plt.subplots(figsize=(12,?4)) births_by_date.plot(ax=ax)#?為圖表添加標注 ax.annotate("New?Year's?Day",?xy=('2012-1-1',?4100),??xycoords='data',xytext=(50,?-30),?textcoords='offset?points',arrowprops=dict(arrowstyle="->",connectionstyle="arc3,rad=-0.2"))ax.annotate("Independence?Day",?xy=('2012-7-4',?4250),??xycoords='data',bbox=dict(boxstyle="round",?fc="none",?ec="gray"),xytext=(10,?-40),?textcoords='offset?points',?ha='center',arrowprops=dict(arrowstyle="->"))ax.annotate('Labor?Day',?xy=('2012-9-4',?4850),?xycoords='data',?ha='center',xytext=(0,?-20),?textcoords='offset?points') ax.annotate('',?xy=('2012-9-1',?4850),?xytext=('2012-9-7',?4850),xycoords='data',?textcoords='data',arrowprops={'arrowstyle':?'|-|,widthA=0.2,widthB=0.2',?})ax.annotate('Halloween',?xy=('2012-10-31',?4600),??xycoords='data',xytext=(-80,?-40),?textcoords='offset?points',arrowprops=dict(arrowstyle="fancy",fc="0.6",?ec="none",connectionstyle="angle3,angleA=0,angleB=-90"))ax.annotate('Thanksgiving',?xy=('2012-11-25',?4500),??xycoords='data',xytext=(-120,?-60),?textcoords='offset?points',bbox=dict(boxstyle="round4,pad=.5",?fc="0.9"),arrowprops=dict(arrowstyle="->",connectionstyle="angle,angleA=0,angleB=80,rad=20"))ax.annotate('Christmas',?xy=('2012-12-25',?3850),??xycoords='data',xytext=(-30,?0),?textcoords='offset?points',size=13,?ha='right',?va="center",bbox=dict(boxstyle="round",?alpha=0.1),arrowprops=dict(arrowstyle="wedge,tail_width=0.5",?alpha=0.1));#?設置圖表標題和坐標軸標記 ax.set(title='USA?births?by?day?of?year?(1969-1988)',ylabel='average?daily?births')#?設置月份坐標居中顯示 ax.xaxis.set_major_locator(mpl.dates.MonthLocator()) ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15)) ax.xaxis.set_major_formatter(plt.NullFormatter()) ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'));ax.set_ylim(3600,?5400);上圖中箭頭和文字框都非常詳盡了:可以看出你幾乎可以使用plt.annotate創建任何你想要的箭頭樣式。不幸的是,這意味著這種特性都需要手工進行調整,因此如果需要獲得印刷質量的圖像,這將是一個非常耗費時間的工作。最后,必須指出,上述這種多種樣式混合的方式來展現數據肯定不是最佳實踐,這里只是為了盡可能多的介紹可用的參數。
更多關于 Matplotlib 的箭頭和標注樣式的討論和例子可以訪問 Matplotlib gallery,特別是標注演示[6]。
10.自定義刻度
Matplotlib 默認的刻度標志和格式被設計成能滿足許多通用場景的需求,但是不會是所有圖表的最佳選擇。本節會介紹一些調整刻度位置和格式的例子來說明自定義刻度的使用。
在介紹例子之前,我們應該加深對 Matplotlib 圖表的對象層次的理解。Matplotlib 的設計目標是展示在圖表中的所有內容都會表達成為 Python 的對象:例如,回憶前面我們介紹過figure指的是用來展示圖表所有內容的方框。每個 Matplotlib 對象也被設計為其子對象的一個容器:例如figure對象中可以包含一個或多個axes對象,每個axes對象都依次包含著其他用來展示圖表的內容對象。
刻度也不例外。每個axes對象都有著屬性xaxis和yaxis,表示 x 和 y 軸,其中包含著所有的屬性用來指代軸的線、刻度和標簽。
主要的和次要的刻度
在每個坐標軸上,都有主要的刻度和次要的刻度概念。正如名字指代的,主要刻度通常是大的和更多用到的,而次要刻度通常是小的。默認 Matplotlib 很少使用次要刻度,但是在對數圖表中我們可能會看到它們:
在 Matplotlib 2.0 之后,當 axis 的跨度過大時,默認次要刻度將會不再展示,因此,下面的代碼經過了修改,加上了 xlim 和 ylim 參數。
import?matplotlib.pyplot?as?plt plt.style.use('classic') %matplotlib?inline import?numpy?as?np ax?=?plt.axes(xscale='log',?yscale='log',?xlim=[10e-5,?10e5],?ylim=[10e-5,?10e5]) ax.grid();我們看到每個主要刻度顯示了一個大的標志和標簽,而每個次要刻度顯示了一個小的刻度標志沒有標簽。
這些刻度屬性,位置和標簽,都可以使用每個軸的formatter和locator對象進行個性化設置。下面我們來查看一下 x 軸的相應對象:
print(ax.xaxis.get_major_locator()) print(ax.xaxis.get_minor_locator()) <matplotlib.ticker.LogLocator object at 0x000001E8074AF108> <matplotlib.ticker.LogLocator object at 0x000001E8074AD908> print(ax.xaxis.get_major_formatter()) print(ax.xaxis.get_minor_formatter()) <matplotlib.ticker.LogFormatterSciNotation object at 0x000001E8074AEB88> <matplotlib.ticker.LogFormatterSciNotation object at 0x000001E8074ADB48>我們看到主要和次要刻度的位置都是使用LogLocator來設置的(對于對數圖表來說那是理所當然的)。然而次要刻度的標簽的格式是NullFormatter:這表示次要刻度不會顯示標簽。
譯者注:新版 Matplotlib 已經修改,可以看到 Formatter 都統一成為了 LogFormatterSciNotation,再根據圖表實際情況選擇是否展示標簽。
下面我們就可以開始介紹一些設置這些 locator 和 formatter 的例子了。
隱藏刻度和標簽
也許最常見的刻度/標簽格式設置的操作是隱藏刻度或標簽。這可以通過使用plt.NullLocator()和plt.NullFormatter()來設置,如下例:
ax?=?plt.axes() ax.plot(np.random.rand(50))ax.yaxis.set_major_locator(plt.NullLocator()) ax.xaxis.set_major_formatter(plt.NullFormatter())注意上圖中我們去除了 x 軸的標簽(但是保留了刻度或網格線),y 軸的刻度和標簽都被去除了。圖表中沒有刻度和標簽在很多情況下很有用,例如,當你希望展示一個圖像的網格。比方說,考慮下面的圖表,包含著不同的頭像,一個很常見的監督機器學習問題:
fig,?ax?=?plt.subplots(5,?5,?figsize=(5,?5)) fig.subplots_adjust(hspace=0,?wspace=0)#?從scikit-learn載入頭像數據集 from?sklearn.datasets?import?fetch_olivetti_faces faces?=?fetch_olivetti_faces().imagesfor?i?in?range(5):for?j?in?range(5):ax[i,?j].xaxis.set_major_locator(plt.NullLocator())ax[i,?j].yaxis.set_major_locator(plt.NullLocator())ax[i,?j].imshow(faces[10?*?i?+?j],?cmap="bone") downloading Olivetti faces from https://ndownloader.figshare.com/files/5976027 to C:\Users\gdc\scikit_learn_data注意上圖中每張圖像都有它自己的 axes,我們將每一個 axes 的 locator 都設置為 null 因為這些刻度值(像素值)在這里并沒有任何實際意義。
減少或增加刻度的數量
默認設置的一個常見問題是當子圖表較小時,刻度標簽可能會粘在一起。我們可以從下面例子看到:
fig,?ax?=?plt.subplots(4,?4,?sharex=True,?sharey=True)特別是 x 軸,標簽的數字就快重疊在一起了,這讓這些標簽難以認清。我們可以通過plt.MaxNLocator()來修正這點,用它可以設置最大展示刻度的數量。Matplotlib 會自己計算按照這個最大數量計算的刻度位置:
#?對x和y軸設置刻度最大數量 for?axi?in?ax.flat:axi.xaxis.set_major_locator(plt.MaxNLocator(3))axi.yaxis.set_major_locator(plt.MaxNLocator(3)) fig上圖就清晰多了。如果你希望對于刻度位置進行更加精細的控制,你可以使用plt.MultipleLocator,我們會接下來討論這個對象。
復雜的刻度格式
Matplotlib 的默認刻度格式只能在很多常見情況下工作良好,但是在特殊情況下你會希望能夠更多的進行個性化。考慮下面的正弦和余弦圖表:
#?繪制正弦和余弦圖表 fig,?ax?=?plt.subplots() x?=?np.linspace(0,?3?*?np.pi,?1000) ax.plot(x,?np.sin(x),?lw=3,?label='Sine') ax.plot(x,?np.cos(x),?lw=3,?label='Cosine')#?設置網格、圖例和軸極限 ax.grid(True) ax.legend(frameon=False) ax.axis('equal') ax.set_xlim(0,?3?*?np.pi);這里有幾個我們希望進行的改變。首先,如果刻度的間距和網格線是??的倍數會顯得更加自然。我們可以通過MultipleLocator來設置它,這個對象用來設置刻度的配置。為了更直觀,我們設置主要刻度為??位置,設置次要刻度為??位置:
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi?/?2)) ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi?/?4)) fig但是上圖看起來有點傻:我們可以看出刻度確實是??的倍數,但是使用了小數的展示讓它們看起來很奇怪。要修正這些標簽,我們需要修改刻度的 formatter。在這種情況中,沒有內建的 formatter 可以給我們使用,因此我們使用plt.FuncFormatter,這個對象能夠接受一個用戶自定義的函數來提供對于刻度標簽的精細控制:
def?format_func(value,?tick_number):#?N是pi/2的倍數N?=?int(np.round(2?*?value?/?np.pi))if?N?==?0:return?"0"?#?0點elif?N?==?1:return?r"$\frac{\pi}{2}$"?#?pi/2elif?N?==?2:return?r"$\pi$"?#?pielif?N?%?2?>?0:return?r"$\frac{{%d}\pi}{2}$"?%N?#?n*pi/2?n是奇數else:return?r"${0}\pi$".format(N?//?2)?#?n*pi?n是整數ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func)) fig上圖看起來好多了。注意到我們使用到了 Matplotlib 的 LaTeX 支持,使用美元符號將 LaTeX 字符串括起來。這是用來展示數學符號和公式的簡便方法:在這個例子中"$\pi$"被渲染成希臘字母?。
plt.FuncFomatter()提供了對于圖表刻度最高級的自定義和精細控制,并且當你需要創建需要印刷或出版的圖表時非常方便。
Formatter 和 Locator 總結
我們已經介紹了一些 formatter 和 locator。在最后我們通過將內建的 locator 和 formatter 參數列出來對本節做一個總結。要獲得更多相關內容,請參閱文檔或 Matplotlib 的在線文檔。下表中列出的對象在plt命名空間中都是有效的:
Locator 對象
描述
NullLocator | 無刻度 |
FixedLocator | 固定刻度位置 |
IndexLocator | 序號圖表刻度 (例如 x = range(len(y))) |
LinearLocator | 從最小到最大值的均勻分割刻度 |
LogLocator | 從最小到最大值的對數分割刻度 |
MultipleLocator | 某個基數的倍數刻度 |
MaxNLocator | 刻度數量最大值 |
AutoLocator | 默認的刻度數量最大值 |
AutoMinorLocator | 默認的次要刻度 |
Formatter 對象
描述
NullFormatter | 無標簽 |
IndexFormatter | 從一個列表獲得標簽 |
FixedFormatter | 從固定的字符串設置標簽 |
FuncFormatter | 使用自定義函數設置標簽 |
FormatStrFormatter | 使用一個格式化字符串設置標簽 |
ScalarFormatter | 默認的標量標簽 |
LogFormatter | 默認的對數標簽 |
11.在 matplotlib 中創建三維圖表
Matplotlib 最開始被設計為僅支持二維的圖表。到 1.0 版本發布左右,一些三維圖表的工具在二維展示的基礎上被創建了出來,結果就是 Matplotlib 提供了一個方便的(同時也是有限的)的可用于三維數據可視化的一套工具。三維圖表可以使用載入mplot3d工具包來激活,這個包會隨著 Matplotlib 自動安裝:
from?mpl_toolkits?import?mplot3d一旦模塊被導入,三維 axes 就可以像其他普通 axes 一樣通過關鍵字參數projection='3d'來創建:
%matplotlib?inline import?numpy?as?np import?matplotlib.pyplot?as?plt fig?=?plt.figure() ax?=?plt.axes(projection='3d')三維 axes 激活后,我們可以在上面繪制不同的三維圖表類型。三維圖表在 notebook 中使用交互式圖表展示會優于使用靜態展示;回憶我們前面介紹過,你可以使用%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° 的水平角(即以 60° 俯視 x-y 平面)和 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');上圖的結果很顯然沒有使用網格繪制表面圖那么清晰,但是對于我們并不是使用函數構建數據樣本(數據樣本通常來自真實世界的采樣)的情況下,這能提供很大的幫助。例如我們下面會看到,能使用這種方法繪制一條三維的莫比烏斯環。
例子:繪制莫比烏斯環
莫比烏斯環是使用一條紙條,一端翻折后與另一端粘起來形成的環形。在拓撲學中這是非常有趣的一個形狀,因為它只有一個面。我們下面使用 Matplotlib 的三維工具繪制莫比烏斯環。創建莫比烏斯環的關鍵在于能參數化它:莫比烏斯環是一個二維的環狀結構,因此我們需要兩個特定的維度。一個我們稱為?,取值范圍是??表示整個環狀,還有一個稱為?,取值范圍是??表示紙帶的寬度:
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)*坐標的位置。
仔細思考一下,我們會發現在莫比烏斯環上有兩個自轉發生:一個是紙帶繞環形中央位置的旋轉(我們稱為?),另一個紙帶繞著中間軸線的旋轉(我們稱為?)。紙帶中央位置旋轉一整圈??時,紙帶繞中間軸線旋轉剛好半圈?,我們將整個旋轉均勻分布在紙帶上時,就會有?。
phi?=?0.5?*?theta現在我們已經有了所有需要獲得三維坐標值的參數了。我們定義??為每個坐標點距離環形中間的位置,使用它來計算最終??三維坐標系的坐標值:
y?=?np.ravel(r?*?np.sin(theta))z?=?np.ravel(w?*?np.sin(phi))
#?r是坐標點距離環形中心的距離值 r?=?1?+?w?*?np.cos(phi) #?利用簡單的三角函數知識算得x,y,z坐標值 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 創建和展現大量三維對象和模式的能力。
參考資料
[1]PythonDataScienceHandbook:https://github.com/jakevdp/PythonDataScienceHandbook/tree/master/notebooks
[2]Pythons數據科學:https://github.com/wangyingsm/Python-Data-Science-Handbook/tree/master/notebooks
[3]顏色代碼:https://www.w3schools.com/colors/colors_names.asp
[4]秒差距:https://zh.wikipedia.org/wiki/%E7%A7%92%E5%B7%AE%E8%B7%9D
[5]有趣討論:http://Matplotlib.org/1.4.1/users/colormaps.html
[6]標注示:http://matplotlib.org/examples/pylab_examples/annotation_demo2.html
往期精彩回顧適合初學者入門人工智能的路線及資料下載(圖文+視頻)機器學習入門系列下載機器學習及深度學習筆記等資料打印《統計學習方法》的代碼復現專輯機器學習交流qq群955171419,加入微信群請掃碼總結
以上是生活随笔為你收集整理的【Python】科研论文绘图实操干货汇总,11类Matplotlib图表,含代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机桌面下面那一栏如何调节,电脑底部的
- 下一篇: 人工智能、深度学习和机器学习有哪些区别?