SAS Macro: Dynamic Data-Driven Programs 快速產生 SAS Macro Variable
目錄
前言
2020 年 (惡靈惡靈?) 因為全球肺炎疫情延燒的關係,SAS 使用者的年度盛會「SAS GLOBAL FORUM」 改為線上召開,儘管如此,還是無損使用者技術交流的熱度。
在此介紹一篇研討會上相當實用的文章,內容介紹如何利用 SAS Macro 快速產生 Macro variable list 以及如何利用這些 Macro variable 來為我們實現 data-driven programs ,省去令人疲憊 (且容易出錯) 的重複編碼,讓 SAS Macro 為我們工作。
如何產生 Macro Variable?
產生 SAS macro variable 的方法有好幾種,其中最簡單的方式是以 %LET macro statement 指定。
%let output_path = C:\\temp;
%let →告訴 SAS 我們要指定某個值為 macro variable
等號左側 →macro variable 的名稱
等號右側 → (macro variable) 要存的內容
指定好之後,C:\temp 就由 macro variable &output_path 所代替 。我們可以應用這個 macro variable,例如:
filename myfile “**&output_path**\\myfile.txt”;
對 SAS 來說會把他解碼為:
filename myfile “C:\\temp\\myfile.txt”;
%LET 的限制
利用 %LET 指定 macro variable 方便又快速,然而指定的步驟 (%LET) 必須要寫在前面。例如我們想要把 Alfred 這位同學的年齡 (age) 存在 macro variable alfred_age 裡的話:
data _null_; /*在 data step 處理資料,但不產生新的資料集*/
set sashelp.classfit;
where name=’Alfred’;
%let alfred_age = age; /*%let 無法完成指定*/
run;
對 SAS 來說, %LET statement 會優先於 DATA step 處理,因此,雖然 %LET 指定了macro variable,但是 data step 的步驟要跑完才會篩選出 Alfred,%LET 此時是無法作用的。SAS 會把語法解讀為:
data _null_;
set sashelp.classfit;
where name=’Alfred’;
run;
如何在執行 SAS 語法時同時處理 Macro Variable
為了達成「使用 macro variable lists 來建立由資料所驅動的動態程式」的目標,這樣的限制顯然造成了困擾。那麼,有沒有辦法讓指定 macro variable 更加方便快速,且不受 DATA step 執行順序的限制呢? 作者提供了二種方法。
1. SYMPUTX routine
data _null_;
set sashelp.classfit;
where name=’Alfred’;
call symputx(“alfred_age”,age);
run;
call symputx →在 DATA step 內直接將某個文字存到 macro variable 中
CALL SYMPUTX的基本寫法是:
CALL SYMPUTX (macro variable , 文字);
如此一來,執行 DATA step 時,CALL SYMPUTX 也同時也替我們產生了 macro variable: ALFRED_AGE,將 age (年齡為14) 存入 ALFRED_AGE。
2. PROC SQL with INTO clause
proc sql noprint;
select age into :alfred_age
from sashelp.classfit
where name=’Alfred’;
quit;
利用 PROC SQL 搭配 INTO clause 的方法,可以得到和上述 CALL SYMPUTX 一樣的結果。這裡要注意的是,冒號要緊貼著 macro variable 才能成功指定。
如何產生 Macro Variable Lists
了解如何指定「一個」 macro variable 之後,我們可以開始來處理指定「很多個」macro variable。
橫列表與直列表
根據資料儲存成 macro variable 的形式,可以分為二種類型,一是橫列表,例如:
%let origin_list = Asia Europe USA;
此時,三個文字儲存在一同個 macro variable 裡,以空格為分隔符號。其他分隔符號亦可,例如:
%let origin_list = Asia~Europe~USA;
另一種類型為直列表,通常會用相同的字首 (prefix) 加上不同的數字做區隔,例如:
%let origin1 = Asia;
%let origin2 = Europe;
%let origin3 = USA;
利用 DATA Step
有了列表的概念,我們現在可以來處理:如何一次產生多個 macro variable。
我們以 SAS 內建的資料夾 CARS 為例。首先,先產生一個名為 unique_origins 的資料集,去重複且排序。
proc sort data=sashelp.cars out=unique_origins(keep=origin) nodupkey;
by origin;
run;
接著,我們想要把變項 origin 的值存成多個 macro variable,形成直列表。
data _null_;
set unique_origins;
call symputx (cats (‘origin’, _n_), origin);
run;
cats() →黏合字串並去除空格
_n_ →自動序號 (1,2,3 …)
CATS function 黏合字串的功能把字串 origin 「黏上」 SAS 內建變項 _n_ 帶出來的序號 1、2、3…,搭配前述的 CALL SYMPUTX 就可以自動產生三個 macro variable ((origin1, origin2, and origin3)。
值得注意的是,在此語法中,我們不必去數有多少值要存成 macro variable 就已完成了指定多個 macro variable,之後如果資料集有更新,這段語法仍然可以完美的執行任務,建立直列表的 macro variable,且不需要做任何更動。
雖然這個 macro variable list 是資料驅動的,有時候知道產生了多少 macro variable 對於列表後續的使用仍然有很大的幫助,這時候可以利用 END= statement 幫我們找出最後一筆資料是第幾筆。
data _null_;
set unique_origins end=eof;
call symputx(cats(‘origin’,_n_),origin);
if eof then call symputx(‘numorigins’,_n_);
run;
end = eof →是否讀到資料的最後一筆,若是則 eof 為 1,否為 0 (eof 意為 end of file,可自行指定)
此時,因為資料只有 3 筆,因此最後的序數為 3,且會存在 macro variable NUMORIGINS 裡,我們就知道總共產生了 3 個 macro variable。
產生橫列表的方式與直列表相似,但是要先把多個欲儲存成 macro variable 的值以 CATX() 串起來。
data _null_;
set unique_origins end=eof;
length origin_list $200;
retain origin_list;
origin_list = catx(‘~’,origin_list,origin);
if eof then do;
call symputx(‘origin_list’,origin_list);
call symputx(‘numorigins’,_n_);
end;
run;
利用 PROC SQL
橫列表與直列表都可以利用 PROC SQL 來完成,大部分情況下甚至會比 DATA step 更簡便一些。例如:
proc sql noprint;
select distinct origin into :origin1-
from sashelp.cars
order by origin;
quit;
同樣以 CARS 資料集為例,使用 PROC SQL 的好處是可以同時去重複 (distinct) 與排序 (order by) 且以 INTO clause 指定多個 macro variable。這裡要注意的是,SAS 9.3 的版本之後,我們只需要指定啟始的 macro variable 的名稱 (origin1),SAS 會自己找到並指定最後一個 macro variable。
同樣地,如果我們想知道最後列表裡到底產生了多少個 macro variable,此時可以簡便的利用 SAS macro 內建的計數器 SQLOBS 來為我們計算:
proc sql noprint;
select distinct origin into :origin1-
from sashelp.cars
order by origin;
%let numorigins = &sqlobs;
quit;
%let numorigins = &sqlobs →將計數的結果存在 macro variable: NUMORIGINS 裡
橫列表對 PROC SQL 來說也是相對容易:
proc sql noprint;
select distinct origin into :origin_list separated by ‘~’
from sashelp.cars
order by origin;
%let numorigins = &sqlobs;
quit;
separated by ‘~’ →列表以 ~ 做為分隔符號
舉例說明
我們以例子看看 macro variable list 的應用。
%macro split_data;
* Create the vertical macro variable list.;
proc sql noprint;
select distinct origin into :origin1-
from sashelp.cars;
%let numorigins = &sqlobs;
quit;* Loop through each value and generate a data step ;
* to create the corresponding subset. ;%do i = 1 %to &numorigins;
data cars_&&origin&i;
set sashelp.cars;
where origin = “&&origin&i”;
run;%end;
%mend split_data;
%split_data;
利用 SAS 內建資料集 CARS,將全球汽車根據製造商產地 (ASIA, EUROPE, USA) 分為子資料集CARS_ASIA, CARS_EUROPE, CARS_USA。此語法完全不需要事先計算產地個數,並且可動態修正調整 (若日後有其他產地的資料加入)。
結語
一個好的語法可以用來正確的處理資料,一個優秀的語法則是可以隨輸入資料的變化而自動調整。SAS macro variable list 提供了使用者更快速且穩健的編碼方式,有了 macro variable list 程式語法維護起來也更加容易,在數據不停產生且更新的時代之下,將大大提高使用者的工作效率,實現舒適且高效能的編碼環境 !
參考文獻
Using SAS® Macro Variable Lists to Create Dynamic Data-Driven Programs