作者:Wes McKinney 本文摘編自《利用Python進(jìn)行數(shù)據(jù)分析》第2版 matplotlib是一個(gè)用于生成出版級(jí)質(zhì)量圖表(通常是二維的)的桌面繪圖包。該項(xiàng)目由John Hunter于2002年發(fā)起,目的在于在Python環(huán)境下進(jìn)行MATLAB風(fēng)格的繪圖。matplotlib和IPython社區(qū)合作簡(jiǎn)化了IPython shell(目前是 Jupyter筆記本)的交互式繪圖。matplotlib支持所有操作系統(tǒng)上的各種GUI后端,還可以將可視化導(dǎo)出為所有常見(jiàn)的矢量和光柵圖形格式(PDF,SVG,JPG,PNG,BMP,GIF等)。 隨著時(shí)間的推移,matplotlib已經(jīng)產(chǎn)生了一些數(shù)據(jù)可視化的附加工具包,使用matplotlib進(jìn)行底層繪圖。 學(xué)習(xí)以下示例代碼最簡(jiǎn)單的方式就是在Jupyter notebook中使用交互式繪圖。在進(jìn)行設(shè)置時(shí),需要在Jupyter notebook中執(zhí)行以下語(yǔ)句:
00 簡(jiǎn)明matplotlib API入門(mén) 在使用matplotlib時(shí),我們使用以下的導(dǎo)入慣例: In [ 11 ]: import matplotlib.pyplot as plt 在Jupyter中運(yùn)行%matplotlib notebook (或在IPython中運(yùn)行%matplotlib ),我們就可以嘗試生成一個(gè)簡(jiǎn)單的圖形。如果所有的設(shè)置都正確,則一個(gè)像圖1的圖形就會(huì)出現(xiàn): In [ 12 ]: import numpy as np In [ 13 ]: data = np.arange( 10 ) In [ 14 ]: data Out[ 14 ]: array([ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]) In [ 15 ]: plt.plot(data) ▲圖1 簡(jiǎn)單的線(xiàn)性圖 盡管seaborn等庫(kù)和pandas內(nèi)建的繪圖函數(shù)可以處理大部分繪圖的普通細(xì)節(jié),但如果你想在提供的函數(shù)選項(xiàng)之外進(jìn)行定制則需要學(xué)習(xí)一些matplotlib的AIP。 本文沒(méi)有足夠的篇幅來(lái)對(duì)matplotlib的功能寬度和深度進(jìn)行全面介紹。但介紹的內(nèi)容應(yīng)該是足以使你入門(mén)的。matplotlib的可視化作品庫(kù)和文檔是學(xué)習(xí)高級(jí)功能的最佳資源。 01 圖片與子圖 matplotlib所繪制的圖位于圖片(Figure)對(duì)象中。你可以使用plt.figure生成一個(gè)新的圖片: In [ 16 ]: fig = plt.figure() 在IPython中,一個(gè)空白的繪圖窗口就會(huì)出現(xiàn),但在Jupyter中則沒(méi)有任何顯示,直到我們使用一些其他命令。plt.figure有一些選項(xiàng),比如figsize是確保圖片有一個(gè)確定的大小以及存儲(chǔ)到硬盤(pán)時(shí)的長(zhǎng)寬比。 你不能使用空白的圖片進(jìn)行繪圖。你需要使用add_subplot創(chuàng)建一個(gè)或多個(gè)子圖(subplot): In [ 16 ]: fig = plt.figure() In [ 17 ]: ax1 = fig.add_subplot( 2 , 2 , 1 ) 上面代碼的意思是圖片應(yīng)該是2*2的(最多四個(gè)圖形),并且我們選擇了四個(gè)圖形中的第一個(gè)(序號(hào)從1開(kāi)始)。如果你接著創(chuàng)建了兩個(gè)子圖,你將會(huì)獲得看上去類(lèi)似圖2的可視化: In [ 18 ]: ax2 = fig.add_subplot( 2 , 2 , 2 ) In [ 19 ]: ax3 = fig.add_subplot( 2 , 2 , 3 ) ▲圖2 一個(gè)帶有三個(gè)子圖的空白matplotlib圖片 使用Jupyter notebook時(shí)有個(gè)細(xì)節(jié)需要注意,在每個(gè)單元格運(yùn)行后,圖表被重置,因此對(duì)于更復(fù)雜的圖表,你必須將所有的繪圖命令放在單個(gè)的notebook單元格中。 我們將下面這些代碼在同一個(gè)單元格中運(yùn)行: fig = plt.figure() ax1 = fig.add_subplot( 2 , 2 , 1 ) ax2 = fig.add_subplot( 2 , 2 , 2 ) ax3 = fig.add_subplot( 2 , 2 , 3 ) 當(dāng)你輸入繪圖命令plt.plot([1.5, 3.5, -2, 1.6]) ,matplotlib會(huì)在最后一個(gè)圖片和子圖(如果需要的話(huà)就創(chuàng)建一個(gè))上進(jìn)行繪制,從而隱藏圖片和子圖的創(chuàng)建。因此,如果我們添加了下面的代碼,你會(huì)得到形如圖3的可視化: In [ 20 ]: plt.plot(np.random.randn( 50 ).cumsum(), 'k--' ) ▲圖3 單個(gè)子圖繪制的數(shù)據(jù)可視化 'k--'是用于繪制黑色分段線(xiàn)的style選項(xiàng)。fig.add_subplot返回的對(duì)象是AxesSubplot對(duì)象,使用這些對(duì)象你可以直接在其他空白的子圖上調(diào)用對(duì)象的實(shí)例方法進(jìn)行繪圖(參考圖4): In [ 21 ]: _ = ax1.hist(np.random.randn( 100 ), bins = 20 , color = 'k' , alpha = 0.3 ) In [ 22 ]: ax2.scatter(np.arange( 30 ), np.arange( 30 ) + 3 * np.random.randn( 30 )) ▲圖4 增加子圖后的數(shù)據(jù)可視化 你可以在matplotlib的官方文檔中找到完整的圖形類(lèi)型。 使用子圖網(wǎng)絡(luò)創(chuàng)建圖片是非常常見(jiàn)的任務(wù),所以matplotlib包含了一個(gè)便捷方法plt.subplots,它創(chuàng)建一個(gè)新的圖片,然后返回要給包含了已生成子圖對(duì)象的NumPy數(shù)組: In [ 24 ]: fig, axes = plt.subplots( 2 , 3 ) In [ 25 ]: axes Out[ 25 ]: array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7fb626374048 >, <matplotlib.axes._subplots.AxesSubplot object at 0x7fb62625db00 >, <matplotlib.axes._subplots.AxesSubplot object at 0x7fb6262f6c88 >], [<matplotlib.axes._subplots.AxesSubplot object at 0x7fb6261a36a0 >, <matplotlib.axes._subplots.AxesSubplot object at 0x7fb626181860 >, <matplotlib.axes._subplots.AxesSubplot object at 0x7fb6260fd4e0 >]], dtype = object )
▲表1 pyplot.subplots選項(xiàng) 調(diào)整子圖周?chē)拈g距 默認(rèn)情況下,matplotlib會(huì)在子圖的外部和子圖之間留出一定的間距。這個(gè)間距都是相對(duì)于圖的高度和寬度來(lái)指定的,所以如果你通過(guò)編程或手動(dòng)使用GUI窗口來(lái)調(diào)整圖的大小,那么圖就會(huì)自動(dòng)調(diào)整。 你可以使用圖對(duì)象上的subplots_adjust方法更改間距,也可以用作頂層函數(shù):
wspace和hspace分別控制的是圖片的寬度和高度百分比,以用作子圖間的間距。下面是一個(gè)小例子,我將這個(gè)間距一直縮小到零(見(jiàn)圖5) : fig, axes = plt.subplots( 2 , 2 , sharex = True , sharey = True ) for i in range ( 2 ): for j in range ( 2 ): axes[i, j].hist(np.random.randn( 500 ), bins = 50 , color = 'k' , alpha = 0.5 ) plt.subplots_adjust(wspace = 0 , hspace = 0 ) ▲圖5 沒(méi)有內(nèi)部子圖間隔的數(shù)據(jù)可視化 你可能會(huì)注意到軸標(biāo)簽是存在重疊的。matplotlib并不檢查標(biāo)簽是否重疊,因此在類(lèi)似情況下你需要通過(guò)顯式指定刻度位置和刻度標(biāo)簽的方法來(lái)修復(fù)軸標(biāo)簽。 02 顏色、標(biāo)記和線(xiàn)類(lèi)型 matplotlib的主函數(shù)plot接收帶有x和y周的數(shù)組以及一些可選的字符串縮寫(xiě)參數(shù)來(lái)指明顏色和線(xiàn)類(lèi)型。例如,要用綠色破折號(hào)繪制x對(duì)y的線(xiàn),你需要執(zhí)行: ax.plot(x, y, 'g--' ) 這種在字符串中指定顏色和線(xiàn)條樣式的方式是方便的; 在實(shí)踐中,如果你以編程方式創(chuàng)建繪圖,則可能不希望將字符串混合在一起以創(chuàng)建具有所需樣式的圖表。 同樣的圖表可以使用更為顯式的方式來(lái)表達(dá): ax.plot(x, y, 'g--' ) ax.plot(x, y, linestyle = '--' , color = 'g' ) 有很多顏色縮寫(xiě)被用于常用顏色,但是你可以通過(guò)指定十六進(jìn)制顏色代碼的方式來(lái)指定任何顏色(例如'#CECECE' )。參考plot函數(shù)的文檔字符串可以看到全部的線(xiàn)類(lèi)型(在IPython或Jupyter中使用plot? )。 折線(xiàn)圖還可以有標(biāo)記用來(lái)凸顯實(shí)際的的數(shù)據(jù)點(diǎn)。由于matplotlib創(chuàng)建了一個(gè)連續(xù)性折線(xiàn)圖,插入點(diǎn)之間有時(shí)并不清除點(diǎn)在哪。標(biāo)記可以是樣式字符串的一部分,樣式字符串中線(xiàn)類(lèi)型、標(biāo)記類(lèi)型必須跟在顏色后面(參考圖6): In [ 30 ]: from numpy.random import randn In [ 31 ]: plt.plot(randn( 30 ).cumsum(), 'ko--' ) ▲圖6 帶有標(biāo)記的折線(xiàn)圖 上面的代碼可以寫(xiě)的更為顯式: plot(randn( 30 ).cumsum(), color = 'k' , linestyle = 'dashed' , marker = 'o' ) 對(duì)于折線(xiàn)圖,你會(huì)注意到后續(xù)的點(diǎn)默認(rèn)是線(xiàn)性?xún)?nèi)插的??梢酝ㄟ^(guò)drawstyle選項(xiàng)進(jìn)行更改(圖7): In [ 33 ]: data = np.random.randn( 30 ).cumsum() In [ 34 ]: plt.plot(data, 'k--' , label = 'Default' ) Out[ 34 ]: [<matplotlib.lines.Line2D at 0x7fb624d86160 >] In [ 35 ]: plt.plot(data, 'k-' , drawstyle = 'steps-post' , label = 'steps-post' ) Out[ 35 ]: [<matplotlib.lines.Line2D at 0x7fb624d869e8 >] In [ 36 ]: plt.legend(loc = 'best' ) ▲圖7 不同drawstyle選項(xiàng)下的折線(xiàn)圖 你可能會(huì)注意到在運(yùn)行代碼后會(huì)有像 這樣的輸出。matplotlib返回的對(duì)象引用了剛剛添加的圖表子組件。很多時(shí)候你可以安全地忽略這些輸出。這里,由于我們向plot傳遞了label,我們可以使用plt.lengend為每條線(xiàn)生成一個(gè)用于區(qū)分的圖例。 無(wú)論你在用數(shù)據(jù)繪圖時(shí)是否傳遞了lebel選項(xiàng),你都必須調(diào)用plt.lengend(如果你有軸的引用,也可以用ax.legend)來(lái)生成圖例來(lái)生成圖例。 03 刻度、標(biāo)簽和圖例 對(duì)于大多數(shù)圖表修飾工作,有兩種主要的方式:使用程序性的pyplot接口(即matplotlib.pyplot)和更多面向?qū)ο蟮脑鷐atplotlib API。 pyplot接口設(shè)計(jì)為交互式使用,包含了像xllim、xtick和xtcklabels等方法。這些方法分別控制了繪圖范圍、刻度位置以及刻度標(biāo)簽。我們可以在兩種方式中使用:
所有的這些方法都會(huì)在當(dāng)前活動(dòng)的或最近創(chuàng)建的AxeSubplot上生效。這些方法中的每一個(gè)對(duì)應(yīng)于子圖自身的兩個(gè)方法;比如xlim對(duì)應(yīng)于ax.get_lim和ax.set_lim。我更傾向于使用subplot的實(shí)例方法,因?yàn)檫@樣更為顯式(尤其是在處理多個(gè)子圖時(shí)),但你當(dāng)然可以使用你覺(jué)得更為方便的方式。 1. 設(shè)置標(biāo)題、軸標(biāo)簽、刻度和刻度標(biāo)簽 為了講解軸的自定義,我會(huì)生成一個(gè)簡(jiǎn)單圖表,并繪制隨機(jī)漫步(參考圖8): In [ 37 ]: fig = plt.figure() In [ 38 ]: ax = fig.add_subplot( 1 , 1 , 1 ) In [ 39 ]: ax.plot(np.random.randn( 1000 ).cumsum()) ▲圖8 表述x軸(以及軸標(biāo)簽)的簡(jiǎn)單圖表 要改變x軸刻度,最簡(jiǎn)單的方式是使用set_xticks和set_xticklebels。set_xticks表示在數(shù)據(jù)范圍內(nèi)設(shè)定刻度的位置;默認(rèn)情況下,這些刻度也有標(biāo)簽。但是我們可以使用set_xticklabels為標(biāo)簽賦值: In [ 40 ]: ticks = ax.set_xticks([ 0 , 250 , 500 , 750 , 1000 ]) In [ 41 ]: labels = ax.set_xticklabels([ 'one' , 'two' , 'three' , 'four' , 'five' ], ....: rotation = 30 , fontsize = 'small' ) rotation選項(xiàng)會(huì)將x軸刻度標(biāo)簽旋轉(zhuǎn)30度。最后,set_xlabel會(huì)給x軸一個(gè)名稱(chēng),set_titel會(huì)給子圖一個(gè)標(biāo)題(參考圖9的結(jié)果圖): In [ 42 ]: ax.set_title( 'My first matplotlib plot' ) Out[ 42 ]: <matplotlib.text.Text at 0x7fb624d055f8 > In [ 43 ]: ax.set_xlabel( 'Stages' ) ▲圖9 x軸刻度的簡(jiǎn)單示例 修改y軸坐標(biāo)是相同過(guò)程,將上面示例中的x替換成y即可。軸的類(lèi)型擁有一個(gè)set方法,允許批量設(shè)置繪圖屬性。根據(jù)之前的示例,我們可以寫(xiě)如下代碼: props = { 'title' : 'My first matplotlib plot' , 'xlabel' : 'Stages' } ax. set ( * * props) 2. 添加圖例 圖例是用來(lái)區(qū)分繪圖元素的重要內(nèi)容。有多種方式可以添加圖例。最簡(jiǎn)單的方式是在添加每個(gè)圖表時(shí)傳遞label參數(shù): In [ 44 ]: from numpy.random import randn In [ 45 ]: fig = plt.figure(); ax = fig.add_subplot( 1 , 1 , 1 ) In [ 46 ]: ax.plot(randn( 1000 ).cumsum(), 'k' , label = 'one' ) Out[ 46 ]: [<matplotlib.lines.Line2D at 0x7fb624bdf860 >] In [ 47 ]: ax.plot(randn( 1000 ).cumsum(), 'k--' , label = 'two' ) Out[ 47 ]: [<matplotlib.lines.Line2D at 0x7fb624be90f0 >] In [ 48 ]: ax.plot(randn( 1000 ).cumsum(), 'k.' , label = 'three' ) Out[ 48 ]: [<matplotlib.lines.Line2D at 0x7fb624be9160 >] 一旦你運(yùn)行了上面的代碼,你也可以調(diào)用ax.legend()或plt.legend自動(dòng)生成圖例。結(jié)果圖表參見(jiàn)圖10: In [ 49 ]: ax.legend(loc = 'best' ) ▲圖10 有三根折線(xiàn)和圖例的簡(jiǎn)單圖表 legend方法有多個(gè)其他的位置參數(shù)loc。參考文檔字符串(使用ax.legend?命令)獲取更多信息。 loc參數(shù)告訴matplotlib在哪里放置圖表。如果你不挑剔,'best'是一個(gè)好選項(xiàng),它會(huì)自動(dòng)選擇最合適的位置。如果取消圖例中的元素,不要傳入label參數(shù)或者傳入label='_nolegend_'。 04 注釋與子圖加工 除了標(biāo)準(zhǔn)的繪圖類(lèi)型,你可能還會(huì)想在圖表上繪制自己的注釋?zhuān)易⑨屩锌赡軙?huì)包含文本、箭頭以及其他圖形。你可以使用text、arrow和annote方法來(lái)添加注釋和文本。text在圖表上給定的坐標(biāo)(x, y),根據(jù)可選的定制樣式繪制文本: ax.text(x, y, 'Hello world!' , family = 'monospace' , fontsize = 10 ) 注釋可以同時(shí)繪制文本和箭頭。作為一個(gè)例子,讓我們繪制標(biāo)普500指數(shù)從2007年來(lái)的收盤(pán)價(jià)(從雅虎財(cái)經(jīng)獲得數(shù)據(jù)),并在圖表中標(biāo)注從2008到2009年金融危機(jī)中的重要日期。你可以在Jupyter notebook的一個(gè)單元格中復(fù)現(xiàn)這些代碼。參考圖11的代碼運(yùn)行結(jié)果: from datetime import datetime fig = plt.figure() ax = fig.add_subplot( 1 , 1 , 1 ) data = pd.read_csv( 'examples/spx.csv' , index_col = 0 , parse_dates = True ) spx = data[ 'SPX' ] spx.plot(ax = ax, style = 'k-' ) crisis_data = [ (datetime( 2007 , 10 , 11 ), 'Peak of bull market' ), (datetime( 2008 , 3 , 12 ), 'Bear Stearns Fails' ), (datetime( 2008 , 9 , 15 ), 'Lehman Bankruptcy' ) ] for date, label in crisis_data: ax.annotate(label, xy = (date, spx.asof(date) + 75 ), xytext = (date, spx.asof(date) + 225 ), arrowprops = dict (facecolor = 'black' , headwidth = 4 , width = 2 , headlength = 4 ), horizontalalignment = 'left' , verticalalignment = 'top' ) # 放大2007 - 2010年 ax.set_xlim([ '1/1/2007' , '1/1/2011' ]) ax.set_ylim([ 600 , 1800 ]) ax.set_title( 'Important dates in the 2008-2009 financial crisis' ) ▲圖11 2008-2009金融危機(jī)中的重要日期 在圖表中有一些重要點(diǎn)需要凸顯:ax.annotate方法可以在指定的x和y坐標(biāo)上繪制標(biāo)簽。我們可以使用set_xlim和set_ylim方法手動(dòng)設(shè)置圖表的邊界,而不是使用matplotlib的默認(rèn)設(shè)置。最后,ax.set_title會(huì)圖表添加了一個(gè)主標(biāo)題。 參考在線(xiàn)的matplotlib展覽館,可以學(xué)習(xí)更多注釋的范例。 繪制圖形時(shí)有更多需要注意的地方。matplotlib含有表示多種常見(jiàn)圖形的對(duì)象,這些對(duì)象的引用是patches。一些圖形,比如Rectangle(矩形)和Circle(圓形),可以在matplotlib.pyplot中找到,但圖形的全集位于matplotlib.patches。 想在圖表中添加圖形時(shí),你需要生成patch(補(bǔ)丁)對(duì)象shp,并調(diào)用ax.add_patch(shp)將它加入到子圖中(參考圖12): fig = plt.figure() ax = fig.add_subplot( 1 , 1 , 1 ) rect = plt.Rectangle(( 0.2 , 0.75 ), 0.4 , 0.15 , color = 'k' , alpha = 0.3 ) circ = plt.Circle(( 0.7 , 0.2 ), 0.15 , color = 'b' , alpha = 0.3 ) pgon = plt.Polygon([[ 0.15 , 0.15 ], [ 0.35 , 0.4 ], [ 0.2 , 0.6 ]], color = 'g' , alpha = 0.5 ) ax.add_patch(rect) ax.add_patch(circ) ax.add_patch(pgon) ▲圖12 三種個(gè)不同patch圖形的可視化 當(dāng)你看到很多常見(jiàn)繪圖類(lèi)型的實(shí)現(xiàn)時(shí),你會(huì)發(fā)現(xiàn)他們都是從patches中組裝而來(lái)。 05 將圖片保存到文件 你可以使用plt.savefig將活動(dòng)圖片保存到文件。這個(gè)方法等價(jià)于圖片對(duì)象的savefig實(shí)例方法。例如將圖片保存為SVG,你只需要輸入以下代碼: plt.savefig( 'figpath.svg' ) 文件類(lèi)型是從文件擴(kuò)展名中推斷出來(lái)的。 所以如果你使用.pdf,則會(huì)得到一個(gè)PDF。 我常常使用幾個(gè)重要的選項(xiàng)來(lái)發(fā)布圖形:dpi,它控制每英寸點(diǎn)數(shù)的分辨率; bbox_inches,可以修剪實(shí)際圖形的空白。 為了得到同樣一個(gè)PNG圖片,且使用最小的空白,擁有400 DPI,你需要運(yùn)行以下代碼: plt.savefig( 'figpath.png' , dpi = 400 , bbox_inches = 'tight' ) savefig并一定是寫(xiě)到硬盤(pán)的,它可以將圖片寫(xiě)入到所有的文件型對(duì)象中,例如BytesIO: from io import BytesIO buffer = BytesIO() plt.savefig( buffer ) plot_data = buffer .getvalue() 表2是savefig其他選項(xiàng)的列表:
▲表2 Figure.savefig選項(xiàng) 06 matplotlib設(shè)置 matplotlib配置了配色方案和默認(rèn)設(shè)置,主要用來(lái)準(zhǔn)備用于發(fā)布的圖片。 幸運(yùn)的是,幾乎所有的默認(rèn)行為都可以通過(guò)廣泛的全局參數(shù)來(lái)定制,包括圖形大小、子圖間距、顏色、字體大小和網(wǎng)格樣式等等。使用rc方法是使用Python編程修改配置的一種方式; 例如,要將全局默認(rèn)數(shù)字大小設(shè)置為10×10,你可以輸入: plt.rc( 'figure' , figsize = ( 10 , 10 )) rc的第一個(gè)參數(shù)是你想要自定義的組件,比如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等等。 之后,可以按照關(guān)鍵字參數(shù)的序列指定新參數(shù)。字典是一種在程序中設(shè)置選項(xiàng)的簡(jiǎn)單方式: font_options = { 'family' : 'monospace' , 'weight' : 'bold' , 'size' : 'small' } plt.rc( 'font' , * * font_options) 如果需要更深入的定制和參看全量選項(xiàng),可以參考matplotlib的設(shè)置文件matplotlibrc,該文件位于matplotlib/mpl-data路徑。如果你定制了這個(gè)文件,并將他放置在home路徑下并且文件名為.matplotlibrc,則每次你使用matplotlib時(shí)都會(huì)讀取該文件。 關(guān)于作者:韋斯·麥金尼(Wes McKinney)是流行的Python開(kāi)源數(shù)據(jù)分析庫(kù)pandas的創(chuàng)始人。他是一名活躍的演講者,也是Python數(shù)據(jù)社區(qū)和Apache軟件基金會(huì)的Python/C++開(kāi)源開(kāi)發(fā)者。目前他在紐約從事軟件架構(gòu)師工作。 本文摘編自《利用Python進(jìn)行數(shù)據(jù)分析》(原書(shū)第2版),經(jīng)出版方授權(quán)發(fā)布。 |
|