經過前幾篇測試我覺得 Gradio 真是一個非常好的 Web app 工具, 可以讓資料分析工程師專注於資料本身, 毋須花大把時間在前端技術上, 這種功能以前使用 Flask 可是要花不少功夫呢. 本篇繼續來測試 Gradio 的繪圖功能.
使用 Gradio 繪製統計圖是以 Pandas 的 DataFrame 做為資料來源 (value 參數), 以下測試主要是在 Gradio Playground 平台上進行, 並將 Web app 佈署到 Hugging Face Spaces 執行空間, 這兩個平台都預載了 Pandas, 可直接 import 使用.
九. 用 Gradio 繪製統計圖 :
目前的 Gradio 版本僅提供了折線圖, 散佈圖, 與長條圖這三種繪圖函式, 但可利用 Matplotlib, Protly, Seaborn 等繪圖函式繪圖後傳給 gradio.Plot() 含是顯示.
Gradio 繪圖元件 | 常用參數 |
LinePlot (折線圖) | title,x_title, y_title, x, y, value, color, interactive |
ScatterPlot (散佈圖) | title,x_title, y_title, x, y, size, color, interactive |
BarPlot (長條圖) | title,x_title, y_title, x, y, value, color, grouped, stacked, interactive |
注意, Gradio 的所有繪圖元件均具可透過設定 interactive=True 開啟互動功能 (預設 False).
1. 用 LinePlot() 繪製折線圖 :
折線圖用來顯示一組或多組資料的變化與關係, LinePlot() 常用參數如下 :
- value : 要顯示的資料, 可以是 Pandas DataFrame 或 JSON 格式.
- x : 指定要做為 x 軸的 DataFrame 欄索引名稱
- y : 指定要做為 y 軸的 DataFrame 欄索引名稱
- color : 指定要做為分組標籤的 DataFrame 欄索引名稱
- title : 圖表的標題 (字串)
- x_title : x 軸標籤
- y_title : y 軸標籤
- interactive : 圖表是否具有互動性 True/False (預設)
資料參數 value 通常是傳入一個 Pandas 的 DataFrame 物件, 這比較簡單且直觀, 如果是用 JSON 格式的字典串列的話處理起來較麻煩.
範例程式碼 :
import gradio as gr
import pandas as pd
data=pd.DataFrame({
'x': ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
'y': [23, 20, 21, 17, 15, 14, 12]
})
plot=gr.LinePlot(
value=data,
x='x',
y='y',
title='本周溫度變化',
x_title='星期',
y_title='溫度',
interactive=True
)
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='折線圖 LinePlot() 測試',
flagging_mode='never',
)
iface.launch()
本例顯示一周溫度變化之折線圖, 因為不須用指名函式處理資料, 所以 fn 參數使用 lambda 匿名函式傳入一個 DataFrame, 它會被傳遞給 LinePlot 物件繪製圖形. 其次, 由於不需要輸入, 故 inputs參數要傳入空串列 (不要傳入 None, 雖然也不會出現錯誤). 結果如下 :
當滑鼠移動到圖表上時會顯示資料點的 X, Y 軸值, 這就是互動圖表的功能.
此 Web App 網址如下 :
如果要繪製兩組以上的折線圖, 那就要在 data 中添加另一個 Y 軸資料, 但需要用到 DataFrame 物件的 melt() 方法將資料擴展, 同時利用 color 參數指定分組標籤, 例如下面程式是比較本周與前一周的溫度變化 :
import gradio as gr
import pandas as pd
data=pd.DataFrame({
'x': ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
'本周': [23, 20, 21, 17, 15, 14, 12], # 第一組資料
'前一周': [26, 25, 24, 27, 25, 24, 23] # 第二組資料
})
data_long=data.melt(id_vars='x', var_name='label', value_name='y')
plot=gr.LinePlot(
value=data_long, # 顯示擴展後之資料
x='x',
y='y',
color='label',
title='本周溫度變化',
x_title='星期',
y_title='溫度',
interactive=True
)
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='折線圖 LinePlot() 測試',
flagging_mode='never',
)
iface.launch()
此例第一組資料為本周溫度, 在 data 中添加前一周溫度為第一組資料, 這時在 LinePlot() 要傳入 color 參數指定分組標籤 (這會決定 legend 圖例), 然後在呼叫 DataFrame 物件的 melt() 方法將寬格式資料展開為長格式資料時要指定 var_name 為此分組標籤 (此處為 'label'), 結果如下 :
可見指定 color 參數使兩個 Y 軸欄索引變成圖例 (legend) 了.
此 Web App 網址如下 :
2. 用 ScatterPlot() 繪製散佈圖 :
散佈圖用來顯示兩組資料之間的關係 (可觀察分佈模式), ScatterPlot() 函式支持分組, 標籤和互動等功能, 透過顏色分組可對比不同類別的資料特徵.
常用參數與上面折線圖相同 :
- value : 要顯示的資料, 可以是 Pandas DataFrame 或 JSON 格式.
- x : 指定要做為 x 軸的 DataFrame 欄索引名稱
- y : 指定要做為 y 軸的 DataFrame 欄索引名稱
- color : 指定要做為分組標籤的 DataFrame 欄索引名稱
- title : 圖表的標題 (字串)
- x_title : x 軸標籤
- y_title : y 軸標籤
- interactive : 圖表是否具有互動性 True/False (預設)
範例程式碼 (1) :
import gradio as gr
import pandas as pd
data=pd.DataFrame({
'x': [1, 2, 3, 4, 5, 6, 7, 8],
'y': [2.5, 3.1, 4.7, 3.8, 5.5, 6.8, 7.9, 8.2],
})
plot=gr.ScatterPlot(
value=data,
x='x',
y='y',
title='練習次數與平均成績的關係',
x_title='練習次數',
y_title='平均成績(0~10)',
interactive=True
)
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='分佈圖 ScatterPlot() 測試',
flagging_mode='never',
)
iface.launch()
此例資料紀錄選手賽前練習次數與平均成績之關係, 結果如下 :
從分布圖可知練習次數與平均成績呈正相關關係.
此 Web App 網址如下 :
不過 Gradio 的 ScatterPlot() 與 Matplotlib 的 scatter() 不同的是, ScatterPlot() 的繪圖時 x, y 軸總是從原點開始, 缺點是若 x, y 值都很小或很大, 那麼分布圖會繪製在離原點很遠的小角落, 導致無法看出資料間的關係; 而 Matplotlib 的 scatter() 則會自動將原點移到比 x, y 軸最小值還低一些的地方, 這樣就能將視野聚焦在資料分布的區域.
範例程式碼 (2) :
import gradio as gr
import pandas as pd
data=pd.DataFrame({
'x': [49, 65, 53, 45, 56, 47, 52, 61],
'y': [159, 177, 156, 163, 164, 158, 166, 171]
})
plot=gr.ScatterPlot(
value=data,
x='x',
y='y',
title='身高與體重關係',
x_title='體重(Kg)',
y_title='身高(Cm)',
interactive=True
)
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='分佈圖 ScatterPlot() 測試',
flagging_mode='never',
)
iface.launch()
此例最低體重 45Kg, 最低身高 158Cm, 都離原點甚遠, 其分布點聚集在右上角, 由於尺度被壓縮了, 使得 x, y 的關係無法清楚地看出來 :
此 Web App 網址如下 :
資料取自之前測試 Matplotlib 時所用之體重與身高關聯性資料, 參考 :
如果使用 Matplotlib 的 scatter() 函式畫分布圖, 它會自動移動畫布原點讓畫布能呈現整個資料分布區域的全貌, Gradio 目前無法做到這樣的功能, 但能透過 Matplotlib 繪圖後將結果交給 Gradio 的 Plot() 函式輸出了, 如下面範例所示 :
範例程式碼 (3) :
import gradio as gr
import pandas as pd
import matplotlib.pyplot as plt
def plot_scatter_chart():
plt.figure(figsize=(8, 5))
x=[49, 65, 53, 45, 56, 47, 52, 61]
y=[159, 177, 156, 163, 164, 158, 166, 171]
plt.scatter(x, y, color='blue', alpha=0.75)
plt.title('Relationship between Height and Weight') # 圖表標題
plt.xlabel('Weight (Kg)') # X 軸標籤
plt.ylabel('Height (Cm)') # Y 軸標籤
plt.grid(True, linestyle='--', alpha=0.5) # 添加網格
plt.tight_layout() # 自動調整布局
return plt.gcf() # 傳回 Figure 物件
iface=gr.Interface(
fn=plot_scatter_chart, # 繪製函數
inputs=[], # 無需用戶輸入
outputs=gr.Plot(), # 輸出為 Gradio 的 Plot 物件
title='Scatter Plot Test(with Matplotlib)',
flagging_mode='never',
)
iface.launch()
此例以 Gradio 的 Plot 物件做為輸出對象, 處理函式參數 fn 指定呼叫 Matplotlib 寫的繪圖函式來繪製散佈圖, 將繪圖結果的 Figure 畫布物件傳回給 Gradio 的 Plot 物件繪製即可, 結果如下 :
可見由於 Matplotlib 會自動移動圖表的原點以聚焦在資料分布之區域, 故能較清楚觀察資料間的似乎呈現一種正相關關係.
此 Web App 網址如下 :
注意, 由於 Hugging Face Spaces 執行空間並未預載 Matplotlib, 因此需編輯 requirements.txt 加入matplotlib 於佈署時安裝此套件才能執行繪圖.
除了 Matplotlib 外也可以利用 Plotly 繪圖後將畫布物件傳給 Gradio 的 Plot 物件顯示, 關於 Plotly 用法參考 :
程式碼如下 :
import gradio as gr
import pandas as pd
import plotly.express as px
def plot_scatter_chart():
x=[49, 65, 53, 45, 56, 47, 52, 61] # 體重
y=[159, 177, 156, 163, 164, 158, 166, 171] # 身高
data=pd.DataFrame({'weight': x, 'height': y})
fig=px.scatter(data, x='weight', y='height')
return fig # 傳回 Figure 物件
iface=gr.Interface(
fn=plot_scatter_chart, # 繪製函數
inputs=[], # 無需用戶輸入
outputs=gr.Plot(), # 輸出為 Gradio 的 Plot 物件
title='Scatter Plot Test(with Plotly)',
flagging_mode='never',
)
iface.launch()
此例使用 Plotly 的 Express 模組來繪圖 (也可以用原生的 graph_objs 來畫), 由於 Gradio Playground 沒有預載 Plotly, 所以只能在本機或直接在 Hugging Face Spaces 佈署, 結果如下 :
可見 Plotly 與 Matplotlib 一樣都會將畫面聚焦在資料分布的區域. 同樣地, 由於 Hugging Face Spaces 執行空間並未預載 Plotly, 因此需編輯 requirements.txt 加入 plotly 於佈署時安裝此套件才能執行繪圖. 注意, Hugging Face Spaces 不支援中文字型.
3. 用 BarPlot() 繪製長條圖 :
長條圖主要用來顯示類別資料的數量變化, 透過柱狀高度或長度來觀察不同類別之數量變化情形. BarPlot() 函式支持分組, 標籤和互動等功能, 透過 color 參數指定之分組欄可對比不同分組之類別資料差異. 注意, Gradio 目前僅支援垂直長條圖, 不支援水平長條圖.
常用參數如下 :
- value : 要顯示的資料, 可以是 Pandas DataFrame 或 JSON 格式.
- x : 指定要做為 x 軸的 DataFrame 欄索引名稱
- y : 指定要做為 y 軸的 DataFrame 欄索引名稱
- color : 指定要做為分組標籤的 DataFrame 欄索引名稱
- title : 圖表的標題 (字串)
- x_title : x 軸標籤
- y_title : y 軸標籤
- interactive : 圖表是否具有互動性 True/False (預設)
- grouped: True/False (預設), 用來是否多組長條圖並列顯示
- stacked: True (預設)/False, 用來是否多組長條圖堆疊顯示
注意, BarPlot() 的 y 參數只能指定一個欄, 不能傳入多欄串列, 也不能像 Matplotlib 那樣指定 y1, y2, ... 等多個 y 軸來顯示多組長條圖, 當有多組資料時需用 color 參數指定分組欄位, y 軸資料與指定之分組欄位均需對應擴展.
範例程式碼 (1) :
import gradio as gr
import pandas as pd
data=pd.DataFrame({
'x': ['一月', '二月', '三月', '四月', '五月', '六月'],
'y': [12000, 15000, 17000, 13000, 19000, 21000],
})
plot=gr.BarPlot(
value=data,
x='x',
y='y',
title='上半年月銷售額變化',
x_title='月份',
y_title='銷售額(萬元)',
interactive=True
)
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='長條圖 BarPlot() 測試',
flagging_mode='never',
)
iface.launch()
結果如下 :
如果要顯示多組資料的長條圖要用到 color, grouped, 與 stacked 參數, 如上所述, 目前 Gradio 版本的 BarPlot() 只能指定一個 y 軸資料, 因此如果要顯示多組資料的長條圖, 必須將各組資料串成一個串列, 然後用分組欄位標示是哪一組資料, 例如上面顯示的是銷售一課的業績, 如果加入銷售二課業績 :
'銷售額': [11000, 14000, 16000, 12000, 18000, 20000], # 銷售二課
那麼要將其合併擴展為一個串列做為 y 軸資料 :
'銷售額': [12000, 15000, 17000, 13000, 19000, 21000, # 銷售一課
11000, 14000, 16000, 12000, 18000, 20000], # 銷售二課
然後增加一個分組欄位來對應每一筆資料所屬分組 :
'部門': ['銷售一課'] * 6 + ['銷售二課'] * 6 # 部門標籤
然後傳入 color 參數指定分組欄位為 '部門' 即可.
範例程式碼 (2) :
import gradio as gr
import pandas as pd
data=pd.DataFrame({ # 將資料轉為長格式
'月份': ['一月', '二月', '三月', '四月', '五月', '六月'] * 2,
'銷售額': [12000, 15000, 17000, 13000, 19000, 21000, # 銷售一課
11000, 14000, 16000, 12000, 18000, 20000], # 銷售二課
'部門': ['銷售一課'] * 6 + ['銷售二課'] * 6 # 分組標籤
})
plot=gr.BarPlot(
value=data,
x='月份', # X 軸:月份
y='銷售額', # Y 軸:銷售額
color='部門', # 分組欄位
title='上半年月銷售額變化',
x_title='月份',
y_title='銷售額(萬元)',
interactive=True
)
# 建立介面
iface=gr.Interface(
fn=lambda: data,
inputs=[],
outputs=plot,
title='分組長條圖 BarPlot() 測試',
flagging_mode='never',
)
iface.launch()
結果如下 :
可見預設會顯示堆疊長條圖, 這是因為 stacked 參數預設是 True, 而 grouped 參數預設是 False 之故. 此例 Web app 網址如下 :
但是如果將 stacked 設為 False 與 grouped 設為 True, 並不會顯示並列的長條圖, 這可能是 Gradio 是高階的套件, 沒辦法像 Matplotlib 那樣支援低階繪圖功能所致. 如果要繪製並列的長條圖, 可以像上面分布圖做法, 利用 Matplotlib 畫好圖後將 Figure 物件傳給 Gradio 的 Plot() 顯示即可.
範例程式碼 (3) :
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
def plot_grouped_barchart():
plt.rcParams["font.family"]=["Microsoft JhengHei"]
months=['一月', '二月', '三月', '四月', '五月', '六月'] # 月份
x=np.arange(len(months)) # 數值型索引
y1=[12000, 15000, 17000, 13000, 19000, 21000] # 銷售一課
y2=[11000, 14000, 16000, 12000, 18000, 20000] # 銷售二課
width=0.35 # 長條寬度
# 繪製長條圖
plt.figure(figsize=(8, 5))
plt.bar(x - width/2, y1, width, label='銷售一課', color='cyan')
plt.bar(x + width/2, y2, width, label='銷售二課', color='orange')
# 設定標籤與標題
plt.xlabel('月份')
plt.ylabel('銷售額(萬元)')
plt.title('上半年月銷售額變化 (並排分組)')
plt.xticks(x, months) # 設定 x 軸刻度標籤
plt.legend() # 顯示圖例
plt.tight_layout() # 自動調整布局
return plt.gcf() # 傳回 Figure 物件
iface=gr.Interface(
fn=plot_grouped_barchart,
inputs=[],
outputs=gr.Plot(),
title='分組長條圖測試',
flagging_mode='never'
)
iface.launch()
此例先利用 X 軸刻度 (月份字串串列) 產生可計算之數值刻度, 這樣才能計算兩組資料的 X 軸偏移位置以繪製並立的長條. 繪圖完成後呼叫 plt.gcf() 取得 Figure 畫布物件並將其傳回給 gradio.Plot() 函式繪製. 由於 Gradio 沒有所需之中文微軟正黑體字型, 所以改在本機執行上面的程式, 結果如下 :
當然也可以用 Matplotlib 的 barh() 繪製水平的分組並立長條圖, 但要將 x, y 軸顛倒 (寬度變成高度). 下面範例除了改用 barh() 繪製水平長條圖外, 還將中文改成英文, 這樣才能在 Gradio Playground 與 Hugging Face Spaces 中正常呈現標題 (因為此二平台的 Matplotlib 的中文顯示) :
範例程式碼 (3) :
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
def plot_grouped_barchart():
months=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] # 月份
y=np.arange(len(months)) # 數值型索引
x1=[12000, 15000, 17000, 13000, 19000, 21000] # 銷售一課
x2=[11000, 14000, 16000, 12000, 18000, 20000] # 銷售二課
height=0.35 # 長條高度
# 繪製長條圖
plt.figure(figsize=(8, 5))
plt.barh(y - height/2, x1, height, label='Sales Team 1', color='cyan')
plt.barh(y + height/2, x2, height, label='Sales Team 2', color='orange')
plt.ylabel('Months')
plt.xlabel('Sales (in ten thousand)')
plt.title('Monthly Sales Performance (Grouped)')
plt.yticks(y, months) # 設定 y 軸刻度標籤
plt.legend() # 顯示圖例
plt.tight_layout() # 自動調整布局
return plt.gcf() # 返回圖表對象
iface=gr.Interface(
fn=plot_grouped_barchart,
inputs=[],
outputs=gr.Plot(),
title='Grouped Bar Chart Test(Horizontal)',
flagging_mode='never'
)
iface.launch()
結果如下 :
此例 Web app 網址如下 :