iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Python

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

[Day21] - 範例3:股票價格表(以TSM為例)

  • 分享至 

  • xImage
  •  

今天我們試著來製作一張TSM的股票價格表(註1)。

本日成果預覽如下:
table

1. 資料收集

首先利用yfinance套件來收集TSM的股價資訊:

import polars as pl
from great_tables import GT, style, loc, nanoplot_options
import yfinance as yf


input_filename = "tsm.csv"


def _collect_data(filename: str) -> None:
    tsm = yf.download("TSM", start="2019-01-01", end="2023-12-31", 
                      interval="1mo")
    tsm.to_csv(filename)


_collect_data(input_filename)

定義一個_collect_data() 函數,接受filename為參數。此處我們選取TSM自「"2019-01-01"」到「"2023-12-31"」的每月資料,並輸出為tsm.csv

tsm.csv部份預覽如下:

Date,Open,High,Low,Close,Adj Close,Volume
...
2023-12-01,97.69999694824219,105.5199966430664,95.25,104.0,102.76931762695312,173271700

2. DataFrame製作

接著使用tsm.csv來製作出DataFrame:

month_cols = [f"{month_index:02}" for month_index in range(1, 13)]


def tweak_df(filename: str) -> pl.DataFrame:
    return (
        pl.read_csv(filename)
        .with_columns(
            pl.col("Date").cast(pl.Date),
            pl.col("Close").alias("lines"),
        )
        .group_by_dynamic("Date", every="1y")
        .agg("lines")
        .with_columns(
            pl.col("Date").dt.year().alias("year"),
            *[
                pl.col("lines").list.get(idx).alias(month)
                for idx, month in enumerate(month_cols)
            ],
            pl.col("lines").alias("bars"),
        )
        .select("year", *month_cols, "lines", "bars")
    )


df = tweak_df(input_filename)

dataframe

分段說明如下:

  • 定義一個month_cols代表「"01"」至「"12"」十二個月份。
  • 定義一個tweak_df()函數,其接收一個filename變數,並返回一個DataFrame。
    • 利用pl.read_csv()讀取之前建立的comebuy.csv
    • DatFrame.with_columns()中:
      • 將「"Date"」欄利用Expr.cast()轉為pl.Date型態。
      • 複製「"Close"」欄並命名為「"lines"」。
    • 利用DataFrame.group_by_dynamic()對「"Date"」欄以年為單位分組,並針對「"lines"」欄進行聚合。此時「"lines"」欄中的每一行皆為pl.list, 其內包含該年十二個月的「"Close"」欄位數值。
    • DatFrame.with_columns()中:
      • 利用Expr.dt.year()定義「"year"」欄。
      • 利用一個list comprehension搭配Expr.list.get()來建立「"01"」至「"12"」共十二個欄位。
      • 複製「"lines"」欄為「"bars"」欄。
    • 最後利用DataFrame.select()重新排列欄位。

3. 表格製作

在產生df之後,以下面這段程式碼來製作gt表格:

def make_gt(df: pl.DataFrame) -> GT:
    domain_min = df.select(month_cols).min().min_horizontal().item()  # 37.61
    domain_max = df.select(month_cols).max().max_horizontal().item()  # 125.94
    return (
        GT(df)
        .tab_header("TSM Stock Price", "2019 ~ 2023")
        .tab_spanner(label="Month", columns=month_cols)
        .tab_spanner(label="Trend", columns=["lines", "bars"])
        .tab_options(table_background_color="#F1F1F1")
        .data_color(
            columns=month_cols,
            palette=["#F5EFE7", "#E7CE91"],
            domain=[domain_min * 0.99, domain_max * 1.01],
        )
        .tab_style(
            style=[style.text(color="red"), style.text(weight="Bold")],
            locations=[
                loc.body(columns="02", rows=2),
                loc.body(columns="01", rows=0),
            ],
        )
        .fmt_currency(month_cols)
        .opt_all_caps()
        .fmt_nanoplot(
            "lines",
            plot_type="line",
            reference_line="mean",
            options=nanoplot_options(
                reference_line_color="black",
                show_reference_line=True,
                currency="USD",
            ),
        )
        .fmt_nanoplot(
            "bars",
            plot_type="bar",
            autoscale=True,
            options=nanoplot_options(currency="USD"),
        )
    )


gtbl = make_gt(df)

定義一個make_gt()函數,其接收df為參數並回傳GT instance。內部程式分段說明如下:

  • 使用Polars expression定義出表格中最大及最小值,將作為背景漸層的上下限使用。
  • 呼叫GT.tab_header()加入標題「"TSM Stock Price"」及副標題「"2019 ~ 2023"」。
  • 呼叫兩次GT.tab_spanner()設定「"Month"」及「"Trend"」兩個階層。
  • 呼叫GT.tab_options()設定表格背景顏色為「"#F1F1F1"」。
  • 呼叫GT.data_color()設定背景漸層為「"#F5EFE7"」~「"#E7CE91"」,這裡我習慣將最小值及最大值的上下界稍微放寬,來避免可能出現於各種計算中的小數點下誤差。
  • 呼叫GT.tab_style()標出最大及最小所在的格子。請留意這邊是直接以索引值來定位,當data不會變動的話,不失為一個快捷的定位方式。但當data有可能變動時,最大最小值將可能不在這些索引位置上。如果想在data有可能變動的情況下,仍然能夠準確定位的話,建議使用Polars expression。
  • 呼叫GT.fmt_currency()來格式化month_cols
  • 呼叫GT.opt_all_caps()來將所有的欄位都變為大寫字母。
  • 呼叫兩次GT.fmt_nanoplot()來將「"line"」及「"bars"」欄位變為兩種nanoplot。

4. 結果輸出

最後編寫一個_write_html()函數,其接收gtblfilename作為參數。其內透過呼叫gtbl.as_raw_html()將製作好的表格輸出為HTML格式並寫入filename檔案中。

output_filename = "tsm.html"


def _write_html(gtbl: GT, filename: str) -> None:
    with open(filename, "w") as f:
        f.write(gtbl.as_raw_html())


_write_html(gtbl, output_filename)

備註

註1:製表過程中,可能會有錯誤,請勿以此表為投資依據。此外,小弟並無宣導此檔股票之意,僅是希望將其作為生活化之製表範例。

Code

本日程式碼傳送門


上一篇
[Day20] - 範例2:手搖飲品表(以COMEBUY為例)
下一篇
[Day22] - 範例4:Euro NCAP 2023安全評分表(1)
系列文
眾裏尋它:Python表格利器Great Tables30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言