快速繪圖¶
使用pyplot模塊繪圖
matplotlib的pyplot模塊提供了和MATLAB類似的繪圖API,方便用戶快速繪制二維圖表。我們先看一個簡單的例子:
05-matplotlib/matplotlib_simple_plot.py
用pylab庫快速繪圖
import numpy as np
import matplotlib.pyplot as plt ❶
x = np.linspace(0, 10, 1000)
y = np.sin(x)
z = np.cos(x**2)
plt.figure(figsize=(8,4)) ❷
plt.plot(x,y,label="$sin(x)$",color="red",linewidth=2) ❸
plt.plot(x,z,"b--",label="$cos(x^2)$") ❹
plt.xlabel("Time(s)") ❺
plt.ylabel("Volt")
plt.title("PyPlot First Example")
plt.ylim(-1.2,1.2)
plt.legend()
plt.show() ❻
程序的輸出如【圖:使用pyplot模塊快速將數據繪制成曲線圖】所示。

使用pyplot模塊快速將數據繪制成曲線圖
❶首先載入matplotlib的繪圖模塊pyplot,並且重命名為plt。
pylab模塊
matplotlib還提供了一個名為pylab的模塊,其中包括了許多NumPy和pyplot模塊中常用的函數,方便用戶快速進行計算和繪圖,十分適合在IPython交互式環境中使用。本書使用下面的方式載入pylab模塊:
>>> import pylab as pl
❷調用figure()創建一個Figure(圖表)對象,並且它將成為當前Figure對象。也可以不創建Figure對象直接調用接下來的plot()進行繪圖,這時matplotlib會自動創建一個Figure對象。figsize參數指定Figure對象的寬度和高度,其單位為英寸。此外還可以用dpi參數指定Figure對象的分辨率,即每英寸所表示的像素數,這里使用缺省值80。因此本例中所創建的Figure對象的寬度為“8*80 = 640”個像素。但是在顯示出繪圖窗口之后,用工具欄中的保存按鈕將圖表保存為圖像時,所保存的圖像的大小是“800*400”像素。這是因為保存圖像時會使用不同的dpi設置。這個設置保存在matplotlib的配置文件中,我們可以通過如下語句查看它的值:
>>> import matplotlib
>>> matplotlib.rcParams["savefig.dpi"]
100
因為保存圖像時的DPI設置為100,因此所保存的圖像的寬度是“8*100 = 800”個像素。rcParams是一個字典,其中保存着從配置文件讀入的所有配置,在調用各種繪圖函數時,這些配置將會作為各種參數的缺省值。后面我們還會對matplotlib的配置文件進行詳細介紹。
❸創建Figure對象之后,接下來調用plot()在當前的Figure對象中繪圖。實際上plot()是在Axes(子圖)對象上繪圖,如果當前的Figure對象中沒有Axes對象,將會為之創建一個幾乎充滿整個圖表的Axes對象,並且使此Axes對象成為當前的Axes對象。plot()的前兩個參數是分別表示X、Y軸數據的對象,這里使用的是NumPy數組。使用關鍵字參數可以指定所繪制的曲線的各種屬性:
- label:給曲線指定一個標簽名稱,此標簽將在圖示中顯示。如果標簽字符串的前后有字符’$’,則matplotlib會使用其內嵌的LaTex引擎將其顯示為數學公式。
- color:指定曲線的顏色,顏色可以用英文單詞,或者以’#’字符開頭的三個16進制數,例如’#ff0000’表示紅色。或者使用值在0到1范圍之內的三個元素的元組表示,例如(1.0, 0.0, 0.0)也表示紅色。
- linewidth:指定曲線的寬度,可以不是整數,也可以使用縮寫形式的參數名lw。
❹直接通過第三個參數’b–’指定曲線的顏色和線型,它通過一些易記的符號指定曲線的樣式。其中’b’表示藍色,’–’表示線型為虛線。在IPython中輸入“plt.plot?”可以查看格式化字符串以及各個參數的詳細說明。
❺接下來通過一系列函數設置當前Axes對象的各個屬性:
- xlabel、ylabel:分別設置X、Y軸的標題文字。
- title:設置子圖的標題。
- xlim、ylim:分別設置X、Y軸的顯示范圍。
- legend:顯示圖示,即圖中表示每條曲線的標簽(label)和樣式的矩形區域。
❻最后調用plt.show()顯示出繪圖窗口。在通常的運行情況下,show()將會阻塞程序的運行,直到用戶關閉繪圖窗口。然而在帶“-wthread”等參數的IPython環境下,show()不會等待窗口關閉。
還可以調用plt.savefig()將當前的Figure對象保存成圖像文件,圖像格式由圖像文件的擴展名決定。下面的程序將當前的圖表保存為“test.png”,並且通過dpi參數指定圖像的分辨率為120,因此輸出圖像的寬度為“8*120 = 960”個像素。
>>> run matplotlib_simple_plot.py
>>> plt.savefig("test.png", dpi=120)
savefig()的第一個參數可以是文件名,也可以是和Python的文件對象有相同調用接口的對象。例如可以將圖像保存到StringIO對象中,這樣就得到了一個表示圖像內容的字符串。這里需要使用fmt參數指定保存的圖像格式。
>>> from StringIO import StringIO
>>> buf = StringIO() # 創建一個用來保存圖像內容的StringIO對象
>>> plt.savefig(buf, fmt="png") # 將圖像以png格式保存進buf中
>>> buf.getvalue()[:20] # 顯示圖像內容的前20個字節
'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x03 '
面向對象方式繪圖
matplotlib實際上是一套面向對象的繪圖庫,它所繪制的圖表中的每個繪圖元素,例如線條、文字、刻度等在內存中都有一個對象與之對應。為了方便快速繪圖matplotlib通過pyplot模塊提供了一套和MATLAB類似的繪圖API,將眾多繪圖對象所構成的復雜結構隱藏在這套API內部。我們只需要調用pyplot模塊所提供的函數就可以實現快速繪圖以及設置圖表的各種細節。pyplot模塊雖然用法簡單,但不適合在較大的應用程序中使用,因此本章將着重介紹如何使用matplotlib的面向對象的方式編寫繪圖程序。
為了將面向對象的繪圖庫包裝成只使用函數的調用接口,pyplot模塊的內部保存了當前圖表以及當前子圖等信息。當前的圖表和子圖可以使用gcf()和gca()獲得,它們分別是“Get Current Figure”和“Get Current Axis”的開頭字母縮寫。gcf()獲得的是表示圖表的Figure對象,而gca()則獲得的是表示子圖的Axes對象。下面我們在IPython中運行上節的“matplotlib_simple_plot.py”程序,然后調用gcf()和gca()查看當前的Figure和Axes對象。
>>> run matplotlib_simple_plot.py
>>> fig = plt.gcf()
>>> axes = plt.gca()
>>> fig
<matplotlib.figure.Figure object at 0x04B30090>
>>> axes
<matplotlib.axes.AxesSubplot object at 0x04BD8E70>
在pyplot模塊中,許多函數都是對當前的Figure或Axes對象進行處理,例如前面所介紹的plot()、xlabel()、savefig()等。我們可以在IPython中輸入函數名並加”??”,查看這些函數的源代碼了解它們是如何調用各種對象的方法進行繪圖處理的。例如下面的例子查看plot()的源程序,可以看到plot()實際上會通過gca()獲得當前的Axes對象ax,然后再調用它的plot()方法實現真正的繪圖。請讀者使用類似的方法查看pyplot模塊的其它函數是如何對各種繪圖對象進行包裝的。
>>> plt.plot??
...
def plot(*args, **kwargs):
ax = gca()
...
try:
ret = ax.plot(*args, **kwargs)
...
finally:
ax.hold(washold)
配置屬性
matplotlib所繪制的圖表的每個組成部分都和一個對象對應,我們可以通過調用這些對象的屬性設置方法set_*()或者pyplot模塊的屬性設置函數setp()設置它們的屬性值。例如plot()返回一個元素類型為Line2D的列表,下面的例子設置Line2D對象的屬性:
>>> x = np.arange(0, 5, 0.1)
>>> line = plt.plot(x, x*x)[0] # plot返回一個列表
>>> line.set_antialiased(False) # 調用Line2D對象的set_*()方法設置屬性值
上面的例子中,通過調用Line2D對象的set_antialiased(False),關閉了它在圖表中對應的曲線的反鋸齒效果。下面的語句同時繪制正弦和余弦兩條曲線,lines是一個有兩個Line2D對象的列表:
>>> lines = plt.plot(x, np.sin(x), x, np.cos(x))
調用setp()可以同時配置多個對象的屬性,這里我們同時設置兩條曲線的顏色和線寬:
>>> plt.setp(lines, color="r", linewidth=2.0)
同樣,可以通過調用Line2D對象的get_*(),或者plt.getp()獲取對象的屬性值:
>>> line.get_linewidth()
1.0
>>> plt.getp(lines[0], "color") # 返回color屬性
'r'
>>> plt.getp(lines[1]) # 輸出全部屬性
alpha = 1.0
animated = False
antialiased or aa = True
axes = Axes(0.125,0.1;0.775x0.8)
...
注意getp()和setp()不同,它只能對一個對象進行操作,它有兩種用法:
- 指定屬性名:返回對象的某個屬性的值
- 不指定屬性名:輸出對象的所有屬性和值
下面通過getp()查看Figure對象的屬性:
>>> f = plt.gcf()
>>> plt.getp(f)
alpha = 1.0
animated = False
...
Figure對象的axes屬性是一個列表,它保存圖表中的所有子圖對象。下面的程序查看當前圖表的axes屬性,可以看出其中包含gca()所獲得的當前子圖對象:
>>> plt.getp(f, "axes")
[<matplotlib.axes.AxesSubplot object at 0x05CDD170>]
>>> plt.gca()
<matplotlib.axes.AxesSubplot object at 0x05CDD170>
用plt.getp()可以繼續獲取AxesSubplot對象的屬性,例如它的lines屬性為子圖中的Line2D對象列表:
>>> alllines = plt.getp(plt.gca(), "lines")
>>> alllines
<a list of 3 Line2D objects>
>>> alllines[0] == line # 其中的第一條曲線就是最開始繪制的那條曲線
True
通過這種方法可以很容易查看對象的屬性值以及各個對象之間的關系,找到需要配置的屬性。
因為matplotlib實際上是一套面向對象的繪圖庫,因此也可以直接獲取對象的屬性,例如:
>>> f.axes
[<matplotlib.axes.AxesSubplot object at 0x05CDD170>]
>>> f.axes[0].lines
<a list of 3 Line2D objects>
繪制多子圖
一個Figure對象可以包含多個子圖(Axes),在matplotlib中用Axes對象表示一個繪圖區域,在本書中稱之為子圖。在前面的例子中,Figure對象只包括一個子圖。我們可以使用subplot()快速繪制包含多個子圖的圖表,它的調用形式如下:
subplot(numRows, numCols, plotNum)
圖表的整個繪圖區域被等分為numRows行和numCols列,然后按照從左到右、從上到下的順序對每個區域進行編號,左上區域的編號為1。plotNum參數指定所創建Axes對象所在的區域。如果numRows、numCols和plotNum三個參數都小於10,則可以把它們縮寫成一個整數,例如subplot(323)和subplot(3,2,3)的含義相同。如果新創建的子圖和之前創建的子圖區域有重疊的部分,則之前的子圖將被刪除。
下面的程序創建如【圖:用subplot()在當前的Figure對象中創建6個子圖】所示的3行2列共6個子圖,並通過axisbg參數給每個子圖設置不同的背景顏色。
for idx, color in enumerate("rgbyck"):
plt.subplot(321+idx, axisbg=color)
plt.show()

用subplot()在當前的Figure對象中創建6個子圖
如果希望某個子圖占據整行或者整列,可以如下調用subplot():
plt.subplot(221) # 第一行的左圖
plt.subplot(222) # 第一行的右圖
plt.subplot(212) # 第二整行
plt.show()
程序的輸出如【圖:將Figure分為三個子圖】所示。

將Figure分為三個子圖
在繪圖窗口的工具欄中,有一個名為“Configure Subplots”的按鈕,點擊它彈出調節子圖間距和子圖與圖表邊框距離的對話框。也可以在程序中調用subplots_adjust()調節這些參數,它有left、right、bottom、top、wspace和hspace等六個參數,這些參數與對話框中的各個控件對應。參數的取值范圍為0到1,它們是以圖表繪圖區域的寬和高進行正規化之后的坐標或者長度。
subplot()返回它所創建的Axes對象,我們可以將它用變量保存起來,然后用sca()交替讓它們成為當前Axes對象,並調用plot()在其中繪圖。如果需要同時繪制多幅圖表,可以給figure()傳遞一個整數參數指定Figure對象的序號,如果序號所指定的Figure對象已經存在,將不創建新的對象,而只是讓它成為當前的Figure對象。下面的程序演示了依次在不同圖表的不同子圖中繪制曲線。
05-matplotlib/matplotlib_multi_figure.py
同時在多幅圖表、多個子圖中進行繪圖
import numpy as np
import matplotlib.pyplot as plt
plt.figure(1) # 創建圖表1
plt.figure(2) # 創建圖表2
ax1 = plt.subplot(211) # 在圖表2中創建子圖1
ax2 = plt.subplot(212) # 在圖表2中創建子圖2
x = np.linspace(0, 3, 100)
for i in xrange(5):
plt.figure(1) ❶ # 選擇圖表1
plt.plot(x, np.exp(i*x/3))
plt.sca(ax1) ❷ # 選擇圖表2的子圖1
plt.plot(x, np.sin(i*x))
plt.sca(ax2) # 選擇圖表2的子圖2
plt.plot(x, np.cos(i*x))
plt.show()
首先通過figure()創建了兩個圖表,它們的序號分別為1和2。然后在圖表2中創建了上下並排的兩個子圖,並用變量ax1和ax2保存。
在循環中,❶先調用figure(1)讓圖表1成為當前圖表,並在其中繪圖。❷然后調用sca(ax1)和sca(ax2)分別讓子圖ax1和ax2成為當前子圖,並在其中繪圖。當它們成為當前子圖時,包含它們的圖表2也自動成為當前圖表,因此不需要調用figure(2)。這樣依次在圖表1和圖表2的兩個子圖之間切換,逐步在其中添加新的曲線。其效果如【圖:同時在多幅圖表、多個子圖中進行繪圖】所示。

同時在多幅圖表、多個子圖中進行繪圖
配置文件
繪制一幅圖需要對許多對象的屬性進行配置,例如顏色、字體、線型等等。我們在繪圖時,並沒有逐一對這些屬性進行配置,許多都直接采用了matplotlib的缺省配置。matplotlib將這些缺省配置保存在一個名為“matplotlibrc”的配置文件中,通過修改配置文件,我們可以修改圖表的缺省樣式。
在matplotlib中可以使用多個“matplotlibrc”配置文件,它們的搜索順序如下,順序靠前的配置文件將會被優先采用。
- 當前路徑:程序的當前路徑。
- 用戶配置路徑:通常在用戶文件夾的“.matplotlib”目錄下,可以通過環境變量MATPLOTLIBRC修改它的位置。
- 系統配置路徑:保存在matplotlib的安裝目錄下的mpl-data中。
通過下面的語句可以獲取用戶配置路徑:
>>> import matplotlib
>>> matplotlib.get_configdir()
'C:\\Documents and Settings\\用戶名\\.matplotlib'
通過下面的語句可以獲得目前使用的配置文件的路徑:
>>> import matplotlib
>>> matplotlib.matplotlib_fname()
'C:\\Python26\\lib\\site-packages\\matplotlib\\mpl-data\\matplotlibrc'
由於在當前路徑和用戶配置路徑中都沒有找到配置文件,因此最后使用的是系統配置路徑下的配置文件。如果讀者將matplotlibrc復制一份到腳本的當前目錄(例如,c:\zhang\doc)下:
>>> import os
>>> os.getcwd()
'C:\\zhang\\doc'
復制配置文件之后再查看配置文件的路徑,就會發現它變為了當前目錄下的配置文件:
>>> matplotlib.matplotlib_fname()
'C:\\zhang\\doc\\matplotlibrc'
如果讀者使用文本編輯器打開此配置文件,就會發現它實際上是一個字典。為了對眾多的配置進行區分,字典的鍵根據配置的種類,用“.”分為多段。
配置文件的讀入可以使用rc_params(),它返回一個配置字典:
>>> matplotlib.rc_params()
{'agg.path.chunksize': 0,
'axes.axisbelow': False,
'axes.edgecolor': 'k',
'axes.facecolor': 'w',
... ...
在matplotlib模塊載入時會調用rc_params(),並把得到的配置字典保存到rcParams變量中:
>>> matplotlib.rcParams
{'agg.path.chunksize': 0,
'axes.axisbelow': False,
... ...
matplotlib將使用rcParams字典中的配置進行繪圖。用戶可以直接修改此字典中的配置,所做的改變會反映到此后創建的繪圖元素。例如下面的代碼所繪制的折線將帶有圓形的點標識符:
>>> matplotlib.rcParams["lines.marker"] = "o"
>>> plt.plot([1,2,3,2])
>>> plt.show()
為了方便對配置字典進行設置,可以使用rc()。下面的例子同時配置點標識符、線寬和顏色:
>>> matplotlib.rc("lines", marker="x", linewidth=2, color="red")
如果希望恢復到缺省的配置(matplotlib載入時從配置文件讀入的配置),可以調用rcdefaults()。
>>> matplotlib.rcdefaults()
如果手工修改了配置文件,希望重新從配置文件載入最新的配置,可以調用:
>>> matplotlib.rcParams.update( matplotlib.rc_params() )
在圖表中顯示中文
matplotlib的缺省配置文件中所使用的字體無法正確顯示中文。為了讓圖表能正確顯示中文,可以有幾種解決方案。
- 在程序中直接指定字體。
- 在程序開頭修改配置字典rcParams。
- 修改配置文件。
在matplotlib中可以通過字體名指定字體,而每個字體名都與一個字體文件相對應。通過下面的程序可以獲得所有可用的字體列表:
>>> from matplotlib.font_manager import fontManager
>>> fontManager.ttflist
[<Font 'cmex10' (cmex10.ttf) normal normal 400 normal>,
<Font 'Bitstream Vera Sans Mono' (VeraMoBd.ttf) normal normal 700 normal>,
...
]
fontManager.ttflist是matplotlib的系統字體索引列表。其中的每個元素都是表示字體的Font對象。例如由第一個Font對象可知,字體名”cmex10”與字體文件“cmex10.ttf”相對應。下面的語句獲得字體文件的全路徑和字體名:
>>> fontManager.ttflist[0].name
'cmex10'
>>> fontManager.ttflist[0].fname
'C:\\Python26\\lib\\site-packages\\matplotlib\\mpl-data\\fonts\\ttf\\cmex10.ttf'
由字體文件的路徑可知’cmex10’是matplotlib自帶的字體。下面的程序利用字體索引列表中的字體顯示中文文字,其效果如【圖:顯示系統中的所有中文字體】所示。
05-matplotlib/matplotlib_fonts.py
顯示所有的中文字體
from matplotlib.font_manager import fontManager
import matplotlib.pyplot as plt
import os
import os.path
fig = plt.figure(figsize=(12,6))
ax = fig.add_subplot(111)
plt.subplots_adjust(0, 0, 1, 1, 0, 0)
plt.xticks([])
plt.yticks([])
x, y = 0.05, 0.08
fonts = [font.name for font in fontManager.ttflist if
os.path.exists(font.fname) and os.stat(font.fname).st_size>1e6] ❶
font = set(fonts)
dy = (1.0-y)/(len(fonts)/4 + (len(fonts)%4!=0))
for font in fonts:
t = ax.text(x, y, u"中文字體", {'fontname':font, 'fontsize':14}, transform=ax.transAxes) ❷
ax.text(x, y-dy/2, font, transform=ax.transAxes)
x += 0.25
if x >= 1.0:
y += dy
x = 0.05
plt.show()

顯示系統中的所有中文字體
❶利用os模塊中的stat()獲取字體文件的大小,並保留字體索引列表中所有大於1M字節的字體文件。由於中文字體文件通常都很大,因此使用這種方法可以粗略地找出所有的中文字體文件。
❷調用子圖對象的text()在其中添加文字,注意文字必須是Unicode字符串。通過一個描述字體的字典指定文字的字體:’fontname’鍵所對應的值就是字體名。
由於matplotlib只搜索TTF字體文件,因此無法通過上述方法使用Windows的Fonts目錄下的許多復合字體文件(*.ttc)。可以直接創建使用字體文件的FontProperties對象,並使用此對象指定圖表中的各種文字的字體。下面是一個例子:
05-matplotlib/matplotlib_simsun_font.py
使用TTC字體文件
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import numpy as np
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) ❶
t = np.linspace(0, 10, 1000)
y = np.sin(t)
plt.plot(t, y)
plt.xlabel(u"時間", fontproperties=font) ❷
plt.ylabel(u"振幅", fontproperties=font)
plt.title(u"正弦波", fontproperties=font)
plt.show()
❶創建一個描述字體屬性的FontProperties對象,並設置其fname屬性為字體文件的絕對路徑。❷通過fontproperties參數將FontProperties對象傳遞給顯示文字的函數。
還可以通過字體工具將TTC字體文件分解為多個TTF字體文件,並將其復制到系統的字體文件夾中。由於為了縮短啟動時間,matplotlib不會每次啟動時都重新掃描所有的字體文件並創建字體索引列表,因此在復制完字體文件之后,需要運行下面的語句以重新創建字體索引列表:
>>> from matplotlib.font_manager import _rebuild
>>> _rebuild()
還可以直接修改配置字典,設置缺省字體,這樣就不需要在每次繪制文字時設置字體了。例如:
>>> plt.rcParams["font.family"] = "SimHei"
>>> plt.plot([1,2,3])
>>> plt.xlabel(0.5,0.5,u"中文字體")
或者修改上節介紹的配置文件,修改其中的“font.family”配置為:
font.family : SimHei
注意“SimHei”是字體名,請讀者運行“matplotlib_fonts.py”查看系統中所有可用的中文字體名。