上學期我在 SPSS 的「變數檢視」裡待了一個下午。三十個變數,每一個都要設名稱、標籤、值標籤——性別 1 要標成「男」、2 要標成「女」,教育程度 1 到 4 要標成專科、學士、碩士、博士。點開值標籤視窗,輸入數字,輸入文字,按新增,三十個變數重複三十次。
做完存檔,隔天發現 Day 14 的清理規則改了一條,資料要重新匯出。變數檢視那個下午,等於白做。
今天要把這個下午變成一支腳本。跟 Day 14 一樣的邏輯:規則寫成檔案,讓工具照著做,我核對結果——只是這次的產物不是 CSV,是 SPSS 讀得懂的 .sav。
兩件事做錯了不會馬上被發現
變數檢視裡要設的東西很多,但真正危險的只有兩種。
值標籤錯了,是解讀錯誤。 性別的 1 和 2 標反了,統計數字全部是對的,只是你在論文裡寫「男性倦怠分數顯著較高」的時候,其實說反了。這種錯口試委員抓不到,因為表格看起來一切正常。
遺漏值代碼沒設,是數字本身錯了,而且錯得很安靜。 這是今天真正的重點,我用一個實際會發生的例子講:問卷裡「年資」這一題,允許受試者選「拒答」,我把拒答編碼成 999。如果我忘記在 SPSS 裡把 999 設成遺漏值,SPSS 會把它當成一個真實的年資——999 年。三個人拒答,年資的平均數不是從十二年變成十三年,是直接跳到三、四十年。
這個錯不會跳出警告。SPSS 算得很開心,數字就是不對,而且通常大到你會發現——但如果拒答的是「每月收入」,跳過去的幅度沒那麼誇張的話,錯誤可能就這樣留在描述統計表裡,一路帶進論文。
值標籤決定你怎麼解讀資料,遺漏值代碼決定資料本身對不對。 後者更危險,因為它連基本的平均數都會壞掉。
先寫字典,不要用滑鼠設定
跟 Day 11 的 APA 規則檔、Day 14 的清理規則檔同一套做法:先寫成人讀的檔案,analysis/spss-dictionary.md。
data/clean/02_cleaned.csv(Day 14 輸出)
| 變數名 | 標籤 |
|---|---|
| bo01–bo22 | 職業倦怠量表第 N 題 |
| bo_missing_flag | 倦怠量表缺失標記(1=遺漏超過 4 題) |
| hours_week | 每週工時(小時) |
| hours_week_flag | 每週工時離群標記 |
| years | 護理年資(年) |
| years_flag | 年資離群標記 |
| sex | 性別 |
| education | 最高學歷 |
| shift | 班別 |
| 變數名 | 值 | 標籤 |
|---|---|---|
| sex | 1 | 男 |
| sex | 2 | 女 |
| sex | 999 | 拒答 |
| education | 1 | 專科 |
| education | 2 | 學士 |
| education | 3 | 碩士 |
| education | 4 | 博士以上 |
| shift | 1 | 白班 |
| shift | 2 | 小夜班 |
| shift | 3 | 大夜班 |
| 變數名 | 代碼 | 意義 |
|---|---|---|
| sex | 999 | 拒答,不納入該變項的描述統計 |
data/clean/03_for_spss.sav
三張表對應三件事:標籤讓委員看得懂變數是什麼,值標籤讓委員看得懂數字代表什麼,遺漏值代碼讓 SPSS 不會把拒答當成真實數字去算平均。
SKILL.md 全文
存在 .claude/skills/spss-export/SKILL.md:
data/clean/02_cleaned.csv,analysis/spss-dictionary.md
腳本長什麼樣
import pandas as pd
import pyreadstat
df = pd.read_csv("data/clean/02_cleaned.csv")
column_labels = {
**{f"bo{i:02d}": f"職業倦怠量表第 {i} 題" for i in range(1, 23)},
"bo_missing_flag": "倦怠量表缺失標記(1=遺漏超過 4 題)",
"hours_week": "每週工時(小時)",
"hours_week_flag": "每週工時離群標記",
"years": "護理年資(年)",
"years_flag": "年資離群標記",
"sex": "性別",
"education": "最高學歷",
"shift": "班別",
}
variable_value_labels = {
"sex": {1: "男", 2: "女", 999: "拒答"},
"education": {1: "專科", 2: "學士", 3: "碩士", 4: "博士以上"},
"shift": {1: "白班", 2: "小夜班", 3: "大夜班"},
}
missing_ranges = {"sex": [999]}
variable_measure = {
**{f"bo{i:02d}": "ordinal" for i in range(1, 23)},
"hours_week": "scale",
"years": "scale",
"sex": "nominal",
"education": "ordinal",
"shift": "nominal",
}
pyreadstat.write_sav(
df,
"data/clean/03_for_spss.sav",
column_labels=column_labels,
variable_value_labels=variable_value_labels,
missing_ranges=missing_ranges,
variable_measure=variable_measure,
)
back, meta = pyreadstat.read_sav("data/clean/03_for_spss.sav")
with open("analysis/export-check.md", "w", encoding="utf-8") as f:
f.write(f"筆數:{len(back)},欄數:{len(back.columns)}\n")
f.write(f"bo01 標籤:{meta.column_names_to_labels.get('bo01')}\n")
f.write(f"sex 值標籤:{meta.variable_value_labels.get('sex')}\n")
f.write(f"sex=999 筆數(應設為遺漏):{(df['sex']==999).sum()}\n")
這支我實際跑過。279 筆、30 個變數,讀回來 bo01 的標籤是「職業倦怠量表第 1 題」,sex 的值標籤是 {1.0: '男', 2.0: '女', 999.0: '拒答'},跟字典檔一致。
哪裡會出錯
遺漏值的參數格式,第一次寫錯。 我一開始寫成 {"sex": [{"hi": 999, "lo": 999}]},以為要用範圍表示單一數值,結果 pyreadstat 直接丟型別錯誤:missing_ranges 的值必須是數字或字串。查了才知道單一遺漏值就是丟數字進列表,{"sex": [999]},範圍才用 hi/lo 那種寫法。錯誤訊息看起來很嚇人,其實是我把兩種寫法混在一起。
忘記把遺漏值代碼一起寫進規則。 第一版字典檔我只寫了值標籤,沒寫遺漏值代碼那一段。腳本照做,.sav 打開之後 sex 的 999 確實標成「拒答」了,但 SPSS 算描述統計時還是把它當一個有效值計算平均——因為標籤只是「這個數字叫什麼名字」,遺漏值代碼才是「這個數字不算數」,兩件事完全獨立,設了前者不會自動設後者。這是我在這個工具上踩到最貴的一個坑,因為它產出的表格看起來完全正常,數字卻是錯的。
布林欄位要先轉成數字。 bo_missing_flag 這種 True/False 的欄位,pyreadstat 寫不進去,要先轉型成 0 和 1,順便在字典檔的值標籤裡把 0、1 標成「否」「是」,不然打開 SPSS 只看得到 0 跟 1,看不出意思。
中文變數標籤的編碼。 寫出來的 .sav 檔用 UTF-8 存中文標籤,比較新的 SPSS(21 版以後,通用 unicode 模式)讀起來沒問題。但如果你的電腦或系所裝的是很舊的版本,或系統語言設定跟檔案編碼對不上,中文標籤打開可能是亂碼。保險的做法是匯出後先自己打開來看一眼,變數檢視裡的中文顯示正常,才算過關,不要匯出完就直接拿去分析。
什麼還是要在 SPSS 裡自己做
這支腳本做完,變數檢視那一頁該有的都有了。但 SPSS 裡有些操作我還是手動做,沒有寫進腳本:算 Cronbach's α、算加總量表分數、跑相關和迴歸。
不是做不到,是這些屬於分析,不是資料整理。今天做的三件事——變數標籤、值標籤、遺漏值代碼——都是「讓資料被正確理解」的層次,錯了會讓下游全部跟著錯。分析的每一步我要看著它跑、看著結果,這條線我還沒想清楚要不要也寫成腳本,先手動做,等熟悉了再考慮。
核對怎麼做
腳本自己輸出的 export-check.md 只驗證「腳本做了它說要做的事」,不代表資料本身沒問題。我自己另外做的核對:打開 .sav,變數檢視掃一遍三十列,資料檢視隨機挑五筆跟 02_cleaned.csv 對一次數字,再跑一次最陽春的次數分配表,確認 sex 的「拒答」在系統遺漏值那一欄,不在有效百分比裡面。
三讀的最後一讀,永遠是在目的地軟體裡親眼看一次。
我還不確定的地方
倦怠量表的測量尺度我標成「次序」,但加總之後的總分實務上大家都當「等距」處理去跑迴歸,這件事統計上有爭議,我目前照領域慣例做,但字典檔裡的標示其實不完全誠實。另外「拒答」要不要整欄排除、還是只在算到那個變項時排除,我還沒有標準答案,等做到相關分析那一步再決定。
明天
Day 16:清理好、匯出好的資料,怎麼把統計結果自動轉成論文要的 APA 格式表格——表一那種,橫線在哪裡都有規定的那種。