量化研究流程討論:以「勞保資料分析育嬰津貼成效」為例
這一節我想要整理執行研究時的流程與可能遭遇到的困難。我們可以想像,執行研究會經歷取得資料、清理資料、合併資料、分析並輸出結果等過程,這些過程會藉由一段段的 code 執行,像是生產線般地運作並產出結果。如果我們只把眼光放在為了達成特定目的而寫的某段 code,這樣的任務大多不難,頂多就是花點巧思或查詢好用的指令。真正困難地方在於把這些 code(零件、設備)組合成一個分析流程(pipeline、生產線),並能夠持續維護與擴充。
寫這篇文章的時候,我發現要把研究流程中的困難或痛點說清楚並不容易,因此我想利用曾經執行過的研究,試著把這份研究的流程說清楚,並討論我為何要這樣執行,以及我所遭遇的困擾。
討論:孕嬰假津貼政策分析¹
背景
台灣於 98 年 5 月開始引入了育嬰留職停薪津貼,只要父母有 1 年以上的就業保險年資並依規定申請育嬰留停,就可以享有最多 6 個月的津貼與最多 2 年的育嬰假(小孩滿 3 歲前)。在 98 年 5 月前只有 2 年的育嬰假且尚未提供育嬰津貼,對於違規公司也還沒有明定罰則。²本研究的目的是想瞭解申請育嬰留職停薪對於婦女長期就業與薪資的影響,比如說:請育嬰假是否會減少婦女生育後中、長期的勞動參與。我們利用了勞(就)保與健保資料庫的幾份資料:
- 就保育嬰津貼給付檔
PLPAY.dta
:記錄育嬰津貼給付記錄,用來看誰申請津貼、 請領的起訖時間。 - 勞保生育津貼給付檔
FERT.dta
:記錄生育津貼給付記錄,用來挑選母親。請注意「生育津貼」和「育嬰留停津貼」是不同的喔! - 勞就保保險人檔
INSU{9301–10512}.dta
:可以看到薪資、就業軌跡與投保單位(從 93年 1 月到 105 年 12 月)。 - 事業單位檔
INST.dta
:投保單位的背景資料。 - 健保出生通報檔
BHP.dta
:可以看到所有母親(2000–2015,無論投保狀態、工作與否)。
讓我們先專注在以下樣本與方法的分析:我們心目中的樣本是在 94–100 年產下第一胎的母親,並利用未生育女性作為對照組。由於育嬰假津貼是在 98 年 5 月引入,這樣我們就有了政策前後、母親與對照組的 DD 架構(差異中差異法)。更進一步地,我們利用政策對不同生育時間點母親的差異來當 IV(工具變數),去看請育嬰假(X)對生育後勞動變數(Y)的影響。我不打算在此扯太多研究或實證方法,取而代之,我試著條列我的目標以及最後要分析的樣本:
- 分析樣本包含母親(實驗組)與未生育女性(控制組)。
- 每位母親都和未生育女性,根據母親生育前 1 年內的特質進行配對。對於未生育女性,配對到的母親的生育時間點,可以想做其「假想的生育時間點」。
- 我們使用到生育前 1 年的特質與生育後 5 年的勞動變數,因此我們挑選 94–100 年生產的母親(保險人檔是從 93–105年)。
流程
為了達成研究目的,以下是整個處理資料的流程。每點對應一份 do-file,並摘要該份 do-file 中執行的任務。為方便討論,我只保留完成研究所需的幾份 do-file,以及其中的核心任務:
0_Prepare.do
:Raw Data 的清理。產出(投勞保的)母親名單fert_child1.dta
與育嬰津貼請領名單pl_child.dta
。1_List_Extract.do
:從保險人中取出第一胎母親的薪資與工作歷程fertprofile.dta
(逐月讀取勞就保保險人檔INSU{9301–10512}.dta
,並利用fert_child1.dta
篩選母親)。2_Mom.do
:計算母親產前特質與產後勞動變數。從fertprofile.dta
中標記符合育嬰津貼請領條件的女性、計算生育前特質以及生育後勞動變數,最後彙整成以母親為單位的資料mother_p1.dta
。3_Control.do
:取出未生育女性(控制組)的薪資與就業歷程、和母親(實驗組)配對、計算產後(配對到的時間點)勞動變數、合併為最終分析資料。³4_Analysis.do
:生成額外的 control variables、分析與繪圖。5_RegTask.do
:執行回歸並輸出回歸結果的報表。
以上是我的研究流程,說起來其實就像是一條(有些繁瑣的)生產線呢!要如何設計、組裝、維護這條生產線是研究人員不得不面對的困擾。對一個經驗老到的研究人員來說,這也許是很直覺的事,但對於新手來說,往往會搞得七零八落,不僅影響研究時的順暢度,也常常增添協作、交接時的成本。老實說,我在執行時,大部分的時候都是走一步算一步,思考準備怎樣的資料、變數對我的下個步驟最有幫助且必要。接下來,我想就我研究流程中遇到的問題做些自問自答,我不保證我的想法正確或最佳,但背後多少有些理由。
流程討論
1. 要怎麼管理 do-files?比如說:命名、分段落、分 do-file。
首先,關於 do-files 的命名,如果是主要流程上的 do-files,我喜歡命名成有 編號與內容的形式(如:1_List_Extract.do
)。然而,分析時經常會遇到從主要流程插出去的任務或是單次性的嘗試,前者我會用改成使用編號 A 或 A1(appendix 之意,如:A_Extrac_HI.do
),後者則是不加任何編號 (如:reg0108.do
、match_try0401.do
等)。這樣簡單的命名就很容易讓人知道該順著哪些 do-files,從 raw data 產出結果。
再來,關於區分段落、do-file。我想大概有幾個方向:
- 盡量讓 do-file 內的工作類似
- 避免 do-file 過於冗長
- do-file 內盡量以讀取與儲存為一個段落並適當地利用 comment operator
\\
、*
,分段與註記
比如說:我的 0_Prepare.do
就是用來讀取 raw data 並暫存中間檔案;我的 4_Analysis.do
、5_RegTask.do
是專門用來跑分析,因此原則上不會再新增樣本點進來(這件事應該在 3_Control.do
就處理完)。
我們還可以在 do-file 或段落的開頭或結尾利用特定指令或註記來增加可讀性。do-file 一 開始,除了我在 2–1 提到的 clear all
、 capture log close
、 cd <path>
,也可以註記這份 do-file 的概要、段落、修改時間等資訊。對 do-file 的每個段落,建議可以在開頭(讀入時)或結尾(儲存前)使用 order
、 sort
、 tab
、 isid
等指令,這樣可以把資料排列成好瀏覽的樣子,像是:把 key variables 擺到前面;或是提供重要資訊,像是:用 isid
確認資料的 key variables。即使實際上不需要這些指令(大可 comment 掉就好),對於研究人員來說也是很好的提示。
2. 關於原始資料、中間資料,有什麼要特別注意的地方?
首先,千萬別更動原始資料(我確實聽過許多案例)。再來,中間檔案大多是產出結果的必要過程,但有些時候也可以多存一些中間檔案來節省迭代更新 code 的時間。那麼要在哪個步驟暫存中間檔案呢?我提供幾個想法:
- 在那些經常需要修改的步驟前存檔,如:matching 經常要嘗試各種設定, 因此我在
3_Control.do
中 matching 的步驟前先逐月存了control{9401–10012}.dta
而不是只存controlprofile.dta
(未生育女性的工作歷程),方便 matching 的步驟可以迅速使用並重複實驗。 - 在那些花時間的步驟後存檔,如:
1_List_Extract.do
逐月合併母親的保險紀錄後,馬上存了fertprofile.dta
,而不是直接跳到2_Mom.do
並彙整資訊到母親上。
3. 從原始資料篩選至分析樣本的過程中,應該在什麼時候丟掉非目標的樣本呢?
哪裡丟有差嗎?當然有。研究過程中,我們常常後來才得知,某些樣本是(或不是)分析對象,因此最終的分析樣本其實經常在變動。因此,太早丟意味著你要從頭跑過整個分析流程。我的想法是:在不過度擴張樣本且顯著拖累分析的前提下,可以盡量在分析前一刻,再來篩選需要的樣本。也就是說,不一定要馬上丟掉非最終目標的樣本,先標記即可。這樣做的好處是增加了分析上的彈性。
在我的例子中,我的樣本從最初的「生第一胎的母親」到「生第一胎且合資格的母親」,最後再到最後「生第一胎、合資格且配對成功的母親」。我在 3.Control.do
做 matching 前的還留著所有「生第一胎的母親」,若我希望用「生第一胎但未合資格的母親」做否證性測試(falsification test,也就是不該估計出效果的 sample),僅需從 3.Control.do
matching 部分的 code 重跑即可。
4. 為什麼要把數期的投保資料(母親、未生育女性)合併成跨期的歷程呢?
這個問題背後的想法是:操作龐大的工作歷程資料很吃運算資源,因此能不能不把分析流程改成逐月抓母親與未生育女性 Matching 呢?不這麼做主要是因為我有許多變數要利用到「跨期的資訊」,因此合併多期後再做運算應該是最自然的方法,而且合併多期而成的歷程(profile)還有繪製薪資軌跡等用途。總的來說,雖然無可避免地龐大,卻也相當實用。
5. 為什麼我在 3_Control.do
的流程,比起其他 do-file 複雜
摘要一下,我在 3_Control.do
要做幾件事:合併非生育女性的工作歷程、擷取「每個時點」的「產前變數」、和母親配對、依非生育女性配對到的時點計算「產後變數」。光看摘要,也許無法想像複雜在哪,我試著說明其中的關鍵:
- 我不僅針對
1_List_Extract.do
、2.Mom.do
的流程重新跑過,而且還做些調整:在「每個時點」都生成(假想的)產前變數。 - 比起母親,非生育女性的樣本更加龐大,因此我在存取上盡量只抓需要的部分。像是我在配對前只用了 93–100 年的保險資料算產前變數,確定配對成功的名單後,才用 101–105 年的保險資料補算產後變數。
也許有人會想,為何不要一起操作母親與非生育女性的資料呢?當然,大部分流程其實高度相似,合併起來一起操作更可以確保處理過程一致,減少出錯的機會。然而,我需要對非生育女性的「每個時點」都做計算這件事,終究讓 code、流程的整合產生難度(也可以說是我一開始沒這麼嘗試所留下的 lagacy)。
6. 修改設定時,到底要改哪裡呢?怎麼總是漏東漏西
這是修改 code 時常常遇到的問題,比如說:我修改迴歸中的控制變數與 options,但改了前 4 條卻漏了第 5 條;我想調整樣本 A 的篩選規則,卻忘了樣本 B 的 code 也要同步修改。我想有幾個可以嘗試的做法:
- 利用
local
變數將流程參數化(參考 2–6 或是 4–2 將產生大量回歸報表過程參數化的範例)。 - 明確地將 code 的某個區塊作為設定參數的地方,內容可能包括篩選、定義
local
、組別變數的設定等。 - 我心中覺得更好的做法是把流程寫成 function,甚至是像 python 一樣,寫成 module、package。但就我所見,STATA 的使用者很少針對處理資料的流程寫 function(STATA 的
program
)。
類似的問題其實也發生在要擴充既有的 code 時,像是:我希望把第二胎的母親也納入分析。
- STATA 上最直接的作法是把既有的 code 複製貼上到新的 do-file 並做必要的修改,像是:修改存取資料的名稱。另外的做法是在既有的 code 上做參數化,利用參數來控制擴張的內容,像是:切換第一胎或第二胎的母親。
- 要用參數化來處理擴張需求的話,也要思考既有的 code 可否適用、是否只要用參數控制幾個關鍵指令即可。
- 中間檔案也建議要另存新檔,並透過有邏輯的命名管理。像是第一胎母親的歷程是
fertprofile1.dta
,而第二胎是fertprofile2.dta
,這時就可以透過local birthorder <n>
、use fertprofile`birthorder'.dta
來做切換。
7. 要如何監督分析流程的正確性?如何偵錯?
有時候分析果不如預期,可能是處理資料流程的某個步驟出錯了,要回頭逐步偵錯便是一件很痛苦的事。有幾件事可以加速或自動地協助偵錯:
- 印出執行進度:每個 do-file 中的段落都可以利用
di <info>
來追蹤 code 執行的進度,像 Fig 3 的最後,我就用di “CEM done”
提醒自己 Matching 的步驟跑完了。 - 印出每個步驟的樣本數:跑過幾次後,通常會對每個步驟的樣本數有概念。這時可以透過
di _N
、tab <group>
之類的指令來確認每次的輸出結果符合預期。 - 確保結果的穩定性:像是 sort 前先想想結果是否穩定(sort 的變數可以 identify 資料嗎?)、利用
set seed
確保每次使用隨機函數的結果一致。 - 透過
assert
、isid
確保結果符合預期。
8. 其他
- do-file 內的 comment 除了註記外,很多時後是為了切換設定,像是切換不同版本的分析檔案(
use analysis_v10904.dta
)。為了和一般的註記區分開,可以考慮用「*
」或「***
」,而不是「//
」。又或者,明確地在 do-file 中建立調整設定的段落。 - 避免在操作背後仰賴過多假設(ad-hoc)。例如:我要先跑 line 100-120、然後再跑 line 140-150,如果讀入的是資料 B,那還要先生成變數 C 並把變數 D 的 missing 設成 0,再把 line 130 comment 掉。老樣子,應該要讓這些設定參數化並集中在 do-file 中某個(些)一目瞭然的段落。
[上一節] 5–2 利用 STATA 處理巨量資料
[1] 這份研究是我的碩論題目,在此特別感謝江淳芳教授在我撰寫碩論期間提供許多方向與建議。
[2] 育嬰津貼、生育津貼的請領資格可以參考勞保局的網站https://www.bli.gov.tw/0015726.html。
[3] 2009 年 5 月通過育嬰津貼後,最早可以讓 2006 年 5 月生育的母親受惠,因為這群母親的小孩尚未(或說至少還有些時間)滿 3 歲,因此我們可以看到 Fig 1 的津貼請領率是從 2006 年 5 月開始遞增。