- 概念
一開始,我以為 SAS 巨集的概念和 R 的函數(function)只是很像而已。其實,SAS的巨集就是R的函數(自定義的)如下:
%macro h11 (startnum,stopnum) /* 定義一個巨集*/ %h11(1,15); /*使用定義好的巨集*/
|
概念真的很簡單,操作起來就不一定了,有可能會非常複雜,甚至有專門的偵錯工具,如SYMBOLGEN,etc。至少,有一個原則就是,絕對把DATA階段與PROC階段分的一清二楚,尤其是加上條件語句( IF … THEN DO;…;END; ELSE DO;…;END;)。因為,在巨集裏面,DATA階段與PROC階段沒有顏色區分,很容易混淆。
若說 DATA階段是山, PROC階段是海,那麼巨集就能包山包海了。
對我而言,第一次做巨集程式,既緊張又興奮,洋洋灑灑寫完,結果跑不出來。仔細一看,原來是用條件語具把DATA階段與PROC階段混在一起了。來個CASE STUDY,細節如下:
%macro h11 (startnum,stopnum) %DO N = &startnum %TO &stopnum; /* 這個非常關鍵,命名為 N */ data T01; if 0<&N<10 then do; /* 這個非常關鍵, &N 調用 */ PROC SUMMARY DATA=T01; run; end; else do; PROC SUMMARY DATA=T01; run; end; run; %END; %MEND H11; |
我的錯誤就是用DATA階段包一個條件語句,再用條件語句包一個PROC階段。明顯低級錯誤,居然用DATA階段包一個PROC階段,而且DATA階段也沒有讀資料檔的動作。所以,把那哥倆好分開之後,料理就出爐了。
%macro h11 (startnum,stopnum) %DO N = &startnum %TO &stopnum; /* 這個非常關鍵,命名為 N */ data T02 T03; set T01; if 0<&N<10 then output T02; /* 這個非常關鍵, &N 調用 */ else if 10<=&N then output T03; PROC SUMMARY DATA=T02; var stha; OUTPUT OUT=T02S SUM=stha; run; PROC SUMMARY DATA=T03; var stha; OUTPUT OUT=T03S SUM=stha; run; %END; %MEND H11; |
NOTE:
- D08012, D08022, D08032, D08042, D08052, …, D08992
巨集的方便就是可以批次的、按規則的料理行變數。上面的數列若要用PROC階段加總,可能會需要99+個。
透過巨集,使用 “&N” 標籤,用 ”D08&N . 2” 表達就可以達成目的。
溫馨提示一,“&N” 標籤需要一個 “ . “ 來與 2隔開,像這樣,D08&N.2。
溫馨提示二,巨集裡的數字是表示成”文字”,不是”值”。所以,N 的範圍要從 01 至 99,那數列在 1 至 9 的範圍就不會少一個 “0”。
Reference:
- http://www.loyhome.com/learning-sas-in-7-days-6/
- http://r97846001.blog.ntu.edu.tw/2011/01/13/sas-macro-%E5%8F%8D%E8%A6%86%E9%81%8B%E7%AE%97do-part1/
- http://carllin76.blogspot.tw/2008/11/sas-macro.html
2. 分解動作
其實,SAS的巨集就是R的函數(自定義的)。一面來講,望文生義,巨集就是一個自己為經常重複而預先定義好的運算單元。比如說,我在整理資料後,經常使用 PROC SUMMARY 把資料加總後生成新的資料集。如果依照行變數可以有 5 種資料整理的方式,這 5 種要用 PROC SUMMARY 加總,並分別生成 5 張資料集。也就是說:
DATA …; PROC SUMMARY …; CLASS AREA; VAR COL1-COL5; OUTPUT OUT=A1_1 SUM=…;DATA …; PROC SUMMARY …; CLASS AREA; VAR COL1-COL5; OUTPUT OUT=A2_3 SUM=…;DATA …; PROC SUMMARY …; CLASS AREA; VAR COL21-COL25; OUTPUT OUT=A3_5 SUM=…;DATA …; PROC SUMMARY …; CLASS BIRTH; VAR COL1-COL5; OUTPUT OUT=A4_1 SUM=…;DATA …; PROC SUMMARY …; CLASS BIRTH; VAR COL6-COL10; OUTPUT OUT=A5_2 SUM=…; |
但是,使用 巨集,一切就變得很簡潔:
%MACRO A1(M, N, O, P) PROC SUMMARY …; CLASS AREA; VAR COL&M-COL&N; OUTPUT OUT=A&O._&P SUM=…;%MACRO A2(M, N, O, P) PROC SUMMARY …; CLASS BIRTH; VAR COL&M-COL&N; OUTPUT OUT=A&O._&P SUM=…;DATA …; %A1(1,5,1,1);DATA …; %A1(1,5,2,3)DATA …; %A1(21,25,3,5)DATA …; %A2(1,5,4,1) DATA …; |
上面只重複 5 次而已,差異沒那麼明顯。但是,若需要 40 組 PROC SUMMARY,那麼差異就會非常顯著了!
也就是說,我把 巨集 A1 與 巨集 A2 預先設定為 列印巨集1 與列印巨集2,下一次我要使用時,就可以直接調用,並把參數丟進去,就能得到自己要的結果。
3. CASE STUDY
data T01; set T01; %MACRO H09(startnum,stopnum); %DO I=&startnum %TO &stopnum; %LET J=%EVAL(2*(&I-1)+101); /* 巨集內的函數運算 */ %LET K=%EVAL(2*(&I-1)+102); /* 巨集內的函數運算 */ IF 1<=&i<=9 then do; IF D7&0i>0 THEN COL&J=1;ELSE COL&J=0; COL&K=D70&i; end; else do; IF D7&i>0 THEN COL&J=1;ELSE COL&J=0; COL&K=D07&i; end; %END; %MEND H09; %h09(1,30); IF D731>0THEN COL161=1;ELSE COL161=0; IF D731=0THEN COL162=1;ELSE COL162=0; /*——————————————*/ %A1(101,161,09,1); %A2(101,161,09,2); |
note:
本例中,code不完美,因為會出現遺漏值。原因出在 IF-THEN-ELSE條件語句,應該要改成:
%IF 1<=&i<=9 %then %do; …; %end;
%IF 10<=&i<=99 %then %do; ….; %end;
〈2014.7.27 更新。其中有很多奧妙之處,還要再研究研究。〉
%LET J=%EVAL(2*(&I-1)+101)
這邊要說明的概念,是如何用一個循環變數(I)影響多個變數(J, K)一起”循環”。這裡有一個觀念,巨集語言把所有的”數字”都當作”文字”,並不是”值”,所以不能直接進行數學運算。所以,在巨集裡,” J = 2*(&I-1)” 是有問題的。因此,要先以 “&LET” 先產生一個新的變數 “J”,之後才能以“&J”調用,並且,要以 ”%EVAL( )” 帶進整數的函數運算,作值的加減乘除。
小發現:用” %h09(1,30)” 調用巨集後,雖然 1 至 30 是數字,但送進迴圈後會按”值”做遞進。也就是說,” %h09(01,30)”的數字送進 “%DO I=&startnum %TO &stopnum” 迴圈後,01至09就會成為 1 至 9 。因此,上面例子就以條件語句判斷 N 為 1 至 9 ,則加一個 ”0”。
%A1(101,161,09,1);
按前例,是一個列印巨集,這裡”09”沒有進迴圈,所以,直接是以”文字表達”,不用擔心是否補 ”0”。