iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
佛心分享-SideProject30

30 天打造公開資料版急診檢傷系統:Side Project 與實驗計畫系列 第 11

Day 11|打開 Kaggle KTAS:1,267 筆專家重標急診資料導讀

  • 分享至 

  • xImage
  •  

韓國急診檢傷與急迫度分級量表(Korean Triage and Acuity Scale, KTAS)是一套以症狀為導向的五級急診檢傷制度。第一級最緊急,第五級最低;它的目的不是替病患下診斷,而是決定誰需要更快接受評估與處置。

今天要看的 Kaggle 公開資料只有 1,267 筆、24 個欄位,原始壓縮檔中也只有一個 data.csv。檔案看起來不複雜,但真正的第一個問題不是「怎麼訓練模型」,而是:24 個欄位中,哪些資訊在作出檢傷級數前已經知道?

例如,同一列同時有病患主訴、血壓、護理師登錄級數、專家重新判定級數、急診診斷、住院去向與急診停留時間。如果把整列直接交給模型,模型可能利用診斷或住院結果猜級數。這種高分不是模型學會檢傷,而是模型偷看了決策之後才會知道的資訊。

下圖先把整件事畫成一張工作台。左側是到院時能取得的資料,中間是兩種不同角色的人工級數,右側紅色盒子則隔離診療後才形成的結果。

研究工作台左側是主訴、意識、疼痛與生命徵象圖卡,中間有兩組五層級數牌,右側診斷、住院與時間結果被鎖在紅色隔離盒,場域與人流資料另放在稽核區

上圖的重點是「時間」,不是欄位和嚴重度的相關性。即使住進加護病房和高急迫度高度相關,只要住院去向在檢傷決定後才形成,就不能成為檢傷輸入。


今天要完成什麼

讀完 Day 11 後,你應該能夠:

  1. 從 Kaggle 取得 KTAS 的 data.csv,確認分隔符、文字編碼與檔案版本。
  2. 說明 1,267 筆紀錄如何從兩所急診的抽樣資料形成。
  3. 把 24 個欄位分成候選輸入、稽核欄位、人類參考、評估目標、事後結果與衍生評估欄位。
  4. 說明 KTAS_RNKTAS_expertError_groupmistriage 的不同角色。
  5. 找出空白以外的「無法測量」代碼,避免把文字誤當生命徵象。
  6. 執行一支有欄位白名單與黑名單的資料準備程式,確認模型輸入沒有偷看到答案。

本篇會用到的名詞

中文名稱 英文全名/縮寫 本篇用途
韓國急診檢傷與急迫度分級量表 Korean Triage and Acuity Scale, KTAS 本篇資料所使用的五級檢傷制度
急診部門 Emergency Department, ED 產生這批病歷紀錄的臨床場域
疼痛數字評定量表 Numeric Rating Scale, NRS 以 0 至 10 分描述病患自述疼痛強度
收縮壓 Systolic Blood Pressure, SBP 心臟收縮時的動脈壓力
舒張壓 Diastolic Blood Pressure, DBP 心臟舒張時的動脈壓力
心率 Heart Rate, HR 每分鐘心跳次數
呼吸率 Respiratory Rate, RR 每分鐘呼吸次數
體溫 Body Temperature, BT 本篇生命徵象欄位之一
周邊血氧飽和度 Peripheral Oxygen Saturation, SpO2 以脈搏血氧儀估計的血氧比例;CSV 欄名是 Saturation
參考標籤 Reference Label 評估模型預測時用來比較的級數
資料洩漏 Data Leakage 模型在訓練或測試時看到決策當下不該知道的資訊
檢傷過度 Overtriage 把病患判得比參考級數更緊急
檢傷不足 Undertriage 把病患判得比參考級數不緊急
逗號分隔值 Comma-Separated Values, CSV Kaggle 原始檔使用的表格格式;本檔實際以分號切欄
Windows-1254 Windows-1254 / cp1254 正確解碼 Kaggle CSV 中特殊字元所需的文字編碼

這份詞彙表先提供快速索引。下面遇到每個名詞時,還會說明它在資料處理流程中的實際作用。

先確認這 1,267 筆資料從哪裡來

本篇使用 Kaggle:Emergency Service - Triage Application提供的 data.csv。Kaggle 頁面將資料出處連回 PLOS ONE 在 2019 年發布的 KTAS 檢傷準確性研究。來源研究採回溯方式檢視急診病歷,也就是在臨床流程完成後,回頭分析既有紀錄,而不是讓第二組人員在現場重新檢傷病患。

急診部門(Emergency Department, ED)就是病患接受緊急初步評估與治療的場域。資料來自韓國兩所都市教學醫院:一所區域急診每年約 45,000 次就醫,一所地方急診每年約 40,000 次就醫。這兩所醫院的工作流程並不完全相同,因此 CSV 保留 Group 欄位,讓研究者知道資料來自哪一種場域。

1,540 筆如何變成 1,267 筆

原始研究的資料形成順序如下:

  1. 研究期間是 2016 年 10 月 1 日到 2017 年 9 月 30 日。
  2. 研究者用亂數產生器選出 20 天,而不是收進期間內的每一天。
  3. 收集這 20 天內,年齡大於 15 歲、到兩所急診就醫的 1,540 份病歷。
  4. 排除檢傷作業時間達 30 分鐘以上的 63 份紀錄,因為延遲完成的紀錄可能已受到其他人建議後修改。
  5. 排除掛號後、檢傷前取消的 119 份紀錄。
  6. 排除資訊不足、無法讓專家重新判定級數的 91 份紀錄。
  7. 最後留下 1,267 份可分析紀錄。

因此,1,267 不是兩所醫院整個年度的完整急診母體,也不是隨機抽出 1,267 位已確認互不重複的病患。CSV 沒有病患識別碼,我們只能確定有 1,267 列病歷紀錄,不能證明每一列都來自不同病患。

這個限制會影響後續資料切分:沒有病患識別碼,就不能保證同一病患的重複就醫不會同時落入訓練與測試集合。Day 15 設計切分策略時,必須把這件事明確列為限制,不能宣稱完成病患層級切分。

專家級數是怎麼建立的

研究團隊挑選三位急診檢傷專家。入選條件包括急診護理背景、KTAS 提供者與講師資格、至少七年急診經驗,以及良好的檢傷能力。三位專家的急診經驗介於 12 到 18 年。

在正式檢視病歷前,研究團隊先用九個病患情境檢查三位專家的判定一致性。研究報告的組內相關係數(Intraclass Correlation Coefficient, ICC)為 0.92。ICC 在這裡用來表示三位專家對相同情境的評分有多一致;數值越接近 1,一致程度越高。

接著,專家依初始護理紀錄重新判定 KTAS 級數。CSV 把急診護理師當下登錄的級數放在 KTAS_RN,把研究定義的專家重新判定級數放在 KTAS_expert。這兩欄都很重要,但用途完全不同,後面會再逐一拆解。

Kaggle 檔案與來源研究要一起記錄

本系列的病患資料下載位置固定在 Kaggle,並以來源研究補充抽樣方法、專家資格與限制。這兩個角色不能混在一起:

  • Kaggle 資料頁負責提供本系列實際使用的 data.csv
  • Moon 等人的 PLOS ONE 文章負責說明資料如何形成,以及護理師與專家級數如何比較。

我們不會把逐筆 CSV 放進專案的版本控制。專案只保存 Kaggle slug、下載日期、檔案雜湊、欄位政策、處理程式與聚合統計。這樣既能讓讀者重新取得資料,也能避免自己的專案變成另一個來源不明的資料鏡像。

實際取得並驗證 Kaggle CSV

以下步驟都在專案根目錄執行。data/raw/ 已被 .gitignore 排除,因此下載檔不會進入版本控制;但執行 git status 前仍應再次確認。

步驟一:建立本機原始資料目錄

  • 目的:把 Kaggle 原始檔與程式、文章及圖片分開保存。
  • 前置條件:終端機目前位於專案根目錄。
  • 輸入:沒有檔案輸入。
  • 操作:建立 data/raw/ktas/
mkdir -p data/raw/ktas
  • 預期輸出:指令不會印出文字,但目錄會存在。
  • 驗證方式:執行 test -d data/raw/ktas && echo "目錄已建立",應看到「目錄已建立」。

步驟二:從 Kaggle 下載並解壓縮

  • 目的:取得本系列固定使用的 Kaggle 資料版本。
  • 前置條件:電腦能使用 curlunzip,並可連上 Kaggle。
  • 輸入:Kaggle 公開資料集下載端點。
  • 操作:下載壓縮檔,再解壓縮到原始資料目錄。
curl -L \
  'https://www.kaggle.com/api/v1/datasets/download/ilkeryildiz/emergency-service-triage-application' \
  -o data/raw/ktas/ktas-kaggle.zip
unzip data/raw/ktas/ktas-kaggle.zip -d data/raw/ktas
  • 預期輸出data/raw/ktas/ 會出現 ktas-kaggle.zipdata.csv
  • 驗證方式:執行 ls -lh data/raw/ktas/,確認兩個檔案都不是 0 位元組。若 Kaggle 調整下載流程,請依資料頁當下提供的方式取得同一個 dataset slug,再核對下一步雜湊。

步驟三:核對 SHA-256 檢查碼

安全雜湊演算法 256 位元版本(Secure Hash Algorithm 256-bit, SHA-256)會把檔案內容轉成固定長度的十六進位字串。只要檔案有一個位元不同,檢查碼通常就會改變,因此可以用來鎖定本次使用的 Kaggle 版本。

  • 目的:確認下載內容和本系列在 2026 年 8 月 2 日稽核的檔案相同。
  • 前置條件:壓縮檔與 data.csv 已下載完成。
  • 輸入:這兩個檔案。
  • 操作:執行下列指令。
shasum -a 256 data/raw/ktas/ktas-kaggle.zip data/raw/ktas/data.csv
  • 預期輸出:檔名旁應分別出現:
f78df0a06c31b6df873c410b0596ecb95ac9335485beaf890dcabf7c69560f26  data/raw/ktas/ktas-kaggle.zip
0e2c088e358fd4cdfd0dcc2fd4c2f085aa303856e3a6df8ceb44a69cef8ed2de  data/raw/ktas/data.csv
  • 驗證方式:若任一字串不同,先重新下載並確認 dataset slug,不要直接改設定檔去配合未知檔案。Kaggle 若真的更新資料,應先留下新版本、差異與稽核日期,再決定是否升級。

24 個欄位先分角色,不先刪欄位

data.csv 第一列是欄名,後面 1,267 列是紀錄,共有 24 欄。雖然副檔名叫 CSV,實際上不是使用逗號,而是使用分號 ; 切欄;若直接採用預設逗號讀取,整列可能被錯誤讀成單一欄位。

CSV 也不是完整資料字典。Kaggle 頁面解釋多數類別代碼,但不代表每個數值欄位的單位、收集細節與字串正規化都已完整說明。正確作法是把「來源未定義」寫進欄位政策,並限制可支持的結論。

下圖把 24 欄分成四個可閱讀的區域。請先看每個區域的角色,再看欄位名稱。

四區卡片把二十四個欄位分成檢傷當下候選輸入、稽核分層、人類參考與評估目標,以及事後結果和衍生誤差欄位

上圖顯示,Group 和每小時到院人數即使在資料裡,也沒有放進預設模型輸入。另一方面,診斷、去向和停留時間不是因為「品質不好」才排除,而是因為它們的取得時點太晚。

檢傷當下候選輸入:14 欄

候選輸入表示「時點原則上合理」,不表示已經證明每欄都應進模型。14 欄可再分成四組:

類型 來源欄位 白話說明 清理重點
人口與到院 SexAgeArrival modeInjury 生理性別、年齡、到院方式、是否外傷 類別代碼不能當成有距離的連續數字
主訴文字 Chief_complain 病患最主要的不舒服或問題 Kaggle CSV 已是英文;保留原字串並另記大小寫、空白與拼字正規化
意識與疼痛 MentalPainNRS_pain 意識反應、是否疼痛、疼痛強度 「沒有分數」不等於「疼痛為 0」
生命徵象 SBPDBPHRRRBTSaturation 血壓、心率、呼吸率、體溫與血氧 除空白外,還有文字型無法測量代碼

到院方式共有七個代碼。Kaggle 頁面明確列出步行、公共救護車、私人車輛與私人救護車,並把代碼 5、6、7 歸入其他;若要把「其他」再細分,必須先找到可追溯的對照來源。這些數字只是類別代碼,例如代碼 6 不代表比代碼 3「多兩倍」或「更嚴重」。後續建模時要把它當類別,而不是直接計算平均值。

Mental 是有順序的意識反應:清醒、對聲音反應、對疼痛反應、無反應。雖然代碼有順序,模型仍需要知道數字方向代表意識狀態變差,不能只把 1 到 4 當成不具語意的文字。

疼痛數字評定量表(Numeric Rating Scale, NRS)讓病患以 0 到 10 分表達疼痛,0 通常表示沒有疼痛,10 表示可想像的最嚴重疼痛。CSV 把「是否疼痛」放在 Pain,把實際分數放在 NRS_pain。兩欄不能任意合併:NRS_pain#BOŞ! 可能是沒有記錄、無法評估或其他流程原因,不應直接補成 0。

生命徵象欄位包括收縮壓(Systolic Blood Pressure, SBP)、舒張壓(Diastolic Blood Pressure, DBP)、心率(Heart Rate, HR)、呼吸率(Respiratory Rate, RR)、體溫(Body Temperature, BT)與周邊血氧飽和度(Peripheral Oxygen Saturation, SpO2)。CSV 的血氧欄名是 Saturation,不是 SpO2;程式會保留來源欄名,文章才使用臨床常用縮寫解釋。

Kaggle 資料頁沒有替每個數值欄逐一提供完整測量說明,因此本篇不靠猜測自動做單位換算。後續建立正式資料字典時,可以依來源研究與常見臨床單位提出假設,但必須把來源未明列細節的限制保留下來。

稽核與分層欄位:2 欄

Group 表示地方急診或區域急診。兩個場域共有 688 筆與 579 筆紀錄。把 Group 放進模型可能讓模型學到「這家醫院通常怎麼分級」,而不是學會病患狀況。因此預設只用它檢查兩個場域的缺失率與錯誤差異。

Patients number per hour 表示每小時到院人數。原始研究用它比較檢傷正確與不正確紀錄所處的人流環境,但 CSV 沒有提供可重建時間窗的逐筆時間戳。這個欄位先用於壅塞分層分析,不放進預設模型。

人類參考與評估目標:各 1 欄

KTAS_RN 中的 RN 指註冊護理師(Registered Nurse, RN)。這欄保存急診護理師在實際流程中登錄的 KTAS 級數。本系列把它當「人類現場參考」,用來回答模型和臨床當下判定有何差異,但不把它交給模型。

KTAS_expert 是研究中的專家重新判定級數。本篇把它設為主要參考標籤(Reference Label),也就是模型預測完成後拿來比較的目標。它比單一現場登錄多了一層專家重判,但仍不是沒有誤差的宇宙真值:專家依病歷回顧,不是親自重新檢查病患;原始研究也指出部分詳細症狀和徵象可能未被記錄。

如果把 KTAS_RNKTAS_expert 放進模型輸入,模型只需要複製級數,就能得到看似漂亮的結果。這是最直接的標籤洩漏。

事後結果與衍生評估:6 欄

下列四欄在檢傷決定後才形成或才能完整確定:

  • Diagnosis in ED:急診診斷。
  • Disposition:急診去向,例如離院、一般病房住院、加護病房住院、轉院或死亡。
  • Length of stay_min:急診停留時間,以分鐘記錄。
  • KTAS duration_min:檢傷作業時間,以分鐘記錄;只有檢傷完成後才知道完整時間。

另外兩欄是比較兩種級數後才產生:

  • Error_group:專家對錯誤原因的分類,例如生命徵象、身體症狀、疼痛或意識判斷。
  • mistriage:護理師級數和專家級數是否一致,以及錯誤方向。

這六欄都不能作為模型輸入。Error_groupmistriage 甚至直接由專家比較結果衍生,若拿來預測 KTAS_expert,等於把評分表交給考生。

空白不是唯一的缺失值

Kaggle CSV 使用三種需要留意的缺失表示:空字串、??#BOŞ!NRS_pain 使用 #BOŞ!,生命徵象中的無法測量值多使用 ??Saturation 還同時包含空字串與 ??。此外,KTAS duration_min 使用逗號作小數點,例如 5,00;程式必須先轉成 5.00 才能解析為數值。

如果沒有先指定文字編碼,讀取 #BOŞ! 中的土耳其字母 Ş 時還可能直接出現解碼錯誤。這就是本系列固定使用 Windows-1254,也就是 Python 中 cp1254 的原因。

正規化後的缺失量如下:

欄位 原始缺失表示 正規化後缺失 可轉成數值
NRS_pain #BOŞ! 556 711
SBP ?? 25 1,242
DBP ?? 29 1,238
HR ?? 20 1,247
RR ?? 22 1,245
BT ?? 18 1,249
Saturation 空字串與 ?? 697 570

Saturation 是最明顯的例子:正規化後共有 697 筆缺失,只剩 570 筆數值。若只讓套件自行猜空值,很容易漏掉 ??,造成欄位同時混有數字和文字。

清理原則不是把缺失藏起來,而是保留「沒有量到」這件事。以血氧為例,填成 0 會製造極端且錯誤的臨床值;填成全體平均值則會假裝病患真的測到平均血氧。Day 15 會再比較缺失指示欄、合理插補與完整案例分析,但今天只做可追溯的正規化,不擅自補值。

兩種五級標籤的分布並不一樣

五級是序位分類(Ordinal Classification):類別不只不同,還有第一級到第五級的順序。級數數字越小,代表越緊急;因此把第一級錯判成第五級,比錯判成第二級偏離得更遠。

下圖的每一組長條都使用 Kaggle data.csv 的實際聚合筆數。淺色是護理師登錄,深色是專家重標。

五組長條比較第一級到第五級的護理師與專家筆數,第三級與第四級最多,專家第一級只有二十六筆,右側標示兩者不一致一百八十六筆

上圖顯示護理師級數分布為 18、214、447、501、87 筆;專家重標分布為 26、220、487、459、75 筆。兩者在 1,081 筆紀錄上相同,在 186 筆紀錄上不同。原始研究報告加權 Kappa(Weighted Kappa)為 0.829;加權 Kappa 會同時考慮一致與錯誤距離,用來描述兩位評分來源的一致程度。

mistriage 把 1,267 筆分成:

類別 CSV 代碼 筆數 本篇解釋
一致 0 1,081 護理師級數等於專家級數
檢傷過度 1 55 護理師把病患判得比專家更緊急
檢傷不足 2 131 護理師把病患判得比專家不緊急

檢傷過度(Overtriage)在五級數字上表示護理師級數小於專家級數,例如專家判第三級、護理師判第二級。檢傷不足(Undertriage)則是護理師級數大於專家級數,例如專家判第二級、護理師判第四級。

這個數字方向很容易寫反,所以設定檔直接固定為:

預測級數 > 專家級數 → 檢傷不足
預測級數 < 專家級數 → 檢傷過度
預測級數 = 專家級數 → 一致

專家第一級只有 26 筆,代表少數高風險級別的百分比會非常不穩定。即使模型在 26 筆中判對 20 筆與 22 筆,召回率就會相差約 7.7 個百分點。因此後續除了整體準確率,還要報告每級樣本數、第一與第二級召回率、錯誤距離、檢傷不足率,以及不確定性的信賴區間。

用時間線判斷資料洩漏

資料洩漏(Data Leakage)是指模型在訓練、調整或測試時,接觸到真實使用情境中不該取得的資訊。急診資料的洩漏不只是一欄答案;診斷、處置與住院結果也可能間接透露病患最後有多嚴重。

下圖用一條時間線把候選輸入、人工判定、專家重判與事後結果分開。閱讀時請沿著灰色箭頭由左往右看。

時間線從到院初評、護理師完成檢傷走到診療後結果,下方把專家重判與比較後指標放在評估區,紅色反向箭頭警示事後結果不能倒灌

上圖顯示,判斷欄位時可以連續問三個問題:

  1. 作出級數前是否已知? 若否,禁止當輸入。
  2. 這欄是不是要預測的答案或人類比較基準? 若是,只能放在標籤檔或評估檔。
  3. 這欄是不是比較答案後才計算? 若是,只能在模型輸出完成後產生或讀取。

這三個問題比「欄位和級數是否相關」更可靠。相關性只能告訴我們兩個變數一起變動,不能證明資訊在決策當下可用。

把欄位規則寫進程式,而不是靠記憶

本篇接下來會帶讀者建立兩個可執行產物:

configs/data/day-11-ktas-field-policy.json
scripts/prepare_ktas.py

JavaScript 物件表示法(JavaScript Object Notation, JSON)設定檔逐欄記錄來源名稱、繁體中文解釋、資料型別、取得角色、代碼與排除原因。頂層分開保存:

  • audit_status: source-inspected:Kaggle data.csv 已完成結構與聚合稽核。
  • experiment_status: planning:模型實驗尚未執行,不能把資料稽核寫成模型結果。

Python 資料準備程式會先驗證 data.csv 的 SHA-256、分隔符、文字編碼、24 個欄位的名稱與順序、1,267 筆資料量,以及兩個級數是否都落在第一到第五級。任何一項不符就停止,不會靜默接受不同版本。

先在自己的專案資料夾建立本篇完整檔案

接下來不會要求你前往任何程式碼網站。請在自己的電腦開啟專案資料夾,依下列順序建立檔案;每個程式碼區塊都是該檔案的完整內容,不含省略號。

先完成本篇前半部的 Kaggle 下載與 SHA-256 核對;以下兩個檔案就是後續指令真正會讀取的內容。

先從專案根目錄建立需要的資料夾:

mkdir -p configs/data scripts

如果指令沒有印出訊息是正常的。可用 test -d 資料夾路徑 && echo "資料夾已建立" 驗證單一資料夾。接著使用你熟悉的文字編輯器新增各檔案,把對應區塊完整貼入後儲存。

檔案 1:建立 configs/data/day-11-ktas-field-policy.json

逐欄保存 24 個來源欄位的型別、角色、缺失代碼與輸出限制。

請在文字編輯器建立 configs/data/day-11-ktas-field-policy.json,貼入以下完整內容並儲存:

{
  "schema_version": "0.2",
  "audit_status": "source-inspected",
  "experiment_status": "planning",
  "source_checked_on": "2026-08-02",
  "public_source": {
    "kaggle_url": "https://www.kaggle.com/datasets/ilkeryildiz/emergency-service-triage-application",
    "kaggle_slug": "ilkeryildiz/emergency-service-triage-application",
    "provenance_article": "https://doi.org/10.1371/journal.pone.0216972",
    "archive_filename": "ktas-kaggle.zip",
    "archive_sha256": "f78df0a06c31b6df873c410b0596ecb95ac9335485beaf890dcabf7c69560f26",
    "data_filename": "data.csv",
    "data_sha256": "0e2c088e358fd4cdfd0dcc2fd4c2f085aa303856e3a6df8ceb44a69cef8ed2de",
    "raw_data_policy": "Kaggle 原始檔只保存在 data/raw/ 的本機工作目錄,不重新提交到 Git。"
  },
  "observed_structure": {
    "record_count": 1267,
    "field_count": 24,
    "delimiter": ";",
    "encoding": "cp1254",
    "decimal_normalization": "數值欄位中的逗號小數點轉為句點後再解析",
    "site_counts": {
      "local_ed": 688,
      "regional_ed": 579
    },
    "nurse_label_counts": {
      "1": 18,
      "2": 214,
      "3": 447,
      "4": 501,
      "5": 87
    },
    "expert_label_counts": {
      "1": 26,
      "2": 220,
      "3": 487,
      "4": 459,
      "5": 75
    },
    "mistriage_counts": {
      "correct": 1081,
      "overtriage": 55,
      "undertriage": 131
    },
    "missing_tokens": ["", "??", "#BOŞ!"],
    "missing_after_normalization": {
      "NRS_pain": 556,
      "SBP": 25,
      "DBP": 29,
      "HR": 20,
      "RR": 22,
      "BT": 18,
      "Saturation": 697,
      "Diagnosis in ED": 2
    }
  },
  "role_definitions": {
    "model_input_candidate": "原則上能在第一次檢傷判定前取得,仍須完成缺失值與時點檢查後才可使用。",
    "audit_or_stratification_only": "只用於資料品質、場域差異或敏感度分析,不作為預設模型輸入。",
    "human_reference_only": "保留作為臨床人員比較基準,不得送入預測專家級數的模型。",
    "reference_label": "本次分類評估的主要參考標籤。",
    "post_triage_outcome": "在檢傷完成後才形成或才能確認,禁止作為模型輸入。",
    "derived_evaluation_only": "由護理師級數與專家級數比較後產生,只能用於結果分析。"
  },
  "fields": [
    {
      "source_name": "Group",
      "traditional_chinese": "急診場域組別",
      "role": "audit_or_stratification_only",
      "source_type": "category",
      "coding": "1=地方急診,2=區域急診",
      "reason": "場域可能成為捷徑訊號;先用於分層報告,不放入預設模型。"
    },
    {
      "source_name": "Sex",
      "traditional_chinese": "生理性別",
      "role": "model_input_candidate",
      "source_type": "category",
      "coding": "1=女性,2=男性",
      "reason": "檢傷當下可取得,但只保留來源提供的二元編碼,不自行推論其他資訊。"
    },
    {
      "source_name": "Age",
      "traditional_chinese": "年齡",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "歲",
      "reason": "檢傷當下可取得;原始值可能包含小數,清理時不得擅自四捨五入。"
    },
    {
      "source_name": "Patients number per hour",
      "traditional_chinese": "每小時到院人數",
      "role": "audit_or_stratification_only",
      "source_type": "number",
      "unit": "人次/小時",
      "reason": "附件沒有精確事件時間與計算窗定義,預設只做壅塞分層與誤差分析。"
    },
    {
      "source_name": "Arrival mode",
      "traditional_chinese": "到院方式",
      "role": "model_input_candidate",
      "source_type": "category",
      "coding": "1=步行,2=公共救護車,3=私人車輛,4=私人救護車,5、6、7=其他;細分類在二次分析前需再核對來源",
      "reason": "到院時已知,可提供情境訊號;不得把代碼直接當連續數字。"
    },
    {
      "source_name": "Injury",
      "traditional_chinese": "是否為外傷",
      "role": "model_input_candidate",
      "source_type": "category",
      "coding": "1=非外傷,2=外傷",
      "reason": "檢傷當下可判斷的來診類型。"
    },
    {
      "source_name": "Chief_complain",
      "traditional_chinese": "主訴",
      "role": "model_input_candidate",
      "source_type": "text",
      "reason": "檢傷當下的核心文字輸入;Kaggle CSV 已提供英文主訴,但仍須保留原字串並記錄正規化規則。"
    },
    {
      "source_name": "Mental",
      "traditional_chinese": "意識反應",
      "role": "model_input_candidate",
      "source_type": "ordinal_category",
      "coding": "1=清醒,2=對聲音反應,3=對疼痛反應,4=無反應",
      "reason": "檢傷當下的重要嚴重度訊號。"
    },
    {
      "source_name": "Pain",
      "traditional_chinese": "是否疼痛",
      "role": "model_input_candidate",
      "source_type": "category",
      "coding": "1=有疼痛,0=無疼痛",
      "reason": "先表示是否有疼痛,再與疼痛分數分開處理。"
    },
    {
      "source_name": "NRS_pain",
      "traditional_chinese": "疼痛數字評定量表分數",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "0 至 10 分",
      "reason": "檢傷當下可取得;缺失可能代表沒有記錄,不可自動補成 0。"
    },
    {
      "source_name": "SBP",
      "traditional_chinese": "收縮壓",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "mmHg",
      "reason": "檢傷當下生命徵象;Kaggle CSV 的「??」必須轉為缺失值。"
    },
    {
      "source_name": "DBP",
      "traditional_chinese": "舒張壓",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "mmHg",
      "reason": "檢傷當下生命徵象;Kaggle CSV 的「??」必須轉為缺失值。"
    },
    {
      "source_name": "HR",
      "traditional_chinese": "心率",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "次/分鐘",
      "reason": "檢傷當下生命徵象;Kaggle CSV 的「??」必須轉為缺失值。"
    },
    {
      "source_name": "RR",
      "traditional_chinese": "呼吸率",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "次/分鐘",
      "reason": "檢傷當下生命徵象;Kaggle CSV 的「??」必須轉為缺失值。"
    },
    {
      "source_name": "BT",
      "traditional_chinese": "體溫",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "攝氏度",
      "reason": "檢傷當下生命徵象;Kaggle CSV 的「??」必須轉為缺失值。"
    },
    {
      "source_name": "Saturation",
      "traditional_chinese": "血氧飽和度",
      "role": "model_input_candidate",
      "source_type": "number",
      "unit": "%",
      "reason": "檢傷當下生命徵象;空白與「??」都必須視為缺失。"
    },
    {
      "source_name": "KTAS_RN",
      "traditional_chinese": "急診護理師登錄級數",
      "role": "human_reference_only",
      "source_type": "ordinal_label",
      "coding": "1 至 5,數字越小越緊急",
      "reason": "這是要與模型和專家重標比較的人類基準,輸入模型會造成標籤洩漏。"
    },
    {
      "source_name": "Diagnosis in ED",
      "traditional_chinese": "急診診斷",
      "role": "post_triage_outcome",
      "source_type": "text",
      "reason": "診斷在檢傷後形成,不能倒灌成檢傷輸入。"
    },
    {
      "source_name": "Disposition",
      "traditional_chinese": "急診去向",
      "role": "post_triage_outcome",
      "source_type": "category",
      "coding": "1=離院,2=一般病房住院,3=加護病房住院,5=轉院,6=死亡;Kaggle 頁面對 4 與 7 的文字不清,二次分析前必須另行核對",
      "reason": "離院或住院結果在檢傷後才知道,只能做結果描述。"
    },
    {
      "source_name": "KTAS_expert",
      "traditional_chinese": "專家重新判定級數",
      "role": "reference_label",
      "source_type": "ordinal_label",
      "coding": "1 至 5,數字越小越緊急",
      "reason": "本次序位分類評估的主要參考標籤,不可放入模型輸入。"
    },
    {
      "source_name": "Error_group",
      "traditional_chinese": "錯誤原因類別",
      "role": "derived_evaluation_only",
      "source_type": "category",
      "reason": "專家判斷錯誤原因後建立,只能用於錯誤分析。"
    },
    {
      "source_name": "Length of stay_min",
      "traditional_chinese": "急診停留時間",
      "role": "post_triage_outcome",
      "source_type": "number",
      "unit": "分鐘",
      "reason": "必須等急診流程結束後才能完整計算,禁止作為檢傷輸入。"
    },
    {
      "source_name": "KTAS duration_min",
      "traditional_chinese": "檢傷作業時間",
      "role": "post_triage_outcome",
      "source_type": "number",
      "unit": "分鐘",
      "reason": "只有完成檢傷後才能確定,不是判定開始時的輸入。"
    },
    {
      "source_name": "mistriage",
      "traditional_chinese": "錯誤檢傷方向",
      "role": "derived_evaluation_only",
      "source_type": "category",
      "coding": "0=一致,1=檢傷過度,2=檢傷不足",
      "reason": "由護理師級數與專家級數比較得到,只能用於評估。"
    }
  ],
  "evaluation_policy": {
    "primary_target": "KTAS_expert",
    "human_reference": "KTAS_RN",
    "direction_definition": "模型級數大於專家級數代表檢傷不足;模型級數小於專家級數代表檢傷過度。",
    "default_input_role": "model_input_candidate",
    "forbidden_input_roles": [
      "human_reference_only",
      "reference_label",
      "post_triage_outcome",
      "derived_evaluation_only"
    ],
    "split_limit": "附件沒有可用的病患識別碼,不能證明每列都是不同病患,也不能主張已完成病患層級切分。",
    "cross_system_limit": "KTAS 結果不得直接改名為 TTAS,亦不得與 ESI 絕對分數合併。"
  }
}

儲存後先確認檔名與相對路徑完全一致,再繼續建立下一個檔案。

檔案 2:建立 scripts/prepare_ktas.py

讀取已驗證的 data.csv,正規化缺失與型別,最後依角色安全拆檔。

請在文字編輯器建立 scripts/prepare_ktas.py,貼入以下完整內容並儲存:

#!/usr/bin/env python3
"""稽核並拆分 Day 11 使用的 Kaggle KTAS 公開資料。

這支程式不會下載資料,也不會把急診診斷、去向或停留時間寫入模型輸入。
輸出目錄應放在已被 Git 忽略的 ``data/interim/`` 或 ``data/processed/``。
"""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
from typing import Any

import pandas as pd


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_POLICY = ROOT / "configs" / "data" / "day-11-ktas-field-policy.json"
def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="驗證 Kaggle KTAS CSV,正規化缺失代碼,並依欄位角色拆分輸出。"
    )
    parser.add_argument("--input", type=Path, required=True, help="Kaggle data.csv 路徑")
    parser.add_argument(
        "--output-dir",
        type=Path,
        default=ROOT / "data" / "interim" / "ktas-day-11",
        help="輸出目錄;預設位於 Git 忽略的 data/interim/",
    )
    parser.add_argument(
        "--policy",
        type=Path,
        default=DEFAULT_POLICY,
        help="欄位角色與來源雜湊設定檔",
    )
    return parser.parse_args()


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as file:
        for chunk in iter(lambda: file.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def load_policy(path: Path) -> dict[str, Any]:
    with path.open("r", encoding="utf-8") as file:
        return json.load(file)


def verify_source(path: Path, expected_sha256: str, label: str) -> str:
    if not path.is_file():
        raise FileNotFoundError(f"找不到 {label}:{path}")
    observed = sha256(path)
    if observed != expected_sha256:
        raise ValueError(
            f"{label} 的 SHA-256 與設定檔不同。\n"
            f"預期:{expected_sha256}\n實際:{observed}\n"
            "請先確認下載來源與版本,不要直接略過驗證。"
        )
    return observed


def read_source(data_path: Path, policy: dict[str, Any]) -> pd.DataFrame:
    structure = policy["observed_structure"]
    data = pd.read_csv(
        data_path,
        sep=structure["delimiter"],
        encoding=structure["encoding"],
        dtype=object,
        keep_default_na=False,
    )
    expected_columns = [field["source_name"] for field in policy["fields"]]
    if data.columns.tolist() != expected_columns:
        raise ValueError(
            "Kaggle CSV 欄位名稱或順序已改變。\n"
            f"預期:{expected_columns}\n實際:{data.columns.tolist()}"
        )
    if len(data) != structure["record_count"]:
        raise ValueError(
            f"Kaggle CSV 預期 {structure['record_count']} 筆,實際讀到 {len(data)} 筆。"
        )
    return data


def normalize_numeric_fields(
    data: pd.DataFrame,
    policy: dict[str, Any],
) -> tuple[pd.DataFrame, dict[str, int]]:
    cleaned = data.copy()
    token_counts: dict[str, int] = {}
    missing_tokens = set(policy["observed_structure"]["missing_tokens"])

    for name in cleaned.columns:
        if cleaned[name].dtype == object:
            cleaned[name] = cleaned[name].astype("string").str.strip()

    numeric_types = {"number", "ordinal_label"}
    for field in policy["fields"]:
        if field["source_type"] not in numeric_types:
            continue
        name = field["source_name"]
        string_values = cleaned[name].astype("string")
        missing_mask = string_values.isin(missing_tokens)
        token_counts[name] = int(missing_mask.sum())
        replaced = string_values.mask(missing_mask, pd.NA).str.replace(
            ",", ".", regex=False
        )
        numeric = pd.to_numeric(replaced, errors="coerce")
        unexpected_invalid = replaced.notna() & numeric.isna()
        if unexpected_invalid.any():
            invalid_count = int(unexpected_invalid.sum())
            raise ValueError(f"{name} 出現 {invalid_count} 個未登錄的非數值代碼。")
        cleaned[name] = numeric
    return cleaned, {key: value for key, value in token_counts.items() if value > 0}


def validate_labels(data: pd.DataFrame) -> None:
    for name in ("KTAS_RN", "KTAS_expert"):
        observed = set(data[name].dropna().astype(int).unique())
        if not observed.issubset({1, 2, 3, 4, 5}):
            raise ValueError(f"{name} 出現五級範圍以外的值:{sorted(observed)}")
        if data[name].isna().any():
            raise ValueError(f"{name} 有缺失值,不能直接建立本次評估。")


def role_columns(policy: dict[str, Any], role: str) -> list[str]:
    return [field["source_name"] for field in policy["fields"] if field["role"] == role]


def value_counts(series: pd.Series) -> dict[str, int]:
    counts = series.value_counts(dropna=False).sort_index()
    return {str(key): int(value) for key, value in counts.items()}


def write_outputs(
    data: pd.DataFrame,
    output_dir: Path,
    policy: dict[str, Any],
    source_hashes: dict[str, str],
    unable_counts: dict[str, int],
) -> None:
    output_dir.mkdir(parents=True, exist_ok=True)
    record_index = pd.Series(range(1, len(data) + 1), name="record_index")

    input_columns = role_columns(policy, "model_input_candidate")
    audit_columns = role_columns(policy, "audit_or_stratification_only")
    reference_columns = (
        role_columns(policy, "human_reference_only")
        + role_columns(policy, "reference_label")
        + role_columns(policy, "derived_evaluation_only")
    )

    pd.concat([record_index, data[input_columns]], axis=1).to_csv(
        output_dir / "model-input-candidates.csv",
        index=False,
    )
    pd.concat([record_index, data[audit_columns]], axis=1).to_csv(
        output_dir / "audit-strata.csv",
        index=False,
    )
    pd.concat([record_index, data[reference_columns]], axis=1).to_csv(
        output_dir / "reference-labels.csv",
        index=False,
    )

    excluded = {
        "note": "以下欄位不會寫入模型輸入檔。",
        "post_triage_outcome": role_columns(policy, "post_triage_outcome"),
        "human_reference_only": role_columns(policy, "human_reference_only"),
        "reference_label": role_columns(policy, "reference_label"),
        "derived_evaluation_only": role_columns(policy, "derived_evaluation_only"),
    }
    with (output_dir / "excluded-fields.json").open("w", encoding="utf-8") as file:
        json.dump(excluded, file, ensure_ascii=False, indent=2)
        file.write("\n")

    summary = {
        "status": "source-audit-complete",
        "record_count": len(data),
        "source_sha256": source_hashes,
        "normalized_missing_tokens": policy["observed_structure"]["missing_tokens"],
        "normalized_missing_counts": unable_counts,
        "missing_after_normalization": {
            name: int(data[name].isna().sum()) for name in input_columns
        },
        "nurse_label_counts": value_counts(data["KTAS_RN"].astype(int)),
        "expert_label_counts": value_counts(data["KTAS_expert"].astype(int)),
        "mistriage_counts": value_counts(data["mistriage"].astype(int)),
        "output_files": [
            "model-input-candidates.csv",
            "audit-strata.csv",
            "reference-labels.csv",
            "excluded-fields.json",
            "summary.json",
        ],
        "privacy_note": "輸出仍包含逐筆公開病歷欄位,只能留在被 Git 忽略的本機資料目錄。",
    }
    with (output_dir / "summary.json").open("w", encoding="utf-8") as file:
        json.dump(summary, file, ensure_ascii=False, indent=2)
        file.write("\n")


def main() -> None:
    args = parse_args()
    policy = load_policy(args.policy)
    source = policy["public_source"]
    source_hashes = {
        "data_csv": verify_source(args.input, source["data_sha256"], "Kaggle data.csv"),
    }
    data = read_source(args.input, policy)
    cleaned, unable_counts = normalize_numeric_fields(data, policy)
    validate_labels(cleaned)
    write_outputs(cleaned, args.output_dir, policy, source_hashes, unable_counts)
    print(f"完成:{len(cleaned)} 筆、{len(cleaned.columns)} 欄")
    print(f"輸出目錄:{args.output_dir.resolve()}")
    print("模型輸入不含護理師級數、專家級數、事後結果或衍生誤差欄位。")


if __name__ == "__main__":
    main()

儲存後先確認檔名與相對路徑完全一致,再繼續建立下一個檔案。

完成後產生必要檔案並做靜態檢查

以下命令會在需要時產生套件鎖定檔,接著檢查設定格式與 Python 語法;它們不會啟動模型,也不會把病患資料送到網路:

python3 -m json.tool configs/data/day-11-ktas-field-policy.json
python3 -m py_compile scripts/prepare_ktas.py

每個命令都應正常結束。若 JSON 顯示行號,先檢查貼上時是否遺漏逗號、引號或括號;若 py_compile 報錯,先依行號修正縮排或漏貼內容。靜態檢查通過後,再執行本文後面的正式步驟。

步驟四:準備 Python 執行環境

  • 目的:安裝讀取 CSV 與處理表格所需套件。
  • 前置條件:系統有 Python 3,並位於專案根目錄。
  • 輸入:沒有病歷資料輸入。
  • 操作:建立虛擬環境並安裝 Pandas。
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'pandas>=2.0,<3'

Pandas 是 Python 的表格資料處理套件,本篇用它指定分號分隔與 cp1254 編碼後讀取 CSV。

  • 預期輸出:安裝完成後不出現錯誤,命令提示字元通常會顯示 (.venv)
  • 驗證方式:執行 python -c "import pandas; print('套件可用')",應看到「套件可用」。

步驟五:執行欄位稽核與安全拆檔

  • 目的:正規化空字串、??#BOŞ!,並依欄位角色建立互相分離的本機輸出。
  • 前置條件:Kaggle data.csv 已通過檢查碼驗證,虛擬環境已啟用。
  • 輸入data.csv 與 Day 11 欄位政策。
  • 操作:執行專案中的資料準備程式。
python scripts/prepare_ktas.py \
  --input data/raw/ktas/data.csv \
  --output-dir data/interim/ktas-day-11
  • 預期輸出:終端機應顯示:
完成:1267 筆、24 欄
輸出目錄:.../data/interim/ktas-day-11
模型輸入不含護理師級數、專家級數、事後結果或衍生誤差欄位。
  • 驗證方式data/interim/ktas-day-11/ 應包含五個檔案:
輸出檔 內容 為什麼要拆開
model-input-candidates.csv record_index 加 14 個候選輸入 模型預設只能讀這一份
audit-strata.csv 場域與每小時人流 只作分層及資料品質檢查
reference-labels.csv 護理師級數、專家級數與兩個衍生評估欄位 只在模型輸出後比較
excluded-fields.json 禁止輸入欄位名稱與角色 留下可機器檢查的黑名單
summary.json 筆數、雜湊、缺失與級數分布 驗證處理是否符合預期

程式刻意不輸出逐筆的急診診斷、去向、停留時間或檢傷作業時間。原始值仍保留在本機 Kaggle CSV,需要做次要結果分析時再由獨立流程讀取,避免方便性使事後欄位悄悄進入模型。

步驟六:確認輸入檔真的沒有答案

  • 目的:不用人工目視 1,267 列,也能檢查輸入白名單與禁止欄位。
  • 前置條件:前一步已成功產生五個輸出檔。
  • 輸入model-input-candidates.csvexcluded-fields.json
  • 操作:執行下列唯讀驗證。
python - <<'PY'
import json
from pathlib import Path

import pandas as pd

output = Path("data/interim/ktas-day-11")
inputs = pd.read_csv(output / "model-input-candidates.csv")
excluded = json.loads((output / "excluded-fields.json").read_text(encoding="utf-8"))

forbidden = {
    name
    for role, names in excluded.items()
    if role != "note"
    for name in names
}

leaked = forbidden.intersection(inputs.columns)
if leaked:
    raise SystemExit(f"發現禁止欄位:{sorted(leaked)}")
if inputs.shape != (1267, 15):
    raise SystemExit(f"輸入尺寸錯誤:{inputs.shape}")

print("通過:1,267 列,record_index + 14 個候選輸入,未發現禁止欄位")
PY
  • 預期輸出:應看到「通過:1,267 列,record_index + 14 個候選輸入,未發現禁止欄位」。
  • 驗證方式:若程式列出禁止欄位,先停止後續實驗並檢查 JSON 角色與資料準備程式;不能只在模型端忽略錯誤欄位。

這些輸出仍包含逐筆公開病歷欄位,只能留在不納入版本控制的本機資料目錄。公開資料不等於應該在自己的專案再複製一份。

為什麼資料是 KTAS,不能直接說成 TTAS

台灣急診檢傷與急迫度分級量表(Taiwan Triage and Acuity Scale, TTAS)與 KTAS 都有五級,也都受到加拿大急診檢傷與急迫度分級量表(Canadian Triage and Acuity Scale, CTAS)的發展影響,但三者的制度內容、修訂脈絡與實務規則並不相同。

這份 CSV 的病患紀錄來自韓國,護理師和專家使用的也是 KTAS。因此它只能直接支持 KTAS 語境內的分類與錯誤方向分析,不能把 KTAS_expert 改名為 TTAS_expert,也不能宣稱模型已在台灣制度上通過驗證。

本系列使用這份小型資料的目的是先驗證共通的工程骨架:

  • 能否把檢傷當下輸入和事後結果分開。
  • 能否把人類登錄級數和專家參考標籤分開。
  • 能否正確處理五級序位與檢傷過度、檢傷不足方向。
  • 能否留下可重跑的來源、雜湊、欄位政策與聚合結果。

這些工程方法可以遷移,但制度標籤不能互換。若未來使用 TTAS 資料,仍要重新取得 TTAS 對應的規則版本、欄位語意與參考標籤,不能沿用 KTAS 名稱替換。

這份資料適合與不適合回答什麼

目前適合回答的問題包括:

  • 只用檢傷當下候選欄位時,模型與 KTAS_expert 的五級差異為何。
  • 模型和護理師 KTAS_RN 相比,錯誤方向是否不同。
  • 第一、二級召回率、檢傷不足率與跨級距離如何計算。
  • 缺失生命徵象、兩種急診場域與不同主訴類型是否出現不同錯誤型態。
  • 資料準備、輸出契約與評估程式能否先在小資料上完整跑通。

目前不適合回答的問題包括:

  • 模型是否已通過 TTAS 臨床驗證。
  • 完整 KTAS 規則檢索是否改善分類;我們尚未確認可用來建立完整知識庫的規則來源與再散布條件。
  • 結果能否推廣到韓國所有急診;資料只來自兩所都市教學醫院與隨機抽出的 20 天。
  • 每列是否來自不同病患;CSV 沒有可驗證的病患識別碼。
  • 系統是否能取代檢傷護理師;公開回溯資料的離線結果不足以支持臨床部署。

最後一點特別重要。即使後續模型的第一、二級召回率很高,也只能表示它在這份固定資料和固定設定下的表現。臨床使用還需要前瞻驗證、工作流程評估、偏差與安全監測,以及專業人員持續覆核。

常見錯誤與排查方式

用預設逗號與 UTF-8 直接讀取

這可能把整列讀成一欄,或在遇到 #BOŞ! 時產生解碼錯誤。修正方式是明確指定 sep=";"encoding="cp1254",再核對 1,267 列與 24 欄。

KTAS_RN 當特徵

模型會直接看到人類級數,無法評估主訴和生命徵象是否足以支持判斷。修正方式是只在 reference-labels.csv 保存 KTAS_RN,並用禁止欄位集合做自動檢查。

把缺失 NRS 補成 0

沒有記錄不等於沒有疼痛。修正方式是保留缺失,另建缺失指示欄或在後續實驗中比較插補策略,不要在來源稽核階段偷偷賦予臨床意義。

只檢查空白,漏掉 ??#BOŞ!

這會讓生命徵象與疼痛欄維持混合的文字與數值型別,或在轉型時出現無法追溯的錯誤。修正方式是先把已知缺失代碼轉成缺失,再檢查是否還有其他未登錄字串。

把 1,267 列說成 1,267 位互不重複病患

CSV 沒有病患識別碼,無法證明這件事。修正方式是使用「1,267 筆病歷紀錄」,並在切分限制中明確說明不能保證病患層級獨立。

把原始研究結果寫成自己的模型成果

1,081 筆一致、186 筆不一致與加權 Kappa 0.829 都是原始研究的護理師-專家比較,不是本系列模型結果。本篇只完成來源與欄位稽核,experiment_status 仍是 planning


本日小結

今天完成的不是模型訓練,而是一份可以阻止未來犯錯的資料契約:

  1. Kaggle 壓縮檔與 data.csv 已用 dataset slug、檔名、尺寸與 SHA-256 鎖定。
  2. 1,267 筆紀錄來自兩所都市教學醫院隨機選出的 20 天,不是完整年度母體。
  3. CSV 沒有病患識別碼,因此不能宣稱每列都是不同病患,也不能保證病患層級切分。
  4. 24 欄已分成 14 個候選輸入、2 個稽核欄位、1 個人類參考、1 個專家目標、4 個事後結果與 2 個衍生評估欄位。
  5. KTAS_RN 只作護理師現場參考,KTAS_expert 才是本次主要評估目標;兩者都不得進模型輸入。
  6. Diagnosis in EDDisposition、兩種時間與比較後欄位已列入禁止輸入清單。
  7. 空字串、??#BOŞ! 必須統一轉成缺失;只檢查空白會低估缺失量。
  8. 專家第一級只有 26 筆,後續不能只用整體準確率掩蓋少數高風險級別。
  9. KTAS 資料只能支持 KTAS 語境內的結果,不能直接改名或推論為 TTAS 表現。
  10. scripts/prepare_ktas.py 會依欄位政策安全拆檔;本篇狀態是「來源稽核完成、模型實驗尚未執行」。

這套流程仍只服務於離線決策支援研究。它不會自動作出臨床決定,也不能取代急診檢傷護理師與醫療團隊。

下一篇預告

Day 12 會把 Kaggle 下載流程正式寫成可重現的資料管線,重點包括:

  • 如何固定 dataset slug、雜湊、分隔符與編碼。
  • 如何保存原始資料層、清理資料層與模型輸入層。
  • 如何產生資料卡與資料處理 manifest。
  • 如何讓第三方在不依賴 Notebook 執行順序的情況下重跑。

Day 11 產生的「取得時點+欄位角色+禁止輸入」契約,會成為 Day 12 資料管線的輸入。


參考資料

  1. Kaggle. Emergency Service - Triage Application.
  2. Moon, S.-H., Shim, J. L., Park, K.-S., & Park, C.-S. (2019). Triage accuracy and causes of mistriage using the Korean Triage and Acuity Scale. PLOS ONE, 14(9), e0216972.
  3. Korean Triage and Acuity Scale. KTAS 官方介紹與五級定義.

上一篇
Day 10|資料選定:為什麼整個系列只使用 Kaggle KTAS?
下一篇
Day 12|從 Kaggle 下載到可重跑:建立 KTAS 資料管線
系列文
30 天打造公開資料版急診檢傷系統:Side Project 與實驗計畫16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言