昨天把 Google 表單匯出的原始檔改好欄名、刪掉重複和測試填答,287 份變成 279 份,存成 data/clean/01_renamed.csv。今天要做的三件事——反向計分、處理遺漏值、找離群值——是資料清理裡最容易做錯、做錯了又最難發現的三件。
做錯了不會報錯。它只會安靜地讓你的相關係數變號、讓平均數偏掉、讓結論反過來。
劑量換算不心算
臨床上藥物劑量換算有公式、有換算表,沒有人心算。不是因為護理師不會算,是因為一天算三十次,總有一次會錯,而錯的那一次沒有人會知道。
反向計分就是劑量換算。五點量表的反向題是 6 減原分,簡單到不可能錯——但 279 份乘以 8 題,兩千多次,一定有錯的。以前我在 Excel 裡一欄一欄拉公式,拉到第五欄的時候少選了一列,整欄往上錯一格,兩週後跑相關才發現倦怠跟睡眠品質變成負相關。
所以今天的原則只有一條:清理規則不寫在 prompt 裡,寫成程式碼。 我不叫 AI「幫我把反向題處理一下」,我叫它「依這份規則檔寫一支腳本」,然後我讀腳本、跑腳本、看紀錄。
為什麼一定要是程式碼?三個理由,都回到前面講過的東西。第一,程式碼可以重跑,這是 Day 4 的原則二——clean/ 裡的東西要能刪掉重生。第二,程式碼可以被讀,我看得懂它做了什麼,prompt 產生的結果我只看得到結果。第三,程式碼可以貼進論文的方法學附錄,「本研究反向題以 6 減原始分數計分」這句話後面,有一行程式可以對。
先寫規則檔
跟 Day 11 的 APA 規則檔一樣,規則先寫成人讀的檔,再讓它變成程式。存在 analysis/cleaning-rules.md:
data/clean/01_renamed.csv(Day 13 輸出,未做任何計分)
規則二特別把「個人平均」寫進去,是因為第一版沒寫,它用了全體平均——這件事後面講。
讓它寫腳本,我讀腳本
在專案資料夾啟動 claude,打「讀 @analysis/cleaning-rules.md,寫成 analysis/02_clean.py,用 pandas,每一條規則對應一段並加註解,不要做規則檔沒寫的事」。
它寫出來大概這樣:
import pandas as pd
df = pd.read_csv("data/clean/01_renamed.csv")
log = []
REV = ["bo04", "bo07", "bo09", "bo12", "bo17", "bo18", "bo19", "bo21"]
df[REV] = 6 - df[REV]
log.append(f"規則一 反向計分:{len(REV)} 題 × {len(df)} 筆")
BO = [f"bo{i:02d}" for i in range(1, 23)]
miss = df[BO].isna().sum(axis=1)
ok = miss <= 4
df.loc[ok, BO] = df.loc[ok, BO].apply(lambda r: r.fillna(r.mean()), axis=1)
df["bo_missing_flag"] = ~ok
log.append(f"規則二 補值:{miss.between(1, 4).sum()} 筆;標記缺失:{(~ok).sum()} 筆")
for col, lo, hi in [("hours_week", 8, 84), ("years", 0, 45)]:
flag = ~df[col].between(lo, hi)
df[f"{col}_flag"] = flag
log.append(f"規則三 {col} 超出 {lo}–{hi}:{flag.sum()} 筆,已標記")
df.to_csv("data/clean/02_cleaned.csv", index=False)
with open("analysis/cleaning-log.md", "w", encoding="utf-8") as f:
f.write("\n".join(log) + "\n")
三十行。我不會寫 pandas,但我看得懂每一段在做什麼,因為它跟規則檔一段對一段。6 - df[REV] 就是規則一的公式;miss <= 4 就是規則二的門檻;between(lo, hi) 就是規則三的範圍。看得懂,才跑。
跑完 cleaning-log.md 長這樣:
規則一 反向計分:8 題 × 279 筆
規則二 補值:23 筆;標記缺失:6 筆
規則三 hours_week 超出 8–84:3 筆,已標記
規則三 years 超出 0–45:1 筆,已標記
這四行就是我論文方法學「資料處理」那一段的原料。補值 23 筆、排除 6 筆、離群 4 筆待處理,每個數字都有一行程式可以對。
然後我做三讀
腳本跑完不代表對。我做的核對是抽三筆手算:隨機挑三個受試者,打開 01_renamed.csv 看原始分數,自己算一次反向和補值,再開 02_cleaned.csv 對。
三筆都對,才算過。三筆裡有一筆不對,整支腳本重看。這跟三讀五對一樣,不是不信任,是流程。
離群值那四筆我一筆一筆看了。每週工時 168 那一筆,是有人把「每月」填成「每週」,看其他欄位可以推斷;年資 45 那一筆年齡填 28,明顯填錯。這四筆怎麼處理是我的決定,不是腳本的——腳本只負責把它們找出來。
哪裡會出錯
它會把反向題做兩次。 第一次跑完我改了規則三的範圍,重跑,結果倦怠分數全部回到原樣。原因是第二次跑的時候它讀的是 02_cleaned.csv——已經反向過的檔——再反向一次,6 減 (6 減 x) 等於 x。這就是為什麼規則一要寫「只對 01_renamed.csv 做,輸出到新檔」,也是為什麼 Day 4 說 clean 裡的東西要能刪掉重生:每一次重跑都從 01 開始,不會疊加。
它用全體平均補值。 第一版規則檔只寫「以平均補值」,它補的是那一題全體受試者的平均。這在統計上是另一種方法,不是錯,但不是我要的,而且兩種補法算出來的分量表分數會差。規則檔要寫到公式層級:「該受試者該量表已作答題目的平均」,一個字都不能省。
它順便把離群值刪了。 我寫「找出離群值」,它找出來然後刪掉,log 寫「已移除 4 筆」。跟 Day 10 一模一樣的問題:我要的是報告,它給我的是動作。刪不刪是研究者的決定,要寫進論文,不能讓腳本替我決定。規則三現在寫得很白:「只標記,不刪除,不修改原值」。
七點量表的反向不是 6 減。 我的睡眠品質量表有一題是七點的,它照倦怠量表的公式用 6 減,算出負數。規則檔裡寫「反向計分」四個字是不夠的,要寫公式,每一個量表各寫一次。
這條線畫在哪
Day 11 說第三週的線會畫得更緊。緊在這裡:AI 不碰資料,只寫碰資料的程式,而且程式我要看得懂才跑。
文獻那週,摘要卡它寫我核對,錯了是一張卡。資料這週,一個補值公式錯,279 筆全錯,而且不會報錯。所以我不讓它直接動資料,連「幫我處理一下遺漏值」這種話都不說。我說的是「依規則檔寫腳本」,中間多一層,那一層就是我讀得懂的程式碼。
我還不確定的地方
補值方法。個人平均補值是老師建議的,但我查到的文獻裡多重插補(multiple imputation)越來越常見,口試委員問起來,「老師說的」不是好答案。這件事我打算在論文限制裡寫,並在 Day 19 週回顧時再看要不要換方法。另一個是直線作答——整份問卷全填 3 的那種——目前規則檔沒有處理,我還沒決定要不要排除,也沒查到護理領域的慣例。
明天
Day 15:把清理好的資料交給 SPSS。變數名稱、value label、遺漏值代碼,這些東西手動設一次要一個下午,而且下次重跑又要再設一次。