2025年6月13日 星期五

Streamlit 學習筆記 : 展示 Bokeh 生成之圖表

由於之前沒學過 Bokeh 套件, 所以先花了幾天時間測試了 Bokeh 功能, 掌握了基本用法後才回頭來測試如何在 Streamlit 上輸出 Bokeh 繪製的圖片. 幾天研究下來發現, Bokeh 真是一款簡單易用, 功能強大且完整的視覺化套件, 用法參考下面幾篇測試 : 


本系列所有測試文章參考 :


1. 在虛擬環境安裝 Bokeh 2.4.3 : 

因為最新版的 Streamlit v1.45.0 目前只支援到 Bokeh v2.4.3 版, 如果系統安裝的 Boke 比這還新 (最新 v3.7.3) 會出現如下錯誤 : 

StreamlitAPIException: Streamlit only supports Bokeh version 2.4.3, but you have version
3.7.3 installed. Please run `pip install --force-reinstall --no-deps bokeh==2.4.3` to
install the correct version.

另外 bokeh v2.4.3 和較新版本的 numpy 不相容, 它僅支援到 Numpy v1.23, 如果系統的 Numpy 版本大於 v1.23 會出現如下錯誤 :

AttributeError: module 'numpy' has no attribute 'bool8'

所以如果要在 Streamlit 應用上輸出 Bokeh 繪製的圖片, 可以建立一個虛擬環境來安裝 Bokeh v2.4.3 與 Numpy v1.23.5 來測試 : 

D:\python\test>virtualenv venv    
created virtual environment CPython3.10.11.final.0-64 in 19312ms
  creator CPython3Windows(dest=D:\python\test\venv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=C:\Users\tony1\AppData\Local\pypa\virtualenv)
    added seed packages: pip==23.3.1, setuptools==69.0.2, wheel==0.42.0
  activators BashActivator,BatchActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

這樣便建好一個虛擬環境目錄 venv 了, 切換到 venv : 

D:\python\test>cd venv  

執行 Scripts 底下的 activate 命令稿來啟動虛擬環境 :

D:\python\test\venv>Scripts\activate   

前面出現 (venv) 表示已進入虛擬環境, 用 pip 安裝最新版 Streamlit : 

(venv) D:\python\test\venv>pip install streamlit   

再指定安裝 Bokeh v2.4.3 版 :

(venv) D:\python\test\venv>pip install bokeh==2.4.3   

完成後用 pip list 檢視虛擬環境下的 Python 套件列表 : 

(venv) D:\python\test\venv>pip list  
Package                   Version
------------------------- -----------
altair                    5.5.0
attrs                     25.3.0
blinker                   1.9.0
bokeh                     2.4.3
cachetools                5.5.2
certifi                   2025.4.26
charset-normalizer        3.4.2
click                     8.2.1
colorama                  0.4.6
gitdb                     4.0.12
GitPython                 3.1.44
idna                      3.10
Jinja2                    3.1.6
jsonschema                4.24.0
jsonschema-specifications 2025.4.1
MarkupSafe                3.0.2
narwhals                  1.42.1
numpy                     1.23.5
packaging                 24.2
pandas                    2.3.0
pillow                    11.2.1
pip                       23.3.1
protobuf                  6.31.1
pyarrow                   20.0.0
pydeck                    0.9.1
python-dateutil           2.9.0.post0
pytz                      2025.2
PyYAML                    6.0.2
referencing               0.36.2
requests                  2.32.4
rpds-py                   0.25.1
setuptools                69.0.2
six                       1.17.0
smmap                     5.0.2
streamlit                 1.45.1
tenacity                  9.1.2
toml                      0.10.2
tornado                   6.5.1
typing_extensions         4.14.0
tzdata                    2025.2
urllib3                   2.4.0
watchdog                  6.0.0
wheel                     0.42.0

這樣便完成測試環境配置了.


2. Bokeh 圖片輸出函式 st.bokeh_chart() : 

Streamlit 提供 st.bokeh_chart() 函式來輸出 Bokeh 繪製的圖表, 其參數結構如下 :

st.bokeh_chart(figure, use_container_width=False)

必要參數為 Bokeh 的 Figure 畫布物件, 只要將它傳給 st.bokeh_chart() 即可將圖片顯示在 Streamlit 的網頁中. 由於是透過 Streamlit 展示, 所以程式中毋須匯入 bokeh.plotting.show() 函式. 


3. 輸出 Bokeh 折線圖 : 

Bokeh 的散布圖方法為 fig.line(), 用法參考 : 


Bokeh 的資料來源可以是串列表示的 x, y 軸資料, 也可以是支援字典與 DataFrame 的 ColumnDataSource 物件. 首先來測試串列資料來源 : 


測試 1 : 資料來源為串列的折線圖 [看原始碼] 

# st-bokeh-test-1.py
import streamlit as st
from bokeh.plotting import figure

x=[0, 1, 2, 3, 4, 5]  # x 軸資料
y=[-5, -2, 6, 12, 4, 9]  # y 軸資料
fig=figure(width=400, height=300, title='Bokeh 折線圖')  # 建立 figure 物件
fig.line(x, y, line_width=4, line_color='cyan')  # 繪製折線圖
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

此例 x, y 軸資料來源為串列, 因為 Bokeh 有設定圖片尺寸為 400x300, 所以 st.bokeh_chart() 要傳入 use_container_width=False, 否則圖片會被自動放大到 Streamlit 容器大小, 結果如下 : 




資料來源也可以包裝成 Bokeh 的 ColumnDataSource 物件後再餵給繪圖方法的 source 參數, 這種方式能展現 Bokeh 強大的功能, 例如互動姓, 資料綁定, 多圖表資料共享, 以及能使用進階的轉換器等. 原始的串列資料可以打包成字典或 DataFrame 傳給 ColumnDataSource 類別的建構式. 


測試 2 : 資料來源為 ColumnDataSource 物件 (傳入字典) 的折線圖 [看原始碼] 

# st-bokeh-test-2.py
import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource

x=[0, 1, 2, 3, 4, 5]  # x 軸資料
y=[-5, -2, 6, 12, 4, 9]  # y 軸資料
# 將字典轉成 ColumnDataSource 物件
source=ColumnDataSource(data=dict(x=x, y=y))   
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 折線圖')
# 繪製折線圖
fig.line(x='x', y='y', line_width=4, source=source)  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

此例將原始的串列放入以 'x' 與 'y' 為鍵的字典, 然後將此字典傳給 ColumnDataSource() 建構式打包成 ColumnDataSource 物件, 並將其傳給 fig.line() 的 source 參數. 注意, fig.line() 的 x 與 y 參數要設為 source 物件的 'x' 與 'y' 鍵. 結果如下 :




下面是傳入 DataFrame 的範例 :


測試 3 : 資料來源為 ColumnDataSource 物件 (DataFrame) 的折線圖 [看原始碼]   

# st-bokeh-test-3.py
import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
import pandas as pd

df=pd.DataFrame({
    'x': [0, 1, 2, 3, 4, 5],    # x 軸資料
    'y': [-5, -2, 6, 12, 4, 9]  # y 軸資料
    })
# 將 DataFrame 轉成 ColumnDataSource 物件
source=ColumnDataSource(df
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 折線圖')  
# 繪製折線圖
fig.line(x='x', y='y', line_width=4, color='red', source=source)  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

此例直接將 DataFrame 傳給 ColumnDataSource(), 結果如下 :




4. 輸出 Bokeh 散布圖 : 

Bokeh 的散布圖方法為 fig.scatter(), 用法參考 : 


下面是資料來源為串列的散布圖範例 : 


測試 4 : 資料來源為串列的散布圖 [看原始碼] 

# st-bokeh-test-4.py
import streamlit as st
from bokeh.plotting import figure

x=[0, 1, 2, 3, 4, 5]  # x 軸資料
y=[-5, -2, 6, 12, 4, 9]  # y 軸資料
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 散布圖')
# 繪製圓點散布圖
fig.scatter(x, y, marker='circle', size=10, color='red')  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

結果如下 : 




下面是資料來源為 ColumnDataSource 物件 (字典) 的散布圖範例 : 


測試 5 : 資料來源為 ColumnDataSource 物件 (字典) 的散布圖 [看原始碼] 

# st-bokeh-test-5.py
import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource   

x=[0, 1, 2, 3, 4, 5]  # x 軸資料
y=[-5, -2, 6, 12, 4, 9]  # y 軸資料
# 將字典轉成 ColumnDataSource 物件
source=ColumnDataSource(data=dict(x=x, y=y)) 
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 散布圖')
# 繪製圓點散布圖
fig.scatter(x='x', y='y', marker='square', size=10, color='navy', source=source)  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

此例改用方點 (marker='square') 為標記符號, 結果如下 : 




下面是資料來源為 ColumnDataSource 物件 (DataFrame) 的散布圖範例 : 


測試 6 : 資料來源為 ColumnDataSource 物件 (字典) 的散布圖 [看原始碼] 

# st-bokeh-test-6.py
import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
import pandas as pd

df=pd.DataFrame({
    'x': [0, 1, 2, 3, 4, 5],    # x 軸資料
    'y': [-5, -2, 6, 12, 4, 9]  # y 軸資料
    })
# 將 DataFrame 轉成 ColumnDataSource 物件
source=ColumnDataSource(df) 
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 散布圖')
# 繪製圓點散布圖
fig.scatter(x='x', y='y', marker='star', size=10, color='purple', source=source)  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

結果如下 :




5. 輸出 Bokeh 直條圖 :   

下面是資料來源為串列的散布圖範例 : 


測試 7 : 資料來源為串列的長條圖 [看原始碼] 

# st-bokeh-test-7.py
import streamlit as st
from bokeh.plotting import figure

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  # 關閉科學記號轉換
# 繪製長條圖
fig.vbar(x, top=y, width=0.5, color='cyan', line_color='blue')  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

結果如下 : 




下面是資料來源為 ColumnDataSource 物件 (字典) 的長條圖範例 : 


測試 8 : 資料來源為 ColumnDataSource 物件 (字典) 的長條圖 [看原始碼] 

# st-bokeh-test-8.py
import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource   

x=[1, 2, 3, 4, 5]  # x 軸資料
y=[120000, 135000, 99000, 150000, 170000]  # y 軸資料
# 將字典轉成 ColumnDataSource 物件
source=ColumnDataSource(data=dict(x=x, y=y)) 
# 建立 figure 物件
fig=figure(width=400, height=300, title='Bokeh 長條圖')
fig.yaxis.formatter.use_scientific=False  # 關閉科學記號轉換
# 繪製長條圖
fig.vbar(x='x', top='y', width=0.5, color='orange', line_color='black', source=source)  
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

結果如下 :




下面是資料來源為 ColumnDataSource 物件 (DataFrame) 的長條圖範例 : 


測試 9 : 資料來源為 ColumnDataSource 物件 (字典) 的長條圖 [看原始碼] 

# st-bokeh-test-9.py
import streamlit as st
from bokeh.plotting import figure
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'
# 在 Streamlit 顯示圖表
st.bokeh_chart(fig, use_container_width=False)

結果如下 :



沒有留言 :