matplotlib使用指南
聲明
本文主要翻譯matplotlib的官方文檔,若有錯誤,歡迎批評指正。
本文包含了一些基本的使用方法以及適合快速上手的實例,幫助你快速熟悉matplotlib。
導入matplotlib模塊的方法為
import matplotlib.pyplot as plt
import numpy as np
一個簡單的例子
matplotlib將你的數據繪制在Figure上,每一個Figure能夠包含一個或多個Axes。每一個Axes相當於一個繪圖區域,並且該區域內的每一個點都可以通過x-y坐標,極坐標或x-y-z坐標(三維情況下)定位。
創建帶有一個Axes的Figure對象的最簡單方法是使用pyplot.subplots,之后,我們便可以使用Axes.plot將數據繪制在Axes上:
fig, ax = plt.subplots() # 創建包含一個axes的figure對象
ax.plot([1,2,3], [1,2,4]) # 將數據繪制在axes上
事實上,對於Axes中的每一個繪圖函數,在matplotlib.pyplot模塊中都存在與之對應的函數。我們可以直接通過matplotlib.pyplot自動創建Axes及其對應的Figure,並在Axes上進行繪圖,並不需要顯式創建Axes。因此,上述繪圖的例子可以簡化為:
plt.plot([1,2,3], [1,2,4])
Figure示意圖
Figure
Figure負責追蹤所有的子Axes,一些特殊的artists(如title,legend)和canvas(canvas是最終呈現圖像的對象)。一個Figure可以包含多個Axes,但至少要包含一個Axes。
創建Figure的方法有以下幾種:
fig = plt.figure() # 一個空的figure,不帶有Axes
fig, ax = plt.subplots() # 帶有一個Axes的figure
fig, axs = plt.subplots(2, 2) # 帶有4個Axes的figure,且Axes的排序方式為2x2
在創建Figure的時候,同時創建與之對應的Axes是常用的方式。但也可以之后在創建Axes,然后加入到Figure當中,用於更復雜的Axes布局。
Axes
Axes就是我們進行繪圖的區域,一個Figure可以包含多個Axes,但一個Axes只能存在於一個Figure當中。一個Axes包含兩個Axis對象(三維情況下包含三個),Axis用於控制數據的顯示范圍。每個Axes還可以控制當前區域的標題、x-y坐標軸的名稱。
fig, ax = plt.subplots()
ax.set_xlim([0, 5]) # 控制x軸顯示范圍
ax.set_xlabel('x') # 設置x軸名稱
ax.set_ylim([-1, 3]) # 控制y軸顯示范圍
ax.set_ylabel('y') # 設置y軸名稱
ax.set_title('ax-title')# 設置標題
Axis
Axis是類似數軸的對象,用於設置數值的顯示范圍、刻度標記以及刻度標簽。刻度的位置由Locator對象決定,刻度標簽(字符串)由Formatter對象生成。正確設置Locator和Formatter能夠對刻度標記的位置和標簽進行精細化的控制。
Artist
幾乎所有你能夠在圖像上看到的對象都是一個artist(即使是Figure,Axes和Axis對象)。以及Text,Line2D,collections,Patch對象都是artist。當圖片被渲染的時候,所有的artist對象都將會被繪制到Canvas之上。大多數的artist對象都被綁定到一個Axes之上,因此一個artist無法被多個Axes分享或移動到另一個Axes之上。
繪圖函數的輸入
所有的繪圖函數都期望獲得numpy.array或numpy.ma.masked_array作為輸入。如果將類似數組(array-like)的對象作為輸入,例如pandas.DataFrame和numpy.matrix,則繪圖函數可能不會按照你預想的方式進行工作。因此,最好的做法是先將數據轉化成numpy.array對象。
# 將DataFrame轉換成array
a = pandas.DataFrame(np.random.rand(4, 5), columns = list('abcde'))
a_asarray = a.values
# 將matrix轉換成array
b = np.matrix([[1, 2], [3, 4]])
b_asarray = np.asarray(b)
面向對象接口與pyplot接口
正如在最開始的例子中所提到的,使用matplotlib由兩個基本的方式:
- 面向對象風格(object-oriented style,OO style):顯式創建Figure和Axes,並調用這些對象的成員函數。
- 依賴pyplot自動創建和管理Figure和Axes對象,以及使用pyplot函數進行繪圖。
# 一個OO風格的示例
x = np.linspace(0, 2, 100)
# 注意:即使是OO風格, 我們仍然使用`.pyplot.figure`創建figure對象
fig, ax = plt.subplots() # Create a figure and an axes.
ax.plot(x, x, label='linear') # Plot some data on the axes.
ax.plot(x, x**2, label='quadratic') # Plot more data on the axes...
ax.plot(x, x**3, label='cubic') # ... and some more.
ax.set_xlabel('x label') # Add an x-label to the axes.
ax.set_ylabel('y label') # Add a y-label to the axes.
ax.set_title("Simple Plot") # Add a title to the axes.
ax.legend() # Add a legend.
# 一個pyplot風格的示例
x = np.linspace(0, 2, 100)
plt.plot(x, x, label='linear') # Plot some data on the (implicit) axes.
plt.plot(x, x**2, label='quadratic') # etc.
plt.plot(x, x**3, label='cubic')
plt.xlabel('x label')
plt.ylabel('y label')
plt.title("Simple Plot")
plt.legend()
事實上,存在第三種使用matplotlib的方法,但該方法的應用場景是將matplotlib集成到一個GUI應用當中。這里只是順帶提及,詳細請參考Embedding Matplotlib in graphical user interfaces。
matplotlib的文檔和示例使用了OO和pyplot這兩種不同的風格進行演示,你可以任意選擇一種你喜歡的風格書寫代碼。但推薦的使用方式是:在交互式終端的情況下(如jupyter notebook),使用pyplot風格;在非交互式情況下(如寫python腳本的時候),使用OO風格。
后端(backends)
什么是后端?
許多網站和郵件列表的文檔都提到了“后端“一詞,許多新用戶經常對這個術語感到困惑。matplotlib針對不同的應用場景,能夠輸出不同格式的結果。一些人在交互式的python shell中使用matplotlib繪圖;一些人使用jupyter notebooks將matplotlib繪制的圖像嵌入到代碼的當中,而不是彈出單獨的圖片窗口;一些人將matplotlib集成到GUI程序中(如wxpython、pygtk)以創建富應用(rich application);以及其它的應用場景。
為了支持不同的應用場景,matplotlib就需要有不同的功能組件(處理機制)來針對不同場景,輸出符合要求的結果。這些不同的功能組件就稱為”后端“。而前端指的是面向用戶的代碼,即繪圖的代碼。后端的作用就是根據前端用戶代碼,生成圖像,並返回給用戶。matplotlib中有兩種不同類型的后端:
- 交互式后端(user interface backends/interactive backends):例如pygtk,wxpython,tkinter,qt4,macosx等
- 非交互式后端(hardcopy backends/non-interactive backends):用於創建圖像文件(如PNG,SVG,PDF)
如何配置后端?
一共有三種不同的方法來配置matplotlib的后端:
- matplotlibrc文件中的
rcParams['backend'](默認是agg)參數 MPLBACKEND環境變量matplotlib.use()函數
需要注意的是如果同時使用了上述3種方法中的多種方法對后端進行配置,那么會優先選擇序號大的方法。也就是說,如果使用函數matplotlib.use()配置了后端,那么matplotlib將無視MPLBACKEND和rcParams['backend']中的參數值。
如果沒有顯式指定后端,matplotlib將基於系統中的可用后端和GUI事件循環(event loop)是否已經正在運行,自動檢測可用的后端。在Linux系統中,如果環境變量DISPLAY未被設置,那么事件循環將會被識別為"headless",從而導致回退到非交互式后端(agg)。
詳細的配置方法如下:
-
配置matplotlibrc文件中的
rcParams['backend'](默認是agg)參數backend : qt5agg # use pyqt5 with antigrain (agg) rendering -
配置
MPLBACKEND環境變量你可以在當前shell或針對單個腳本中設置該環境變量
# On Unix > export MPLBACKEND=qt5agg > python simple_plot.py > MPLBACKEND=qt5agg python simple_plot.py # On Windows,只能針對當前shell設置該環境變量 > set MPLBACKEND=qt5agg > python simple_plot.py設置
MPLBACKEND環境變量將覆蓋掉matplotlibrc文件中的配置,即使matplotlibrc文件存在於你當前的工作目錄之下。因此,將MPLBACKEND環境變量設置為全局的環境變量(即在文件.bashrc或.profile中定義)是不妥當的,如果這樣做了,可能會導致一些反直覺的結果。 -
如果你的腳本依賴於特定的后端,你可以使用
matplotlib.use()函數import matplotlib matplotlib.use('qt5agg')應該在Figure創建之前就調用該函數,否則matplotlib可能無法轉換后端,從而導致ImportError。使用該方法指定后端,如果其它用戶想要更改使用其它的后端,那么就需要對原有的代碼進行改動。因此,你應該盡量避免顯式調用
matplotlib.use()指定后端,除非不得不這么做。
內置后端及其它
matplotlib的內置后端、如何使用非內置后端等信息請參考:詳細信息
什么是交互(interactive)模式
使用交互式后端允許我們直接向屏幕輸出圖像。是否與何時向屏幕輸出圖像?向屏幕輸出圖像之后,python腳本或shell是否繼續運行?這些問題都與我們所調用的函數和方法直接相關。在matplotlib中可以通過一個狀態(布爾)變量的取值來控制當前是否處於“交互模式”。就像其它配置參數一樣,我們可以在matplotlibrc文件里修改狀態變量。我們也可以通過matplotlib.interactive()來設置該變量的值,以及通過matplotlib.is_isteractive()查看該變量的值。另一種開啟交互模式的方法是matplotlib.pyplot.ion(),與之對應的關閉方法為matplotlib.pyplot.ioff()。
注意
- 交互功能以及
show()的角色和行為,在matplotlib更新到1.0版本后,發生了重大變化。並在1.0.1版本中修復了一些bug。這里我們將描述在1.0.1版本中基本交互式后端的行為,以及macosx中的部分例外情況。- “交互模式”能夠在ipython和普通的python shell中正常運行,但不能在IDLE IDE中運行。如果默認的后端不支持交互功能,那么我們可以顯式配置一個支持交互的后端,就像上述“如何配置后端?”中描述的那樣。
交互模式的示例
啟動一個普通的python shell或者不帶任何選項參數的ipython,執行下述代碼:
import matplotlib.pyplot as plt
plt.ion()
plt.plot([1.6, 2.7])
此時,將彈出一個繪圖窗口。並且,終端提示符仍然處於激活狀態。因此,我們可以追加新的命令,例如:
plt.title("interactive test")
plt.xlabel("index")
可以發現追加新的命令之后,繪圖窗口會自動更新內容。即使使用面向對象接口更新圖像,大多數的可交互后端也能夠自動更新。例如:
ax = plt.gca() # 獲取當前axes對象, gca = get current axes ?
ax.plt([3.1, 2.2])
如果你正在使用某些后端(例如macosx),或更老版本的matplotlib,在追加新的命令之后,繪圖窗口的圖形可能不會立即自動更新。在這種情況下,你需要手動調用plt.draw()來刷新圖像。
非交互模式的示例
重新啟動一個新的會話窗口,並且關閉交互模式:
import matplotlib.pyplot as plt
plt.ioff()
plt.plot([1.6, 2.7])
執行完上述命令之后,不會像“交互模式”那樣,立馬彈出一個繪圖窗口(除非你正在使用macosx后端,但這是異常情況)。為了彈出繪圖窗口,我們需要手動輸入:
plt.show()
彈出繪圖窗口之后,你會發現此時無法像“交互模式”那樣繼續追加新的命令。這是因為plt.show()阻塞了命令的輸入,直到你手動關閉繪圖窗口。
但這樣的功能有什么用呢?被迫使用阻塞函數?
假設你需要編寫一個腳本將某個文件的內容繪制成圖,並輸出到屏幕顯示。你想要先查看繪制好的圖像,然后再結束腳本。如果沒有show()這樣的阻塞命令存在,那么運行腳本之后,繪制好的圖像將只會在屏幕上“一閃而過”,隨后腳本便停止運行。
此外,非交互模式將所有的實際繪圖工作延遲到show()調用之后。這樣更有效率,而不需要每次輸入一個命令之后,就立即更新圖像。
在1.0版本之前,通常情況下,在一個腳本內show()只能被調用一次。而在1.0.1和更新的版本中,該限制被移除了,因此,我們可以像這樣書寫腳本:
import numpy as np
import matplotlib.pyplot as plt
plt.ioff()
for i in range(3):
plt.plot(np.random.rand(10))
plt.show()
這段代碼將按照順序繪制三張圖,並且每次只顯示一張圖。之后圖像只會在之前的圖像關閉之后顯示。
小結
如果啟用交互模式,那么pyplot函數將自動向屏幕輸出圖像。如果使用面向對象方法而不是pyplot函數,那么當我們需要更新圖像的時候調用函數draw()。
如果我們需要在腳本結束前顯示圖像,就可以使用非交互模式。同樣地,利用show()的阻塞特性,我們可以按照順序顯示一系列圖像,新的圖像只會在舊的圖像手動關閉后才顯示。
性能(performance)
無論是在交互模式中,還是在腳本中進行繪圖,圖像的渲染時間將會是實際任務中不得不解決的性能瓶頸。matplotlib提供了一些減少圖像渲染時間的方法,代價是圖像外觀會發生一些改變(在我們所能容忍的范圍內)。圖像的類型將直接影響到我們能夠使用哪些方法來減少渲染時間。
線段簡化(line segment simplification)
對於包含線段的圖像(例如,經典的折線圖、多邊形的輪廓等),可以通過變量rcParams["path.simplify"](默認值:True)以及rcParams["path.simplify_threshold"](默認值:1/9)來控制圖像的渲染時間。rcParams["path.simplify"]是個布爾變量,用於控制是否啟用線段簡化功能。rcParams["path.simplify_threshold"]用於控制有多少線段會被簡化,更高的閾值(threshold)意味着更快的渲染速度。
以下腳本首先展示了未啟用線段簡化和啟用了線段簡化的結果:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# Setup, and create the data to plot
y = np.random.rand(100000)
y[50000:] *= 2
y[np.geomspace(10, 50000, 400).astype(int)] = -1
mpl.rcParams['path.simplify'] = True
mpl.rcParams['path.simplify_threshold'] = 0.0
plt.plot(y)
plt.show()
mpl.rcParams['path.simplify_threshold'] = 1.0
plt.plot(y)
plt.show()
matplotlib默認將簡化閾值設置為1/9。如果你想要更改默認設置,那么可以到matplotlibrc文件中進行設置。或者,你可能有這樣的需求:在不同的應用場景中,應用不同的繪圖風格。例如,在交互模式時,將線段簡化閾值設置為最大值1;在有印刷圖像需求時,將線段簡化閾值設置為最小值0。我們可以為不同的應用場景事先配置好不同的風格表單(style sheets),等到需要時直接應用該風格表單即可。詳細可參見Customizing Matplotlib with style sheets and rcParams。
線段簡化的原理:通過迭代的方式,依次將線段合並成單個向量,直到下一個線段與當前向量的垂直距離大於path.simplify_threshold。距離的計算在顯示坐標空間(display-coordinate space)中進行。
注意
在2.1版本中,對線段簡化如何實現進行了一些改動。對於2.1之前的版本,渲染時間仍然能通過配置這些參數提高渲染效率,但對於某些數據而言,2.1及之后的版本,渲染效率能夠獲得更大的改善。
標記簡化(marker simplification)
標記同樣能夠被簡化,盡管不如線段簡化功能強大。標記簡化只能應用於Line2D對象,通過設置markevery屬性來實現:
plt.plot(x, y, markevery=10)
# 或者
ax.plot(x, y, markevery=10)
markevery參數允許朴素下采樣(naive subsampling),或嘗試沿着x軸方向進行均勻采樣,詳細內容請查看Markevery Demo。
將折線划分成更小的塊(splitting lines into smaller chunks)
如果你正在使用Agg后端,那么你可用通過rcParams["agg.path.chunksize"](默認值:0)參數指定一個塊大小(chunk size)。任何頂點數大於agg.path.chunksize的折線將會被划分成多個折線,划分后各個折線的頂點數不大於agg.path.chunksize。如果chunksize設置為0,意味着不對原始折線進行任何划分。對於某些類型的數據,合理設置一個chunksize能夠極大提高渲染效率。
下面的腳本,展示了chunkszie=0和10000的圖像對比。因為在圖像比較大的時候,比較容易看出二者的區別,因此請嘗試最大化繪圖窗口,進行觀察。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['path.simplify_threshold'] = 1.0
# Setup, and create the data to plot
y = np.random.rand(100000)
y[50000:] *= 2
y[np.geomspace(10, 50000, 400).astype(int)] = -1
mpl.rcParams['path.simplify'] = True
mpl.rcParams['agg.path.chunksize'] = 0
plt.plot(y)
plt.show()
mpl.rcParams['agg.path.chunksize'] = 10000
plt.plot(y)
plt.show()
圖例(legends)
圖例的默認行為是在圖像中找到最佳一個位置,使得被圖列覆蓋住的數據點最少(即loc='best')。當數據點非常多的時候,這種查找行為非常消耗計算資源。在這種情況下,手動指定圖例的位置是更好的選擇。
使用fast風格
fast風格能夠自動為簡化和分塊參數設置一個較合理的值,依次來提高繪圖效率。使用方法如下:
import matplotlib.style as mplstyle
mplstyle.use('fast')
fast風格是一種非常輕量級的配置,能夠很好地與其它風格配合使用。但需要注意的是,要確保fast風格是最后一個應用生效的風格,以此保證其它風格不會覆蓋掉fast風格的配置。
mplstyle.use(['dark_background', 'ggplot', 'fast'])
關於matplotlib內置風格和如何自定義風格的詳細內容,參見Customizing Matplotlib with style sheets and rcParams。
