绘图: matplotlib核心剖析
作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉(zhuǎn)載,也請(qǐng)保留這段聲明。謝謝!
?
matplotlib是基于Python語言的開源項(xiàng)目,旨在為Python提供一個(gè)數(shù)據(jù)繪圖包。我將在這篇文章中介紹matplotlib API的核心對(duì)象,并介紹如何使用這些對(duì)象來實(shí)現(xiàn)繪圖。實(shí)際上,matplotlib的對(duì)象體系嚴(yán)謹(jǐn)而有趣,為使用者提供了巨大的發(fā)揮空間。用戶在熟悉了核心對(duì)象之后,可以輕易的定制圖像。matplotlib的對(duì)象體系也是計(jì)算機(jī)圖形學(xué)的一個(gè)優(yōu)秀范例。即使你不是Python程序員,你也可以從文中了解一些通用的圖形繪制原則。
matplotlib使用numpy進(jìn)行數(shù)組運(yùn)算,并調(diào)用一系列其他的Python庫來實(shí)現(xiàn)硬件交互。matplotlib的核心是一套由對(duì)象構(gòu)成的繪圖API。
?
matplotlib項(xiàng)目是由John D. Hunter發(fā)起的。John D. Hunter由于癌癥于去年過世,但他發(fā)為社區(qū)作出的無比貢獻(xiàn)將永遠(yuǎn)留存。
John D. Hunter
?
你需要安裝Python, numpy和matplotlib。(可以到python.org下載Python編譯器。相關(guān)Python包的安裝,請(qǐng)參看我的Python小技巧)
matplotlib的官網(wǎng)是:?http://matplotlib.org/? 官網(wǎng)有豐富的圖例和文檔說明。
matplotlib在github的地址為:https://github.com/matplotlib?歡迎有興趣的開發(fā)者fork。
?
?
函數(shù)式繪圖
matplotlib是受MATLAB的啟發(fā)構(gòu)建的。MATLAB是數(shù)據(jù)繪圖領(lǐng)域廣泛使用的語言和工具。MATLAB語言是面向過程的。利用函數(shù)的調(diào)用,MATLAB中可以輕松的利用一行命令來繪制直線,然后再用一系列的函數(shù)調(diào)整結(jié)果。
matplotlib有一套完全仿照MATLAB的函數(shù)形式的繪圖接口,在matplotlib.pyplot模塊中。這套函數(shù)接口方便MATLAB用戶過度到matplotlib包。下面,我們調(diào)用該模塊繪制一條直線。
# a strait line: use pyplot functionsfrom matplotlib.pyplot import *plot([0, 1], [0, 1]) # plot a line from (0, 0) to (1, 1) title("a strait line") xlabel("x value") ylabel("y value") savefig("demo.jpg")
上面的每一條命令都很簡單,你可以從函數(shù)名讀出該函數(shù)所要實(shí)現(xiàn)的功能。比如plot為畫線,title為增加標(biāo)題。最終保存的demo.jpg如下:
上面的函數(shù)式調(diào)用很方便。在Python特殊方法與多范式中,我們已經(jīng)談到,Python中的函數(shù)式編程是通過封裝對(duì)象實(shí)現(xiàn)的。matplotlib中的函數(shù)式調(diào)用其實(shí)也是如此。matplotlib本質(zhì)上還是構(gòu)建對(duì)象來構(gòu)建圖像。函數(shù)式編程將構(gòu)建對(duì)象的過程封裝在函數(shù)中,從而讓我們覺得很方便。
在matplotlib.pyplot中,你還可以找到下面的繪圖函數(shù)。如果你經(jīng)常使用數(shù)據(jù)繪圖程序,應(yīng)該會(huì)很熟悉這些圖形:
?
?
繪圖程序如下:
View Code 上面用到的marvin.jpg是下圖,請(qǐng)保存到當(dāng)?shù)仉娔X:
?
函數(shù)式編程創(chuàng)造了一個(gè)仿真MATLAB的工作環(huán)境,并有許多成形的繪圖函數(shù)。如果只是作為Matplotlib的一般用戶(非開發(fā)者),pyplot可以滿足大部分的需求。
(當(dāng)然,matplotlib是免費(fèi)而開源的,MATLAB昂貴而封閉。這是不“仿真”的地方)
?
面向?qū)ο缶幊?/span>
盡管函數(shù)式繪圖很便利,但利用函數(shù)式編程會(huì)有以下缺點(diǎn):
1) 增加了一層“函數(shù)”調(diào)用,降低了效率。
2) 隸屬關(guān)系被函數(shù)掩蓋。整個(gè)matplotlib包是由一系列有組織有隸屬關(guān)系的對(duì)象構(gòu)成的。函數(shù)掩蓋了原有的隸屬關(guān)系,將事情變得復(fù)雜。
3) 細(xì)節(jié)被函數(shù)掩蓋。pyplot并不能完全復(fù)制對(duì)象體系的所有功能,圖像的許多細(xì)節(jié)調(diào)中最終還要回到對(duì)象。
4) 每件事情都可以有至少兩種方式完成,用戶很容易混淆。
而對(duì)于開發(fā)者來說,了解對(duì)象是參與到Matplotlib項(xiàng)目的第一步。
?
我們將上面的直線繪圖更改為面向?qū)ο笫?OO, object-oriented)的,為此,我們引入兩個(gè)類:?Figure和FigureCanvas。(函數(shù)式編程也調(diào)用了這些類,只是調(diào)用的過程被函數(shù)調(diào)用所遮掩。)
# object-oriented plotfrom matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvasfig = Figure() canvas = FigureCanvas(fig) ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])line, = ax.plot([0,1], [0,1]) ax.set_title("a straight line (OO)") ax.set_xlabel("x value") ax.set_ylabel("y value")canvas.print_figure('demo.jpg')
新的demo.jpg如下:
?
理解對(duì)象
上面的例子中,我們至少構(gòu)建了四個(gè)對(duì)象: fig, canvas, ax, line。它們分別屬于Figure類,FigureCanvas類,Axes類和Line2D類。(使用obj.__class__.__name__來查詢對(duì)象所屬的類)
在深入各個(gè)對(duì)象之前,我們先來做一個(gè)比喻。看下面一個(gè)圖片:
這個(gè)圖片是用KTurtle繪制。參看把你的孩子打造成為碼農(nóng)
可以看到,圖中有一個(gè)房子,房子上有窗戶和門,窗戶上有條紋,門上有把手,此外圖像外還有一只小烏龜。我們所提到的房子,窗戶,門,條紋,把手,都可以稱其為對(duì)象。不同的對(duì)象之間有依附關(guān)系,比如窗戶和門屬于房子,而把手屬于門。烏龜和房子則是并行的兩個(gè)對(duì)象。此外,整個(gè)圖像外有一個(gè)方框,用來表明可繪圖的范圍,所有上面提到的元素都依附于該方框。
這就是用面向?qū)ο蟮姆绞絹砝斫庖粋€(gè)圖像。事實(shí)上,對(duì)象是描述圖像的最自然的方式,面向?qū)ο缶幊套畛晒Φ念I(lǐng)域就是在計(jì)算機(jī)圖形方面。
?
我們先來看什么是Figure和Axes對(duì)象。在matplotlib中,整個(gè)圖像為一個(gè)Figure對(duì)象。在Figure對(duì)象中可以包含一個(gè),或者多個(gè)Axes對(duì)象。每個(gè)Axes對(duì)象都是一個(gè)擁有自己坐標(biāo)系統(tǒng)的繪圖區(qū)域。其邏輯關(guān)系如下:
轉(zhuǎn)過頭來看直線圖。整個(gè)圖像是fig對(duì)象。我們的繪圖中只有一個(gè)坐標(biāo)系區(qū)域,也就是ax。此外還有以下對(duì)象。(括號(hào)中表示對(duì)象的基本類型)
Title為標(biāo)題。Axis為坐標(biāo)軸,Label為坐標(biāo)軸標(biāo)注。Tick為刻度線,Tick Label為刻度注釋。各個(gè)對(duì)象之間有下面的對(duì)象隸屬關(guān)系:
(yaxis同樣有tick, label和tick label,沒有畫出)
盡管data是數(shù)據(jù)繪圖的關(guān)鍵部分,也就是數(shù)據(jù)本身的圖形化顯示,但是必須和xaxis, yaxis, title一起,才能真正構(gòu)成一個(gè)繪圖區(qū)域axes。一個(gè)單純的,無法讀出刻度的線是沒有意義的。xaxis, yaxis, title合起來構(gòu)成了數(shù)據(jù)的輔助部分(data guide)。
上面元素又包含有多種圖形元素。比如說,我們的data對(duì)象是一條線(Line2D)。title, tick label和label都是文本(Text),而tick是由短線(Line 2D)和tick label構(gòu)成,xaxis由坐標(biāo)軸的線和tick以及l(fā)abel構(gòu)成,ax由xaxis, yaxis, title, data構(gòu)成,ax自身又構(gòu)成了fig的一部分。上面的每個(gè)對(duì)象,無論是Line2D, Text還是fig,它們都來自于一個(gè)叫做Artist的基類。
OO繪圖的原程序還有一個(gè)canvas對(duì)象。它代表了真正進(jìn)行繪圖的后端(backend)。Artist只是在程序邏輯上的繪圖,它必須連接后端繪圖程序才能真正在屏幕上繪制出來(或者保存為文件)。我們可以將canvas理解為繪圖的物理(或者說硬件)實(shí)現(xiàn)。
在OO繪圖程序中,我們并沒有真正看到title, tick, tick label, xaxis, yaxis對(duì)象,而是使用ax.set_*的方法間接設(shè)置了這些對(duì)象。但這些對(duì)象是真實(shí)存在的,你可以從上層對(duì)象中找到其“真身”。比如,fig.axes[0].xaxis就是我們上面途中的xaxis對(duì)象。我們可以通過fig -> axes[0] (也就是ax) -> xaxis的順序找到它。因此,重復(fù)我們剛才已經(jīng)說過的,一個(gè)fig就構(gòu)成了一個(gè)完整的圖像。對(duì)于每個(gè)Artist類的對(duì)象,都有findobj()方法,來顯示該對(duì)象所包含的所有下層對(duì)象。
?
坐標(biāo)
坐標(biāo)是計(jì)算機(jī)繪圖的基礎(chǔ)。計(jì)算機(jī)屏幕是由一個(gè)個(gè)像素點(diǎn)構(gòu)成的。想要在屏幕上顯示圖像,計(jì)算機(jī)必須告訴屏幕每個(gè)像素點(diǎn)上顯示什么。所以,最貼近硬件的坐標(biāo)體系是以像素為單位的坐標(biāo)體系。我們可以通過具體說明像素位置來標(biāo)明顯示器上的某一點(diǎn)。這叫做顯示坐標(biāo)(display coordinate),以像素為單位。
然而,像素坐標(biāo)不容易被納入繪圖邏輯。相同的程序,在不同的顯示器上就要調(diào)整像素值,以保證圖像不變形。所以一般情況下,還會(huì)有圖像坐標(biāo)和數(shù)據(jù)坐標(biāo)。
圖像坐標(biāo)將一張圖的左下角視為原點(diǎn),將圖像的x方向和y方向總長度都看做1。x方向的0.2就是指20%的圖像在x方向的總長,y方向0.8的長度指80%的y方向總長。(0.5, 0.5)是圖像的中點(diǎn),(1, 1)指圖像的右上角。比如下面的程序,我們在使用add_axes時(shí),傳遞的參數(shù)中,前兩個(gè)元素為axes的左下角在fig的圖像坐標(biāo)上的位置,后兩個(gè)元素指axes在fig的圖像坐標(biāo)上x方向和y方向的長度。fig的圖像坐標(biāo)稱為Figure坐標(biāo),儲(chǔ)存在為fig.transFigure
(類似的,每個(gè)axes,比如ax1,有屬于自己的圖像坐標(biāo)。它以ax1繪圖區(qū)域總長作為1,稱為Axes坐標(biāo)。也就是ax1.transAxes。(0.5, 0.5)就表示在Axes的中心。Axes坐標(biāo)和Figure坐標(biāo)原理相似,只是所用的基準(zhǔn)區(qū)域不同。)
# object-oriented plot from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvasfig = Figure() canvas = FigureCanvas(fig)# first axes ax1 = fig.add_axes([0.1, 0.1, 0.2, 0.2]) line, = ax1.plot([0,1], [0,1]) ax1.set_title("ax1")# second axes ax2 = fig.add_axes([0.4, 0.3, 0.4, 0.5]) sca = ax2.scatter([1,3,5],[2,1,2]) ax2.set_title("ax2")canvas.print_figure('demo.jpg')我們在繪圖,比如使用plot的時(shí)候,繪制了兩點(diǎn)間的連線。這兩點(diǎn)分別為(0, 0)和(1, 1)。(plot中的第一個(gè)表為兩個(gè)x坐標(biāo),第二個(gè)表為兩個(gè)y坐標(biāo))。這時(shí)使用的坐標(biāo)系為數(shù)據(jù)坐標(biāo)系(ax1.transData)。我們可以通過繪出的坐標(biāo)軸讀出數(shù)據(jù)坐標(biāo)的位置。
?
如果繪制的是具體數(shù)據(jù),那么數(shù)據(jù)坐標(biāo)符合我們的需求。如果繪制的是標(biāo)題這樣的附加信息,那么Axes坐標(biāo)符合符合我們的需求。如果是整個(gè)圖像的注解,那么Figure坐標(biāo)更符合需求。每一個(gè)Artist對(duì)象都有一個(gè)transform屬性,用于查詢和改變所使用的坐標(biāo)系統(tǒng)。如果為顯示坐標(biāo),transform屬性為None。
?
深入基礎(chǔ)
在上面的例子中,無論是使用plot繪制線,還是scatter繪制散點(diǎn),它們依然是比較成熟的函數(shù)。matplotlib實(shí)際上提供了更大的自由度,允許用戶以更基礎(chǔ)的方式來繪制圖形,比如下面,我們繪制一個(gè)五邊形。
# object-oriented plotfrom matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvasfig = Figure() canvas = FigureCanvas(fig) ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])from matplotlib.path import Path import matplotlib.patches as patchesverts = [(0., 0.), (0., 1.),(0.5, 1.5),(1., 1.),(1., 0.),(0., 0.),]codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY,]path = Path(verts, codes)patch = patches.PathPatch(path, facecolor='coral') ax.add_patch(patch) ax.set_xlim(-0.5,2) ax.set_ylim(-0.5,2)canvas.print_figure('demo.jpg')在上面的程序中。我們首先確定頂點(diǎn),然后構(gòu)建了一個(gè)path對(duì)象,這個(gè)對(duì)象實(shí)際上就是5個(gè)頂點(diǎn)的連線。在codes中,我們先使用MOVETO將畫筆移動(dòng)到起點(diǎn),然后依次用直線連接(LINETO)(我們也可以用曲線來連線,比如CURVE4,但這里沒有用到)。 在path建立了封閉的5邊形后,我們在path的基礎(chǔ)上構(gòu)建了patch對(duì)象,是一個(gè)圖形塊。patch的背景顏色選為coral。最后,我們將這個(gè)patch對(duì)象添加到預(yù)先準(zhǔn)備好的ax上,就完成了整個(gè)繪圖。
上面的過程中,我們就好像拿著一個(gè)畫筆的小孩,一步步畫出心目中的圖畫。這就是深入理解matplotlib的魅力所在——?jiǎng)?chuàng)造你自己的數(shù)據(jù)繪圖函數(shù)!
(將上面的程序封裝到函數(shù)中,保留頂點(diǎn)以及其它參數(shù)接口,就構(gòu)成了一個(gè)五邊形繪圖函數(shù)。O(∩_∩)O~ 我們也創(chuàng)造了新的“一鍵繪圖”)
?
可以相像,一個(gè)plot函數(shù)如何用path對(duì)象實(shí)現(xiàn)。
?
總結(jié)
我們已經(jīng)了解了matplotlib的最重要的方面,它們是:
1) pyplot函數(shù)繪圖借口
2) 對(duì)象如何組合成為圖像
3) 坐標(biāo)系統(tǒng)
希望我的講解沒有消耗完你對(duì)matplotlib的興趣。事實(shí)上,matplotlib是發(fā)展相當(dāng)迅猛的繪圖包,而它的開放性也讓它成為了解計(jì)算機(jī)圖形學(xué)的一個(gè)好接口。利用開放的核心對(duì)象,你可以隨心的定制自己的數(shù)據(jù)繪圖,而不用受制于高層的調(diào)用函數(shù)。謝謝John D. Hunter。
總結(jié)
以上是生活随笔為你收集整理的绘图: matplotlib核心剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 Python Matplotlib
- 下一篇: matplotlib-绘制精美的图表