繪圖是數據分析工作中最重要的任務之一,是探索過程的一部分。python有許多可視化工具,下面主要學習matplotlib
matplotlib是一個用於創建出版質量圖表的桌面繪圖包(主要是2D方面)。該項目是由John Hunter於2002年啟動的,其目的是為了python構建一個MATLAB式的繪圖接口。如果結合使用一種GUI工具包(如IPython),matplotlib還具有諸如縮放和平移等交互功能。它不僅支持各種操作系統上許多不同的GUI后端,而且還能將圖片導出為各種常見的矢量(vector)和光柵(raster)圖:PDF、SVG、JPG、PNG、BMP、GIF等。
要使用文中的代碼實例,要確保我們的IPython是以Pylab模式啟動的(ipython --pylab),或通過%gui魔術命令打開了GUI事件循環集成。
一.matplotlib API入門
使用matplotlib的方法有很多種,最常用的方式是Pylab模式的IPython(ipython --pylab)。這樣會將IPython配置為使用我們所指定的matplotlib GUI后端(Tk、wxPython、PyQt、Mac OS X native、GTK).對大部分用戶而言,默認的后端就已經夠用了。Pylab模式還會向IPython引入一大堆模塊和函數以提供一種更接近於MATLAB的界面。繪制一張簡單的圖表即可測試是否一切准備就緒:
In [1]: plot(np.arange(10))
Out[1]: [<matplotlib.lines.Line2D at 0xfb13518>]

matplotlib API函數(如plot和close)都位於matplotlib.pyplot模塊中,其通常的引入約定是:
import matplotlib.pyplot as plt
雖然pandas的繪圖函數能夠處理許多普通的繪圖任務,但如果需要自定義一些高級功能的話就必須學習matplotlib API。
1.Figure和Support
matplotlib的圖像都位於Figure對象中。我們可以用plt.figure創建一個新的Figure:
In [4]: fig=plt.figure()
這時會彈出一個空窗口(不要關閉空窗口)。plt.figure有一些選項,特別是figsize,它用於確保當圖片保存到磁盤時具有一定的大小和縱橫比。matplotlib中的Figure還支持一種MATLAB式的編號架構(例如plt.figure(2))。通過plt.gcf()即可得到當前Figure的引用。
不能通過空Figure繪圖。必須用add_subplot創建一個或多個subplot才行:
In [5]: ax1=fig.add_subplot(2,2,1)
這條代碼的意思是:圖像應該是2X2的,且當前選中的是4個subplot中的第一個(編號從1開始)。如果再把后面兩個subplot也創建出來,最終得到的圖像如下圖所示:
In [6]: ax2=fig.add_subplot(2,2,2)
In [7]: ax3=fig.add_subplot(2,2,3)

如果這時發出一條繪圖命令(如plt.plot([1.8,3.8,-6,1.9])),matplotlib就會在最后一個用過的subplot(如果沒有則創建一個)上進行繪制。因此,如果我們執行下列命令,我們就會得到如下圖所示的結果:
In [12]: from numpy.random import randn
In [13]: plt.plot(randn(50).cumsum(),'k--')
Out[13]: [<matplotlib.lines.Line2D at 0xd8e6ef0>]

"k--"是一個線性選項,用於告訴matplotlib繪制黑色虛線圖。上面那些由fig.add_subplot所返回的對象是AxesSubplot對象,直接調用它們的實例方法就可以在其他空着的格子里面畫圖了,如下圖所示:

我們可以在matplotlib的文檔中找到各種圖表類型。由於根據特定布局創建Figure和subplot是一件非常常見的任務,於是便出現了一個更為方便的方法(plt.subplots),它可以創建一個新的Figure,並返回一個含有已創建的subplot對象的NumPy數組:
In [10]: fig,axes=plt.subplots(2,3)

In [11]: axes
Out[11]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D6C47F0>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D8172E8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D83C860>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D864DD8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D925390>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000000000D94E1D0>]],
dtype=object)
這是非常實用的,因為可以輕松地對axes數組進行索引,就好像是一個二維數組一樣,例如,axes[0,1]。我們還可以通過sharex和sharey指定subplot應該具有相同的X軸或Y軸。在比較相同范圍的數據時,這也是非常實用的,否則,matplotlib會自動縮放各圖表的界限。有關該方法的更多信息,參見下表
參數 說明
nrows subplot的行數
ncols subplot的列數
sharex 所有subplot應該使用相同的X軸刻度(調節xlim將會影響所有subplot)
sharey 所有subplot應該使用相同的Y軸刻度(調節ylim將會影響所有subplot)
subplot_kw 用於創建各subplot的關鍵字字典
調整subplot周圍的間距
默認情況下,matplotlib會在subplot外圍留下一定的邊距,並在subplot之間留下一定的間距。間距跟圖像的高度和寬度有關,因此,如果我們調整了圖像大小(不管是編程還是手工),間距也會自動調整。利用Figure的subplots_adjust方法可以輕而易舉地修改間距,此外,它也是個頂級函數:
subplot_adjust(left=None,bottom=None,right=None,top=None,wspace=None,hspace=None)
wspace和hspace用於控制寬度和高度的百分比,可以用作subplot之間的間距。下面是一個簡單的例子,其中將間距收縮到0,如下圖所示:
In [1]: import matplotlib.pyplot as plt
In [2]: fig=plt.figure()
In [3]: ax1=fig.add_subplot(2,2,2)
In [4]: ax2=fig.add_subplot(2,2,3)
In [5]: from numpy.random import randn
In [6]: fig,axes=plt.subplots(2,2,sharex=True,sharey=True)
In [7]: for i in range(2):
...: for j in range(2):
...: axes[i,j].hist(randn(500),bins=50,color='k',alpha=0.5)
...:
In [8]: plt.subplots_adjust(wspace=0,hspace=0)

不難看出,其中的軸標簽重疊了。matplotlib不會檢查標簽是否重疊,所以對於這樣情況,我們只能自己設定刻度位置和刻度標簽。后面將會詳細介紹該內容。
顏色、標記和線型
matplotlib的plot函數接受一組X和Y坐標,還可以接受一個表示顏色和線型的字符串縮放。例如,要根據x和y繪制綠色虛線,我們可以執行如下代碼:
ax.plot(x,y,'g--')
這種在一個字符串中指定顏色和線型的方式非常方便。通過下面這種更為明確的方式也能得到同樣的效果:
ax.plot(x,y,linestyle='--',color='g')
常用的顏色都有一個縮寫詞,要使用其他任意顏色則可以通過指定其RGB值的形式使用(例如,‘#CECECE’)。
線型圖還可以加一些標記(marker),以強調實際的數據點。由於matplotlib創建的是連續的線型圖(點與點之間插值),因此有時可能不太容易看出真實數據點的位置。標記也可以放到格式字符串中,但標記類型和線型必須放在顏色后面:
In [24]: plt.plot(randn(30).cumsum(),'ko--')
Out[24]: [<matplotlib.lines.Line2D at 0x105ce208>]

還可以將其寫成更為明確的形式:
In [25]: plot(randn(30).cumsum(),color='k',linestyle='dashed',marker='o')
Out[25]: [<matplotlib.lines.Line2D at 0x10608278>]
在線型圖中,非實際數據點默認是按線性方式插值的。可以通過drawstyle選項修改:
In [26]: data=randn(30).cumsum()
In [27]: plt.plot(data,'k--',label='Default')
Out[27]: [<matplotlib.lines.Line2D at 0x107c20f0>]
In [29]: plt.plot(data,'k--',drawstyle='steps-post',label='steps-post')
Out[29]: [<matplotlib.lines.Line2D at 0x107d9ac8>]
In [31]: plt.legend(loc='best')
No handles with labels found to put in legend.
Out[31]: <matplotlib.legend.Legend at 0x10d2f080>
刻度、標簽和圖例
對於大多數的圖表裝飾項,其主要實現方式有二:使用過程型的pyplot接口(MATLAB用戶非常熟悉)以及更為面向對象的原生matplotlib API。
pyplot接口的設計目的就是交互式使用,含有諸如xlim、xticks和xticklabels之類的方法。它們分別控制圖表的范圍、刻度位置、刻度標簽等。其使用方式有以下兩種:
(1)調用時不帶參數,則返回當前的參數值。例如,plt.xlim()返回當前的X軸繪圖范圍。
(2)調用時帶參數,則設置參數值。因此,plt.xlim([0,10])會將X軸的范圍設置為0到10。
所有這些方法都是對當前或最近創建的AxesSuplot起作用的。它們各自對應subplot對象上的兩個方法,以xlim為例,就是ax.get_xlim和ax.set_xlim。本人更喜歡使用subplot的實例方法(因為本人喜歡明確的事情,而且在處理多個subplot時這樣也更清楚一些)。當然我們完全可以選擇自己覺得方便的那個。
設置標題、軸標簽、刻度以及刻度標簽
為了說明軸的自定義,我們將創建一個簡單的圖像並繪制一段隨機漫步:
In [34]: fig=plt.figure()
In [35]: ax=fig.add_subplot(1,1,1)
In [36]: ax.plot(randn(1000).cumsum())
Out[36]: [<matplotlib.lines.Line2D at 0x108361d0>]
要修改X軸的刻度,最簡單的辦法是使用set_xticks和set_xticklabels。前者告訴matplotlib要將刻度放在數據范圍中的哪些位置,默認情況下,這些位置也就是刻度標簽。但我們可以通過set_xticklabels將任何其他的值用作標簽:
In [37]: ticks=ax.set_xticks([0,250,500,750,1000])
In [38]: labels=ax.set_xticklabels(['one','two','three','four','five'],rotation
...: =30,fontsize='small')
最后,再用set_xlabel為X軸設置一個名稱,並用set_title設置一個標題:
In [6]: ax.set_title('My first matplotlib plot')
Out[6]: Text(0.5, 1.0, 'My first matplotlib plot')
In [7]: ax.set_xlabel('Stages')
Out[7]: Text(0.5, 10.763891973024519, 'Stages')
最終的結果如下圖所示。Y軸的修改方式與此類似,只需將上述代碼中的x替換為y即可。

添加圖例
圖例(legend)是另一種用於標識圖表元素的重要工具。添加圖例的方式有二。最簡單的是在添加subplot的時候傳入label參數:
In [9]: fig=plt.figure();ax=fig.add_subplot(1,1,1)
In [10]: ax.plot(randn(1000).cumsum(),'k',label='one')
Out[10]: [<matplotlib.lines.Line2D at 0xd638320>]
In [11]: ax.plot(randn(1000).cumsum(),'k--',label='two')
Out[11]: [<matplotlib.lines.Line2D at 0xd663128>]
In [12]: ax.plot(randn(1000).cumsum(),'k.',label='three')
Out[12]: [<matplotlib.lines.Line2D at 0xd6a2208>]
在此之后,我們可以調用ax.legend()或plt.legend()來自動創建圖例:
In [13]: ax.legend(loc='best')
Out[13]: <matplotlib.legend.Legend at 0xd6f52e8>
如下圖所示。loc告訴matplotlib要將圖例放在哪。如果我們不是吹毛求疵的話,"beat"是不錯的選擇,因為它會選擇最不礙事的位置。要從圖例中去除一個或多個元素,不傳入label或傳入label='_nolegend_'即可。

注解以及在subplot上繪圖
除標准的圖表對象之外,我們可能還希望繪制一些自定義的注解(比如文本、箭頭或其他圖形等)。注解可以通過text、arrow和annotate等函數進行添加。text可以將文本繪制在圖表的指定坐標(x,y),還可以加上一些自定義格式:
ax.text(x,y,'Hello world!'.family='monospace',fontsize=10)
注解中可以既含有文本也含有箭頭。例如,我們用箭頭和文本注解如下圖的波浪線
In [1]: import matplotlib.pyplot as plt
In [2]: fig=plt.figure()
In [3]: ax=fig.add_subplot()
In [4]: ax=fig.add_subplot(1,1,1)
In [5]: import numpy as np
In [7]: t=np.arange(0,5,0.01)
In [9]: s=np.cos(2*np.pi*t)
In [10]: line,=ax.plot(t,s,lw=2)
In [11]: ax.annotate('local max',xy=(2,1),xytext=(3,1.5),arrowprops=dict(faceco
...: lor='black',shrink=0.05),)
Out[11]: Text(3, 1.5, 'local max')
In [13]: ax.set_ylim(-2,2)
Out[13]: (-2, 2)

圖形的繪制要麻煩一些。matplotlib有一些表示常見圖形的對象。這些對象被稱為塊(patch)。其中有些可以在matplotlib.pyplot中找到(如Rectangle和Circle),但完整集合位於matplotlib.patches。
要在圖表中添加一個圖形,我們需要創建一個塊對象shp,然后通過ax.add_patch(shp)將其添加到subplot中(如下圖所示):
In [26]: fig=plt.figure()
In [27]: ax=fig.add_subplot(1,1,1)
In [28]: rect=plt.Rectangle((0.2,0.75),0.8,0.25,color='k',alpha=0.3)
In [29]: circ=plt.Circle((0.7,0.2),0.1,color='b',alpha=0.3)
In [30]: pgon=plt.Polygon([[0.15,0.15],[0.35,0.6],[0.2,0.8]],color='g',alpha=0.
...: 5)
In [31]: ax.add_patch(rect)
Out[31]: <matplotlib.patches.Rectangle at 0xdb339b0>
In [32]: ax.add_patch(circ)
Out[32]: <matplotlib.patches.Circle at 0xdb33780>
In [33]: ax.add_patch(pgon)
Out[33]: <matplotlib.patches.Polygon at 0xda5c828>
如果查看許多常見圖表對象的具體實現代碼,我們就會發現它們其實就是由塊組裝而成的。

將圖表保存到文件
利用plt.savefig可以將當前圖表保存到文件。該方法相當於Figure對象的實例方法savefig。例如,要將圖表保存為SVG文件,我們只需輸入:
plt.savefig('figpath.svg')
文件類型是通過文件擴展名推斷出來的。因此,如果我們使用的是.pdf,就會得到一個PDF文件。在發布圖片時最常用到兩個重要的選項是dpi(控制“每英寸點數”分辨率)和bbox_inches(可以剪除圖表周圍的空白部分)。要得到一張帶有最小白邊且分辨率為400DPI的PNG圖片,我們可以:
plt.savefig('figpath.png',dpi=400,bbox_inches='tight')
savefig並非一定要寫入磁盤,也可以寫入任何文件型的對象,比如StringIO:
from io import StringIO
buffer=StringIO()
plt.savefig(buffer)
plot_data=buffer.getvalue()
這對在Web上提供動態生成的圖片是很實用的。
Figure.savefig方法的部分參數及說明如下表所示:
參數 說明
fname 含有文件路徑的字符串或Python的文件型對象。圖像格式由文件擴展名推斷得出,例如,.pdf推斷出PDF,.png推斷出PNG
dpi 圖像分辨率(每英寸點數),默認為100
facecolor、edgecolor 圖像的背景色,默認為“w”(白色)
format 顯式設置文件格式(“png”、”pdf”、“svg”、“ps”、“eps”......)
bbox_inches 圖表需要保存的部分。如果設置為“tight”,則將嘗試剪除圖表周圍的空白部分
matplotlib配置
matplotlib自帶一些配色方案,以及為生成出版質量的圖片而設定的默認配置信息。幸運的是,幾乎所有默認行為都能通過一組全局參數進行自定義,它們可以管理圖像大小、subplot邊距、配色方案、字體大小、網格類型等。操作matplotlib配置系統的方式主要有兩種。第一種是Python編程方式,即利用rc方法。比如說,要將全局的圖像默認大小設置為10X10,我們可以執行:
plt.rc('lines',linewidth=10)
rc的第一個參數是希望自定義的對象,如'lines'、'axes'、‘xtick’、'ytick'、'grid'、'legend'等。其后可以跟上一系列的關鍵字參數。最簡單的辦法是將這些選項寫成一個字典:
font_options={'family':'monospace',
'weight':'bold',
'size':'small'}
plt.rc('font',**font_options)
要了解全部的自定義選項,查閱https://matplotlib.org/api/_as_gen/matplotlib.pyplot.rc.html?highlight=rc#matplotlib.pyplot.rc。如果對該文件進行了自定義,並將其放在自己的.matplotlib目錄中,則每次使用matplotlib時就會加載該文件。
例子,繪出地球的兩極
In [1]: import matplotlib.pylab as plt
In [2]: import numpy as np
#自定義圖像
In [3]: plt.rc('grid',color='#316931',linewidth=1,linestyle='-')
In [4]: plt.rc('xtick',labelsize=15)
In [5]: plt.rc('ytick',labelsize=15)
#繪出一個圖像
In [6]: fig=plt.figure(figsize=(8,8))
#在上面圖像上繪出兩極圖形
In [7]: ax=fig.add_axes([0.1,0.1,0.8,0.8],projection='polar',facecolor='#d5de9c
...: ')
#兩極上繪出兩條線
In [8]: r=np.arange(0,3.0,0.01)
In [9]: theta=2*np.pi*r
In [10]: ax.plot(theta,r,color='#ee8d18',lw=3,label='a line')
Out[10]: [<matplotlib.lines.Line2D at 0xfb00da0>]
In [11]: ax.plot(0.5*theta,r,color='blue',ls='--',lw=3,label='another line')
Out[11]: [<matplotlib.lines.Line2D at 0xfb4c080>]
In [12]: ax.legend()
Out[12]: <matplotlib.legend.Legend at 0xfb58630>
In [13]: plt.show()

