2020年4月22日 星期三

Python 學習筆記 : Matplotlib 資料視覺化 (一) 基本篇

最近在測試 SciPy 時深深覺得 Matplotlib 這個繪圖套件蠻重要的, 所謂一圖勝千言, 資料只是冰冷的數據, 圖形化 (visualization) 才能讓人秒懂數據所傳達的意義. 我過去在測試機器學習時只學了幾招簡單的 Matplotlib 就趕鴨子上架, 基礎實在太薄弱, 所以想在深入 SciPy 前先對 Matplotlib 做個較完整的測試, 這樣比較不會心虛. 其實我從 2019 年初即想要學 Matplotlib, 但一直都分心去做別的事, 頗感無奈. Python 資料科學三劍客 Numpy, Pandas, 與 Matplotlib 是機器學習最重要的先修課程, 這三個不熟會阻礙機器學習的修練進程, 必須盡快搞定才行.

Matplotlib 是一套類似 MATLAB 的 Python 物件導向繪圖函數套件, 最初開發者是已故美國神經生物學家 John D. Hunter,  他在 2003 年做博士後研究時為了要顯示癲癇患者的腦皮層電圖  (electrocorticography) 而開發, 在此之前他原先使用 MATLAB (商業軟體), 但因為無法應付檔案格式轉換與多重資料來源等需求, 最後選擇用 Python 語言基於 Numpy 與 SciPy 套件自行開發, 並以 BSD 授權開放原始碼. 2008 年 NASA 的鳳凰號火星探測器與第一個黑洞照片之影像處理均使用了 Matplotlib. Hunter 博士於 2012 年因大腸癌逝世, Python 軟體基金會於當年頒發了第一座最高榮譽的傑出服務獎座給 Hunter, 以表彰其對 Python 社群的巨大貢獻, 參考 :

https://matplotlib.org/
https://zh.wikipedia.org/wiki/Matplotlib
https://www.python.org/community/awards/psf-distinguished-awards/

資料視覺化由於只是個圖形化技術常常會被忽視, 但其實它非常重要, 因為不適當或不正確的呈現方式可能會將原本做得非常棒的資料分析都搞砸了. Matplotlib 可以繪製出效果媲美 MATLAB 之多種格式精美圖形 (包括 2D 與 3D), 而且也被封裝到 Pandas 裡面, 可以快速且方便地繪製 Series 與 DataFrame 物件之圖形, 因為它具有如下特點, 所以近年來 Matplotlib 隨著 Python 的普及而被廣泛使用, 特別是科學與工程界 :
  • 用法簡單且可控制圖形中的元件
  • 可產生具有互動性 (interactive) 的圖形
  • 文字與數學運算式以 LaTeX 格式顯示
  • 可輸出為 PNG, PDF, SVG, 以及 EPS 等格式
Matplotlib 設計的初衷是在使用介面與語法上盡量貼近 MATLAB, 而且繼承了 MATLAB 最受歡迎的互動性 (即透過一道道指令來控制圖形的漸進發展), 這樣就可讓大部分熟悉 MATLAB 的使用者幾乎都能無痛轉移到 Matplotlib.

Matplotlib 的原始碼寄存於 GitHub, 參見 :

https://github.com/matplotlib/matplotlib

本系列測試參考書目如下 :

# Python 資料運算與分析實戰 (旗標, 中久喜健司)
# 王者歸來-Python 在大數據科學計算上的最佳實作 (佳魁, 張若愚)
# Python 資料科學與人工智慧應用實務 (旗標, 陳允傑)
# Python 入門邁向高手之路王者歸來 (深石, 洪錦魁)
# Python 程式設計學習經典 (碁峰, 黃立政)
# 一步到位-Python 程式設計 (旗標, 陳惠貞)
# Matplotlib Plotting Cookbook (Packt, 2014)
Python資料科學學習手冊 (Oreilly, 2017) 第四章
Matplotlib 3.0 Cookbook (Packt, 2019)
# Python Data Analytics 2nd Ed. (Apress, 2018) 第七章
Python for Data Analysis 2nd Ed. (Oreilly, 2018) 第九章
# Python Data Analytics and Visualization (Packt, 2017)  第四章
# Matplotlib for Python Developers 2nd Ed. (Packt, 2018)
# Practical Python Data Visualization (Apress, 2021)

更多 Python 筆記參考 :

Python 學習筆記索引 


一. 安裝 Matplotlib 套件 : 

Matplotlib 套件主要是在 Numpy 基礎上建立起來的, 因此須先安裝 Numpy 後再安裝 matplotlib. 在命令列視窗中用 pip 指令即可安裝 :

pip3 install matplotlib 

若之前已安裝過, 則可以加上 -U 參數更新為最新版 :

pip3 install matplotlib -U 

D:\>pip3 install matplotlib -U
Collecting matplotlib
  Downloading matplotlib-3.2.1-cp37-cp37m-win_amd64.whl (9.2 MB)
Requirement already satisfied, skipping upgrade: python-dateutil>=2.1 in c:\python37\lib\site-packages (from matplotlib) (2.7.5)
Requirement already satisfied, skipping upgrade: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in c:\python37\lib\site-packages (from matplotlib) (2.3.1)
Requirement already satisfied, skipping upgrade: numpy>=1.11 in c:\python37\lib\site-packages (from matplotlib) (1.16.0)
Requirement already satisfied, skipping upgrade: cycler>=0.10 in c:\python37\lib\site-packages (from matplotlib) (0.10.0)
Requirement already satisfied, skipping upgrade: kiwisolver>=1.0.1 in c:\python37\lib\site-packages (from matplotlib) (1.0.1)
Requirement already satisfied, skipping upgrade: six>=1.5 in c:\python37\lib\site-packages (from python-dateutil>=2.1->matplotlib) (1.12.0)
Requirement already satisfied, skipping upgrade: setuptools in c:\python37\lib\site-packages (from kiwisolver>=1.0.1->matplotlib) (40.9.0)
Installing collected packages: matplotlib
  Attempting uninstall: matplotlib
    Found existing installation: matplotlib 3.0.2
    Uninstalling matplotlib-3.0.2:
      Successfully uninstalled matplotlib-3.0.2
Successfully installed matplotlib-3.2.1

可見已從原先的 3.0.2 升版為 3.2.1 版.


二. Matplotlib 基本繪圖 : 

Matplotlib 提供三種繪圖方式 :
  • 使用 pyplot 模組 (類似 MATLAB)
  • 使用 pylab 模組 (與 MATLAB 介面相同)
  • 使用物件導向方式
因為 Matplotlib 是物件導向繪圖函式庫, 使用者可直接操控 Matplotlib 底層的 Figure (單一的繪圖容器) 與 Axes 物件 (具有邊界, 刻度, 與標籤之方形繪圖區) 物件來繪圖, 使用者對於圖形有完全的操控能力, 由於具有靈活性與可客製化優點, 通常用於複雜度高的大型程式開發, 雖然比 pyplot 要麻煩些, 但使用物件導向方式來繪圖其實主要也只是在呼叫 Figure 與 Axes 物件的各種方法而已.

pylab 模組的使用介面與知名版權軟體 MATLAB 極為相似, 主要是為了讓 MATLAB 使用者可無縫接軌, 而且語法簡潔, 例如圓周率就用 pi, 繪圖是呼叫 plot() 函數等. 但是由於 pylab 使用 star import 方式將 Numpy 與 pyplot 合併到單一的命名空間中 (這樣使用者就不需要自行 import), 並將部分 Python 內建函數例如 sum() 與 all() 等以 Numpy 的函數取代, 這可能產生命名空間汙染問題. 使用 pylab 只是遷就 MATLAB 的方便性, 反而背離了 Matplotlib 的物件導向精神, 故不建議使用 pylab.

pyplot 模組則是將 Matplotlab 的物件導向函式庫再包裝成類似 MATLAB 的函數式 API, 將許多繪圖物件的複雜結構隱藏起來, 並透過有限狀態機 (state machine) 來追蹤目前圖表與子圖的狀態, 我們只要呼叫 pyplot 所提供函數即可透過簡單的設定實現快速繪圖, 非常適合用一般的資料圖形化繪圖 (但不適合用在大型程式設計), 因為使用 pyplot 繪製最簡單的資料圖形只需要 2~3 個指令而已, 與 MATLAB 相似度高, 又沒有 pylab 命名空間可能被汙染之問題.

參考 :

理解matplotlib、pylab与pyplot之间的关系 
matplotlib的常用的两种方式以及pylab
pylab是matplotlib的一个模块吗,跟pyplot又是什么关系呢?

如果使用 Jupyter Notebook, 請先輸入下列指令 :

%matplotlib inline 

使用 pyplot 繪圖首先要匯入 matplotlib.pyplot 模組 :

import matplotlib.pyplot as plt   

通常都為 pyplot 取 plt 這個別名來簡化打字, 別名可任取, 不過, 如同 numpy 用 np 與 pandas 用 pd 一樣, 取 plt 只是慣例. 如果有要用到 Numpy 或 Matplotlib 本身 (例如設定中文時會用到), 再另外匯入即可 (matplotlib 的別名通常取 mpl) :

import matplotlib as mpl
import numpy as np 

接著呼叫 pyplot.plot() 函數並傳入要顯示的資料, 然後呼叫 pyplot.show() 方法即可顯示圖形, 預設是繪製一個折線圖 (line chart), 透過樣式設定也可以繪製散佈圖 (scatter chart) :

plt.plot(data)
plt.show()

注意, plt.show() 會開啟一個事件迴圈並檢視作用中的繪圖物件, 然後開啟一個或多個交談式繪圖視窗. plt.show() 都是放在程式最後面, 它應該只被呼叫一次, 多次呼叫 plt.show() 可能會導致非預期的結果. 其次, 若使用 Jupyter Notebook, 則不需要呼叫 plt.show() 即可在頁面上繪圖. 

下面以過去一周的每日平均溫度為例, 將溫度數據以串列傳給 pyplot() 即可繪圖 :


範例 1 : 繪製一周平均溫度變化圖 [GitHub 原始碼]

import matplotlib.pyplot as plt
plt.plot([25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9])   #傳入串列數據
plt.show()

可見使用 pyplot 繪圖非常簡單, 只要三行指令就可以了, 結果如下 :




以下是在 Python shell 中的操作 :

>>> import matplotlib.pyplot as plt
>>> temperature=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]
>>> plt.plot(temperature)
[<matplotlib.lines.Line2D object at 0x000001A00EBBF780>]
>>> plt.show()   

可見呼叫 plot() 會傳回一個 Line2D 物件.

如果是在 Jupyter Lab 或 Notebook 環境中, 只要呼叫 pyplot.plot() 即輸出圖形了, 不需要呼叫 show(), 下面是在 Jupyter 官網測試網頁測試的結果 :

https://jupyter.org/try




如果呼叫 plot() 方法時只傳入一個參數, 則此參數會被 Matplotlib 認為是 Y 軸資料, 並以其索引  (0 起始) 當作 X 軸資料, 即 [0, 1, 2, 3, .... N-1], N 為元素個數. 這種情況下 Matplotlib 會以 plot() 的參數預設值繪製出一個沒有標題 (title), 沒有座標軸標籤 (axis label), 也沒有圖例 (legend) 與格線的藍色折線圖, 雖然很陽春, 但都可以透過 plot() 參數與 pyplot 模組的其他函數來設定.


1. 用樣式字串設定圖形樣式 :

其實 plot() 函數可以傳入多個參數, 其中包括圖形樣式字串, 格式如下 :

plot([x1], y1, [樣式字串1], [x2], y2, [樣式字串2], ..., [關鍵字參數]) 

除了必要參數 y1 外, 其餘均為選擇性參數, 都有預設值, 除樣式字串外都是關鍵字參數 (keyword argument), 可指定關鍵字與以設定, 常用的呼叫格式例如 :

plot(y)                                         #以預設線條樣式 (藍色折線) 繪製 y 軸資料
plot(x, y)                                     #以預設線條樣式 (藍色折線) 繪製 x, y 軸資料
plot(x1, y1, x1, y2)                     #以預設線條樣式 (藍色折線) 繪製兩條 x, y 軸資料
plot(y, 'ro')                                  #以指定樣式 'ro' (紅色原點) 繪製 x, y 軸資料
plot(x, y, 'g--')                             #以指定樣式 (綠色虛線) 繪製 x, y 軸資料
plot(x1, y1, 'ro', x1, y2, 'g--')      #以指定樣式繪製兩條 x, y 軸資料
plot(x, y, 'r.', linewidth=2)          #以指定樣式 (紅色點線) 與指定線寬繪製 x, y 軸資料

參考 :

https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html

上面範例中未傳入 X 軸資料, Matplotlib 自動以 Y 軸資料的索引當作 X 軸資料, 下面的範例則傳入 X 軸資料 (星期字串之縮寫) 並指定用紅色圓點繪製, 例如 :


範例 2 : 指定繪圖樣式 [GitHub 原始碼]

import matplotlib.pyplot as plt
temperature=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']             #X 軸資料
plt.plot(week,temperature,'ro')                                     #指定紅色圓點樣式
plt.show()



可見 X 軸刻度已經變成所傳入之星期字串了, 且資料以紅色圓點繪製 (沒有連線). 樣式字串用來控制資料點標記符號, 連線型式, 以及它們的顏色, 由下表中的格式字元組合而成 :


 資料點標記 說明
 "." 圓點 (point, 預設)
 "," 像素 (pixel)
 "*" 星號 (star)
 "+" 加號 (plus)
 "x" 乘號 (x)
 "o" 圓點 (circle)
 "s" 方形 (square)
 "p" 五角形 (pentagon)
 "D" 鑽石形 (diamond)
 "d" 細鑽石形 (thin_diamond)
 "h" 六角形1 (hexagon1)
 "H" 六角形2 (hexagon2)
 "^" 上三角形 (triangle_up)
 "v" 下三角形 (triangle_down)
 "<" 左三角形 (triangle_left)
 ">" 右三角形 (triangle_right)
 "|" 垂直線 (vline)
 "_" 底線 (hline)
 "1" 下三叉 (tri_down)
 "2" 上三叉 (tri_up)
 "3" 左三叉 (tri_left)
 "4" 右三叉 (tri_right)

 連線型式 說明
 "-" 或 "solid" 實線 (solid line, 預設)
 "--" 或 "dashed" 短線虛線 (dashed line)
 ":" 或 "dotted" 點虛線 (dotted)
 "-:" 或 "dash-dotted" 短線點虛線 (dash-dotted line)

 顏色 說明
 "r" 紅色 red
 "g" 綠色 green
 "b" 藍色 blue (預設)
 "c" 青色 cyan
 "m" 洋紅色 magenta
 "y" 黃色 yellow
 "k" 黑色 black
 "w" 白色 white


這些樣式可以任意組合, 例如 'g--+' 表示資料點標記符號為加號 '+', 連線為短虛線 '--', 顏色為綠色. 注意, 顏色樣式是一起套用到資料點標記與連線上. 例如 :


範例 2-1 : 資料點標記 (markers) [GitHub 原始碼]

import matplotlib.pyplot as plt

markers=['.', ',', '*', '+', 'x', 'o', 's', 'p', 'D', 'd', 'h',
         'H', '^', 'v', '<', '>', '|', '_', '1', '2', '3', '4']
x=range(len(markers))
for i in x:
    plt.plot(i, i, markers[i])
plt.show()

此程式將全部資料點標記 markers 字元放在串列中, 然後在繪製 y=x 線條時逐一指定不同的標記符號, 結果如下 : 



 
注意, 第二個標記 ',' 是一個 pixel 的小點, 所以看起來不明顯. 

下面範例是連線型式的測試 :


範例 2-2 : 連線型式 (line styles) [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt

x=np.arange(5)
y=x
plt.plot(x, y, '-')                # 實線
plt.plot(x, y + 1 , '--')       # 短線虛線
plt.plot(x, y + 2, '-.')        # 短線點虛線
plt.plot(x, y + 3, ':')         # 點虛線
plt.show()

此例分別以四種連線型式繪製 y=x 線, 結果如下 : 




由下至上為實線, 短線虛線, 短線點虛線, 以及點虛線. 


2. 繪製多個圖形 : 

可傳入多組 x, y 軸數據在同一張圖上繪製多個圖形, 例如比較上週與本周氣溫變化 :


範例 3 : 繪製兩組資料之圖形 [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']              #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]     #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]     #上周氣溫
plt.plot(week,this_week,'r--o',week, last_week,'b-s')    #繪製兩組資料
plt.show() 

此例有本周與上周兩組氣溫資料 this_week 與 last_week 當 Y 軸, 分別用紅圓點標記短虛線樣式 'r--o' 與方形藍色標記實線樣式 'b-s' 繪製, 它們共用相同的 X 軸資料, 結果如下 :




此例中使用 plot(x1, y1, x2, y2) 呼叫方式一次繪製兩組資料, 其實也可以每組資料分別呼叫 plot(x, y) 分兩次來繪製, 亦即 plt.plot(week,this_week,'r--o',week, last_week,'b-s') 可以拆成如下兩次呼叫, 結果一樣 :

plt.plot(week,this_week,'r--o')
plt.plot(week, last_week,'b-s')


3. 用參數設定圖形樣式 : 

事實上除了使用樣式字串設定資料點標記符號, 連線的形式與兩者的顏色外, 還可以使用 plot() 函數的選擇性參數來設定圖面元素的樣式, 且其操控項目比樣式字串更豐富, 如下表所示 :


 plot() 的選擇性參數 說明
 alpha 透明度 (0 透明~1 不透明)
 color 連線的顏色, 例如 'red', '#ff0000', (1, 0, 0)
 linestyle 連線樣式, 例如 'solid', 'dashed', 'dashdot', 'dotted'
 linewidth 連線寬度 (整數, 單位 px)
 marker 資料點標記符號類型, 例如 'o' 圓形, 's' 方形, 'p' 五角形等
 markersize 資料點標記符號大小 (整數, 單位 px)
 markerfacecolor 資料點標記符號顏色, 例如 'green', '#00ff00', (0, 1, 0)
 markeredgecolor 資料點標記符號周圍顏色, 例如 'blue', '#0000ff', (0, 0, 1)
 markeredgewidth 資料點標記符號邊緣寬度 (整數, 單位 px)


可見使用選擇性參數還多出了顏色透明度, 連線寬度, 資料點標記符號的大小, 邊緣寬度與顏色等樣式的微調. 顏色參數 color, markerfacecolor, 與 markeredgecolor 可用文字, 16 進位字串, 或者 (R, G, B) 數值元組, 每個顏色值為 0~1, 例如 (0, 0, 1) 表示藍色. 16 進位顏色代碼參考 :

命名顏色代碼
https://wiki.tcl-lang.org/page/Colors+with+Names

上面範例 3 可以用參數改寫為如下範例 3-1 :


範例 3-1 : 使用參數設定樣式 (1) [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']         #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]     #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]     #上周氣溫
plt.plot(week,this_week, color='red', marker='o', linestyle='dashed') #繪製第一組資料
plt.plot(week,last_week, color='b', marker='s', linestyle='solid')    #繪製第二組資料
plt.show()

繪圖結果與上面範例 3 使用樣式字串者完全一樣.

下面範例 3-2 測試了更多參數 :


範例 3-2 : 使用參數設定樣式 (2) [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']         #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]     #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]     #上周氣溫
plt.plot(week,this_week,
         color='black',
         marker='p',
         linestyle='dotted',
         linewidth=1) #繪製第一組資料
plt.plot(week,last_week,
         alpha=0.5,
         color='magenta',
         marker='s',
         markersize=8,
         markerfacecolor='green',
         markeredgecolor='yellow',
         linestyle='solid')    #繪製第二組資料
plt.show()

結果如下 :




可見第二組資料不論是資料點或連線都因為 alpha=0.5 之設定變成半透明.


4. 設定標題 (title) 與座標軸標籤 (xlabel, ylabel) : 

以上所繪製的圖形預設都沒有標題 (title), 以及座標軸標籤 (axis label), 所傳達的資訊並不完整, 這可以分別呼叫 pyplot 的 title(), 以及 xlable() 與 ylabel() 函數來設定, 參考 : 


例如 :


範例 4-1 : 添加標題與軸標籤 [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']             #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]    #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]    #上周氣溫
plt.plot(week,this_week,'r--o',week, last_week,'b-s')   #繪製兩組資料
plt.title('Temperature Comparison')                             #設定圖形標題
plt.xlabel('Week day')                                                   #設定 X 軸標籤
plt.ylabel('Celsius')                                                       #設定 Y 軸標籤
plt.show()

結果如下 :




可見坐標軸標籤與標題都有了, 其大小與顏色都是預設值 (黑色), 如果要修改字型大小與顏色, 可在呼叫 title(), xlabel(), 以及 ylabel() 時傳入 fontsize, fontname (字串, 例如 'Times New Roman') 與 color (字串, 數值, 或元組) 等參數, 如下面範例所示 :


範例 4-2 : 設定標題與軸標籤大小與顏色 [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']        #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]    #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]    #上周氣溫
plt.plot(week,this_week,'r--o',week, last_week,'b-s')   #繪製兩組資料
plt.title('Temperature Comparison', fontsize=16, color='blue') #設定圖形標題
plt.xlabel('Week day', fontsize=14, color='blue')       #設定 X 軸標籤
plt.ylabel('Celsius', fontsize=14, color='blue')        #設定 Y 軸標籤
plt.show()

此例標題大小設為 16, 坐標軸標籤大小設為 14, 顏色均為藍色, 結果如下 :




顏色 color 參數也可以使用 '#ffdd0e, 0~1 數值, 或 0~1 數值組成之 (r, g, b) 元組.

雖然座標軸標籤與圖形標題都有了, 但還缺一個圖例 (legend) 來說明每個圖形是甚麼數據.


5. 設定圖例 (legend) :

添加圖例可呼叫 pyplot 的 legend() 函數來達成, 並配合在呼叫 plot() 時傳入 label 參數為每個圖形設定圖例標籤, 不過這樣就不能呼叫 plot(x1, y1, x2, y2) 來一次繪製這兩組資料了, 必須拆開分別呼叫 plot(x, y) 分兩次繪製, 這樣才能分別為各組資料設定 label 參數, 參考 : 


例如 :


範例 5-1 : 設定圖例標籤與位置 (1) [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']              #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]     #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]     #上周氣溫
plt.plot(week,this_week,'r--o',label='this week')           #指定資料之圖例標籤
plt.plot(week, last_week,'b-s',label='last week')            #指定資料之圖例標籤
plt.legend()                                                                    #顯示圖例
plt.title('Temperature Comparison')                               #設定圖形標題
plt.xlabel('Week day')                                                    #設定 X 軸名稱
plt.ylabel('Celsius')                                                        #設定 Y 軸名稱
plt.show()
此例在呼叫 plot() 時傳入 label 參數字串, 這就是在圖例中顯示的字串, 結果如下 :




可見圖例預設會顯示在左上角, 但可以在呼叫 legend() 函數時傳入 loc 參數 (字串或整數) 來指定圖例擺放的位置, 可用的位置字串如下表 :


 圖例 loc 參數 數值 說明
 'best' 0 自動挑選最佳位置
 'upper right' 1 右上角
 'upper left' 2 左上角
 'lower left' 3 左下角
 'lower right' 4 右下角
 'right' 5 右邊
 'center left' 6 左邊中間
 'center right' 7 右邊中間
 'lower center' 8 下方中間
 'upper center' 9 上方中間
 'center' 10 中間


此外, 圖例標籤除了可在呼叫 plot() 函數時傳入 label 參數字串來設定外, 也可以在呼叫 legend() 時傳入一個表示圖例標籤的字串串列來設定, 這樣做的話每一組資料要分別呼叫 plot (x, y) 函數來繪圖, 而串列中的元素順序就對應呼叫 plot() 之順序, 例如 :


範例 5-2 : 設定圖例標籤與位置 (2) [GitHub 原始碼]

import matplotlib.pyplot as plt
week=['Sun','Mon','Tue','Wen','Thu','Fri','Sat']                #X 軸
this_week=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]      #本周氣溫
last_week=[22.2, 25.2, 26.6, 22.8, 20.1, 24.3, 28.9]      #上周氣溫
plt.plot(week, last_week,'b-s')
plt.plot(week,this_week,'r--o')
plt.legend(['last_week', 'this_week'], loc='lower left')    #設定圖例標籤與位置
plt.title('Temperature Comparison')     #設定圖形標題
plt.xlabel('Week day')                          #設定 X 軸標籤
plt.ylabel('Celsius')                              #設定 Y 軸標籤
plt.show()

此例呼叫 legend() 時所傳入的圖例標籤串列依序對應前面的兩個 plot() 呼叫, 結果如下 :




可見傳入 loc='lower left' 可將圖例位置設為左下角.


6. 繪製函數圖形 :

以上的範例使用串列所提供的列舉資料繪圖, 但 Matplotlib 的強項是搭配 Numpy 進行函數繪圖, 事實上傳入 plot() 的 X, Y 軸串列資料都會被 Matplotlib 轉成 Numpy 的 ndarray 陣列後再進行繪圖. 關於 Numpy 陣列基本操作參考 :

Python 學習筆記 : Numpy 測試 (一) : 建立陣列

下面範例利用 Numpy 的 arange() 與 linspace() 函數繪製函數圖形, 這兩個函數的介面如下 :


 np 建立陣列方法 說明
 arange(e) 建立元素值為 0, 1, 2 ... (e-1) 的陣列
 arange(b, e) 建立元素值為 b, b+1, b+2 ... (e-1) 的陣列
 arange(b, e, s) 建立元素值為 b, b+s, b+2s ... (e-1) 的陣列 (步階為 s)
 linspace(b, e) 建立 50 個等差數列元素之陣列 (含 e), 公差為 (e-b)/49
 linspace(b, e, n) 建立 n 個等差數列元素之陣列 (含 e), 公差為 (e-b)/(n-1) 


下面是繪製代數函數 (即多項式方程式) 的範例 :


範例 6-1 : 繪製代數函數圖形 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=np.arange(0,10)                    #X 軸
y1=x                                          #Y軸1=x
y2=x**2                                    #Y軸2=x**2
plt.plot(x, y1, 'r--o', label='x')          #指定資料之圖例標籤
plt.plot(x, y2, 'b-s', label='x**2')     #指定資料之圖例標籤
plt.legend()                                #顯示圖例
plt.title('functions')                    #設定圖形標題
plt.xlabel('X')                             #設定 X 軸標籤
plt.ylabel('Y')                             #設定 Y 軸標籤
plt.show()

此例繪製兩個函數 : y=x (線性函數) 與 y=x**2 (平方函數), X 軸是以 numpy.arange() 函數產生的 0~10 之等差數列陣列 (ndarray 物件), 也可以用 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 串列代替, 不過傳入 plot() 後會被轉成 Numpy 的 ndarray 陣列. 結果如下 :




下面則是繪製超越函數 (即非代數函數), 以正弦 sin(x) 與餘弦 cos(x) 為例 :


範例 6-2 : 繪製超越函數圖形 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=np.linspace(0,10,100)            #X 軸
y1=np.sin(x)                              #Y軸1=sin(x)
y2=np.cos(x)                             #Y軸2=cos(x)
plt.plot(x, y1, '-r', label='sin(x)')    #指定資料之圖例標籤
plt.plot(x, y2, ':b', label='cos(x)')    #指定資料之圖例標籤
plt.legend()                               #顯示圖例
plt.title('functions')                   #設定圖形標題
plt.xlabel('X')                           #設定 X 軸標籤
plt.ylabel('Y')                           #設定 Y 軸標籤
plt.show()

此例利用 Numpy 的 linspace() 函數產生 [0, 10] 之間的 100 個均距數值陣列 (含 0 與 100) 當作 X 軸, 繪製三角函數 sin(x) 與 cos(x) 的圖形, 結果如下 :





7. 設定坐標軸範圍 :

由上面範例中可知, Matplotlib 預設會給 X, Y 軸留一些適當的邊限餘裕 (margin), 如果不想要這些餘裕可呼叫 axis() 並傳入一個四元素串列 [xmin, xmax, ymin, ymax] (或元組) 來設定圖形的 viewport (顯示範圍) :

plt.axis([xmin, xmax, ymin, ymax])    

如果呼叫 axis() 時沒有傳入參數, 則會傳回一個表示 X, Y 軸範圍的四元素元組 (getter) :

plt.axis()   #傳回 (xmin, xmax, ymin, ymax)

參考 :


如下面範例所示 :


範例 7-1 : 用 axis() 設定坐標軸範圍 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=np.linspace(0,10,100)                  #X 軸
y1=np.sin(x)                             #Y軸1=sin(x)
y2=np.cos(x)                             #Y軸2=cos(x)
plt.plot(x, y1, '-r', label='sin(x)')    #指定資料之圖例標籤
plt.plot(x, y2, ':b', label='cos(x)')    #指定資料之圖例標籤
plt.axis([0, 10, -1, 1])                 #設定坐標軸範圍
print(plt.axis())                        #顯示坐標軸範圍
plt.legend()                             #顯示圖例
plt.title('functions')                   #設定圖形標題
plt.xlabel('X')                          #設定 X 軸標籤
plt.ylabel('Y')                          #設定 Y 軸標籤
plt.show()

此例 X 軸範圍設定與 x 變數範圍一樣即可, 而 Y 軸則因 sin(x) 與 cos(x) 值域範圍為 [-1, 1], 故傳入 [0, 10, -1, 1] 時圖框剛好與圖形密接而無餘裕, 結果如下 :




呼叫 axis() 會傳回一個元組 :

(0.0, 10.0, -1.0, 1.0)

設定坐標軸範圍除了呼叫 axis() 一次設定 X, Y 軸範圍外, 還可以呼叫 xlim() 與 ylim() 並傳入二元素的串列 (或元組) 分別設定 X, Y 軸座標範圍 :

plt.xlim([xmin, xmax])
plt.ylim([ymin, ymax])


範例 7-2 : 用 axis() 設定坐標軸範圍 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=np.linspace(0,10,100)                  #X 軸
y1=np.sin(x)                             #Y軸1=sin(x)
y2=np.cos(x)                             #Y軸2=cos(x)
plt.plot(x, y1, '-r', label='sin(x)')    #指定資料之圖例標籤
plt.plot(x, y2, ':b', label='cos(x)')    #指定資料之圖例標籤
plt.xlim([0, 10])                        #設定X坐標軸範圍
plt.ylim([-1, 1])                        #設定Y坐標軸範圍
print(plt.xlim())                        #顯示X坐標軸範圍
print(plt.ylim())                        #顯示Y坐標軸範圍
plt.legend()                             #顯示圖例
plt.title('functions')                   #設定圖形標題
plt.xlabel('X')                          #設定 X 軸標籤
plt.ylabel('Y')                          #設定 Y 軸標籤
plt.show()

此例用 xlim([xmin, xmax]) 與 ylim([ymin, ymax]) 設定 X, Y 軸範圍, 結果與上面範例相同, 呼叫 xlim() 與 ylim() 則傳回兩個元組 :

(0.0, 10.0)
(-1.0, 1.0)


8. 設定坐標軸刻度 : 

由上面的範例可知, Matplotlib 會自動為 X, Y 坐標軸打上適當的刻度 (ticks), 如果想要自訂刻度可以透過呼叫 xticks(seq, labels) 與 yticks(seq, labels) 來設定 :

xticks(seq, labels) : 以序列物件 seq 與對應之標籤 labels 設定 X 軸座標之刻度
yticks(seq, labels) : 以序列物件 seq 與對應之標籤 labels 設定 X 軸座標之刻度

其中序列物件 seq 可以是元組或串列, 參考 : 

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.yticks.html

例如上面範例 6-1 的 X 軸座標刻度是顯示 [0, 2, 4, 6, 8], 如果要改為 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 可將此序列傳入 xticks(), 如下列範例所示 :


範例 8-1 : 用 xticks() 使 X 坐標軸刻度標示更精細 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=np.arange(0,10)                    #X 軸
y1=x                                    #Y軸1=x
y2=x**2                              #Y軸2=x**2
plt.plot(x, y1, 'r--o', label='x')   #指定資料之圖例標籤
plt.plot(x, y2, 'b:d', label='x**2') #指定資料之圖例標籤
plt.xticks(x)                        #設定 X 軸刻度讀數
plt.legend()                         #顯示圖例
plt.title('functions')             #設定圖形標題
plt.xlabel('X')                      #設定 X 軸標籤
plt.ylabel('Y')                      #設定 Y 軸標籤
plt.show()

此例僅用 X 軸資料設定 X 軸座標之刻度 (因 Y 軸 OK 不需要改), 結果如下 :




可見 X 軸刻度已經改變為間隔 1 了.

除了使刻度標示更精細外, 調整刻度標示最主要的目的還是改善資料的視覺傳達效果. 例如下面這個範例是比較台股兩個主要 ETF 台灣五十 (0050) 與台灣高股息 (0056) 的殖利率變化, 更能清楚了解調整座標軸刻度的必要性.


範例 8-2 : ETF 殖利率比較 (1) [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=(2015, 2016, 2017, 2018, 2019)      #X 軸
y1=(4.33, 5.67, 3.78, 5.62, 6.7)      #Y軸1=0056殖利率
y2=(3.01, 1.28, 6.1, 7.1, 7.22)       #Y軸2=0050殖利率
plt.plot(x, y1, 'r--o', label='0056')  #指定資料之圖例標籤
plt.plot(x, y2, 'b:d', label='0050')   #指定資料之圖例標籤
plt.legend()                                    #顯示圖例
plt.title('ETF Dividend Yield')       #設定圖形標題
plt.xlabel('Year')                             #設定 X 軸標籤
plt.ylabel('Dividend yield(%)')       #設定 Y 軸標籤
plt.show()

此例中的 x 是以數值元組表示的近五年年份, 作為 X 軸資料; Y 軸資料部分, y1 是近五年 0056 的殖利率數據; y2 則是 0050 的殖利率, 資料來源為 Goodinfo (台灣股市資訊網), 參考 :

https://goodinfo.tw/StockInfo/StockDividendPolicy.asp?STOCK_ID=0050&SHOW_ROTC=
https://goodinfo.tw/StockInfo/StockDividendPolicy.asp?STOCK_ID=0056&SHOW_ROTC=

結果如下 :



可見由於 X 軸資料是數值 tuple, 使得年份的 X 軸刻度被 Matplotlib 自動當作是浮點數處理, 與一般年份以整數表示的習慣不符; 其次, Y 軸數據是浮點數, 但刻度標示卻只有整數, 如果能精細到 0.5 會比較好, 應該用 xticks() 與 yticks() 予以調整, 如下面範例所示 :


範例 8-3 : ETF 殖利率比較 (2) : 調整 X/Y 軸刻度 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=(2015, 2016, 2017, 2018, 2019)      #X 軸
y1=(4.33, 5.67, 3.78, 5.62, 6.7)      #Y軸1=0056殖利率
y2=(3.01, 1.28, 6.1, 7.1, 7.22)       #Y軸2=0050殖利率
plt.plot(x, y1, 'r--o', label='0056') #指定資料之圖例標籤
plt.plot(x, y2, 'b:d', label='0050')  #指定資料之圖例標籤
plt.xticks(x)                                   #設定 X 軸刻度讀數
plt.yticks(np.arange(1, 8, 0.5))      #設定 Y 軸刻度讀數
plt.legend()                                    #顯示圖例
plt.title('ETF Dividend Yield')       #設定圖形標題
plt.xlabel('Year')                             #設定 X 軸標籤
plt.ylabel('Dividend yield(%)')       #設定 Y 軸標籤
plt.show()

此例以 X 軸資料傳給 xticks() 設定座標軸刻度標示, 以 Numpy 的 arange() 函數產生 1~8 間隔 0.5 的浮點數陣列傳給 yticks() 設定 Y 軸刻度標示, 結果如下 :




經過調整後的座標軸刻度就比較合理清楚了. 如果不想顯示座標軸刻度, 只要將一個空串列傳入 plt.xticks() 與 plt.yticks() 即可, 但這種應用場合並不多見. 

坐標軸刻度的樣式除了可在 plt.xticks() 與 plt.yticks() 中個別設定外, 也可以用 plt.tick_params() 函式統一設定, 常用語法如下 :

plt.tick_params(axis [, labelsize, color, labelcolor, width, length])    

其中必要參數 axis 指定要套用的座標軸 ('x', 'y', 'both'), 備選參數 labelsize 是刻度標籤的大小 (單位 pixel), color 是刻度的顏色 (注意, 這是刻度短線顏色, 不是標籤顏色), labelcolor 是刻度標籤的顏色, width 與 length 分別是刻度短線的厚度與長度 (單位 pixel), 更多參數參考 :



範例 8-4 : 用 plt.tick_params() 設定刻度標籤樣式 [GitHub 原始碼]

import numpy as np
import matplotlib.pyplot as plt
x=(2015, 2016, 2017, 2018, 2019)      #X 軸
y1=(4.33, 5.67, 3.78, 5.62, 6.7)           #Y軸1=0056殖利率
y2=(3.01, 1.28, 6.1, 7.1, 7.22)             #Y軸2=0050殖利率
plt.plot(x, y1, 'r--o', label='0056')        #指定資料之圖例標籤
plt.plot(x, y2, 'b:d', label='0050')         #指定資料之圖例標籤
plt.xticks(x)                                          #設定 X 軸刻度讀數
plt.yticks(np.arange(1, 8, 0.5))             #設定 Y 軸刻度讀數
plt.tick_params(axis='both',   
                labelsize='12',   
                color='blue',  
                labelcolor='blue')                  #設定刻度參數
plt.legend()                                            #顯示圖例
plt.title('ETF Dividend Yield')              #設定圖形標題
plt.xlabel('Year')                                    #設定 X 軸標籤
plt.ylabel('Dividend yield(%)')             #設定 Y 軸標籤
plt.show()

此處是在上例的基礎上添加 plt.tick_params() 以設定刻度與其標籤之樣式, 主要是將 X/Y 軸刻度與標籤顏色均設為藍色, 字型大小設為 12 DPI, 結果如下 : 




可見刻度與其標籤顏色都變成藍色了. 

坐標軸刻度還可以利用 rotation 參數加以旋轉, 這在 X/Y 軸標籤文字太長時特別有用 (例如時間序列使用日期或時間當刻度時), 因為過長的刻度標籤會互相重疊而影響可讀性, 例如下面的範例由於 X 軸標籤適日期, 預設作為水平刻度會彼此重疊 : 


範例 8-5 : 軸標籤文字太長導致的重疊問題 [GitHub 原始碼]

import matplotlib.pyplot as plt
x=['2022-02-25','2022-02-26','2022-02-27','2022-02-28',
   '2022-03-01','2022-03-02','2022-03-03']                #X 軸(日期)
temp=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]         #氣溫
humid=[56, 63, 75, 72, 66, 59, 77]                            #濕度
plt.plot(x,temp,'r--o',x, humid,'b-s')                           #繪製兩組資料
plt.title('Temperature & Humidity')                           #設定圖形標題
plt.xlabel('day')                                                          #設定 X 軸標籤
plt.ylabel('Temperature & Humidity')                       #設定 Y 軸標籤
plt.show()

結果如下 : 




可見若沒有呼叫 plt.xticks() 的話, 標籤預設會被拿來當 X 軸刻度文字, 此例由於 X 軸標籤是日期, 長度太長使得前後重疊. 解決方法是在呼叫 plt.xticks() 時傳入 rotation 參數指定旋轉角度使其適當地旋轉, 例如 : 


範例 8-6 : 呼叫 plt.xticks() 以 rotation 參數旋轉刻度標籤 [GitHub 原始碼]

import matplotlib.pyplot as plt
x=['2022-02-25','2022-02-26','2022-02-27','2022-02-28',
   '2022-03-01','2022-03-02','2022-03-03']              #X 軸(日期)
temp=[25.4, 23.7, 28.6, 29.2, 24.8, 22.5, 21.9]      #氣溫
humid=[56, 63, 75, 72, 66, 59, 77]                         #濕度
plt.plot(x,temp,'r--o',x, humid,'b-s')                        #繪製兩組資料
plt.title('Temperature & Humidity')                        #設定圖形標題
plt.xlabel('day')                                                       #設定 X 軸標籤
plt.ylabel('Temperature & Humidity')                    #設定 Y 軸標籤
plt.xticks(x, rotation=20)                                     #旋轉 20 度 
plt.show()

此例呼叫 plt.xticks() 並傳入 rotation 參數指定將 X 軸刻度標籤旋轉 20 度, 結果如下 : 




可見旋轉之後就沒有刻度標籤重疊的問題了. 不過如果旋轉角度過大, 部分文字會被截掉, 這是因為超出畫布的預設大小的緣故, 必須使用物件導向方式利用畫布容器 Figure 物件來調整大小.  


沒有留言 :