跳到主要內容

DS 301 · 模組 1

從 Notebook 到可重複分析工作流

從定義 data product 的使用者、輸入輸出與 owner 出發,把探索程式拆成明確的 functions 與 stages,讓 config 與 secret 與程式碼分離,用 data contract 讓 pipeline 提早失敗,再於 Guided Lab 把 notebook 改寫成 pipeline。

開始第一節
已完成 0 / 5
同步我的進度加入梯次後,進度可跨裝置保存,講師也能看到

資訊

加入後,你已完成的模組編號會連同「學員代碼」傳送給講師,講師可以看到你的進度。請使用講師提供的代碼,不要輸入姓名、公司機密或其他個資。進度資料與該梯次的其他資料一起保存。

學習目標

  • 定義使用者、輸入、輸出、freshness、品質期望與 owner
  • 將探索程式拆成具明確 input/output、可重跑的 stages
  • 設定與程式分離;禁止 hardcode secrets;理解 dev/test/prod
  • schema/quality 不符時提早失敗,區分 warning 與 blocking error
  • 建立 ingest→validate→transform→analyze→publish stages 與 checkpoints

什麼是 Data Product / Analytics Workflow

40 分鐘

什麼是 Data Product / Analytics Workflow

學習目標:定義使用者、輸入、輸出、freshness、品質期望與 owner。

你每個月手動跑一份 notebook,產出一張給主管看的月報表。上個月你請假,同事臨時接手,卻不知道這份 notebook 該用哪一份原始資料、輸出要放在哪裡、多久要更新一次,也不知道出錯了要找誰。這份分析從來沒有真正「產品化」:它只活在你的腦中與你的筆電裡。Data product 的第一步,不是寫程式,而是把這些隱性知識寫下來:誰會用這份輸出、他們期待多新的資料、什麼品質才算可信、誰對它的正確性負責。

Data product 的五個起手式問題
問題這門課資料包的答案
使用者是誰每月營運會議的主管與各業務單位窗口
輸入是什麼月度分析資料包:run_id, source_system, batch_id, event_time, ingested_at, entity_id, metric, value, schema_version
輸出是什麼一張依 source_system 與 metric 彙總的月度指標表,附驗證紀錄
Freshness 期望每月第 3 個工作天前完成上月資料
Owner負責維護、修 bug、回答「這個數字怎麼來的」的那個人
本課程的資料集(先看 schema,再談 data product)
import pandas as pd

# clean:月度分析資料包,一列代表某個來源系統在某個 batch 下、某個實體的一筆指標觀測
df = pd.read_csv("DS301_Analytics_Package_Clean.csv", parse_dates=["event_time", "ingested_at"])
df.shape
df.dtypes
df[["run_id", "source_system", "batch_id", "metric", "value", "schema_version"]].head()

# 先問:這份資料的 owner 是誰?如果 schema_version 變了,誰該被通知?
df["schema_version"].unique()

一次性 notebook

「我跑一份 notebook,把結果貼進報告」。沒有人知道下次誰來跑、輸入資料多舊算過期、輸出品質怎麼算合格。

Data product

使用者與用途
寫清楚誰會用這份輸出、拿來做什麼決策。
輸入輸出契約
明確定義輸入資料的 schema 與輸出的格式、位置與更新頻率。
品質與 owner
定義什麼叫「這份輸出可信」,以及誰對它的正確性負責、誰接得住失敗。

資訊

Human, AI, Human:人定義 data product 的使用者、用途與品質期望;AI 可以協助草擬 pipeline 程式碼;人負責驗證輸出是否符合契約,並對交付結果負責。
  • 只有你知道要去哪裡抓原始資料,流程全部記在腦袋裡
  • 沒有人知道這份輸出多久要更新一次,也沒有人檢查它是否真的如期更新
  • 「這個數字對不對」只能靠原作者口頭保證,沒有任何驗證紀錄
  • 換一台機器或換一個人執行,就會因為路徑或帳密問題直接失敗
自我檢測

一份月度分析 notebook 只存在原作者的筆電裡,沒有人知道它的輸入來源、更新頻率與品質標準。這份分析缺少的最核心的東西是什麼?

一份月度分析 notebook 只存在原作者的筆電裡,沒有人知道它的輸入來源、更新頻率與品質標準。這份分析缺少的最核心的東西是什麼?

重點: 能完成步驟不代表結果可信;本節的能力必須能被驗證、重做並清楚說明。

Notebook → Functions → Stages

45 分鐘

Notebook → Functions → Stages

學習目標:將探索程式拆成具明確 input/output、可重跑的 stages。

探索階段的 notebook 常常是一長串由上到下執行的 cell:讀資料、清理、篩選、彙總、畫圖全部混在一起,變數互相依賴,改了第 3 格就要重跑後面所有格子。這種寫法適合探索,但不適合變成可靠的工作流:沒有人能只重跑「清理」這一步而不影響其他部分,也沒辦法針對某一步單獨寫測試。把探索程式拆成 functions 與 stages,是把「一長串魔法」變成「一串可以個別檢查、個別重跑的積木」。

從一長串 cell 到有明確 input/output 的 stage
# 探索階段:一長串互相依賴的 cell(難以重跑、難以測試)
df = pd.read_csv("DS301_Analytics_Package_Clean.csv")
df = df[df["schema_version"] == "v2"]
df["value"] = df["value"].fillna(0)
summary = df.groupby(["source_system", "metric"])["value"].sum()

# 拆成 stage:每個 function 有清楚的 input 與 output,可以個別測試、個別重跑
def load_raw(path: str) -> pd.DataFrame:
    return pd.read_csv(path, parse_dates=["event_time", "ingested_at"])

def filter_current_schema(df: pd.DataFrame, version: str) -> pd.DataFrame:
    return df[df["schema_version"] == version].copy()

def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame:
    df["value"] = df["value"].fillna(0)
    return df

def summarize_by_metric(df: pd.DataFrame) -> pd.DataFrame:
    return df.groupby(["source_system", "metric"], as_index=False)["value"].sum()
拆成 function 之後帶來的工程價值
價值說明
明確介面每個 function 的輸入輸出型態清楚,別人不用讀完整段程式就知道它做什麼
可單獨測試可以只針對 fill_missing_values 寫測試,不需要先跑完整個 pipeline
可單獨重跑summarize_by_metric 出錯時,不需要重新讀取與清理整份資料
可重用同一個 filter_current_schema 可以被其他 pipeline 引用,不必複製貼上
  1. 找出自然的斷點
    在探索 notebook 中找出讀取、清理、篩選、彙總等自然階段的邊界。
  2. 為每個階段寫一個 function
    明確定義輸入型態與輸出型態,避免依賴全域變數。
  3. 移除隱性依賴
    確認每個 function 不依賴前一個 cell 遺留在記憶體中的變數。
  4. 為關鍵 function 加上測試
    至少為一個容易出錯的 stage(如清理或聚合)寫一個簡單測試。

注意

拆 function 不是為了讓程式碼「看起來更專業」,而是為了讓每個 stage 可以被單獨驗證、單獨重跑。如果拆完之後仍然只能整份重跑,等於沒有真正拆開依賴關係。
拆開之後如何縮小 bug 範圍

彙總結果出現負的加總值。因為每個 stage 都有明確輸出,你可以依序檢查:load_raw() 的 row 數量正常嗎?filter_current_schema() 篩完後的列數合理嗎?fill_missing_values() 是否把本來不該填的值也填成了 0?只要針對每個 function 單獨印出中間結果,就能在幾分鐘內定位出問題出在哪一個 stage,而不必從頭讀一長串混在一起的程式碼。

自我檢測

把一份探索 notebook 拆成多個 function 之後,最主要能得到的工程價值是什麼?

把一份探索 notebook 拆成多個 function 之後,最主要能得到的工程價值是什麼?

重點: 能完成步驟不代表結果可信;本節的能力必須能被驗證、重做並清楚說明。

Configuration、Secrets 與 Environments

40 分鐘

Configuration、Secrets 與 Environments

學習目標:設定與程式分離;禁止 hardcode secrets;理解 dev/test/prod。

同事把資料庫密碼直接寫進 notebook 的第一格,方便自己開發時貼上就能跑。三個月後這份 notebook 被分享給另一個團隊做教學示範,密碼也跟著一起外流。更常見的問題是:同一段程式碼在你的筆電上跑得動,換一台機器、換一個資料庫就整段失敗,因為路徑、帳密、資料表名稱全部寫死在程式碼裡。把 configuration 與 secrets 從程式碼中抽離,是讓同一份程式碼可以安全地在 dev、test、prod 三種環境下運作的前提。

從 hardcode 到 config/secret 分離
# 錯誤做法:帳密與環境設定直接寫死在程式碼裡
DB_PASSWORD = "sup3r-secret-2026"  # 絕對不要這樣做
INPUT_PATH = "/Users/alice/Desktop/data.csv"

# 正確做法:從環境變數 / secret 管理機制讀取,程式碼本身不含任何機密
import os

DB_PASSWORD = os.environ["DS301_DB_PASSWORD"]  # 由 secret 管理機制注入,不寫入版本控制
ENVIRONMENT = os.environ.get("DS301_ENV", "dev")  # dev / test / prod
INPUT_PATH = os.environ["DS301_INPUT_PATH"]  # 依環境不同指向不同資料來源
dev / test / prod 三種環境的差異
環境典型用途資料來源
dev開發與除錯,可以隨意重跑、修改小型樣本或合成資料
test驗證 pipeline 邏輯是否正確,接近正式環境設定固定的測試資料集,結果可預期
prod產出真正交付給使用者的結果正式資料來源,變更需要審核
  1. 找出所有寫死的值
    檢查程式碼中的密碼、路徑、資料表名稱是否直接寫在程式裡。
  2. 搬進環境變數或 config 檔
    把這些值改成從環境變數或設定檔讀取,程式碼本身不再包含機密。
  3. 區分 dev/test/prod 設定
    確保同一份程式碼可以透過不同的環境設定,在不同環境下正確運作。
  4. 確認 secret 不會被提交
    檢查版本控制設定,確保 secret 檔案不會被意外提交進版本紀錄。

注意

任何寫進程式碼、notebook 或提交進版本控制的密碼、API key 或 token,都應視為已經外洩,需要立即撤銷並更換,而不是事後刪除那一行程式碼就結束。

只用環境變數

  • 設定簡單,適合小型團隊或單人專案
  • 需要自行確保每台機器、每個 CI 環境都正確設置變數
  • 沒有內建的存取紀錄或自動輪替機制

搭配 Secret 管理服務

  • secret 集中存放,可以設定存取權限與到期時間
  • 可以記錄誰在何時讀取過哪一個 secret
  • 支援自動輪替,降低憑證長期不變的風險
自我檢測

一份 pipeline 程式碼裡直接寫著資料庫密碼字串,方便開發時使用。這樣做的主要風險是什麼?

一份 pipeline 程式碼裡直接寫著資料庫密碼字串,方便開發時使用。這樣做的主要風險是什麼?

重點: 能完成步驟不代表結果可信;本節的能力必須能被驗證、重做並清楚說明。

Data Contracts 與 Pipeline Validation

45 分鐘

Data Contracts 與 Pipeline Validation

學習目標:schema/quality 不符時提早失敗,區分 warning 與 blocking error。

上游系統在沒有通知任何人的情況下,把 metric 欄位的其中一個值從 "conversion_rate" 拼成了 "converion_rate"。你的 pipeline 沒有檢查這件事,直接把資料丟進彙總邏輯,結果那筆指標被靜靜地歸到「其他」類別,月報表少算了一整條產品線的轉換率,而且完全沒有跳出任何錯誤。Data contract 就是把「這份資料應該長什麼樣子」寫下來,並在資料違反契約時提早、大聲地失敗,而不是讓錯誤的資料悄悄流進下游。

用 contract 在資料進入 pipeline 前先驗證
ALLOWED_METRICS = {"conversion_rate", "active_users", "avg_response_ms"}
REQUIRED_COLUMNS = {"run_id", "source_system", "batch_id", "event_time", "ingested_at", "entity_id", "metric", "value", "schema_version"}

def validate_contract(df: pd.DataFrame) -> list[str]:
    errors = []
    missing = REQUIRED_COLUMNS - set(df.columns)
    if missing:
        errors.append(f"缺少必要欄位(blocking):{sorted(missing)}")

    unknown_metrics = set(df["metric"].unique()) - ALLOWED_METRICS
    if unknown_metrics:
        errors.append(f"出現未知的 metric 值(blocking):{sorted(unknown_metrics)}")

    null_ratio = df["value"].isna().mean()
    if null_ratio > 0.2:
        errors.append(f"value 缺值比例過高(warning,需人工確認):{null_ratio:.1%}")

    return errors

errors = validate_contract(df)
blocking = [e for e in errors if "blocking" in e]
if blocking:
    raise ValueError(f"Pipeline 因 contract 違反而提早失敗:{blocking}")
Warning 與 blocking error 的區分
類型範例處理方式
Blocking error缺少必要欄位、metric 值不在允許清單內停止 pipeline,禁止不完整或不可信資料繼續流入下游
Warningvalue 缺值比例偏高但仍在可接受範圍記錄警告、繼續執行,但提醒負責人事後確認
  • 必要欄位清單與各欄位的資料型態
  • 允許的類別值清單(例如 metric 可以出現哪些值)
  • 數值範圍與缺值比例的可接受上限
  • 違反規則時該視為 warning 還是 blocking error
  • schema 變更時的通知流程與版本號規則

注意

沒有 contract 檢查的 pipeline,不會因為資料變質而報錯:它只會安靜地產出錯誤的結果。提早失敗看似「浪費一次執行」,但遠比讓錯誤結果流到使用者手上便宜。
自我檢測

上游資料的 metric 欄位出現一個從未見過、不在允許清單內的值。依 data contract 的原則,pipeline 應該怎麼處理?

上游資料的 metric 欄位出現一個從未見過、不在允許清單內的值。依 data contract 的原則,pipeline 應該怎麼處理?

重點: 能完成步驟不代表結果可信;本節的能力必須能被驗證、重做並清楚說明。

Guided Lab:把 Notebook 改成 Pipeline

70 分鐘

Guided Lab:把 Notebook 改成 Pipeline

學習目標:在教師引導下,建立 ingest→validate→transform→analyze→publish stages 與 checkpoints。

資訊

實作問題:如果你今天請假,另一個人要接手執行你的月度分析,你的程式碼能不能讓他在不問你任何問題的情況下,安全地重新跑一次並且知道哪裡可能出錯?
  1. Ingest
    把讀取原始資料的邏輯獨立成一個 stage,只負責讀取,不做任何清理或計算。
  2. Validate
    在資料進入後續處理前,套用 data contract 檢查,違反 blocking 規則時提早失敗。
  3. Transform
    把清理、篩選、型態轉換等邏輯獨立成 stage,維持每個 function 的明確 input/output。
  4. Analyze
    把彙總與計算邏輯獨立成 stage,並在此加入一個 checkpoint(例如列數、加總值)確認結果合理。
  5. Publish
    把輸出寫入最終位置的邏輯獨立成 stage,並記錄這次執行使用的 config 與環境。
Guided Lab 應包含的最少內容(請填入你自己的輸出結果)
項目要回報的內容
五個 stage你各自命名的 ingest / validate / transform / analyze / publish function
至少一個 checkpoint你在 analyze 階段檢查的列數或加總值,以及它是否符合預期
Config/secret 處理你如何把帳密與路徑從程式碼中抽離
仍待人工判斷的項目至少一項需要業務端確認的假設或決策
  • 把 notebook 的 cell 直接複製成 function,卻忘了移除對全域變數的依賴
  • validate stage 只是形式上存在,沒有真正套用 contract 檢查
  • 把 config 或 secret 直接寫在 pipeline 程式碼裡,而不是從環境變數讀取
  • 完全沒有設 checkpoint,只看 pipeline 有沒有報錯,不看中間結果是否合理

注意

「拆成好幾個 function」不等於「pipeline 就可靠」。每個 stage 都要有明確的 input/output,並且要能在不重跑前面步驟的情況下單獨重跑。

完成

完成後請自問:如果換一個人接手,他能不能只看你的 stage 名稱與 checkpoint,就大致理解這個 pipeline 在做什麼、哪裡可能出錯?
自我檢測

你完成了 Guided Lab,把 notebook 拆成 ingest/validate/transform/analyze/publish 五個 stage,但 validate stage 目前是空的,沒有真正檢查任何 contract 規則。這樣算完成本節的核心能力嗎?

你完成了 Guided Lab,把 notebook 拆成 ingest/validate/transform/analyze/publish 五個 stage,但 validate stage 目前是空的,沒有真正檢查任何 contract 規則。這樣算完成本節的核心能力嗎?

重點: 能完成步驟不代表結果可信;本節的能力必須能被驗證、重做並清楚說明。

接下來做什麼?