1. 程式人生 > >[轉載] 繪圖: matplotlib核心剖析

[轉載] 繪圖: matplotlib核心剖析

其它 mpi move 有一個 nat 兩個 axis range copy

技術分享圖片 技術分享圖片
import matplotlib.pyplot as plt

# 1D data
x = [1,2,3,4,5]
y = [2.3,3.4,1.2,6.6,7.0]

plt.figure(figsize=(12,6))

plt.subplot(231)
plt.plot(x,y)
plt.title("plot")

plt.subplot(232)
plt.scatter(x, y)
plt.title("scatter")

plt.subplot(233)
plt.pie(y)
plt.title("pie")

plt.subplot(234)
plt.bar(x, y)
plt.title("bar")

# 2D data
import numpy as np
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z    = Y**2 + X**2

plt.subplot(235)
plt.contour(X,Y,Z)
plt.colorbar()
plt.title("contour")

# read image
import matplotlib.image as mpimg
img=mpimg.imread(‘marvin.jpg‘)

plt.subplot(236)
plt.imshow(img)
plt.title("imshow")

plt.savefig("matplot_sample.jpg")
技術分享圖片

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

matplotlib是基於Python語言的開源項目,旨在為Python提供一個數據繪圖包。我將在這篇文章中介紹matplotlib API的核心對象,並介紹如何使用這些對象來實現繪圖。實際上,matplotlib的對象體系嚴謹而有趣,為使用者提供了巨大的發揮空間。用戶在熟悉了核心對象之後,可以輕易的定制圖像。matplotlib的對象體系也是計算機圖形學的一個優秀範例。即使你不是Python程序員,你也可以從文中了解一些通用的圖形繪制原則。

matplotlib使用numpy進行數組運算,並調用一系列其他的Python庫來實現硬件交互。matplotlib的核心是一套由對象構成的繪圖API。

技術分享圖片

matplotlib項目是由John D. Hunter發起的。John D. Hunter由於癌癥於去年過世,但他發為社區作出的無比貢獻將永遠留存。

技術分享圖片

John D. Hunter

你需要安裝Python, numpy和matplotlib。(可以到python.org下載Python編譯器。相關Python包的安裝,請參看我的Python小技巧)

matplotlib的官網是: http://matplotlib.org/ 官網有豐富的圖例和文檔說明。

matplotlib在github的地址為:https://github.com/matplotlib 歡迎有興趣的開發者fork。

函數式繪圖

matplotlib是受MATLAB的啟發構建的。MATLAB是數據繪圖領域廣泛使用的語言和工具。MATLAB語言是面向過程的。利用函數的調用,MATLAB中可以輕松的利用一行命令來繪制直線,然後再用一系列的函數調整結果。

matplotlib有一套完全仿照MATLAB的函數形式的繪圖接口,在matplotlib.pyplot模塊中。這套函數接口方便MATLAB用戶過度到matplotlib包。下面,我們調用該模塊繪制一條直線。

技術分享圖片
# a strait line: use pyplot functions

from 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")
技術分享圖片

上面的每一條命令都很簡單,你可以從函數名讀出該函數所要實現的功能。比如plot為畫線,title為增加標題。最終保存的demo.jpg如下:

技術分享圖片

上面的函數式調用很方便。在Python特殊方法與多範式中,我們已經談到,Python中的函數式編程是通過封裝對象實現的。matplotlib中的函數式調用其實也是如此。matplotlib本質上還是構建對象來構建圖像。函數式編程將構建對象的過程封裝在函數中,從而讓我們覺得很方便。

在matplotlib.pyplot中,你還可以找到下面的繪圖函數。如果你經常使用數據繪圖程序,應該會很熟悉這些圖形:

技術分享圖片

繪圖程序如下:

技術分享圖片

import matplotlib.pyplot as plt

# 1D data
x = [1,2,3,4,5]
y = [2.3,3.4,1.2,6.6,7.0]

plt.figure(figsize=(12,6))

plt.subplot(231)
plt.plot(x,y)
plt.title("plot")

plt.subplot(232)
plt.scatter(x, y)
plt.title("scatter")

plt.subplot(233)
plt.pie(y)
plt.title("pie")

plt.subplot(234)
plt.bar(x, y)
plt.title("bar")

# 2D data
import numpy as np
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z = Y**2 + X**2

plt.subplot(235)
plt.contour(X,Y,Z)
plt.colorbar()
plt.title("contour")

# read image
import matplotlib.image as mpimg
img=mpimg.imread(‘marvin.jpg‘)

plt.subplot(236)
plt.imshow(img)
plt.title("imshow")

plt.savefig("matplot_sample.jpg")

技術分享圖片
import matplotlib.pyplot as plt

# 1D data
x = [1,2,3,4,5]
y = [2.3,3.4,1.2,6.6,7.0]

plt.figure(figsize=(12,6))

plt.subplot(231)
plt.plot(x,y)
plt.title("plot")

plt.subplot(232)
plt.scatter(x, y)
plt.title("scatter")

plt.subplot(233)
plt.pie(y)
plt.title("pie")

plt.subplot(234)
plt.bar(x, y)
plt.title("bar")

# 2D data
import numpy as np
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z    = Y**2 + X**2

plt.subplot(235)
plt.contour(X,Y,Z)
plt.colorbar()
plt.title("contour")

# read image
import matplotlib.image as mpimg
img=mpimg.imread(‘marvin.jpg‘)

plt.subplot(236)
plt.imshow(img)
plt.title("imshow")

plt.savefig("matplot_sample.jpg")
技術分享圖片

上面用到的marvin.jpg是下圖,請保存到當地電腦:

技術分享圖片

函數式編程創造了一個仿真MATLAB的工作環境,並有許多成形的繪圖函數。如果只是作為Matplotlib的一般用戶(非開發者),pyplot可以滿足大部分的需求。

(當然,matplotlib是免費而開源的,MATLAB昂貴而封閉。這是不“仿真”的地方)

面向對象編程

盡管函數式繪圖很便利,但利用函數式編程會有以下缺點:

1) 增加了一層“函數”調用,降低了效率。

2) 隸屬關系被函數掩蓋。整個matplotlib包是由一系列有組織有隸屬關系的對象構成的。函數掩蓋了原有的隸屬關系,將事情變得復雜。

3) 細節被函數掩蓋。pyplot並不能完全復制對象體系的所有功能,圖像的許多細節調中最終還要回到對象。

4) 每件事情都可以有至少兩種方式完成,用戶很容易混淆。

而對於開發者來說,了解對象是參與到Matplotlib項目的第一步。

我們將上面的直線繪圖更改為面向對象式(OO, object-oriented)的,為此,我們引入兩個類: Figure和FigureCanvas。(函數式編程也調用了這些類,只是調用的過程被函數調用所遮掩。)

技術分享圖片
# object-oriented plot

from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas fig = 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如下:

技術分享圖片

理解對象

上面的例子中,我們至少構建了四個對象: fig, canvas, ax, line。它們分別屬於Figure類,FigureCanvas類,Axes類和Line2D類。(使用obj.__class__.__name__來查詢對象所屬的類)

在深入各個對象之前,我們先來做一個比喻。看下面一個圖片:

技術分享圖片

這個圖片是用KTurtle繪制。參看把你的孩子打造成為碼農

可以看到,圖中有一個房子,房子上有窗戶和門,窗戶上有條紋,門上有把手,此外圖像外還有一只小烏龜。我們所提到的房子,窗戶,門,條紋,把手,都可以稱其為對象。不同的對象之間有依附關系,比如窗戶和門屬於房子,而把手屬於門。烏龜和房子則是並行的兩個對象。此外,整個圖像外有一個方框,用來表明可繪圖的範圍,所有上面提到的元素都依附於該方框。

這就是用面向對象的方式來理解一個圖像。事實上,對象是描述圖像的最自然的方式,面向對象編程最成功的領域就是在計算機圖形方面。

我們先來看什麽是Figure和Axes對象。在matplotlib中,整個圖像為一個Figure對象。在Figure對象中可以包含一個,或者多個Axes對象。每個Axes對象都是一個擁有自己坐標系統的繪圖區域。其邏輯關系如下:

技術分享圖片

轉過頭來看直線圖。整個圖像是fig對象。我們的繪圖中只有一個坐標系區域,也就是ax。此外還有以下對象。(括號中表示對象的基本類型)

技術分享圖片

Title為標題。Axis為坐標軸,Label為坐標軸標註。Tick為刻度線,Tick Label為刻度註釋。各個對象之間有下面的對象隸屬關系:

技術分享圖片

(yaxis同樣有tick, label和tick label,沒有畫出)

盡管data是數據繪圖的關鍵部分,也就是數據本身的圖形化顯示,但是必須和xaxis, yaxis, title一起,才能真正構成一個繪圖區域axes。一個單純的,無法讀出刻度的線是沒有意義的。xaxis, yaxis, title合起來構成了數據的輔助部分(data guide)。

上面元素又包含有多種圖形元素。比如說,我們的data對象是一條線(Line2D)。title, tick label和label都是文本(Text),而tick是由短線(Line 2D)和tick label構成,xaxis由坐標軸的線和tick以及label構成,ax由xaxis, yaxis, title, data構成,ax自身又構成了fig的一部分。上面的每個對象,無論是Line2D, Text還是fig,它們都來自於一個叫做Artist的基類。

OO繪圖的原程序還有一個canvas對象。它代表了真正進行繪圖的後端(backend)。Artist只是在程序邏輯上的繪圖,它必須連接後端繪圖程序才能真正在屏幕上繪制出來(或者保存為文件)。我們可以將canvas理解為繪圖的物理(或者說硬件)實現。

在OO繪圖程序中,我們並沒有真正看到title, tick, tick label, xaxis, yaxis對象,而是使用ax.set_*的方法間接設置了這些對象。但這些對象是真實存在的,你可以從上層對象中找到其“真身”。比如,fig.axes[0].xaxis就是我們上面途中的xaxis對象。我們可以通過fig -> axes[0] (也就是ax) -> xaxis的順序找到它。因此,重復我們剛才已經說過的,一個fig就構成了一個完整的圖像。對於每個Artist類的對象,都有findobj()方法,來顯示該對象所包含的所有下層對象。

坐標

坐標是計算機繪圖的基礎。計算機屏幕是由一個個像素點構成的。想要在屏幕上顯示圖像,計算機必須告訴屏幕每個像素點上顯示什麽。所以,最貼近硬件的坐標體系是以像素為單位的坐標體系。我們可以通過具體說明像素位置來標明顯示器上的某一點。這叫做顯示坐標(display coordinate),以像素為單位。

然而,像素坐標不容易被納入繪圖邏輯。相同的程序,在不同的顯示器上就要調整像素值,以保證圖像不變形。所以一般情況下,還會有圖像坐標和數據坐標。

圖像坐標將一張圖的左下角視為原點,將圖像的x方向和y方向總長度都看做1。x方向的0.2就是指20%的圖像在x方向的總長,y方向0.8的長度指80%的y方向總長。(0.5, 0.5)是圖像的中點,(1, 1)指圖像的右上角。比如下面的程序,我們在使用add_axes時,傳遞的參數中,前兩個元素為axes的左下角在fig的圖像坐標上的位置,後兩個元素指axes在fig的圖像坐標上x方向和y方向的長度。fig的圖像坐標稱為Figure坐標,儲存在為fig.transFigure

(類似的,每個axes,比如ax1,有屬於自己的圖像坐標。它以ax1繪圖區域總長作為1,稱為Axes坐標。也就是ax1.transAxes。(0.5, 0.5)就表示在Axes的中心。Axes坐標和Figure坐標原理相似,只是所用的基準區域不同。)

技術分享圖片
# object-oriented plot
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

fig    = 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的時候,繪制了兩點間的連線。這兩點分別為(0, 0)和(1, 1)。(plot中的第一個表為兩個x坐標,第二個表為兩個y坐標)。這時使用的坐標系為數據坐標系(ax1.transData)。我們可以通過繪出的坐標軸讀出數據坐標的位置。

技術分享圖片

如果繪制的是具體數據,那麽數據坐標符合我們的需求。如果繪制的是標題這樣的附加信息,那麽Axes坐標符合符合我們的需求。如果是整個圖像的註解,那麽Figure坐標更符合需求。每一個Artist對象都有一個transform屬性,用於查詢和改變所使用的坐標系統。如果為顯示坐標,transform屬性為None。

深入基礎

在上面的例子中,無論是使用plot繪制線,還是scatter繪制散點,它們依然是比較成熟的函數。matplotlib實際上提供了更大的自由度,允許用戶以更基礎的方式來繪制圖形,比如下面,我們繪制一個五邊形。

技術分享圖片
# object-oriented plot

from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

fig    = 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 patches

verts = [
    (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‘)
技術分享圖片

在上面的程序中。我們首先確定頂點,然後構建了一個path對象,這個對象實際上就是5個頂點的連線。在codes中,我們先使用MOVETO將畫筆移動到起點,然後依次用直線連接(LINETO)(我們也可以用曲線來連線,比如CURVE4,但這裏沒有用到)。 在path建立了封閉的5邊形後,我們在path的基礎上構建了patch對象,是一個圖形塊。patch的背景顏色選為coral。最後,我們將這個patch對象添加到預先準備好的ax上,就完成了整個繪圖。

技術分享圖片

上面的過程中,我們就好像拿著一個畫筆的小孩,一步步畫出心目中的圖畫。這就是深入理解matplotlib的魅力所在——創造你自己的數據繪圖函數!

(將上面的程序封裝到函數中,保留頂點以及其它參數接口,就構成了一個五邊形繪圖函數。O(∩_∩)O~ 我們也創造了新的“一鍵繪圖”)

可以相像,一個plot函數如何用path對象實現。

總結

我們已經了解了matplotlib的最重要的方面,它們是:

1) pyplot函數繪圖借口

2) 對象如何組合成為圖像

3) 坐標系統

希望我的講解沒有消耗完你對matplotlib的興趣。事實上,matplotlib是發展相當迅猛的繪圖包,而它的開放性也讓它成為了解計算機圖形學的一個好接口。利用開放的核心對象,你可以隨心的定制自己的數據繪圖,而不用受制於高層的調用函數。謝謝John D. Hunter。

[轉載] 繪圖: matplotlib核心剖析