Python可以這樣玩(11):數學繪圖


Matplotlib

matplotlib Python程式語言及其數值數學擴展包 NumPy的可視化操作界面。它為利用通用的圖形用戶界面工具包,如Tkinter, wxPython, QtGTK+向應用程式嵌入式繪圖提供了應用程式接口(API)。此外,matplotlib還有一個基於圖像處理庫(如開放圖形庫OpenGL)的pylab接口,其設計與MATLAB非常類似--儘管並不怎麼好用。SciPy就是用matplotlib進行圖形繪製。

曲線圖

>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> a = np.linspace(0,10,100)
>>> a
array([ 0.        ,  0.1010101 ,  0.2020202 ,  0.3030303 ,  0.4040404 ,
        0.50505051,  0.60606061,  0.70707071,  0.80808081,  0.90909091,
        1.01010101,  1.11111111,  1.21212121,  1.31313131,  1.41414141,
        1.51515152,  1.61616162,  1.71717172,  1.81818182,  1.91919192,
        2.02020202,  2.12121212,  2.22222222,  2.32323232,  2.42424242,
        2.52525253,  2.62626263,  2.72727273,  2.82828283,  2.92929293,
        3.03030303,  3.13131313,  3.23232323,  3.33333333,  3.43434343,
        3.53535354,  3.63636364,  3.73737374,  3.83838384,  3.93939394,
        4.04040404,  4.14141414,  4.24242424,  4.34343434,  4.44444444,
        4.54545455,  4.64646465,  4.74747475,  4.84848485,  4.94949495,
        5.05050505,  5.15151515,  5.25252525,  5.35353535,  5.45454545,
        5.55555556,  5.65656566,  5.75757576,  5.85858586,  5.95959596,
        6.06060606,  6.16161616,  6.26262626,  6.36363636,  6.46464646,
        6.56565657,  6.66666667,  6.76767677,  6.86868687,  6.96969697,
        7.07070707,  7.17171717,  7.27272727,  7.37373737,  7.47474747,
        7.57575758,  7.67676768,  7.77777778,  7.87878788,  7.97979798,
        8.08080808,  8.18181818,  8.28282828,  8.38383838,  8.48484848,
        8.58585859,  8.68686869,  8.78787879,  8.88888889,  8.98989899,
        9.09090909,  9.19191919,  9.29292929,  9.39393939,  9.49494949,
        9.5959596 ,  9.6969697 ,  9.7979798 ,  9.8989899 , 10.        ])
>>> b = np.exp(-a)
>>> plt.plot(a,b)
[<matplotlib.lines.Line2D object at 0x000001D4BCE015F8>]
>>> plt.show()





直方圖

>>> import matplotlib.pyplot as plt
>>> from numpy.random import normal,rand
>>> x = normal(size=200)
>>> plt.hist(x,bins=30)
(array([ 3.,  2.,  8., 11.,  9.,  1.,  7.,  5., 12., 15., 17., 20., 12.,
        9.,  8., 10.,  8., 11.,  3.,  7.,  3.,  5.,  3.,  4.,  3.,  0.,
        2.,  0.,  0.,  2.]), array([-1.85874394, -1.70879901,
-1.55885409, -1.40890916, -1.25896423,
       -1.1090193 , -0.95907438, -0.80912945, -0.65918452, -0.5092396 ,
       -0.35929467, -0.20934974, -0.05940482,  0.09054011,  0.24048504,
        0.39042997,  0.54037489,  0.69031982,  0.84026475,  0.99020967,
        1.1401546 ,  1.29009953,  1.44004446,  1.58998938,  1.73993431,
        1.88987924,  2.03982416,  2.18976909,  2.33971402,  2.48965894,
        2.63960387]), <a list of 30 Patch objects>)
>>> plt.show()



散點圖


>>> import matplotlib.pyplot as plt
>>> from numpy.random import normal,rand
>>> a = rand(100)
>>> b = rand(100)
>>> plt.scatter(a,b)
<matplotlib.collections.PathCollection object at 0x000001D4BD8EF9B0>
>>> plt.show()




3D

>>> from matplotlib import cm
>>> from mpl_toolkits.mplot3d import Axes3D
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig = plt.figure()
>>> ax = fig.gca(projection='3d')
>>> X = np.arange(-5, 5, 0.25)
>>> Y = np.arange(-5, 5, 0.25)
>>> X, Y = np.meshgrid(X, Y)
>>> R = np.sqrt(X**2 + Y**2)
>>> Z = np.sin(R)
>>> surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm)
>>> plt.show()
>>> 



看完這些示範之後,由於這些圖的複雜度比較高,對初學者而言可能不知道用途在哪裡,我們就選幾個簡單的例子讓讀者可以 step-by-step 做出自己的圖形。

繪製函數圖形

從最簡單的開始,如果我們要繪製一個 y = 2x + 1 的函數圖形該如何做呢?首先要先 import 兩個重要的擴展包,就是 numpy matplotlib,由於 matplotlib 的字數太多,其實有個簡單的寫法,就是 import pylab 就好:

>>> import numpy as np
>>> import pylab as pl

不要偷懶,請務必按照範例親自輸入。接下來我們就要思考圖形的範圍,也就是xy的值,先決定x的值,y是由x運算出來的。假如我們希望x -5 開始到 +5 出現 100個點,有一個方法可以達成,就是 linspace(起始,結束,個數),然後再把y計算出來,我們已經學過一堆的陣列與列表運算了,下面的指令應該不成問題:

>>> x = np.linspace(-5,5,100)
>>> y = 2 * x + 1

在畫圖之前,你可以偷看一下 x, y 的值。接下來我們就要用 plot() 這個方法繪製圖形,在用 show()方法把圖形顯示出來:

>>> pl.plot(x,y)
[<matplotlib.lines.Line2D object at 0x0000026CD71678D0>]
>>> pl.show()

結果如下圖:


為了把我們練習的程式存起來,我們換一種方式,把程式寫在檔案裏面。開啟一個新檔案,檔名存為 myplot.py

還記得一開始我們在寫程式的時候會加上 def main() 這樣的架構,不過如果是簡單的測試,可以簡化一點,直接把指令存起來執行就行,如下圖:


馬上改一下題目,我們加畫一個 y = 2 * x **2 +3 x -1,修改如下:
import numpy as np
import pylab as pl
x = np.linspace(-10,10,100)
y = 5 * x + 50
pl.plot(x,y)
y = 2 * x ** 2 + 3 * x - 1
pl.plot(x,y)
pl.show()



還記得聯立方程式求解嗎?這兩個交點就是聯立方程式的兩個解。如果你現在還是國中生又會寫Python,那你的數學一定也會進步。

當然,matplotlib 的主要功能並不是用來求解的,可以拿來做任何方面的應用,這裡講的只是基本概念,後面的課程會慢慢的把所有的基本概念整合起來,製作複雜的應用程式。

Excel繪圖

延續上一節的課程,我們發現要繪圖其實需要一些資料來源,當我們繪製數學圖形的時候,使用數學方程式產生資料來源比較容易,一行程式碼就可以產生了,如果我們要繪製直方圖這一類需要真實資料來源(例如公司業績)的時候,就必須一筆一筆輸入,其實很不方便,我們不可能把數據存在程式裡面,而是要存在另一個資料檔案裏面。

最適合做類似業績資料的儲存格式,除了資料庫之外,有一樣工具其實很好用,就是 Excel

PS: 順帶說明,到目前為止我都沒有提到物件導向程式設計或是資料庫程式設計,原因是如果讓初學者在一開始學程式語言的時候不太適合切入這麼艱深的東西,很可能會影響學習興趣,即使不談物件導向,程式還是寫得出來;不談資料庫,我們還是可以先用 Excel 來實現。

這個部份其實有兩個主題,一是如何讓 Python 讀取 Excel 的試算表,第二則是使用 matplotlib 來取代Excel繪製圖形。為何要這樣做?Excel 不是就有很強的繪圖功能嗎?因為很多時候我們需要的是系統整合,當我們用 Python 做系統的時候,使用Python 提供的功能才能讓系統更天人合一,其他周邊的東西都只是我們的工具。

建立Excel檔案

xlrd顧名思義,就是excel檔的尾碼名.xlread的擴展包。這個包只能讀取檔,不能寫入。寫入需要使用另外一個包。但是這個包,其實也能讀取.xlsx文件。從excel中讀取資料的過程比較簡單,首先要安裝 xlrd,如下圖,接著在程式中從xlrd包導入open_workbook,就可以打開excel文件:



安裝好之後,我們要建立一個 Excel file


簡單建立一個 Excel file 檔名為 Data.xlsx,如果電腦沒有安裝 Office,那就從我的 GitHub 下載此檔案即可。

接著建立一個新的 excelplot.py 程式檔,請確定 Excel 檔案與 Python 程式檔放在同一個目錄。

讀取資料

在這裡我提供一個標準的 Excel 讀取程式碼,無論有多少工作表,多少資料,都可以透過迴圈讀取進來:

from xlrd import open_workbook
x_data1=[]
y_data1=[]
wb = open_workbook('Data.xlsx')
for s in wb.sheets():
    print('工作表:',s.name)
    for row in range(s.nrows):
        print('資料列:',row)
        values = []
        for col in range(s.ncols):
            values.append(s.cell(row,col).value)
        print(values)
        x_data1.append(values[0])
        y_data1.append(values[1])

執行結果如下:

RESTART: C:/Users/huskywang/Documents/GitHub/python/PlotProgram/excelplot.py
工作表: 首例一
資料列: 0
['', '第一季', '第二季', '第三季', '第四季']
資料列: 1
['北區', 12000.0, 15000.0, 15400.0, 13340.0]
資料列: 2
['中區', 11000.0, 12500.0, 13200.0, 12330.0]
資料列: 3
['南區', 9000.0, 10000.0, 8500.0, 8900.0]
資料列: 4
['總和', 32000.0, 37500.0, 37100.0, 34570.0]
>>> 

如果我們再加一頁,如下圖(建議表頭可以先改成 Q1 – Q4)



RESTART: C:/Users/huskywang/Documents/GitHub/python/PlotProgram/excelplot.py
工作表: 首例一
資料列: 0
['', '第一季', '第二季', '第三季', '第四季']
資料列: 1
['北區', 12000.0, 15000.0, 15400.0, 13340.0]
資料列: 2
['中區', 11000.0, 12500.0, 13200.0, 12330.0]
資料列: 3
['南區', 9000.0, 10000.0, 8500.0, 8900.0]
資料列: 4
['總和', 32000.0, 37500.0, 37100.0, 34570.0]
工作表: 總和
資料列: 0
['第一季', '第二季', '第三季', '第四季']
資料列: 1
[32000.0, 37500.0, 37100.0, 34570.0]
>>> 

如果,我們希望把 總和這個工作表繪製成直方圖,那麼就必須指定要讀取哪個工作表,並且將要繪圖的資料放在一個二維 list 裡面,程式改寫如下:

from xlrd import open_workbook
data=[]
wb = open_workbook('Data.xlsx')
s = wb.sheet_by_name('總和')
print('工作表:',s.name)
for row in range(s.nrows):
    print('資料列:',row)
    values = []
    for col in range(s.ncols):
        values.append(s.cell(row,col).value)
    print(values)
    data.append(list(values))

print(data)

就會得到下面的結果:

RESTART: C:/Users/huskywang/Documents/GitHub/python/PlotProgram/sheetplot.py
工作表: 總和
資料列: 0
['第一季', '第二季', '第三季', '第四季']
資料列: 1
[32000.0, 37500.0, 37100.0, 34570.0]
[['第一季', '第二季', '第三季', '第四季'], [32000.0, 37500.0, 37100.0, 34570.0]]
>>> 

繪製柱狀圖

要顯示這種業績資料,最適合的圖表就是 bar,我們在前面有提過直方圖,使用 hist 方法來達成,但是 hist 所畫的直方圖適用於統計圖,而 bar() 畫出來的我們稱之為柱狀圖,則是商業用途。透過下面的方式來呼叫,其中x 就是x軸,height 就是y軸,當成前兩個參數傳入即可:

bar(x,heightwidth=0.8bottom=None* , align=’center’,**kwargs)

程式碼如下:

from xlrd import open_workbook
import matplotlib.pyplot as plt

data=[]
wb = open_workbook('Data.xlsx')
s = wb.sheet_by_name('總和')
print('工作表:',s.name)
for row in range(s.nrows):
    print('資料列:',row)
    values = []
    for col in range(s.ncols):
        values.append(s.cell(row,col).value)
    print(values)
    data.append(list(values))

print(data)

plt.bar(data[0],data[1])
plt.xlabel("Quarters") 
plt.ylabel("Dollars") 
plt.title("Sales")
plt.show()

結果如下:



但是如果是中文,你就會發現,中文字都變成方塊,這是因為MatplotlibPython的一個很好的繪圖包,但是其本身並不支持中文(貌似其預設配置中沒有中文字體),所以如果繪圖中出現了中文,就會出現亂碼。

因為亂碼是Matplotlib缺少中文配置所導致的,所以我們只需要在程式中說明使用中文字體即可。

首先先選一個字體。在電腦中找到字體,選擇一種中文字體,比如我這裡用的是繁體:


Widows Fonts 目錄底下的字型分成字型群組與字型檔案,從上圖就可以看出來,凡是單張圖示的都是字型檔案,而三張圖示的則是字型群組。當我們看到群組的時候,必須再點進去,才能看到檔案。

請一直往下找,就會看見我們常用的新細明體、標楷體等字體,請選取標楷體,然後按下滑鼠右鍵,選擇[內容],則出現下圖,記得把檔案名稱複製下來,我們後面會用到:



然后在程序中定义Matplotlib的字体管理,这里将其命名为zhfont1,完整代码如下:

from xlrd import open_workbook
import matplotlib.pyplot as plt
import matplotlib.font_manager as pf

data=[]
wb = open_workbook('Data.xlsx')
s = wb.sheet_by_name('總和')
print('工作表:',s.name)
for row in range(s.nrows):
    print('資料列:',row)
    values = []
    for col in range(s.ncols):
        values.append(s.cell(row,col).value)
    print(values)
    data.append(list(values))

print(data)

zhfont1 = pf.FontProperties(fname='C:\Windows\Fonts\kaiu.ttf')
plt.bar(x=data[0],height=data[1])
plt.xticks(fontproperties=zhfont1)
plt.xlabel("季度",fontproperties=zhfont1) 
plt.ylabel("金額",fontproperties=zhfont1) 
plt.title("業績",fontproperties=zhfont1)
plt.show()

這樣,就可以正確地顯示中文了:


特別說明,程式中 xticks() 方法就是設定 bar() 方法中的 x 值,也就是上圖所看到的 第一季” ~ “第四季這些中文字。而 xlabel(), ylabel() title() 都必須個別設定,不能偷懶,如果有用到 legend() ,則必須用 legend(prop=zhfont1) 來設定。

請務必用我提供的方法來解決中文顯示問題,千萬不要用一些笨方法,例如把中文字型檔案複製到 Python 的字型目錄,因為程式不見得是在自己的電腦執行,如果用這樣的方法,每換一次執行環境都必須複製中文字型檔案,豈不是很麻煩。


留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(15):蜂鳴器與音樂

Python可以這樣玩(13):外部LED控制