iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Python

眾裏尋它:Python表格利器Great Tables系列 第 27

[Day27] - 如何與Panel整合 - 靜態與動態表格

  • 分享至 

  • xImage
  •  

今天我們來說明如何將gt表格呈現於Panel app中,內容參考自官方Solar Zenith Angles範例及Panel主要維護者之一的Marc Skov Madsen在Holoviz論壇中所給出的範例(註1)。

Panel是一個類似於StreamlitGradio的web app框架,因為其內部使用Param來作為互動操作的核心,學習起來雖然有一定的難度,但是個人覺得其程式碼相對容易維護。此外,Panel與gt的搭配也比一般框架來的簡單,所以想在今天的內容分享給大家。

今天的內容將部署於Py.Cafe,是一個使用Pyodide為技術的Python app部署平台,讓我們可以在瀏覽器中直接執行Python。由於Panel可以使用Pyodide執行,所以是Py.Cafe平台推薦的選擇之一。此外,因為Polars尚無法於Py.Cafe上安裝,所以我們會使用Pandas來整理Dataframe。

今天的內容將分為四大部份:

  • 使用Pandas整理dataframe。
  • 表格製作。
  • 靜態表格展示。
  • 動態表格展示。

程式碼將存在main_static.pymain_dynamic檔中,其中動態表格成果可以於Py.Cafe中觀看

此外需留意幾乎所有的Panel app都會需要加上一行pn.extension(),其功用為呼叫所需的JavaScript程式或套件。

使用Pandas整理Dataframe

原先範例中是使用Polars整理Dataframe:

sza_pivot = (
    pl.from_pandas(sza)
    .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
    .select(pl.col("*").exclude("latitude"))
    .drop_nulls()
    .pivot(values="sza", index="month", on="tst", sort_columns=True)
)

我們使用Pandas改寫如下:

import panel as pn

from great_tables import GT, html
from great_tables.data import sza


@pn.cache
def get_sza_pivot():
    return (
        sza.assign(tst_int=lambda df_: df_.tst.astype(int))
        .query("latitude == '20' and tst_int <= 1200")
        .drop(["latitude", "tst_int"], axis=1)
        .dropna()
        .pivot(values=["sza"], index=["month"], columns=["tst"])
        .droplevel(0, axis=1)
        .reindex(
            [
                "jan",
                "feb",
                "mar",
                "apr",
                "may",
                "jun",
                "jul",
                "aug",
                "sep",
                "oct",
                "nov",
                "dec",
            ],
            axis=0,
        )
        .reset_index(names="month")
    )

我們將整理DataFrame的邏輯寫入在get_sza_pivot(),並使用pn.cache()裝飾在該函數上。

眼尖的您可能發現,今天的例子不僅是快取一個返回DataFrame的函數,我們也將整理的步驟加入其中。原因是如果DataFrame的整理邏輯不會改變的話,一起擺進快取內也是個不錯的選擇。

DataFrame整理的步驟如下:

  • 使用DataFrame.assign()新增一個「"tst_int"」,其值為將「"tst"」欄轉換為int而來(註2)。
  • 使用DataFrame.query()選擇符合條件的行數(註2)。
  • 使用DataFrame.drop()刪除「"latitude"」及「"tst_int"」欄,其中axis=1即是指定刪除欄位對象為列(axis=0則是指定刪除欄位對象為行)。
  • 使用DataFrame.dropna()刪除有缺失值的行數。
  • 使用DataFrame.pivot()為根據給定參數,重新塑造一個新的DataFrame。
  • 使用DataFrame.droplevel()刪除因為使用DataFrame.pivot()多出來的階層,其中axis=1即是指定刪除階層對象為列(axis=0則是指定刪除階層對象為行)。
  • 使用DataFrame.reindex()來重新排列索引,即原先的「"month"」欄。
  • 使用DataFrame.reset_index()來將索引「擠」回欄位中,並命名欄位名稱為原先的「"month"」。

靜態表格展示

步驟1:建立回傳表格的get_table()函數

建立get_table(),將範例中的製表程式碼存於其內,並使用get_sza_pivot()的回傳值作為被GT所包裹的DataFrame:

def get_table() -> GT:
    return (
        GT(get_sza_pivot(), rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=["rebeccapurple", "white", "orange"],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

步驟2:使用FastListTemplate呈現表格

這裡我們使用了Panel的FastListTemplate,其內部已經預設好許多樣式,且可通過參數快速調整。而我們所要做的只是需要將get_table()的回傳值main_content,指定給main參數。最後呼叫FastListTemplate.servable()來告知Panel,這將是我們想要Panel渲染的最終樣式。

# main_static.py
main_content = get_table()

pn.template.FastListTemplate(
    site="Panel",
    title="Great Tables",
    main=[main_content],
    main_layout=None,
    accent="#70409f",
).servable()

現在我們可以於命令列中執行下列指令:

panel serve main_static.py

接著打開瀏覽器前往預設網址,如http://127.0.0.1:5006/main_static,就可以看見我們於Panel中建立的靜態表格:

table

動態表格展示

接下來我們想要將靜態表格修改為動態表格,目標是希望透過新增兩個widget來選擇顏色,而表格的背景漸層顏色能做出相對應的變化。

步驟1:建立回傳表格的get_table()函數

修改get_table()函數,使其接受color1color2兩個參數,並將這兩個參數置入GT.data_color()palette參數中。

# main_dynamic.py
def get_table(color1: str, color2: str) -> GT:
    return (
        GT(get_sza_pivot(), rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=[color1, "white", color2],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

步驟2:建立顏色選擇器

使用pn.widgets.ColorPicker來做為兩種顏色的選擇器,並使用Pn.Row將兩個ColorPicker包裹起來,以及使用pn.layout.HSpacer來加上一些水平空白。

# main_dynamic.py
color1 = pn.widgets.ColorPicker(name="Color Picker1", value="#663399")
color2 = pn.widgets.ColorPicker(name="Color Picker2", value="#FFA500")
colors = pn.Row(
    pn.layout.HSpacer(),
    color1,
    pn.layout.HSpacer(),
    color2,
    pn.layout.HSpacer(),
)

Panel提供許多可用來排版的widget,其名字都非常直觀,如Pn.Row就是一個橫向的排版widget,而pn.layout.HSpacer就是施加水平空白的widget。

步驟3:建立pn.bind()連接表格與顏色選擇器

使用pn.bind()來將get_table()函數與所需傳入的顏色連結起來(註3)。

# main_dynamic.py
bind_table = pn.bind(get_table, color1, color2)
table = pn.panel(bind_table, align="center")
main_content = pn.Column(colors, table)

pn.bind()就像是Panel版本的functools.partial(),可以連結一個callable與需要變動的widget。當widget進行變動時,Panel將會負責傳遞其變動後的值給所綁定的callable。一旦綁定完成後,您可以將此綁定結果,也視為是一種widget。

接著使用pn.panel來包裹bind_table,並設定align參數為「"center"」。

再來使用pn.Columncolorstable依垂直方向排版,並命名為main_content

步驟4:使用FastListTemplate呈現表格

最後一樣使用FastListTemplate,並指定main參數為main_content後,呼叫FastListTemplate.servable()來渲染成果。

pn.template.FastListTemplate(
    site="Panel",
    title="Great Tables",
    main=[main_content],
    main_layout=None,
    accent="#70409f",
).servable()

再次於命令列中執行下列指令:

panel serve main_dynamic.py

接著打開瀏覽器前往預設網址,如http://127.0.0.1:5006/main_dynamic,就可以看見我們於Panel中建立的動態表格:

table

備註

註1:今日改寫內容已事先取得Marc同意。

註2:其實這裡如果不新增「"tst_int"」欄(int型別),而是針對「"tst"」欄(str型別),使用DataFrame.query("latitude == '20' and tst_int <= '1200'")也是可以。但我個人不是很喜歡這種依賴str型別來比較大小的寫法,比較喜歡先轉換為數字之後再來比較大小,邏輯會比較清楚。

註3:pn.bind()不是唯一的連結方式。如果您對其它方式有興趣,可以參考小弟於官方文件中改寫的範例,會說明如何使用更有效率的param.rx()來連結。

Code

本日所有程式碼可參考gt-panel repo。


上一篇
[Day26] - 如何與Django整合 - 靜態表格
下一篇
[Day28] - 如何與FastHTML整合 - 靜態表格
系列文
眾裏尋它:Python表格利器Great Tables30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言