公告

[公告]
2014/01/17
由於已經是faculty的關係,不太有足夠時間寫部落格。因此更新的速度會相當緩慢。再加上近幾年來SAS GLOBAL FORUM沒有出現讓我覺得驚艷的技術文件,所以能分享的文章相對也減少許多。若有人推薦值得分享的SAS技術文件,請利用『問題討論區』告知。

2013/07/19
臉書留言板的功能因為有不明原因故障,因此特此移除。而intensedebate的留言板因管理不易,也一併移除。目前已經開啟內建的 G+ 留言系統,所以請有需要留言的朋友,可直接至『問題討論區』裡面留言。


2008年1月14日 星期一

Automatically Renaming Common Variables Before Merging

原文載點:http://www.nesug.info/Proceedings/nesug07/cc/cc06.pdf

在 merge 不同的資料集之前,如果有些變數名稱相同時會導致 merge 失敗。處理這種問題的方法不外乎是先把重複的變數名稱重新命名。rename 是 SAS data step 裡面用來重新替變數命名的基本語法,可是一旦重複的變數太多,用 rename 一個個輸入新變數名稱就會變的冗長耗時。Christopher J. Bost 在 NESUG 2007 提供了一種用 PROC SQL 快速大量自動重新命名的方法,在此提供給大家一個參考。

簡單地來看一個例子。假設下面兩個資料集準備要拿來合併:


在這兩個不同的資料集裡面,變數 b 和 c 具有重複的變數名稱。因此在使用 merge 合併時,我們通常都會用下面這段程式碼替其中一邊的 b 和 c 重新命名:
data onetwo;
merge one two(rename=(b=TWOb C=TWOC));
by id;
run;

結果如下:


可是,如果有幾十個,甚至幾百個變數都需要 rename 的話,那就必須得一一輸入 oldname = newname 這段程序。雖然在技術上不是什麼太大的問題,只要你有時間,一定是可以慢慢輸入完的。不過如果有更快速的方法能夠把 rename 這個動作用極短的程式碼來完成,而且還不需要另外呼叫其他 macro 來處理的話,那就更好了。

PROC SQL 程序可以取得一些 SAS 內部的訊息。這些訊息當然不是擺著好看的,可是懂得利用這些訊息的人並不多。所有變數和資料集的訊息,都會放在一個叫做 DICTIONARY.COLUMNS 的物件裡面。想要知道裡面有什麼訊息,可以用這段程式碼呼叫出來:
proc sql;
describe table dictionary.columns;
quit;

結果不是在 output,而是在 log 視窗裡面:


其中有三個訊息是可以拿來做重新命名的,分別是 libname、memname 和 name。如果想要知道這三個東西的詳細資訊,則可以用下面這段程式碼叫出:
proc sql;
select libname, memname, name
from dictionary.columns
where libname='WORK' and memname='ONE';
quit;

結果如下:


這段程式碼把資料集 one 和所在的 libary、資料集檔名以及內部變數名稱都列舉出來。特別注意一點是,library 和 memname 裡面的資訊都一概大寫,所以在 where statement 後面的 libname= 和 memname= 都要接上大寫的 library name 和 data set name,即便是這些原先都是小寫,還是得改成全部大寫。

如果只想知道變數訊息,則程式碼得改成:
select upcase(name)
from dictionary.columns
where libname='WORK' and memname='ONE' and upcase(name) ne 'ID';

由於 name 裡面的變數名稱大小寫會符合原始資料裡面的設定,但是 PROC SQL 在這部分只認得大寫,所以必須使用 upcase(name) 把裡面的資訊全部變成大寫,然後再把小寫的 id 變成大寫的 ID。如果單獨用 PROC SQL 跑這段程式,輸出結果如下所示:


接著,針對資料集 two,我們也只想知道其變數資訊,則可用下面這段程式碼:
select name
from dictionary.columns
where libname='WORK' and memname='TWO' and upcase(name) in ( previous query )

其中 previous query 上指上一段叫出資料集 one 變數訊息的程式碼。如果單獨跑這段程式,輸出結果如下所示:


最後,重新命名的動作就交給下面這段程式碼:
select trim(name) || '=' || 'TWO' || name
from ( previous query )

這邊的 previous query 則是繼續把前兩段程式碼給複製進來。trim() 函示是把可能的空白給去掉。這個 select statement 會產生下面這種結果:


這個結果不就是 rename statement 後面要輸入的 oldname=newname 嗎?為了要把這段結果放進 data step 來自動重新命名,需要再多一行程式:
into :renamelist separated by ' '

這行程式會把之前所有 oldname=newname 的結果放在 renamelist 這個巨集參數裡面,並且每一段都用一個空格區分開來(這就是 separated by ' ' 的目的)。結合上面的程式:
proc sql noprint;
select trim(name) || '=' || 'TWO' || name
into :renamelist separated by ' ' from
(select name
from dictionary.columns
where libname='WORK' and memname='TWO' and upcase(name) in
(select upcase(name)
from dictionary.columns
where libname='WORK' and memname='ONE' and upcase(name) ne 'ID'));
quit;

這段程式碼混在一起就會把「b=TWOb c=TWOc」這段程式碼放進 renamelist 裡面。雖然他看起來很複雜,但根據不同的情況只需要改兩個地方即可:
1. 第一個 select statement 後面的 TWO 可以改成自己想要重新命名的變數名稱開頭。
2. 最後一個 where statement 後面的 ID 改成你自己要合併資料集所使用的合併準則。此例是 merge by id,所以這邊就要用 ID。如果你是 merge by subject,則此處就改成 SUBJECT。

然後使用下面這段程式碼把 renamelist 設定成巨集變數:
%put &renamelist;

最後只要在一開始的 data step 裡面做點小更改即可:
data onetwo;
merge one two(rename=(&renamelist));
by id;
run;

只要把 rename 後面可能的一長串 oldname=newname 換成 &renamelist 就大功告成了。結果如下所示:


當然,如果你覺得之前介紹的 text utility 裡面的 rename macro 很好用的話也可以使用他。

CONTACT INFORMATION
Christopher J. Bost
Director, Research Technology Unit
MDRC
16 East 34th Street, 19th Floor
New York, NY 10016
(212) 340-8613 telephone
(212) 684-0832 fax
christopher.bost@mdrc.org
www.mdrc.org
CODE { display: block; /* fixes a strange ie margin bug */ font-family: Courier New; font-size: 8pt; overflow:auto; background: #f0f0f0 url(http://klcintw.images.googlepages.com/Code_BG.gif) left top repeat-y; border: 1px solid #ccc; padding: 10px 10px 10px 21px; max-height:200px; height:200px; // for IE6 line-height: 1.2em; }