今天我們試著來製作一張TSM的股票價格表(註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
接著使用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)
分段說明如下:
month_cols
代表「"01"」至「"12"」十二個月份。tweak_df()
函數,其接收一個filename
變數,並返回一個DataFrame。
comebuy.csv
。pl.Date
型態。DatFrame.with_columns()
中:
在產生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。內部程式分段說明如下:
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。最後編寫一個_write_html()
函數,其接收gtbl
及filename
作為參數。其內透過呼叫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:製表過程中,可能會有錯誤,請勿以此表為投資依據。此外,小弟並無宣導此檔股票之意,僅是希望將其作為生活化之製表範例。