2022年10月1日 星期六

好書 : 線上教學的技術-快速上手的12堂必修課

 此書已大致看完, 因有人預約所以先做個筆記後還書. 


Source : 博客來


作者王永福是專業培訓教練與講師, 這本書不是在講如何操作線上教學軟體, 而是 "線上教學的技術", 把實體教學的技術搬到線上同樣成功. 
  1. 線上課程非常容易分心, 必須避免單純講課, 一定要把內容變成互動模式, 例如舉手回答 (問答法), 單選複選 (選擇&排序法), 小組討論, 遊戲化 (演練法) 等. 
  2. 線上教學核心策略是 : 最小化技術需求, 最大化教學效果.
  3. 講述法效果要好, 投影片的輔助很重要, 其三大技巧是 : 大字流, 半圖文, 全圖像, 最重要的關鍵是 : 講到時才出現投影片. 
  4. 選擇與排序法可一次對多人, 事先請學員準備半張 A4 紙, 講師利用投影片出題目, 然後請大家寫答案, 對答案, 用最簡單的工具與方法就可以創造互動參與. 
  5. 演練法是說給你聽, 做給你看, 讓你做做看. 最重要的是要有示範, 其次是指令要清楚, 甚至要多次重覆. 
  6. 影片法只是教學輔助, 而非教學主體. 影片一定要短且聚焦, 播放前後要有提示重點或有學習活動. 
  7. 教學的本質是教學成效, 想辦法用最簡單的工具完成最有效的教學, 以最小化的資訊需求 (白紙與粗筆) 最大化教學成果. 但如果要做小組討論就需要軟體提供的功能.
  8. 教學中加入遊戲元素可增加參與動力, 但遊戲化並非真的要玩遊戲, 而是把遊戲的 PBL 三大元素 (Point 點數, Benifit 獎勵, Leaderboard 排行榜) 融入教學教學過程中, 另外, 遊戲規則, 公平, 即時回饋等也很重要.

2022年9月30日 星期五

Python 學習筆記 : Matplotlib 資料視覺化 (六) 物件導向篇 (中)

因為篇幅太長, 所以把物件導向拆成三篇, 本系列之前的測試文章參考 : 


四. 使用 GridSpec 物件為子圖排版 : 

前一篇物件導向 (上) 的測試中, 不論是呼叫 fig.add_subplot() 或 plt.subplots(), 子圖都是以網格矩陣排版, 沒有放置子圖 AxesSubplot 物件的那一格會出現一個空缺, 沒辦法合併儲存格. 在進階篇中有介紹合併儲存格的做法, 但那只能整列或整欄合併. 如果要對網格作較彈性的儲存格合併來排版, 必須以物件導向模式使用 GridSpec 類別的物件來設定版面, 參考 :


GridSpec 類別放在 Matplotlib 的 gridspec 模組中, 使用前須先匯入, 例如 : 

>>> import matplotlib.pyplot as plt   
>>> import matplotlib.gridspec as gridspec    
>>> type(gridspec)    
<class 'module'>   

首先呼叫 GridSpec 類別的建構式 GridSpec() 來建立 GridSpec 物件, 常用語法如下 :

grid=gridspec.GridSpec(nrow, ncol [figure, wspace, hspace])   

此建構式會傳回一個 GridSpec 物件. 參數說明如下 :
  • nrows 與 ncols 表示子圖網格的列數與欄數 (必要參數)
  • figure 為 Figure 物件, 用於 contrained_layout 為 True 時. 
  • wspace 與 hspace 為相對於子圖尺寸 (寬高) 之百分比做為子圖間距 (值 0~1)
以建立如下版面為例, 基本架構為 2*3 網格, 然後將右上角與左下角兩格合併形成 4 個繪圖區 :




例如建立間距是 0.4 倍子圖寬高尺寸的 2*3 網格 : 

>>> grid=gridspec.GridSpec(2, 3, wspace=0.4, hspace=0.4)   
>>> type(grid)   
<class 'matplotlib.gridspec.GridSpec'>   

注意, 不指定參數關鍵字的話, 第一參數為 nrows, 第二參數為 ncols, 為了增進程式可讀性, 最好還是指定關鍵字. 

接著建立畫布物件, 然後依據繪圖區在網格中的位置指定 GridSpec 物件的索引, 將其傳給 Figure 物件的 add_subplot() 方法來建立 4 個繪圖區 (子圖) 物件 : 

>>> fig=plt.figure()       
>>> ax1=fig.add_subplot(grid[0, 0])   
>>> ax2=fig.add_subplot(grid[0, 1:])   
>>> ax3=fig.add_subplot(grid[1, :2])   
>>> ax4=fig.add_subplot(grid[1, 2])    
>>> plt.show()   

此處 ax1 子圖位置是 2*3 網格中的第 0 列第 0 行, 故傳入 grid[0, 0]; ax2 子圖位置是 2*3 網格中的第 0 列第 1 行與第 2 行的合併儲存格, 故傳入 grid[0, 1:], 其中欄索引 1: 表示索引 1 以後全部; ax3 子圖位置是 2*3 網格中的第 1 列第 0 行與第 1 行的合併儲存格, 故傳入 grid[1, :2], 其中欄索引 :2 表示索引 2 以前全部 (不含 2, 即欄索引 0 與 1); ax1 子圖位置是 2*3 網格中的第 1 列第 2 行, 故傳入 grid[1, 2], 結果如下 : 




注意, 若傳入 GridSpec() 的間距 wspace 與 hspace 太小 (例如 0.2), 可能會使坐標軸刻度標籤會與子圖標題部分重疊, 需要視畫布大小微調. 

完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (1) [看原始碼]


在前一篇測試中, 子圖間距的懶人設定法有兩個, 一是呼叫 fig.tight_layout() 函式讓版面緊密, 但這方法在使用 GridSpec 物件排版時無效, 而且會出現如下警告 :

>>> fig.tight_layout()   
matplotlib_fig_adjust_subplot.py:1: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.

第二個方法是呼叫 fig.subplots_adjust(), 這雖然不會出現警告, 但也同樣無效. 

如果不想微調 wspace 與 hspace 的麻煩, 可以在建立 Figure 物件時傳入 constrained_layout=True 參數讓版面受限制並自動調整間距, 然後於建立 GridSpec 物件時將 Figure 物件傳給 figure 參數, 例如 :

>>> fig=plt.figure(constrained_layout=True)    # 讓版面受限
>>> grid=gridspec.GridSpec(nrows=2, ncols=3, figure=fig)    # 指定 Figure 物件
>>> ax1=fig.add_subplot(grid[0, 0])   
>>> ax2=fig.add_subplot(grid[0, 1:])   
>>> ax3=fig.add_subplot(grid[1, :2])   
>>> ax4=fig.add_subplot(grid[1, 2])    
>>> plt.show() 

結果如下 : 




完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (2) [看原始碼]


除了直接使用 GridSpec 類別來排版外, 也可以利用 Figure 物件的 add_gridspec() 方法來建立 GridSpec 物件, 這樣就不需要匯入 gridspec.GridSpec 類別了. 語法如下 : 

grid=fig.add_gridspec(nrow, ncol [figure, wspace, hspace]) 

參數用法與上面的 GridSpec() 建構式一樣, 例如 : 

>>> import matplotlib.pyplot as plt 
>>> fig=plt.figure()  
>>> grid=fig.add_gridspec(nrows=2, ncols=3, wspace=0.4, hspace=0.4)    
>>> type(grid)   
<class 'matplotlib.gridspec.GridSpec'>      
>>> ax1=fig.add_subplot(grid[0, 0])   
>>> ax2=fig.add_subplot(grid[0, 1:])   
>>> ax3=fig.add_subplot(grid[1, :2])   
>>> ax4=fig.add_subplot(grid[1, 2])    
>>> plt.show() 

可見 fig.add_gridspec() 同樣會傳回 GridSpec 物件, 結果與上面用 GridSpect 物件排版的一樣.

完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (3) [看原始碼]


當然也可以用 constrained_layout 參數讓 Figure 畫布自動調節版面與子圖間距 :

>>> import matplotlib.pyplot as plt 
>>> fig=plt.figure(constrained_layout=True)      # 讓版面受限
>>> grid=fig.add_gridspec(nrows=2, ncols=3)    # 建立 2*3 網格之 GridSpec 物件
>>> ax1=fig.add_subplot(grid[0, 0])   
>>> ax2=fig.add_subplot(grid[0, 1:])   
>>> ax3=fig.add_subplot(grid[1, :2])   
>>> ax4=fig.add_subplot(grid[1, 2])    
>>> plt.show() 

結果與上面用 GridSpect 物件排版的一樣. 完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (4) [看原始碼]


更複雜的版面配置範例如下 : 




此例將 3*3 網格作局部合併成 5 個子圖, 配置時主要的工作是利用切片技巧決定各子圖的索引, 其中冒號與負號的運用是重點, 例如 : 

>>> import matplotlib.pyplot as plt 
>>> fig=plt.figure(constrained_layout=True)      # 讓版面受限
>>> grid=fig.add_gridspec(nrows=3, ncols=3)    # 建立 3*3 網格之 GridSpec 物件
>>> fig=plt.figure(constrained_layout=True)       
>>> grid=fig.add_gridspec(nrows=3, ncols=3)    
>>> ax1=fig.add_subplot(grid[0, :])   
>>> ax2=fig.add_subplot(grid[1, :-1])   
>>> ax3=fig.add_subplot(grid[2, 0])     
>>> ax4=fig.add_subplot(grid[2, 1])   
>>> ax5=fig.add_subplot(grid[1:, 2])   
>>> plt.show()  

此例使用 constrained_layout 來限制版面配置, 由畫布自動調節版面與子圖間距, 結果如下 :




完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (5) [看原始碼]


利用 GridSpec 物件可以彈性地對子圖網格進行排版, 下面是改編自 Matplotlib 與官網與 "Python 資料科學學習手冊" 書中第四章的多軸值方圖範例 : 

>>> import matplotlib.pyplot as plt 
>>> import numpy as np   
>>> np.random.seed(42)                # 設定隨機種子
>>> x=np.random.randn(1000)     
>>> y=np.random.randn(1000)   
>>> grid=fig.add_gridspec(nrows=2, ncols=2, wspace=0.05, hspace=0.05)  # 2*2 網格      
>>> ax=fig.add_subplot(grid[1, 0])          # 繪製散佈圖用
>>> ax_histx=fig.add_subplot(grid[0, 0])   # 繪製垂直直方圖用   
>>> ax_histy=fig.add_subplot(grid[1, 1])   # 繪製水平直方圖用
>>> ax_histx.set_xticklabels([])  # 取消垂直直方圖 X 軸刻度標籤
>>> ax_histy.set_yticklabels([])  # 取消水平直方圖 Y 軸刻度標籤
>>> ax.scatter(x, y)                      # 繪製散佈圖
<matplotlib.collections.PathCollection object at 0x0000020AB16D0F28>
>>> ax_histx.hist(x, bins=10)     # 繪製垂直直方圖
(array([  7.,  21., 101., 194., 277., 220., 129.,  42.,   5.,   4.]), array([-3.28006281, -2.57456585, -1.86906889, -1.16357192, -0.45807496,
        0.247422  ,  0.95291897,  1.65841593,  2.36391289,  3.06940985,
        3.77490682]), <a list of 10 Patch objects>)
>>> ax_histy.hist(y, bins=10, orientation='horizontal')     # 繪製水平直方圖
(array([  8.,  24.,  69., 190., 258., 222., 164.,  49.,  13.,   3.]), array([-3.21292639, -2.54757846, -1.88223054, -1.21688261, -0.55153469,
        0.11381324,  0.77916116,  1.44450909,  2.10985701,  2.77520494,
        3.44055286]), <a list of 10 Patch objects>)
>>> plt.show()   

此例使用隨機數來繪製散佈圖與 X, Y 軸直方圖, 直方圖分成 10 個 bin (值區段), 散佈圖放在 [1, 1] 網格; X 軸直方圖 (垂直) 放在 [0, 0] 網格; Y 軸直方圖 (水平) 放在 [1, 1] 網格, 並用 set_xticklabels([]) 與 set_yticklabels([]) 拿掉直方圖的刻度標籤, 結果如下 : 




從直方圖的分布可知這近似常態分布, 如果將 bin 設大一些例如 50 就很接近常態分佈了. 

完整程式碼參考 :

測試  : 使用 GridSpec 物件排版 (6) [看原始碼]

2022年9月29日 星期四

啟用 Hahow 學校帳號

今天在整理公司信箱時發現上周收到的一封信, 沒仔細看還以為只是內訓單位寄來的課程通知函, 原來是七月時申請的 Hahow 學校帳號開通信, 係我司向 Hahow 學校集體購買的企業帳號 (Hahow for Business), 網址為 :


帳號是公司 email 名稱改換為 WID, 密碼是用預設登入後再自行更改 (akf). 登入後發現裡面課程很多, 我比較有興趣的是機器學習與資料科學方面的課程, 在有效期限一年內要好好地來把這些課程都消化掉, 練功唄! 

高科大還書 3 本 (機器學習 & Deap Learning 3 & Arduino 自走車)

昨天因為不用丟垃圾, 晚飯後去母校圖書館還書與取書 : 
No.1 與 No.2 這兩本書有人預約需還, No.3 是暫時沒看的, 想先還借其它書. 這樣就有 3 個空缺, 改借下面這三本, 最近用得著 :
穿越停車場前往圖書館時發現不管是汽車或機車都停滿了, 這讓我想起以前去西子灣讀書的日子, 晚點到車位就要停到防波堤尾端了. 多年來母校夜間部都是高雄技職進修的首選 (地點好), 但經過教室時卻看到學生聽課的在聽課, 但發呆與滑手機也不少. 畢竟來讀夜間部的人有些是為了進修, 有的是為了拿學歷, 但願意花時間來學校沒翹課已經很可取了. 

2022年9月27日 星期二

Python 學習筆記 : Matplotlib 資料視覺化 (五) 物件導向篇 (上)

Matplotlib 提供了 Matlab 與物件導向 (OOP) 兩種風格的介面, 在前面的 Matplotlib 測試中都是使用 Matlab 風格, 此乃模仿自 Matlab, 以狀態為基礎 (stateful) 的簡易繪圖介面, 它會追蹤目前的作用中畫布物件與繪圖區物件, 只要呼叫 pyplot.plot() 函數即可在目前作用中的繪圖區物件上畫圖, 這種寫法優點是快速簡易, 但是無法做細部微調. 較進階的設定 (例如版面配置等) 必須使用物件導向方式.    

本系列之前的測試文章參考 : 

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

更多 Python 筆記參考 : 

本篇測試參考書籍如下 :

# Hands-on Matplotlib (Ashwin Pajankar, 2022, Appress)
# Python 資料科學學習手冊 (何敏煌譯, 2017, 碁峰)


. Matplotlib 的物件結構 : 

Matplotlib 本質上是一個物件導向繪圖函式庫, 由底層的 FigureCanvas, Renderer, 以及高層的 Artist 物件構成, FigureCanvas 負責建立繪圖用的畫布; Renderer 負責在 FigureCanvas 畫布上渲染繪圖, 而 Artist 物件則負責所有高層結構. 

Artist 物件分成簡單型與容器型兩種, 簡單型 Artist 物件用來建構基本繪圖元素, 例如線條, 文字, 圖像等; 而容器型 Artist 物件則是用來盛裝簡單型 Artist 物件, 透過繼承容器型 Artist 物件衍生出 Axis,  Axes, 與 Figure 等實際繪圖使用之物件, 其用途如下 :
  • Figure 物件 : 可以理解為一塊畫布, 是最上層的 Artist 物件, 用來盛裝所有圖表元素, 其下一層容器為 Axes 物件, 一個 Figure 裡面可以包含多個 Axes 物件 (子圖). 
  • Axes 物件 : 可以理解為畫布中的一塊具有獨立坐標系的繪圖區域, 也是 Matplotlib 繪圖的核心物件, 它包含了組成圖表的各種 Artist 物件, 例如 Line2D 物件 (圖形), Text (文字), AxesImage (圖像) 等.  
  • Axis 物件 : 此容器用來盛裝刻度線, 刻度標籤, 座標網格, 以及座標標題. 
三者之層次如下圖所示 : 




Matplotlib 將畫布與繪圖區中的全部元素都視為物件, Figure 物件代表整塊畫布 , 裡面可包含一個以上具有獨立坐標系的繪圖區, 稱為 Axes 物件 (亦即子圖), 繪圖區也是由多個物件組成, 例如核心的 Line2D (圖形),  AxesImage (圖片), Text (標註文字), 以及 Axis 物件所裝的 X, Y 軸刻度等. 

事實上, 在 Matplotlib 的物件導向繪圖實務中, 會直接操作的只有 Figure 與 Axes 物件而已, Artist 物件則是在呼叫 Figure 與 Axes 物件的方法時自動建立. 一個 Python 程式中可以建立多個 Figure 物件 (會產生多個繪圖視窗); 每個 Figure 物件裡面可以建立多個 Axes 物件 (會在畫布中產生多個子圖). 


二. Figure 與 Axes 物件 : 

Matplotlib 的物件導向模式繪圖程序如下 : 
  1. 建立 Figure 物件 (畫布)
  2. 在 Figure 物件中建立一或多個 Axes 物件 (繪圖區)
  3. 呼叫 Axes 物件的方法建立簡單型 Artist 物件 (Line2D 與 Text 等圖表元素物件)
可見 Artist 物件是 Matplotlib 物件導向繪圖模式的基礎, 但在實務上 Axes 物件才是主角. 

首先來看 Figure 物件, 之前在進階篇中曾呼叫 pyplot.figure() 函式並傳入參數來更改預設畫布的設定 (尺寸, 解析度, 背景色, 邊框顏色與寬度等), 事實上呼叫此函式會建立一個 Figure 物件, 只是沒將傳回來的物件存起來而已, 參考下面這篇 :



1. 建立 Figure 物件 : 

在 mtplotlib.pyplot 模組中有一個 Figure 類別, 可呼叫其建構式 Figure() 來建立 Figure 物件, 但通常不這麼做, 而是呼叫 mtplotlib.pyplot 模組下的輔助函式 figure() 來建立, 因為它會根據傳入參數或預設值對 Figure 進行初始化. 

呼叫 pyplot.figure() 時不傳入參數會建立一個空白畫布並傳回一個 Figure 物件, 例如 : 

>>> import matplotlib.pyplot as plt  
>>> fig=plt.figure()                               # 以預設值建立空白畫布
>>> type(fig)             
<class 'matplotlib.figure.Figure'>          # plt.figure() 會傳回 Figure 物件 
>>> plt.show()   

上面程式碼執行後會跳出一個名為 Figure 1 的空白畫布視窗 : 




plt.figure() 函式可傳入參數以設定畫布, 語法如下 :

plt.figure([num=None, figsize=(8, 6), dpi=80, facecolor=None, edgecolor=None, frameon=True])   

常用參數說明如下 :  


 pyplot.figure() 參數 說明
 num 畫布編號 (整數) 或名稱 (字串), 不指定會自動產生
 figsize 畫布尺寸 (元組), 單位為英吋, 預設為 (寬, 高)=(6.4, 4.8)
 dpi 解析度 (整數, 每英吋之畫素個數), 預設為 80 點
 facecolor 背景色 (預設白色), 可用色彩名 'yellow' 或色碼 '#ffff00'
 edgecolor 邊框顏色 (預設白色), 可用色彩名 'yellow' 或色碼 '#ffff00'
 frameon 是否顯示邊框, 預設 True
 constrained_layout 是否限制版面配置 (布林值), 預設 False


其中較常用的參數是用來設定畫布大小的 facecolor 與 figsize, facecolor 用來設定畫布背景色; 繪製多個子圖時通常需要用 figsize 將畫布尺寸設定為比預設值要大, 這時就需要指定適當的 figsize 來容納這些子圖. 另外 constrained_layout 參數用來限制版面, 主要配合 GridSpec 物件來彈性調整版面 (此時須設為 True).  

參考 :

Figure 物件的預設值可呼叫 matplotlib.rc_params() 查詢, 例如 : 

>>> import matplotlib as mpl    
>>> mpl.rc_params()      
RcParams({'_internal.classic_mode': False,
          'agg.path.chunksize': 0,
          'animation.avconv_args': [],
          'animation.avconv_path': 'avconv',
          'animation.bitrate': -1,
          'animation.codec': 'h264',
          'animation.convert_args': [],
          'animation.convert_path': 'convert',
          'animation.embed_limit': 20.0,
          'animation.ffmpeg_args': [],
          'animation.ffmpeg_path': 'ffmpeg',
          'animation.frame_format': 'png',
          'animation.html': 'none',
          'animation.html_args': [],
          'animation.writer': 'ffmpeg',
          'axes.autolimit_mode': 'data',
          'axes.axisbelow': 'line',
          'axes.edgecolor': 'black',
          'axes.facecolor': 'white',
          'axes.formatter.limits': [-5, 6],
          'axes.formatter.min_exponent': 0,
          'axes.formatter.offset_threshold': 4,
          'axes.formatter.use_locale': False,
          'axes.formatter.use_mathtext': False,
          'axes.formatter.useoffset': True,
          'axes.grid': False,
          'axes.grid.axis': 'both',
          'axes.grid.which': 'major',
          'axes.labelcolor': 'black',
          'axes.labelpad': 4.0,
          'axes.labelsize': 'medium',
          'axes.labelweight': 'normal',
          'axes.linewidth': 0.8,
          'axes.prop_cycle': cycler('color', ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']),
          'axes.spines.bottom': True,
          'axes.spines.left': True,
          'axes.spines.right': True,
          'axes.spines.top': True,
          'axes.titlecolor': 'auto',
          'axes.titlelocation': 'center',
          'axes.titlepad': 6.0,
          'axes.titlesize': 'large',
          'axes.titleweight': 'normal',
          'axes.unicode_minus': True,
          'axes.xmargin': 0.05,
          'axes.ymargin': 0.05,
          'axes3d.grid': True,
          'backend': 'TkAgg',
          'backend_fallback': True,
          'boxplot.bootstrap': None,
          'boxplot.boxprops.color': 'black',
          'boxplot.boxprops.linestyle': '-',
          'boxplot.boxprops.linewidth': 1.0,
          'boxplot.capprops.color': 'black',
          'boxplot.capprops.linestyle': '-',
          'boxplot.capprops.linewidth': 1.0,
          'boxplot.flierprops.color': 'black',
          'boxplot.flierprops.linestyle': 'none',
          'boxplot.flierprops.linewidth': 1.0,
          'boxplot.flierprops.marker': 'o',
          'boxplot.flierprops.markeredgecolor': 'black',
          'boxplot.flierprops.markeredgewidth': 1.0,
          'boxplot.flierprops.markerfacecolor': 'none',
          'boxplot.flierprops.markersize': 6.0,
          'boxplot.meanline': False,
          'boxplot.meanprops.color': 'C2',
          'boxplot.meanprops.linestyle': '--',
          'boxplot.meanprops.linewidth': 1.0,
          'boxplot.meanprops.marker': '^',
          'boxplot.meanprops.markeredgecolor': 'C2',
          'boxplot.meanprops.markerfacecolor': 'C2',
          'boxplot.meanprops.markersize': 6.0,
          'boxplot.medianprops.color': 'C1',
          'boxplot.medianprops.linestyle': '-',
          'boxplot.medianprops.linewidth': 1.0,
          'boxplot.notch': False,
          'boxplot.patchartist': False,
          'boxplot.showbox': True,
          'boxplot.showcaps': True,
          'boxplot.showfliers': True,
          'boxplot.showmeans': False,
          'boxplot.vertical': True,
          'boxplot.whiskerprops.color': 'black',
          'boxplot.whiskerprops.linestyle': '-',
          'boxplot.whiskerprops.linewidth': 1.0,
          'boxplot.whiskers': 1.5,
          'contour.corner_mask': True,
          'contour.negative_linestyle': 'dashed',
          'datapath': 'C:\\Python37\\lib\\site-packages\\matplotlib\\mpl-data',
          'date.autoformatter.day': '%Y-%m-%d',
          'date.autoformatter.hour': '%m-%d %H',
          'date.autoformatter.microsecond': '%M:%S.%f',
          'date.autoformatter.minute': '%d %H:%M',
          'date.autoformatter.month': '%Y-%m',
          'date.autoformatter.second': '%H:%M:%S',
          'date.autoformatter.year': '%Y',
          'docstring.hardcopy': False,
          'errorbar.capsize': 0.0,
          'figure.autolayout': False,
          'figure.constrained_layout.h_pad': 0.04167,
          'figure.constrained_layout.hspace': 0.02,
          'figure.constrained_layout.use': False,
          'figure.constrained_layout.w_pad': 0.04167,
          'figure.constrained_layout.wspace': 0.02,
          'figure.dpi': 100.0,
          'figure.edgecolor': 'white',
          'figure.facecolor': 'white',
          'figure.figsize': [6.4, 4.8],
          'figure.frameon': True,
          'figure.max_open_warning': 20,
          'figure.subplot.bottom': 0.11,
          'figure.subplot.hspace': 0.2,
          'figure.subplot.left': 0.125,
          'figure.subplot.right': 0.9,
          'figure.subplot.top': 0.88,
          'figure.subplot.wsp…

其中藍色字部分就是 Figure 物件的預設值. 也可以呼叫 pyplot.rcParams 類別的靜態方法 items() 查詢 : 

>>> dir(plt.rcParams)    
['_MutableMapping__marker', '__abstractmethods__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', 'clear', 'copy', 'find_all', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'validate', 'values']

因為 items() 會傳回全部配置參數與其預設值鍵值對, 其長度很長, 可以用 stratswith() 方法來過濾 以 'figure' 開頭的鍵, 例如 :

>>> items=plt.rcParams.items()    
>>> for k, v in items:    
    if (k.startswith('figure')):     # 只顯示以 'figure' 開頭的鍵
        print(k, ':', v)     
    
figure.autolayout : False
figure.constrained_layout.h_pad : 0.04167
figure.constrained_layout.hspace : 0.02
figure.constrained_layout.use : False
figure.constrained_layout.w_pad : 0.04167
figure.constrained_layout.wspace : 0.02
figure.dpi : 100.0
figure.edgecolor : white
figure.facecolor : white
figure.figsize : [6.4, 4.8]
figure.frameon : True
figure.max_open_warning : 20
figure.subplot.bottom : 0.11
figure.subplot.hspace : 0.2
figure.subplot.left : 0.125
figure.subplot.right : 0.9
figure.subplot.top : 0.88
figure.subplot.wspace : 0.2
figure.titlesize : large
figure.titleweight : normal

呼叫 plt.figure() 時傳入參數可更改這些預設之畫布屬性, 下面的範例會建立兩個 Figure 物件, 一個是不傳入參數建立預設 Figure 物件, 另一個則是傳入參數建立自訂的 Figure 物件, 例如 :

>>> import matplotlib.pyplot as plt   
>>> import numpy as np  
>>> fig1=plt.figure()                              # 建立預設的 Figure 物件
>>> x=np.linspace(0, 2*np.pi, 360)       
>>> y=np.sin(x)      
>>> plt.plot(x, y, 'b-', lw=2)                   # 在預設的 Figure 物件內繪圖
[<matplotlib.lines.Line2D object at 0x000001F9F8BB65F8>]
>>> fig2=plt.figure('fig2', figsize=(8, 6), dpi=72, facecolor='#FFD700', edgecolor='blue')         
>>> plt.plot(x, y, 'g-', lw=2)                   # 在自訂的 Figure 物件內繪圖
[<matplotlib.lines.Line2D object at 0x000001F9F8BFC2B0>]
>>> plt.grid() 
>>> plt.show()  

此例先呼叫 plt.figure() 建立一個預設的 Figure 物件 fig1 (畫布 1), 然後呼叫 plt.plot() 在此 Figure 物件上繪圖; 接著再次呼叫 plt.figure() 並傳入參數建立一個自訂的 Figure 物件 fig2 (畫布 2), 這時呼叫 plt.plot() 就會在自訂的畫布 fig2 上繪圖, 而不是在原先的 fig1 上. 執行後會出現兩個繪圖視窗, 分別代表畫布 1 與畫布 2 :




可見左邊的預設 Figure 物件預設的標題是自動編號的 Figure 1, 而右邊自訂的 Figure 物件之標題是傳入的 'fig2', 但可能會讓人覺得奇怪的是, 右邊 fig2 的尺寸被指定為 (8, 6) 吋, 為何會比左邊預設的預設的 (6.4, 4.8) 吋小? 原因是 fig2 的 dpi 比較小 (72), 而左邊 fig1 預設為 dpi=100, 若將 fig2 的 dpi 改為預設的 100 就會比 fig1 大了. 

要注意的是, 此例呼叫兩次 plt.plot(), 第一次是在建立 fig1 物件後, 圖形會繪製在 fig1 上; 第二次是在建立 fig2 物件後, 圖形會繪製在 fig2 上, 呼叫 plt.plot() 時 Matplotlib 會依據內部狀態來控制畫在哪塊畫布上 (即 Matlab 風格的 stateful 機制). 但真正的物件導向用法是透過 Axes 物件的 plot() 繪圖, Axes 物件提供更多微調功能. 

完整原始碼參考 :

測試 : 建立預設與自訂的 Figure 物件 [看原始碼]


2. Figure 物件的成員 : 

在進入純物件導向用法之前, 先來檢視 Figure 物件的內容有哪些公開成員, 這可以透過內建函式 dir() 與 eval() 來一窺堂奧 : 

>>> import matplotlib.pyplot as plt   
>>> fig=plt.Figure()   
>>> members=dir(fig)    
>>> for mbr in members:    
    obj=eval('fig.' + mbr)    
    if not mbr.startswith('_'):    
        print(mbr, type(obj))   
        
add_artist <class 'method'>
add_axes <class 'method'>
add_axobserver <class 'method'>
add_callback <class 'method'>
add_gridspec <class 'method'>
add_subplot <class 'method'>
align_labels <class 'method'>
align_xlabels <class 'method'>
align_ylabels <class 'method'>
matplotlib_table_5_5.py:1: MatplotlibDeprecationWarning: 
The aname attribute was deprecated in Matplotlib 3.1 and will be removed in 3.3.
  import matplotlib.pyplot as plt
aname <class 'str'>
artists <class 'list'>
autofmt_xdate <class 'method'>
axes <class 'list'>
bbox <class 'matplotlib.transforms.TransformedBbox'>
bbox_inches <class 'matplotlib.transforms.Bbox'>
callbacks <class 'matplotlib.cbook.CallbackRegistry'>
canvas <class 'matplotlib.backends.backend_tkagg.FigureCanvasTkAgg'>
clear <class 'method'>
clf <class 'method'>
clipbox <class 'NoneType'>
colorbar <class 'method'>
contains <class 'method'>
convert_xunits <class 'method'>
convert_yunits <class 'method'>
delaxes <class 'method'>
dpi <class 'float'>
dpi_scale_trans <class 'matplotlib.transforms.Affine2D'>
draw <class 'method'>
draw_artist <class 'method'>
eventson <class 'bool'>
execute_constrained_layout <class 'method'>
figimage <class 'method'>
figure <class 'NoneType'>
findobj <class 'method'>
format_cursor_data <class 'method'>
frameon <class 'bool'>
gca <class 'method'>
get_agg_filter <class 'method'>
get_alpha <class 'method'>
get_animated <class 'method'>
get_axes <class 'method'>
get_children <class 'method'>
get_clip_box <class 'method'>
get_clip_on <class 'method'>
get_clip_path <class 'method'>
get_constrained_layout <class 'method'>
get_constrained_layout_pads <class 'method'>
get_contains <class 'method'>
get_cursor_data <class 'method'>
get_default_bbox_extra_artists <class 'method'>
get_dpi <class 'method'>
get_edgecolor <class 'method'>
get_facecolor <class 'method'>
get_figheight <class 'method'>
get_figure <class 'method'>
get_figwidth <class 'method'>
get_frameon <class 'method'>
get_gid <class 'method'>
get_in_layout <class 'method'>
get_label <class 'method'>
get_path_effects <class 'method'>
get_picker <class 'method'>
get_rasterized <class 'method'>
get_size_inches <class 'method'>
get_sketch_params <class 'method'>
get_snap <class 'method'>
get_tight_layout <class 'method'>
get_tightbbox <class 'method'>
get_transform <class 'method'>
get_transformed_clip_path_and_affine <class 'method'>
get_url <class 'method'>
get_visible <class 'method'>
get_window_extent <class 'method'>
get_zorder <class 'method'>
ginput <class 'method'>
have_units <class 'method'>
images <class 'list'>
init_layoutbox <class 'method'>
is_transform_set <class 'method'>
legend <class 'method'>
legends <class 'list'>
lines <class 'list'>
mouseover <class 'bool'>
number <class 'int'>
patch <class 'matplotlib.patches.Rectangle'>
patches <class 'list'>
pchanged <class 'method'>
pick <class 'method'>
pickable <class 'method'>
properties <class 'method'>
remove <class 'method'>
remove_callback <class 'method'>
savefig <class 'method'>
sca <class 'method'>
set <class 'method'>
set_agg_filter <class 'method'>
set_alpha <class 'method'>
set_animated <class 'method'>
set_canvas <class 'method'>
set_clip_box <class 'method'>
set_clip_on <class 'method'>
set_clip_path <class 'method'>
set_constrained_layout <class 'method'>
set_constrained_layout_pads <class 'method'>
set_contains <class 'method'>
set_dpi <class 'method'>
set_edgecolor <class 'method'>
set_facecolor <class 'method'>
set_figheight <class 'method'>
set_figure <class 'method'>
set_figwidth <class 'method'>
set_frameon <class 'method'>
set_gid <class 'method'>
set_in_layout <class 'method'>
set_label <class 'method'>
set_path_effects <class 'method'>
set_picker <class 'method'>
set_rasterized <class 'method'>
set_size_inches <class 'method'>
set_sketch_params <class 'method'>
set_snap <class 'method'>
set_tight_layout <class 'method'>
set_transform <class 'method'> 
set_url <class 'method'>
set_visible <class 'method'>
set_zorder <class 'method'>
show <class 'method'>
stale <class 'bool'>
stale_callback <class 'function'>
sticky_edges <class 'matplotlib.artist._XYPair'>
subplotpars <class 'matplotlib.figure.SubplotParams'>
subplots <class 'method'>
subplots_adjust <class 'method'>
suppressComposite <class 'NoneType'>
suptitle <class 'method'>
text <class 'method'>
texts <class 'list'>
tight_layout <class 'method'>
transFigure <class 'matplotlib.transforms.BboxTransformTo'>
update <class 'method'>
update_from <class 'method'>
waitforbuttonpress <class 'method'>
zorder <class 'int'>

可見 Figure 物件提供了非常豐富的屬性與方法, 常用屬性如下表 :


 Figure 物件常用屬性 說明
 axes 儲存畫布中的 Axes 物件之串列 
 lines 儲存畫布中的 Line2D 物件之串列
 images 儲存畫布中的 FigureImage 物件 (圖片) 之串列
 legends 儲存畫布中的 Legend 物件 (圖例) 之串列
 texts 儲存畫布中的 Text 物件 (註解文字) 之串列
 patch 儲存畫布中的 Rectangle 物件 (背景)
 patches 儲存畫布中的 Patch 物件之串列

 
常用方法如下表 : 


 Figure 物件常用方法 說明
 add_axes([x, y, w, h]) 新增一個繪圖區, 傳回一個 Axes 物件
 add_subplot(nrow, ncol, index) 新增 nrow*ncol 網格中索引為 index 的 SubplotAxes 物件
 delaxes([ax]) 刪除指定之 Axes 物件 ax, 若未傳入 ax 則刪除目前之 Axes
 set_tight_layout(True) 設定為緊湊版面與否 (預設 False), 緊湊版面可避免標題被裁切
 tight_layout() 設定為緊湊版面, 功能與 set_tight_layout(True) 相同
 set_facecolor(color) 設定畫布背景色為 color, 例如 'red', '#ff0000', (1, 0, 0)
 set_size_inches(width, height) 設定畫布尺寸為寬 width, 高 height (均為英吋)
 gca() 傳回目前的 Axes 物件
 subplots_adjust(wspace, hspace) 設定子圖間的水平 (wspace) 與垂直 (hspace) 間距, 相對於子圖寬與高
 add_gridspec(nrows, ncols) 傳回一個 GridSpec 物件來做彈性版面配置


其中最常用的方法是 add_axes()  與 add_subplots(), 它們都會在 Figure 畫布中建立一個擁有獨立坐標系的繪圖區 (或稱為子圖), 這兩個方法也是 Matplotlib 物件導向繪圖的核心, 利用 Figure 物件與這兩個方法傳回的 Axes 物件繪圖才算是真正物件導向模式用法. 


3. 建立 Axes 物件 : 

呼叫 Figure 物件的 add_axes()  方法會傳回一個 Axes 物件, 一個 Figure 物件內可以建立不限個數的 Axes 物件, 亦即一個畫布上可以擁有多個擁有各自獨立坐標系的繪圖區, Figure 物件有一個 axes 方法用來管理畫布內的 Axes 物件, 每建立一個 Axes 物件, 其參考會被放進 axes 屬性字串中, Figure 物件會記錄目前繪圖區狀態 (其中只有一個是 active). 使用 Matlab 模式繪圖時, 呼叫 plt.plot() 就是將圖形繪製到此作用中的 Axes 物件上, 呼叫 Figure 物件的 gca() 方法可取得目前作用中的 Axes 物件參考. 

Figure 物件的 add_axes() 方法的語法如下 : 

ax=fig.add_axes(rect [, projection=None, polar=False])      

參數說明如下 : 
  • rect : 必要參數, 用來定位繪圖區左下角座標位置與尺寸的串列 [left, bottom, width, height] 或元組 (left, bottom, width, height), 其值均為 0~1 的浮點數 (相對於 Figure 的尺寸), 其中 left 與 bottom 為繪圖區左下角座標位置  (Figure 畫布的原點是在左下角), 而 width 與 height 則 表示繪圖區的寬度與高度. 例如 [0.1, 0.1, 0.8, 0.8] 表示此繪圖區左下角座標為畫布左下角向上向右的 10% 位置, 而其寬度與高度是畫布的 80%. 
  • projection : 備選參數 (字串), 用來指定坐標系投影模式.
  • polar : 是否使用極座標, 預設 False 表示使用直角坐標系. 
必要參數 rect 名稱可指定也可不指定 (即也是關鍵字參數), 傳入 [0.1, 0.1,0.8, 0.8] 或 rect=[0.1, 0.1,0.8, 0.8] 均可. 注意, 呼叫 add_axes() 不傳入參數雖然不會出現錯誤, 但不會建立 Axes 物件, 傳回 None 而非 Axes 物件, 且 Figure 物件的 axes 屬性串列為空, 故呼叫 ax.plot() 會出現 AttributeError 錯誤 (無 plot 方法), 總之, 呼叫 add_axes() 時一定要傳 rect 串列進去. 

可用的坐標系投影模式可透過呼叫 Matplotlib 的 projections 模組下的 get_projection_names() 函式來查詢 :

>>> from matplotlib import projections  
>>> projections.get_projection_names()   
['3d', 'aitoff', 'hammer', 'lambert', 'mollweide', 'polar', 'rectilinear']    
 
其中  'aitoff', 'hammer', 'lambert', 'mollweide' 屬於地圖投影模式; 'polar' 為極座標投影模式; 而 'rectilinear' 則為直線投影模式 (projection 預設值 None 表示使用此模式). 例如在繪製 3D 立體圖時就會使用 '3d' 模式, 使用極座標繪圖時則傳入 'polar', 且 polar 參數要設為 True. 

參考 : 


呼叫 fig.add_axes() 建立一個 Axes 物件後就可呼叫其 plot() 方法並傳入資料 (串列或 Numpy 陣列) 即可在 Axes 物件上繪圖, 語法與 plt.plot() 相同 : 

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

其中 x1, y1, x2, y2, .... 是成對的 X, Y 軸資料, 如果只傳入一對表示只畫一個圖形, 兩對就是兩個圖形, ....  X 軸資料可有可無的 , 未傳入時預設是 0, 1, 2, .... 

參考 : 


如果是在 Colab 或 Jupyter Notbook 下有使用神奇指令 %matplotlib inline, 則呼叫 ax.plot() 後就會在儲存格中繪製圖形; 但如果是在命令列環境下, 則必須再呼叫 plt.show() 才會顯示圖形, 例如 : 

>>> import matplotlib.pyplot as plt   
>>> fig=plt.figure()     
>>> ax=fig.add_axes([0, 0, 1, 1])                # 繪圖區占滿整個畫布
>>> type(ax)     
<class 'matplotlib.axes._axes.Axes'>            # add_axes() 傳回 Axes 物件
>>> fig.axes                                                  # Axes 物件串列
[<matplotlib.axes._axes.Axes object at 0x000001A077B4A048>]    
>>> fig.gca()    
<matplotlib.axes._axes.Axes object at 0x000001A077B4A048>   
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])        #  傳入串列繪製折線圖   
[<matplotlib.lines.Line2D object at 0x000001A077B37940>]
>>> plt.show()    

此例用 rect=[0, 0, 1, 1] 滿版配置傳入 add_axes() 建立繪圖區, 產生的 Axes 物件參考也會被記錄在 Figure 物件的 axes 屬性中, 可見 axes 屬性串列中目前只有一個 Axes 物件, 呼叫其 gca() 方法也顯示該物件就是作用中的 Axes 物件. 結果如下 : 




可見滿版配置只會顯示圖形 (即 Line2D 物件), 坐標軸 (即 Axis 物件) 消失了. 

完整原始碼參考 :

測試 : 呼叫 fig.add_axes() 建立 Axes 物件(1) [看原始碼]


若要完整顯示座標軸, 通常會使用較小的 rect 來放置 Axes 繪圖區, 例如 [0.1, 0.1, 0.8, 0.8] 以保留上下左右 10% 的餘裕空間來顯示座標軸, 例如 :

>>> import matplotlib.pyplot as plt  
>>> fig=plt.figure()    
>>> ax=fig.add_axes([0.1, 0.1, 0.8, 0.8])      # 保留 10% 餘裕顯示座標軸與標題
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])          #  傳入串列繪製折線圖 
[<matplotlib.lines.Line2D object at 0x000001A074D59198>]   
>>> ax.set_xlabel('X')            # 設定繪圖區的 X 軸標籤
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y')            # 設定繪圖區的 Y 軸標籤
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo')        # 設定繪圖區的標題
Text(0.5, 1.0, 'Demo')    
>>> plt.show()      

此例呼叫了 Axes 物件的 set_xlabel(), set_ylabel(), 以及 set_title() 方法分別設定子圖的 X, Y 軸標籤與圖形標題, 結果如下 : 




可見只要留足夠的餘裕就能顯示整個 Axes 物件的內容, 如果只繪製一個子圖, 則 rec=[0.1, 0.1, 0.8, 0.8] 的繪圖區尺寸就足夠容納標題與座標軸的顯示了. 

完整原始碼參考 :

測試 : 呼叫 fig.add_axes() 建立 Axes 物件(2) [看原始碼]


透過版面安排可以在一個畫布上製作子母疊圖效果, 例如 : 

>>> import matplotlib.pyplot as plt  
>>> import numpy as np 
>>> fig=plt.figure(figsize=[10, 8])                  # 建立 10*8 吋之畫布
>>> x=np.linspace(0, 2*np.pi, 360)                # X 軸陣列  0~2pi
>>> ax1=fig.add_axes([0.1, 0.1, 0.8, 0.8])      # 建立 Axes 物件 ax1
>>> ax1.plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001A001939A20>]
>>> ax2=fig.add_axes([0.55, 0.55, 0.3, 0.3])      # 建立 Axes 物件 ax2
>>> ax2.plot(x, np.cos(x))    
[<matplotlib.lines.Line2D object at 0x000001A00100A0F0>]
>>> ax1.set_title('Function of Sin(x)')      
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax2.set_title('Function of Cos(x)')         
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()    

此例先建立指定為 10*8 吋的畫布物件 fig1, 然後呼叫 fig1.add_axes() 在畫布上建立兩個 Axes 物件 ax1 與 ax2, 前者原點座標 [0.1, 0.1], 長寬大小均為 0.8; 後者原點座標在 [0.55,0.55], 長寬為 0.3, 結果如下 : 




由於 ax1 尺寸較大, ax2 較小, 所以疊加起來像是子母圖形. 

完整原始碼參考 :

測試 : 呼叫 fig.add_axes() 建立 Axes 物件(3) [看原始碼]


其實只要版面設計得宜 (不要有覆蓋現象), fig.add_axes() 也可以繪製多子圖效果, 例如 : 

>>> import matplotlib.pyplot as plt  
>>> import numpy as np 
>>> x=np.linspace(0, 10, 100)      # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()   
>>> ax1=fig.add_axes([0.1, 0.6, 0.8, 0.3])    # 上方的 Axes 物件
>>> ax2=fig.add_axes([0.1, 0.1, 0.8, 0.3])    # 下方的 Axes 物件
>>> ax1.plot(x, np.sin(x))   
[<matplotlib.lines.Line2D object at 0x000001A001445390>]
>>> ax2.plot(x, np.cos(x))    
[<matplotlib.lines.Line2D object at 0x000001A001445518>]
>>> ax1.set_title('Function of Sin(x)')    
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax2.set_title('Function of Cos(x)')    
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()   

此例以畫布中央水平線 y=0.5 為界將版面分成上下兩塊, 上面用來放 ax1 物件; 下面用來放 ax2 物件, 分別在這兩個 Axes 物件上繪製 sin(x) 與 cos(x) 圖形, 結果如下 : 




完整原始碼參考 :

測試 : 呼叫 fig.add_axes() 建立 Axes 物件(4) [看原始碼]


如果覺得要自行分割版面很麻煩, 那就改用 Figure 物件的 add_subplot() 方法. 

呼叫 Figure 物件的 add_subplots() 方法並傳入子圖網格配置參數會傳回一個 SubplotAxes 物件, 此乃 Axes 的衍生物件, 因此其參考也會被記錄在 Figure 物件的 axes 屬性中, 與 Axes 物件一樣納入狀態管理, 且同樣有 plot() 方法可在所建立的子圖上繪圖, 其常用語法如下 : 

ax=fig.add_subplot([nrows, ncols, index, projection=None, polar=False])   

參數說明如下 :
  • nrows 與 ncols 表示子圖網格的列數與欄數, 而 index 是則是子圖索引 (1 起始)
  • projection : 坐標系投影模式 (字串), 見上面 add_axes() 說明
  • ploar : 是否使用極座標系繪圖
此外還有另一種語法 : 

ax=fig.add_subplot([pos, projection=None, polar=False])   

參數 pos 其實就是將 nrows, ncols, 與 index 這三個參數黏合成的 3 位數的數值, 例如 add_subplot(3, 2, 2) 可以寫成 add_sunplot(322), 參考 :


另外, add_subplot() 還可以傳入一個 GridSpec 物件來進行彈性或複雜的版面配置, 參考 :


注意, add_subplot() 的參數都是可有可無的, 不傳入參數的話, 預設會建立一個 111 的子圖 (1 列 1 行的第 1 個子圖), 例如 : 

>>> import matplotlib.pyplot as plt    
>>> fig=plt.figure()   
>>> ax=fig.add_subplot()     # 未傳入參數, 預設 rect=111 (只有一個子圖)
>>> type(ax)     
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])        #  傳入串列繪製折線圖 
[<matplotlib.lines.Line2D object at 0x000001A077871400>]  
>>> fig.axes         # AxesSubplot 物件的參考也會放進 axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A0777B7F28>]
>>> ax.set_xlabel('X')        # 設定子圖的 X 軸標籤
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y')        # 設定子圖的 Y 軸標籤
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo')    # 設定子圖的標題  
Text(0.5, 1.0, 'Demo')
>>> plt.show()    

此例在呼叫 fig.add_subplot() 時未傳入參數, 預設是建立一個 1*1 的子圖網格, 亦即畫布中只有一個子圖, 結果與上面用 fig.add_axes() 所繪製的圖一樣 : 




完整原始碼參考 :

測試 : 呼叫 fig.add_subplot() 建立 AxesSubplot 物件(1) [看原始碼]


fig.add_subplot() 主要是用在繪製多子圖情況, 它會自動將各子圖放在網格中, 如果使用 fig.add_axes() 來繪製多子圖則需要為各子圖在畫布中進行人工排版. 多子圖範例如下 : 

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 10, 100)      # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()   
>>> ax1=fig.add_subplot(211)     # 在 2*1 網格的索引 1 位置新增一個子圖 ax1
>>> type(ax1)      # 傳回之物件類型為 AxesSubplot 物件 (Axes 之子類別物件)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> fig.axes         # ax1 物件參考被記錄在 fig.axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E57C88>]
>>> ax1.plot(x, np.sin(x))              # 在 ax1 子圖上繪製 sin 正弦函數
[<matplotlib.lines.Line2D object at 0x000001A078E9B080>]
>>> ax1.set_title('Sin function')     
Text(0.5, 1.0, 'Sin function')
>>> ax2=fig.add_subplot(212)     # 在 2*1 網格的索引 2 位置新增一個子圖 ax2
>>> ax2.plot(x, np.cos(x))             # 在 ax2 子圖上繪製 cos 餘弦函數
[<matplotlib.lines.Line2D object at 0x000001A078EC6668>]
>>> ax2.set_title('Cos function')     
Text(0.5, 1.0, 'Cos function')
>>> type(ax2)     # 傳回之物件類型為 AxesSubplot 物件 (Axes 之子類別物件)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> fig.axes        # ax1, ax2 物件參考都被記錄在 fig.axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E57C88>, <matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E88FD0>]
>>> plt.show()    

此例先用 Numpy 的 linsspace() 函式在 [0, 10] 區間建立 100 個等間隔點之陣列做為 X 軸資料點, 然後呼叫兩次 fig.add_subplot() 在畫布的 2*1 網格版面中依序建立 ax1 與 ax2 子圖, 然後在子圖內分別繪製 sin(x) 與 cos(x) 函式圖形, 結果如下 : 




完整原始碼參考 :

測試 : 呼叫 fig.add_subplot() 建立 AxesSubplot 物件(2) [看原始碼]


上例中, 下方子圖的標題會與上方子圖的 X 軸刻度標籤有部分重疊現象, 這只要在呼叫 plt.show() 之前先呼叫 plt.tight_layout() 令版面緊密即可解決 :

>>> plt.tight_layout()   
>>> plt.show()    

結果如下 : 




完整原始碼參考 :

測試 : 呼叫 fig.add_subplot() 建立 AxesSubplot 物件(3) [看原始碼]


另一個方式是使用 Figure 物件的 subplots_adjust(wspace, hspace) 方法, 此方法其實還有用來設定子圖位置的 left, right, top, bottom 等參數, 但較常用的是用來設定子圖間距的 hspace (垂直) 與 wspace (水平), 其值為 0~1, 是相對於子圖尺寸, 例如 :

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 10, 100)       # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()   
>>> fig.subplots_adjust(0.2,  0.2)     # 設定子圖間距為畫布尺寸的 0.2
>>> ax1=fig.add_subplot(211)      # 在 2*1 網格的索引 1 位置新增一個子圖 ax1
>>> ax1.plot(x, np.sin(x))              # 在 ax1 子圖上繪製 sin 正弦函數
[<matplotlib.lines.Line2D object at 0x000001A078E9B080>]
>>> ax1.set_title('Sin function')     
Text(0.5, 1.0, 'Sin function')
>>> ax2=fig.add_subplot(212)      # 在 2*1 網格的索引 2 位置新增一個子圖 ax2
>>> ax2.plot(x, np.cos(x))              # 在 ax2 子圖上繪製 cos 餘弦函數
[<matplotlib.lines.Line2D object at 0x000001A078EC6668>]
>>> ax2.set_title('Cos function')  
>>> plt.show() 

效果與上面使用 plt.tight_layout() 一樣 (但更有彈性). 

完整原始碼參考 :

測試 : 呼叫 fig.add_subplot() 建立 AxesSubplot 物件(4) [看原始碼]


如果網格中部分儲存格沒有建立 AxesSubplot 物件, 則該儲存格會空白; 有建立 AxesSubplot 物件但卻沒呼叫 plot() 函式則會僅顯示座標軸沒有圖形, 例如 : 

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 2*np.pi)                # X 軸陣列  0~2pi
>>> fig=plt.figure()    
>>> ax1=fig.add_subplot(231)   
>>> ax1.plot(x, np.sin(x))       
[<matplotlib.lines.Line2D object at 0x000001301FD885C0>]
>>> ax1.set_title('Sin(x)')      
Text(0.5, 1.0, 'Sin(x)')
>>> ax2=fig.add_subplot(235)      
>>> ax2.plot(x, np.cos(x))    
[<matplotlib.lines.Line2D object at 0x000001301EA096D8>]
>>> ax2.set_title('Cos(x)')      
Text(0.5, 1.0, 'Cos(x)')
>>> ax3=fig.add_subplot(236)      
>>> ax3.plot(x, np.tan(x))      
[<matplotlib.lines.Line2D object at 0x000001301FD88550>]
>>> ax3.set_title('Tan(x)')     
Text(0.5, 1.0, 'Tan(x)')
>>> ax4=fig.add_subplot(233)     
>>> plt.show()    

此例有 2*3 共六個網格, 但只在 231, 233, 235, 236 四個儲存格有建立子圖物件, 其餘儲存格都是空白, 其中 233 只建立子圖物件卻沒呼叫 plot() 繪圖, 故只有顯示座標軸而無圖形, 結果如下 : 




完整原始碼參考 :

測試 : 呼叫 fig.add_subplot() 建立 AxesSubplot 物件(5) [看原始碼]


4. Axes 與 AxesSubplot 物件的成員 : 

以上使用 Axes 物件與其衍生物件 AxesSubplot 的 plot() 方法繪圖, 並呼叫 set_xlabel() 與 set_ylabel() 來設定 X, Y 軸標籤, 事實上 Axes 與 AxesSubplot 物件提供了非常豐富的繪圖方法可對圖形進行設定或微調. 

以下使用一個自訂模組 members 的 list_members() 函式來檢視模組或套件中的公開成員 (即屬性與方法), 參考 :

Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法 

>>> import matplotlib.pyplot as plt  
>>> import members   
>>> fig=plt.figure()   
>>> ax=fig.add_axes([0.1, 0.1,0.8, 0.8])    
>>> members.list_members(mpf)    
acorr <class 'method'>
add_artist <class 'method'>
add_callback <class 'method'>
add_child_axes <class 'method'>
add_collection <class 'method'>
add_container <class 'method'>
add_image <class 'method'>
add_line <class 'method'>
add_patch <class 'method'>
add_table <class 'method'>
aname <class 'str'>
angle_spectrum <class 'method'>
annotate <class 'method'>
apply_aspect <class 'method'>
arrow <class 'method'>
artists <class 'list'>
autoscale <class 'method'>
autoscale_view <class 'method'>
axes <class 'matplotlib.axes._axes.Axes'>
axhline <class 'method'>
axhspan <class 'method'>
axis <class 'method'>
.... (略) ....
twinx <class 'method'>
twiny <class 'method'>
update <class 'method'>
update_datalim <class 'method'>
update_datalim_bounds <class 'method'>
update_from <class 'method'>
use_sticky_edges <class 'bool'>
viewLim <class 'matplotlib.transforms.Bbox'>
violin <class 'method'>
violinplot <class 'method'>
vlines <class 'method'>
xaxis <class 'matplotlib.axis.XAxis'>
xaxis_date <class 'method'>
xaxis_inverted <class 'method'>
xcorr <class 'method'>
yaxis <class 'matplotlib.axis.YAxis'>
yaxis_date <class 'method'>
yaxis_inverted <class 'method'>
zorder <class 'int'>

由於 Axes 物件的公開成員多達 303 個, 所以上面僅列出頭尾, 完整的成員列表參考 :


而呼叫 fig.add_subplot(), plt.axes(), 以及 plt.subplots() 所傳回 AxesSubplot 物件因為是 Axes 物件的衍生物件, 所以除繼承 Axes 外還添加新的成員, 總計有 319 個, 參考 : 


其中較常用的方法如下表 :


 Axes 物件常用方法 說明
 ax.set_title(str) 設定圖形標題 str (字串)
 ax.set_xlabel(str) 設定 X 軸標籤 str (字串)
 ax.set_ylabel(str) 設定 Y 軸標籤 str (字串)
 ax.set_xlim([xmin, xmax]) 設定 X 軸範圍為 [xmin, xmax]
 ax.set_ylim([ymin, ymax]) 設定 Y 軸範圍為 [ymin, ymax]
 ax.set_xticks(ticks, labels) 設定 X 軸刻度, ticks=刻度值串列, labels=刻度標籤串列
 ax.set_yticks(ticks, labels) 設定 Y 軸刻度, ticks=刻度值串列, labels=刻度標籤串列
 ax.legend(loc) 設定圖例位置, loc=0 (最佳位置) ~10
 ax.grid() 顯示網格格線
 ax.plot(x1, y1, x2, y2,...) 繪製 (x1, y1), (x2, y2), ... 等多個函數圖形
 ax.plot(x1, y1, x2, y2,...) 繪製 (x1, y1), (x2, y2), ... 等多個函數圖形
 ax.set(kargs) 綜合設定以上之全部繪圖區屬性
 ax.plot(x1, y1, x2, y2,...) 繪製 (x1, y1), (x2, y2), ... 等多個函數圖形


可見與 Matlab 風格寫法的差別主要是很多方法的名稱前面多了 "set_", 例如 plt.title() 要改用 ax.set_title() 等, 但 plot(), legend(), grid() 以及統計圖函式如 bar(), scatter() 等函式除外 (前面不須加 'set_').

pyploy 函式與 Figure/Axes 物件常用方法的對照如下表 :


 pyplot 函式 Figure/Axes 物件方法
 plt.title() ax.set_title()
 plt.xlabel() ax.set_xlabel()
 plt.ylabel() ax.set_ylabel()
 plt.xlim() ax.set_xlim()
 plt.ylim() ax.set_ylim()
 plt.xticks() ax.set_xticks()
 plt.yticks() ax.set_yticks()
 plt.xticklabels() ax.set_xticklabels()
 plt.yticklabels() ax.set_yticklabels()
 plt.text() ax.set_text()
 plt.grid() ax.grid()
 無 ax.xasix.set_visible(False)
 plt.plot() ax.plot()
 plt.figure(figuresize) fig.set_size_inches()
 plt.tight_layout() fig.tight_layout()
 plt.legend() ax.legend()
 plt.scatter() ax.scatter()
 plt.bar() ax.bar()
 plt.barh() ax.barh()


三. 用 pyplot 模組的函式進行物件導向繪圖 : 

Matplotlib 物件導向繪圖除了使用 Figure 物件外, 也可以利用 pyplot 模組提供的 axes() 與 subplots() 函式來建立 Figure 與 AxesSubplot 物件. 


1. 呼叫 plt.axes() 方法建立 AxesSubplot 物件 : 

呼叫 plt.axes() 會傳回一個 AxesSubplot 物件, 常用語法如下 :

ax=plt.axes(rect [, projection=None, polar=False])

參數與上述 fig.add_axes() 用法相同, rect 是四個浮點數元素的 tuple 或 list, 用來定位繪圖區左下角在畫布中的座標位置與尺寸 [left, bottom, width, height], 其值均為 0~1. 注意, 第一參數 rect 不是關鍵字參數, 如果傳入例如 rect=[0.1, 0.1, 0.8, 0.8] 會出現 TypeError 錯誤. 參考 :


例如 : 

>>> import matplotlib.pyplot as plt 
>>> fig=plt.figure()    
>>> ax=plt.axes()           # 傳回 AxesSubplot 物件
>>> type(ax)    
<class 'matplotlib.axes._subplots.AxesSubplot'>    
>>> fig.axes  
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078F147F0>]
>>> ax.get_position()     # 傳回 Axes 物件在畫布中的座標位置
Bbox([[0.125, 0.10999999999999999], [0.9, 0.88]]) 
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])    
[<matplotlib.lines.Line2D object at 0x000001A0790A8CF8>]
>>> ax.set_xlabel('X')   
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y')   
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo of plt.axes()')    
Text(0.5, 1.0, 'Demo of plt.axes()')    
>>> plt.show()   

此例在呼叫 plt.axes() 時未傳入參數, 所建立的繪圖區 (即 AxesSubplot 物件) 預設會放在畫布座標 [0.125, 0.11] (左下角) 與 [0.9, 0.88] (右上角), 可利用 ax.get_position() 得知預設之繪圖區位置與尺寸, 結果如下 : 
   



如果傳入 rect 參數 [0.1, 0.1, 0.8, 0.8], 則呼叫 ax.position() 會傳回 Bbox([[0.1, 0.1], [0.9, 0.9]]). 

完整程式碼參考 :

測試  : 呼叫 plt.axes() 方法繪製單一子圖 [看原始碼]


如果要用 plt.axes() 繪製多子圖必須自行在畫布上用 rect 參數排版, 例如 :

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 10, 100)      # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()   
>>> ax1=plt.axes([0.1, 0.6, 0.8, 0.3])        # sin(x) 圖形放在上半部  
>>> ax1.get_position()                 # 上方繪圖區在畫布 fig 上的座標
Bbox([[0.1, 0.6], [0.9, 0.8999999999999999]])
>>> ax2=plt.axes([0.1, 0.1, 0.8, 0.3])        # cos(x) 圖形放在下半部
>>> ax2.get_position()     
Bbox([[0.1, 0.1], [0.9, 0.4]])         # 下方繪圖區在畫布 fig 上的座標
>>> ax1.plot(x, np.sin(x))    
[<matplotlib.lines.Line2D object at 0x000001A079B95BA8>]
>>> ax2.plot(x, np.cos(x))        
[<matplotlib.lines.Line2D object at 0x000001A079B95F60>]
>>> ax1.set_title('Sin(x)')    
Text(0.5, 1.0, 'Sin(x)')
>>> ax2.set_title('Cos(x)')        
Text(0.5, 1.0, 'Cos(x)')
>>> plt.show()    

此例以畫布水平中央 y=0.5 為界分成上下兩個相等版面, 上半部的繪圖區 ax1 繪製 sin(x); 下半部的繪圖區 ax2 繪製 cos(x), 結果如下 : 




可見 plt.axes() 比較適合用在繪製一個子圖的場合, 繪製多個子圖用 plt.subplots() 較方便. 

完整程式碼參考 :

測試  : 呼叫 plt.axes() 方法繪製多子圖 [看原始碼]


2. 呼叫 plt.subplots() 方法建立 Figure 與 AxesSubplot 物件 : 

以物件導向方式繪製多個子圖時可呼叫 plt.subplots() 函式, 因為其傳回值是一個包含 Figure 物件與 Axes 物件陣列的 tuple, 所以不需要用 plt.figure() 自行建立 Figure 物件, 省了一個指令, 其常用語法如下 : 

fig, ax=plt.subplots([nrows=1, ncols=1, sharex=False, sharey=False]) 

參數說明如下 :
  • nrows 與 ncols 表示子圖網格的列數與欄數, 預設都是 1 (單子圖)
  • sharex 為布林值, 表示是否共用 X 軸 (預設 False 不共用)
  • sharey 為布林值, 表示是否共用 Y 軸 (預設 False 不共用)
傳回值是一個 tuple, 第一個元素為 Figure 物件; 第二個元素為子圖 AxesSubplot 物件 (單子圖時)或 AxesSubplot 物件組成之 ndarray 陣列 (多子圖時).  

參考 :


呼叫 plt.subplots() 時不傳入參數預設就是建立 1*1 網格的單子圖, 例如 : 

>>> import matplotlib.pyplot as plt  
>>> fig, ax=plt.subplots()             # 沒有傳入參數就是建立單一子圖
>>> type(fig)     
<class 'matplotlib.figure.Figure'>   
>>> type(ax)    
<class 'matplotlib.axes._subplots.AxesSubplot'>  
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])     
[<matplotlib.lines.Line2D object at 0x000001A07B2DBA20>]
>>> ax.set_title('Demo of plt.subplots()')     
Text(0.5, 1.0, 'Demo of plt.subplots()')
>>> plt.show()    

此例於呼叫 plt.subplots() 未傳入參數, 故會建立單一子圖, 結果如下 : 




這與 plt.subplots(1, 1) 效果是一樣的. 

完整程式碼參考 :

測試  : 呼叫 plt.subplots() 方法繪製單一子圖 [看原始碼]


呼叫 plt.subplots() 並傳入網格參數繪製多子圖的範例如下 :

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 10, 100)      # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig, ax=plt.subplots(2, 1)       # 建立 2*1 網格排版繪製兩個子圖
>>> type(ax)                                  # 傳回 AxesSubplot 物件組成之 ndarray 陣列  
<class 'numpy.ndarray'>   
>>> ax      
array([<matplotlib.axes._subplots.AxesSubplot object at 0x000001A07F286198>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x000001A07F3FBB00>],
      dtype=object)  
>>> ax[0].plot(x, np.sin(x))           # ax[0] 物件上繪製 sin(x)
[<matplotlib.lines.Line2D object at 0x000001A07FC3E470>]
>>> ax[1].plot(x, np.cos(x))           # ax[1] 物件上繪製 cos(x)
[<matplotlib.lines.Line2D object at 0x000001A07FC3E5C0>]
>>> ax[0].set_title('Sin(x)')      
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax[1].set_title('Cos(x)')    
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()    

此例呼叫 plt.subplots(2, 1) 傳回的 ax 是 AxeSubplot 物件組成之 ndarray 陣列, 可用 ax[0] 與 ax[1] 來存取這兩個 AxesSubplot 物件, 然後分別呼叫 plot() 方法繪製 sin(x) 與 cos(x), 結果如下 : 




完整程式碼參考 :

測試  : 呼叫 plt.subplots() 方法繪製多子圖 (預設) [看原始碼]


可見物件導向繪圖是在明確指定的 AxesSubplot 物件上進行, 而不是像 Matlab 模式那樣依賴狀態的控制. 但在預設版面配置下, 子圖的標題可能會與其它子圖的 X 坐標軸刻度標籤部分重疊, 解決此問題的一個方法是在呼叫 plt.show() 之前先呼叫 plt.tight_layout() 將版面緊緻化 :

>>> plt.tight_layout()    # 使版面緊緻
>>> plt.show()    

結果如下 :




這樣下方子圖的標題就不會與上方子圖的 X 軸刻度重疊了. 

同樣地也可以利用 fig.subplts_adjust(wspace=0.2, hspace=0.2) 設定子圖間距來避免重疊, 參考上面 fig.add_subplot() 之範例. 


完整程式碼參考 :

測試  : 呼叫 plt.subplots() 方法繪製多子圖 (緊密版面) [看原始碼]


下面是建立垂直多子圖時傳入 sharex=True 共用 X 軸的範例 : 

>>> import matplotlib.pyplot as plt  
>>> import numpy as np   
>>> x=np.linspace(0, 10, 100)      # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig, ax=plt.subplots(2, 1, sharex=True     # 兩個子圖共用 X 軸 
>>> ax[0].plot(x, np.sin(x))        
[<matplotlib.lines.Line2D object at 0x000001A001D659B0>]
>>> ax[1].plot(x, np.cos(x))    
[<matplotlib.lines.Line2D object at 0x000001A001D65DA0>]
>>> ax[0].set_title('Sin(x)')   
Text(0.5, 1.0, 'Sin(x)')
>>> ax[1].set_title('Cos(x)')     
Text(0.5, 1.0, 'Cos(x)')
>>> plt.show()   

此例在呼叫 plt.subplots() 時傳入 sharex=True 參數以共用 X 軸, 結果如下 :



 
共用 x 軸因為上方的子圖不會顯示刻度標籤, 不會有與下方子圖之標題重疊情形, 因此通常不需要呼叫 plt.tight_layout() 讓圖形緊密. 

完整程式碼參考 :

測試  : 呼叫 plt.subplots() 方法繪製多子圖 (共用 X 軸) [看原始碼]


綜合以上測試, 建立物件導向的 Figure 與 Axes/AaxeSubplot 物件的方式歸納如下表 :


 建立 Figure/Axes 物件的方式 說明
 fig=plt.figure(figsize=[8, 6])
 ax=fig.add_axes([0.1, 0.1, 0.8, 0.8])
 建立 Axes 物件
 呼叫 add_axes() 一定要傳入繪圖區尺寸
 fig=plt.figure(figsize=[8, 6])
 ax=fig.add_subplot(3, 2, 1)
 建立 3*2 網格索引 1 之 AxesSubplot 物件 (傳 321 亦可)
 呼叫 add_subplot() 可不傳網格配置與索引, 預設 111 (單圖)
 fig=plt.figure(figsize=[8, 6])
 ax=plt.axes([0.1, 0.1, 0.8, 0.8])
 建立 AxesSubplot 物件 
 呼叫 axes() 可不傳繪圖區尺寸, 預設 [0.125, 0.11, 0.9, 0.88]
 fig, ax=plt.subplots(3, 2, 1) 建立 3*2 網格索引 1 之  AxesSubplot 物件 (傳 321 亦可)
 呼叫 plt.subplots() 可不傳網格配置與索引, 預設 111 (單圖)
 不須呼叫 plt.figure() 建立 Figure 物件, plt.subplots() 會傳回


如果不需要自訂畫布大小, 使用 plt.subplots() 最方便, 因為不需要另外呼叫 plt.figure() 來建立 Figure 物件, 此函式會自行建立並傳回. 需要調整畫布大小也可以用 fig.set_size() 設定. 


參考 :