健保資料分析時,會遇到循環性的資料處理步驟,以1:1個案配對(individual matching)的資料處理過程為例,1個暴露個案(cohort study 的 exposure)或1個病例個案(case-control study 中的 case)依特定條件配到1個對照個案,此時要將該對照個案從對照組的抽樣母體中扣除,再繼續找下一個exposure或case的對照個案,直到對照組的個案建立完成,因此是由巨集指令(Macro Language)的迴圈進行此連續動作。而扣除的方式是以身份證號(ID)以及生日(ID_birthday)做為關鍵變項(key variable),將被抽中者從抽樣母體中扣除,因此在對照組的抽樣母體中再也找不到該位病人的資料,因此不會被之後的exposure或case再抽中,以致相同的對照個案有重覆出現的情形。
如果在過程中,其中的一個exposure或case找不到對照個案,就毋需從對照組的抽樣母體中做任何排除的動作,但必須有程式作為判斷機制,若未抽中對照組,下一個抽樣步驟所使用的對照組之抽樣母體與前一個步驟的抽樣母體相同。但問題是,要如何判斷該進行扣除的動作,還是下一個步驟延用前一步驟的對照組抽樣母體?沒有抽中符合條件的對照個案,log視窗(日誌)中將出現類似以下的訊息:
NOTE: There were 55759 observations read from the data set F.CONTROL_1.
NOTE: The data set WORK.CONTROL_1 has 0 observations and 12 variables.
f.control_1為某retrospective cohort study(回溯性世代研究)的第一個exposure之對照組抽樣母體,依據特定編號的exposure的配對條件進行資料截取後,發現截取出來的筆數是0筆,檔名是work.control_1。
而扣除已抽中的對照個案的方式,是以身份證號(ID)以及生日(ID_birtthday)將被抽中者從抽樣母體中扣除,執行的程式為比對程式(BASE語法中的Merge或SQL語法中的except)。因此,若是有抽中的情況下,符合條件的筆數可能不只一個對照組個案,可能有好幾個個案,需要經過隨機取樣,從符合特定編號的exposure之配對條件的一群個案中抽出一個對照組個案,同時給與和exposure相同的配對編號,如此才算完成一個案的配對動作。如果沒有符合的條件,抽樣結果為樣本數為0,檔案中沒有任何變項,於是無法利用比對程式進行扣除的動作(因為無比對的關鍵變項)。所以要將程式改寫成,若未抽出符合條件者,不進行比對,下一個exposure的抽樣母體與前一個exposure的抽樣母體相同。但為了讓程式循環進行,檔名要作一點更動,讓迴圈能運作起來。
若未抽中任何符合條件的對照個案,在f.control_1為0筆的情況下,會造成循環中斷,因此要造一個檔案,筆數至少1筆,將該檔案與f.control_1合併起來,使新產生的檔案為至少1筆資料的檔案,才可讓程式趨動下去。
在程式一中,製造一筆檔名為obs_1的檔案,該檔只有一筆資料,並放入資料比對所需要的關鍵變項:身份證號(ID)以及生日(ID_birtthday),內容可隨意編寫,只要有資料即可,如以下所示。
【程式一】
/*--產生一筆資料檔----*//*-當沒抽到control時使用-*/
data obs_1;
length id $32.;
input id $ birthday yymmdd8.;
format birthday yymmdd10.;
cards;
* 20160717
;
proc print;
run;
以下程式,係將從對照組抽樣母體所抽出之結果檔(control&x)與【程式一】的檔案(obs_1)合併,並產生新的資料檔,檔名為_NULL_,這個檔存在的目的,是為建立巨集指令的參數(parameter)與參數所對應的值,但以proc print指令查看資料內容,該檔的內容卻無法顯示,要以 「%put _USER_; 」指令將兩者輸出到log視窗(日誌)中,而參數所要對應的值是每個迴圈下的_NULL_的筆數,變項名稱為_N_,而參數被命名為NOBS,這兩者以call symput() 指令關聯起來。
在set指令後方出現end=eof,其中end=eof的指令,代表將資料讀取到最後一個observation。(eof為end of file的簡寫。)
「if eof then call symput('NOBS', _N_)」若讀到最後一筆,將最後一筆的筆數編號放入_N_變項中,該變項所對應的參數為NOBS,因為巨集指令中必須以參數代替參數所對應的值,才能趨動迴圈,讓迴圈不斷循環。
在執行巨集指令之前,要先宣告%MACRO, 巨集名(macro name)為data,參數為x,此參數對應至exposure的配對編號(no) 【註: 由於篇幅關係,在此並未呈現no如何產生之程式段】,透過巨集的迴圈,讓不同配對編號的exposure依迴圈進行扣除對照個案的動作。
【程式二】
%macro data(x);
data _NULL_;
set control&x obs_1 end = eof;
if eof then call symput('NOBS', _N_);
run;
%put _USER_;
如果至少挑到一筆符合條件的對照個案,且利用隨機抽樣抽出一筆對照個案,檔名為f.m&x ,則利用比對程式將f.m&x中的1筆資料從對照組的抽樣母體 (檔名為f.control&x)中扣除掉。
【%if &NOBS > 1 %then %do; 】左邊的條件句,係說明參數 NOBS 所對應的 _N_若大於 1, 那麼進行以下動作, 而以下的動作為比對程式。
比對的關鍵變項為id, birthday,比對的條件為 【if a=a-b】,a與b為集合名,b代表f.m&x,a代表f.control&x,由in=a,in=b的方式將兩者對應起來。因此,如果b集合中的id, birthday,與集合a有相同的id, birthday,則將兩集合相同的部份扣除掉。
新產生的檔名為f.control%eval(&x.+1),其中%eval(&x.+1)的%eval(),是巨集指令的計算函數,參數x加上1,將新的檔名序號推向下一個循環,產生下一個循環的對照組之抽樣母體檔。
若完成以上指令,要記得將【%if &NOBS > 1 %then %do】此條件句做個結束,因此要寫%end才可結束。
/*--(1) 有挑到一筆control時--*/
%if &NOBS > 1 %then %do; /*只有在整體筆數>=1時, 才繼續動作*/
proc sort data=f.m&x;
by id birthday;
proc sort data=f.control&x;
by id birthday;
data f.control%eval(&x.+1);
merge f.control&x(in=a) f.m&x(in=b);
by id birthday;
if a-a=b;
run;
%end;
【%else %if &NOBS =1 %then %do; 】左邊的條件句,係說明參數 NOBS 所對應的 _N_若等於 1, 那麼進行以下動作。當_N_=1,即說明沒有抽中任何符合條件的個案,而筆數為1的原因,因這一唯一的一筆資料來自【程式一】的obs_1。
而以下的動作為:若未抽出符合條件者,不進行比對,下一個exposure的抽樣母體與前一個exposure的抽樣母體相同。
新產生的檔名為f.control%eval(&x.+1),set後方的檔名為f.control&x,即f.control&x內的資料內容直接搬入新的檔案f.control%eval(&x.+1)中。
若完成以上指令,要記得將【%else %if &NOBS =1 %then %do】此條件句做個結束,因此要寫%end才可結束。
結束巨集指令的運作後,要宣告巨集結束,指令為%mend。
/*--(2) 沒有挑到任何control時--*/
%else %if &NOBS =1 %then %do;
data f.control%eval(&x.+1);
set f.control&x;
run;
%end;
%mend;
以下迴圈,用來產生參數x的對應值,對應至每一個exposure的配對編號 (no)。
如果我們不會寫迴圈程式,直覺會寫的程式如下,即直接寫參數所對應的解開的值,但要一一寫入編號,如果有1000個exposre,要寫1000個參數所對應的解開的值,程式顯得冗長,因此可以迴圈的方式代替。
%data(1) %data(2) %data(3) ……省略……%data(1000)
在以下程式中,以%data(&x) 代替以上的程式,但此程式必需被巨集包覆,因此要宣告%macro,給定巨集名 loop,結束時宣告%mend,再宣告一次巨集名%loop。
以【%do x=1 %to 1000;】及【%end;】趨動迴圈,使%data(&x)中的參數x對應至1到1000,如此即完成任務。
%macro loop;
%do x=1 %to 1000;
%data(&x);
%end;
%mend;
%loop
留言列表