本篇旨在測試 Bokeh 的長條圖繪製方法.
本系列之前文章參考 :
Bokeh 繪製長條圖的 Figure 物件方法有四個 :
| 方法名稱 | 說明 |
|---|---|
vbar() |
繪製垂直長條圖, 每個長條代表一個 x 類別,使用 top 設定高度 |
hbar() |
繪製水平長條圖, 每個長條對應一個 y 類別,使用 right 設定長度 |
vbar_stack() |
繪製堆疊式垂直長條圖, 以 stackers 指定多個欄位搭配 ColumnDataSource |
hbar_stack() |
繪製堆疊式水平長條圖, 與 vbar_stack() 類似,但方向為水平 |
首先來測試垂直長條圖方法 vbar(), 其參數結構如下 :
fig.vbar(x, top, *, width=0.9, bottom=0, color='blue', alpha=1.0, line_color=None, legend_label=None, **kwargs)
參數說明如下表 :
| fig.vbar() 參數 | 說明 |
|---|---|
| x | 指定長條的 x 軸位置(通常為分類值) |
| top | 長條的高度(y 軸數值) |
| width | 長條的寬度(占該分類寬度比率 0~1, 預設為 1) |
| bottom | 長條的底部 y 座標(預設為 0) |
| fill_color | 長條內部填滿的顏色,例如 "blue"、"#FF0000"、或 "rgba(255, 0, 0, 0.5)" |
| line_color | 長條邊框顏色 |
| line_width | 邊框寬度(以像素為單位, 預設 1px) |
| legend_label | 圖例標籤,若要在圖上加入圖例需設此參數 |
| alpha | 直條透明度 (0~1),0 表示完全透明,1 表示不透明 |
例如 :
測試 1 : 預設的垂直長條圖 [看原始碼]
# bokeh-bar-test-1.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(x, top=y)
show(fig) # 顯示圖表
此例 x 與 y 軸皆為數值資料 (top 參數要指定為 y 軸資料), 結果如下 :
可見預設情況長條會占滿其分配到的全部寬度, 因此每一條都黏在一起, 這可以利用 width 參數設定 (0~1, 預設 1) :
# bokeh-bar-test-2.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, color='red', alpha=0.5)
show(fig) # 顯示圖表
此例設定長條寬度占該分類寬度之 50%, 底色+邊框顏色為 red, 填色透明度為 0.5, 結果如下 :
可見 0.5 的透明度讓顏色變成近乎粉紅. 也可以用 line_with 設定邊框寬度 (預設為 0), 然後用 line_color 設定框線顏色, 用 fill_color 設定直條填色 :
# bokeh-bar-test-3.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(
x,
top=y,
width=0.5,
line_width=3,
line_color='#0000ff',
fill_color='yellow',
alpha=0.5
)
show(fig) # 顯示圖表
結果如下 :
注意, alpha 會影響整個直條 (含邊框與填色), 如果只想設定填色透明度, fill_color 可以改為 RGBA 格式 "rgba(255, 255, 0, 0.5)", 例如 :
# bokeh-bar-test-4.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(
x,
top=y,
width=0.5,
line_width=3,
line_color='#0000ff',
fill_color='rgba(255, 255, 0, 0.5)'
)
show(fig) # 顯示圖表
此例拿掉 alpha, 將 fill_color 改成 'rgba(255, 255, 0, 0.5)', 這樣邊框預設的藍色就不會受到 alpha 影響了, 結果如下 :
下面範例是測試圖例標籤參數 legend_lebel 與其位置設定 :
測試 5 : 設定圖例標籤 legend_label 與圖例位置 [看原始碼]
# bokeh-bar-test-5.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, legend_label='今年前五月月營收')
fig.legend[0].location='top_left' # 設定圖例位置為左上
show(fig) # 顯示圖表
注意, 用 Legend 物件的 location 屬性設定圖例位置一定要在呼叫 fig.vbar() 之後, 因為在這之前 Legend 物件還不存在. 結果如下 :
可見圖例標籤已經放在左上角了 (預設為右上角, 這會蓋住長條圖).
在上面的範例中, 由於 y 軸資料數字較大, Bokeh 會自動轉成科學記號來呈現, 如果想要以原來的整數表示, 關閉自動轉換機制, 只要將 fig.yaxis.formatter.use_scientific 設為 False 即可, 例如 :
# bokeh-bar-test-6.py
from bokeh.plotting import figure, show
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
fig.yaxis.formatter.use_scientific=False # y 軸為整數
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, color='cyan', line_color='blue')
show(fig) # 顯示圖表
結果如下 :
可見 Y 軸刻度標籤回復整數形式了.
但如果要對 Y 軸刻度標籤進行格式化, 例如讓整數顯示千位逗號, 那就需要用到 bokeh.models 模組中的 NumeralTickFormatter 類別來設定 Y 軸軸物件 fig.yaxis 的 formatter 屬性了, 呼叫其建構式 NumeralTickFormatter() 並傳入格式化字串 (例如 "0,0" 表示整數) 會傳回一個 NumeralTickFormatter 物件, 將其設定給軸物件 fig.yaxis 的 formatter 屬性即可讓 Y 軸刻度標籤顯示指定格式了. 可傳入 NumeralTickFormatter() 的常用格式化字串如下表 :
| 常用格式化字串 | 說明 |
|---|---|
| "0,0" | 整數加千位分隔符,例如 10000 會顯示為 10,000 |
| "0,0.0" | 千位分隔符並保留一位小數,例如 10000.5 顯示為 10,000.5 |
| "0%" | 百分比格式,自動乘以 100,例如 0.5 顯示為 50% |
| "0.00%" | 百分比格式,保留兩位小數,例如 0.5 顯示為 50.00% |
| "$0,0.00" | 貨幣格式,有千位分隔與兩位小數,例如 1234.56 顯示為 $1,234.56 |
| "+0,0" | 顯示正負號的整數,例如 1000 顯示為 +1,000 |
| "0.00a" | 縮寫數值(k、M、B 等),例如 12300 顯示為 12.30k |
| "0.0b" | 以位元組單位顯示數值,例如 1048576 顯示為 1.0MB |
| "0.000e+0" | 科學記號表示法,例如 1230 顯示為 1.230e+3 |
| "0o" | 加上序數後綴,例如 1 顯示為 1st,2 顯示為 2nd |
例如 :
測試 7 : 設定 Y 軸格式化刻度標籤 [看原始碼]
# bokeh-bar-test-7.py
from bokeh.plotting import figure, show
from bokeh.models import NumeralTickFormatter
x=[1, 2, 3, 4, 5] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
fig.yaxis.formatter=NumeralTickFormatter(format='0,0') # y 軸為帶千位逗號整數
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, color='cyan', line_color='blue')
show(fig) # 顯示圖表
結果如下 :
可見 Y 軸刻度的標籤都以整數而非科學記號表示了.
上面範例所用的 X 軸資料均為數值, 如果直接改為類別資料, 例如 :
x=['一月', '二月', '三月', '四月', '五月']
Bokeh 並不知道 x 軸是類別資料, 它仍會以數值來處理類別資料, 執行時雖然不會出現錯誤, 但繪製結果會是空白圖片. 解決之道是要在呼叫 figure() 時傳入 x_range 參數指定 x 軸為類別資料 x_range=x, 例如 :
測試 8 : 呼叫 figure() 時用 x_range 參數設定 x 軸為類別資料 [看原始碼]
# bokeh-bar-test-8.py
from bokeh.plotting import figure, show
x=['一月', '二月', '三月', '四月', '五月'] # x 軸資料
y=[120000, 135000, 99000, 150000, 170000] # y 軸資料
# 建立 figure 物件
fig=figure(x_range=x, width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, color='orange', line_color='gray')
show(fig) # 顯示圖表
結果如下 :
可見 x 軸的刻度標籤已經顯示 "一月", "二月" 等字串了.
上面的範例均使用串列做為 X, Y 軸的資料來源, 其實 Bokeh 的 bokeh.models 模組裡面有一個 ColumnDataSource 類別是專門為了處理資料來源而設計的, 與單純使用串列做為資料來源相比, ColumnDataSource 有如下之好處 :
- 支援互動與資料綁定 :
可讓 Bokeh 元件與圖表綁定相同資料來源, 同步更新資料. - 支援資料更新與動態重繪 :
程式執行中若動態更新 ColumnDataSource 的資料, 圖表會自動重新繪製. - 多圖表資料共享 :
多圖表使用 ColumnDataSource 共享同一批資料可以使它們同步並節省記憶體. - 整合更進階的轉換器 (transformers) 函式 :
在 bokeh.transform 模組裡面有許多轉換器函式例如 dodge(), jitter(), factor_cmap() 等都需要搭配 ColumnDataSource 才能使用.
呼叫建構式 ColumnDataSource() 並傳入一個字典或 DataFrame 會傳回一個 ColumnDataSource 物件, 然後在呼叫 Figure 物件的繪圖方法例如 line(), scatter() 或 vbar() 時將此物件傳給 source 參數即可依據此資料來源繪圖, 例如 :
測試 9 : 使用 ColumnDataSource 物件做為資料來源 (字典) [看原始碼]
# bokeh-bar-test-9.py
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
# 原始資料
x=[1, 2, 3, 4, 5]
y=[120000, 135000, 99000, 150000, 170000]
# 建立 ColumnDataSource
source=ColumnDataSource(data=dict(x=x, y=y))
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
# 繪製長條圖,欄位名稱須用字串對應到 source 中的欄位
fig.vbar(
x='x', # 對應到 source 的 'x' 鍵 (注意是字串)
top='y', # 對應到 source 的 'y' 鍵 (注意是字串)
width=0.5,
color='orange',
line_color='gray',
source=source # 指定資料來源為 ColumnDataSource 物件
)
# 顯示圖表
show(fig)
此例將原始的 x, y 軸串列資料來源用 dict() 打包成 'x', 'y' 鍵的字典後傳給 ColumnDataSource() 的 data 參數建立 ColumnDataSource 物件, 並於呼叫 fig.vbar() 時將其傳給 source 參數. 注意此處 vbar() 的 x 與 top 參數必須是 source 物件中的 'x' 與 'y' 鍵, 不是 x 與 y. 結果如下 :
這與直接使用串列做為資料來源之結果是一樣的.
Bokeh 的 ColumnDataSource 與 Pandas 高度整合, 也可以直接傳入 DataFrame, 例如 :
測試 10 : 使用 ColumnDataSource 物件做為資料來源 (DataFrame) [看原始碼]
# bokeh-bar-test-10.py
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
import pandas as pd
# 原始資料
df=pd.DataFrame({
'x': ['一月', '二月', '三月', '四月', '五月'],
'y': [120000, 135000, 99000, 150000, 170000]
})
# 建立 ColumnDataSource
source=ColumnDataSource(df) # 傳入 DataFrame
# 建立 figure 物件
fig=figure(x_range=df['x'], width=400, height=300, title='Bokeh 長條圖')
fig.yaxis.formatter.use_scientific=False
# 繪製長條圖,欄位名稱須用字串對應到 source 中的欄位
fig.vbar(
x='x', # 對應到 source 的 'x' 鍵 (注意是字串)
top='y', # 對應到 source 的 'y' 鍵 (注意是字串)
width=0.5,
color='orange',
line_color='gray',
source=source # 指定資料來源為 ColumnDataSource 物件
)
# 顯示圖表
show(fig)
此例改用分類資料 (中文月份字串) 做為 x 軸, 需要注意的是 figure() 要傳入 x_range 參數且指定 df 的 'x' 鍵 Series, 結果如下 :
ColumnDataSource 在繪製多個 Y 軸資料時 (群組式長條圖) 很有用, 但要在 X 軸的一個刻度上顯示多個長條還需要用到 bokeh.transform 模組裡面的 dodge() 函式, 其功能類似在 Matplotlib 繪製群組長條圖的做法, 用來在刻度左右兩側的偏移位置分別畫各類別的長條.
dodge() 的參數結構如下 :
dodge(field_name, value, range)
參數說明如下表 :
| dodge() 參數 | 說明 |
|---|---|
| field | 欄位名稱 (字串),代表分類資料所在欄位,例如 "x"。 |
| value | 數值型的偏移量,用來決定每組資料應在分類軸上左右偏移多少,例如 ±0.2。 |
| range | 分類軸的範圍 (通常為 fig.x_range 或 FactorRange),用於正確計算偏移。 |
dodge() 會傳回一個 Dodge 物件, 這是一個 Bokeh 中的 transform 轉換器, 用來告訴 Bokeh 如何處理資料座標偏移, Bokeh 會在繪圖時根據 Dodge 物件和 ColumnDataSource 物件的資料自行計算長條的實際繪製位置, 例如 :
測試 11 : 利用 bokeh.transform.dodge() 繪製群組式長條圖 [看原始碼]
# bokeh-bar-test-11.py
from bokeh.plotting import figure, show
from bokeh.transform import dodge
from bokeh.models import ColumnDataSource
x=['一月', '二月', '三月', '四月', '五月'] # x 軸資料
y1=[120000, 135000, 99000, 150000, 170000] # y 軸資料
y2=[100000, 125000, 87000, 140000, 160000]
# 建立 ColumnDataSource
source=ColumnDataSource(data=dict(x=x, y1=y1, y2=y2))
# 建立 figure 物件
fig=figure(x_range=x, width=500, height=400, title='Bokeh 群組長條圖')
fig.yaxis.formatter.use_scientific=False # y 軸為整數
# 繪製第一個長條圖
fig.vbar(
x=dodge('x', -0.2, range=fig.x_range), # 往左偏移 20%
top='y1',
width=0.4,
color='navy',
legend_label='今年營收',
source=source
)
# 繪製第二個長條圖
fig.vbar(
x=dodge('x', 0.2, range=fig.x_range), # 往右偏移 20%
top='y2',
width=0.4,
color='cyan',
legend_label='去年營收',
source=source
)
fig.legend.location='top_left'
show(fig) # 顯示圖表
此例呼叫了兩次 fig.vbar() 分別繪製今年與去年的營收長條, 關鍵在傳入的 x 參數是由 dodge() 向左向右偏移 20% 後調整過的座標位置, 結果如下 :
此例將圖片尺寸放大為 500x400 以避免圖例遮蓋了長條.











沒有留言 :
張貼留言