Back to Blog
前端工程師轉 DE:我用 Streamlit Dashboard 拆解了一條醫療數據 Pipeline
📋 Case Study

前端工程師轉 DE:我用 Streamlit Dashboard 拆解了一條醫療數據 Pipeline

B
Blake
Feb 27, 2026 By Blake 9 min read
一個前端工程師在轉職 Data Engineer 的路上,不靠 Dagster UI,自己寫了一個 5 頁的 Streamlit Dashboard 來理解醫療級數據 Pipeline 的每一層。從 Raw Data 到 Partitioning、CGM 補值、業務分析、到全局瓶頸 — 用視覺化的方式搞懂資料怎麼流、怎麼變、哪裡會爆。

聲明: 本專案為個人學習用途的 side project。所有數據皆為程式產生的 synthetic data,不包含任何真實患者資料。架構設計參考公開的醫療數據工程實務,不代表任何特定公司的內部實作。

背景:我的 mentor 說了一句話

「工程師不能只跑數字,要能解釋數字。」

這是我 mentor 給我的第一課。他不讓我碰 Dagster UI,也不讓我花時間在 Airflow 上。他說:「工具會換,但你對資料的理解不會。」

問題是 — 我是前端出身,看到 Polars 的 group_by().agg() 像在讀天書。Pipeline 跑完吐出一堆數字,我知道它跑了,但不知道它做了什麼

所以我決定用我擅長的方式來學:寫一個 Dashboard

Glu_A

我的 Pipeline 長什麼樣

先講一下我在拆解的東西。我根據公開的醫療數據工程實務,自己設計了一條 CGM(連續血糖監測)數據 Pipeline,用程式產生 500 位虛擬患者 90 天的 synthetic 血糖數據。

Generator → Ingestion → Quality (補值) → Analytics
  500 users    4 assets    1 asset        3 assets
  966K CGM     分區寫入     補值到 1.08M    聚合成報告

8 個 Dagster Asset、3 層架構、16.5 秒跑完。聽起來不大,但裡面藏了很多值得挖的東西。


Page 1: Raw Data — 第一個發現:資料本來就有缺

我以為 Generator 會給我完美的資料,但打開一看:

指標

理論值

實際值

差異

CGM Readings

1,080,000

966,799

-10.5%

少了 11 萬筆。

原因是我在 Data Generator 中刻意模擬了 CGM 感測器的真實世界問題 — 感測器會斷訊、會脫落、會沒電。每個 user 有 2-5 次 gap event,加上 5-15% 的隨機 drop。這些模擬規則參考了公開的 CGM 臨床文獻。

這是我學到的第一件事:真實世界的資料從來不是完整的。Pipeline 的 Quality 層存在的意義,就是處理這些「不完美」。

當我選了一個 user 畫出他 90 天的血糖曲線,那些斷掉的線段就是 gap — 非常直觀。

Glu_B

Page 2: Partitioning — 第二個發現:35.6 倍的 Skew 是假的

Ingestion 層做的事很簡單:把一個大檔拆成按 country × month 分區的小檔。

data/iceberg/cgm_readings/
├── country=TW/month=2026-01/data.parquet  (60K rows)
├── country=TW/month=2025-11/data.parquet  (4K rows)
├── country=SG/month=2025-11/data.parquet  (1.7K rows)
└── ...(共 16 個 partition)

Dashboard 上的 Heatmap 一打開,我就看到一個嚇人的數字:Data Skew = 35.6x。最大的 partition 是最小的 35.6 倍。

但我學到不能只看數字。拆開分析:

  • 2025-11 只有 3 天資料(邊界月),所以那些 partition 本來就小

  • 排除邊界月後,真實 skew 只有 2.9x — 反映 TW 37.8% vs SG 15.8% 的用戶分布

這是 mentor 說的「解釋數字」。如果有人問你 skew 多少,你不能只說 35.6x,要能拆出「邊界效應」和「業務現實」兩個因素。

Glu_C

我還做了 Partition Pruning 模擬器 — 選一個 country + month,Dashboard 會即時算出跳過了多少 I/O。查 TW 的 2026-01 只需要讀 6% 的資料,省了 93% I/O

Glu_D

Page 3: Imputation — 第三個發現:最重要的一頁

這是整個 Dashboard 最核心的頁面。Quality 層對 CGM 的 gap 做醫療級補值,規則來自 FDA CGM Guidance + ATTD Consensus

Gap ≤ 30 min  → Linear Interpolation(血糖短期近似線性)
Gap 30min-2hr → LOCF(前值遞補,保守策略)
Gap > 2hr     → NULL(不補,避免 survivorship bias)

Dashboard 上的 Pie Chart 顯示了一個反直覺的結果:Linear Interpolation = 0

為什麼?因為 Demo 的 interval 是 60 分鐘(每小時一筆),所以最小的 gap = 120 分鐘,已經超過 30 分鐘門檻。Production 用 5 分鐘 interval 時,短 gap 才會觸發 Linear Interpolation。

最直觀的視覺化是單一 User 的補值前後對比:

  • 藍色線 = 原始讀數

  • 紅色 × = LOCF 補值(出現在藍色線斷掉的地方)

  • 灰色 △ = NULL(gap 太大,不補)

Glu_E

看到這張圖的瞬間,我才真正理解「Last Observation Carried Forward」是什麼意思 — 它就是把斷訊前的最後一個值,直接複製到 gap 裡面


Page 4: Analytics — 第四個發現:百萬行壓成 90 行

Analytics 層做的是 multi-table join — 把 users(500)、glp1_logs(1,080)、weight_logs(6,000)三張表 JOIN 起來,算出每位 GLP-1 用藥者的療效。

輸出只有 90 行(因為只有 90 位 GLP-1 用戶)。從百萬級壓到兩位數,這就是聚合的力量。

Dashboard 上的散點圖讓我看到 adherence rate 和 weight loss 的關係,雖然模擬資料的相關性不強,但 join 邏輯是對的 — 這才是 POC 的價值。

藥物比較也很有趣:模擬數據中 Mounjaro 的副作用率 21.4% 最低,Adherence 也最高。當然這是 synthetic data 的結果,真實世界的藥物比較需要 RCT(隨機對照試驗)才有臨床意義。

Glu_F

Page 5: Pipeline Overview — 第五個發現:一個 Asset 吃掉九成時間

最後一頁把所有認知串在一起。

時間瀑布圖上,imputed_cgm_readings 像一根巨大的橫條,佔了 89.8% 的時間。其他 7 個 Asset 加起來只佔 10%。

時間

佔比

Ingestion (4 assets)

1.08s

6.6%

Quality (1 asset)

14.77s

89.8%

Analytics (3 assets)

0.41s

2.5%

原因是 Python for-loop — 逐 user 處理 500 個用戶,每次都要把 Polars DataFrame 序列化成 Python dict,再逐行產生補值結果。用 Python 的方式操作了一個 Rust 引擎的工具

Glu_G

Sankey 資料流量圖讓我看到另一個視角 — CGM 的 966K rows 是絕對大宗,其他表都是千位數以下。Pipeline 的「胖瘦」一目了然。

Glu_H

規模推算表告訴我:100K users 時 imputation 會跑 ~49 分鐘。不優化就上 Production 是不行的

優化路線:

  1. 短期:Python for-loop → Polars group_by().map_groups() — 預估 5-10x 提升

  2. 中期:Eager → Lazy API — 再 2-3x

  3. 長期:上 Spark — 水平擴展


我學到了什麼

Dashboard 教會我的

回到 mentor 的那句話:「工程師不能只跑數字,要能解釋數字。」

5 頁 Dashboard 下來,我可以回答:

  • Pipeline 有幾個 Asset?各層做什麼?(8 個,3 層)

  • 瓶頸在哪?為什麼?(imputation 90%,Python for-loop)

  • Data Skew 多少?怎麼解讀?(35.6x 是假象,排除邊界月後 2.9x)

  • 補值規則是什麼?為什麼 Linear = 0?(interval 60min > 30min 門檻)

  • Production 怎麼 scale?(Polars lazy → Spark)

Dashboard 沒教會我的

但我也很誠實地面對一件事:我還不會用程式碼操作這些資料

我看得懂散點圖上每個點的意義,但寫不出 pl.col("adherence_rate").mean().over("country")。我知道 Partition Pruning 省了 93% I/O,但說不清楚 Parquet 的 column pruning 為什麼比 CSV row scan 快。

Dashboard 讓我有了「資料直覺」— 我知道每行 code 應該產出什麼結果。接下來的 Module 1-3,我要帶著這份直覺去真正寫 code、做優化、拆成本。

給同樣在轉職的人

如果你也是前端轉 DE,我的建議是:從你擅長的工具開始

我用 Streamlit 而不是 Jupyter,因為我對「做出可互動的頁面」有直覺。每一頁的 st.selectbox + st.plotly_chart 都是我熟悉的 UI pattern,但背後的 pl.read_parquet() + pl.filter() + pl.group_by() 是新的。


Tech Stack

工具

用途

Streamlit

Dashboard framework — 純 Python,30 行就能出一頁

Polars

DataFrame 操作 — Rust 引擎,比 Pandas 快

Plotly

互動式圖表 — scatter、bar、heatmap、sankey

Graphviz

DAG 視覺化 — 畫 Pipeline 依賴圖

Dagster

Pipeline 編排 — 8 個 Asset 的定義和排程

Parquet + Hive Partitioning

儲存格式 — column-oriented + partition pruning

Enjoyed this article? Show some love!

0
Clap

Enjoyed this article?

Subscribe for engineering notes and AI development insights

We respect your privacy. No spam, unsubscribe anytime.

Share this article

Comments