這是一篇教導如何使用 data step 和 proc sql 合併和分割資料的 SAS 技術文件,由 Emmy Pahmer 於 NESUG 2006 發表。
本文所使用的範例如下:
這筆資料總共只有五個觀測值,每個觀測值包含三個變數:年齡、姓名和性別。其中第五個觀測值的性別是打錯的 G。
。分割資料
若想要將上述資料依照性別切割成兩個分開的資料集,則有下列幾種方式。
1. 使用兩個 data step:這是最笨但也是最直接的方式。程式碼如下所示:
data males;
set everyone;
if sex = 'M';
run;
data females;
set everyone;
if sex = 'F';
run;
2. 使用兩個 data step 配合 where:這個方法僅僅是縮短程式碼行數。程式碼如下:
data female;
set everyone (where=(sex=’F’)) ;
run;
data male;
set everyone (where=(sex=’M’)) ;
run;
有此程式可知,這只是把 IF 的指令改成 WHERE 並放在 SET 後面。對行數來說,的確是減少了,不過打的字變多了,因為 WHERE 後面的條件式要加上括弧。
3. 使用兩個 data step 配合 where:這個方法僅僅是把 WHERE 從 SET 移到 DATA 後面,並沒有太特別的地方。程式碼如下:
data female (where= (sex = ‘F’)) ;
set everyone ;
run;
data male (where= (sex = ‘M’)) ;
set everyone ;
run;
4. 使用一個 data step 並配合 IF...ELSE... 和 OUTPUT:這是比較進階的方式,可以大幅縮短程式碼的行數。如下所示:
data males females;
set everyone;
if sex = 'F' then output females;
else if sex = 'M' then output males;
run;
由於本資料中某觀測值的性別是打錯的。為了確定有沒有這類的情況,當資料相當龐大時,建議使用下列程式碼:
data males females;
set everyone;
if sex = 'F' then output females;
else if sex = 'M' then output males;
else put “Neither F nor M - check “ _all_; *or output to another dataset ;
run;
其中紅色那行的程式碼會讓性別變數不是 F 和 M 的觀測值通通分類到 _all_ 這個資料集中。亦或是另外定義一個資料集把他們 output 進去。當打開這個資料集時,如果裡面是空的,就可以確定沒有打錯的情況產生。
5. 使用一個 data step 並配合 WHERE:這是從方法二改良來。程式碼如下:
data female (where=(sex=’F’)) male (where=(sex=’M’)) ;
set everyone ;
run;
這個程式碼比方法四 要來的更精簡精簡。如果想要擁有方法三第二個可以偵測有沒有打錯的資料,則可以改進如下:
data female (where=(sex=’F’)) male (where=(sex=’M’)) checkothers (where = (sex not in (‘M’,’F’))) ;
set everyone ;
run;
道理同方法四,把 sex 不是 M 和 F 的通通丟到 checkothers 這個資料集裡面,然後再去看看該資料集是不是空的。
6. 使用 proc sql:使用 proc sql 看起來好像比較高檔,但是行數並沒有減少。
proc sql;
create table males as
select *
from everyone (where=(sex='M'));
create table females as
select *
from everyone (where=(sex='F'));
quit;
7. 使用一個 data step 把 不同的變數放到不同的資料集:若想要把 who 和 age 放到新資料集 age,然後再把 who 和 sex 放到新資料集 sex,則可仿照方法五來切割。程式如下:
data age (keep = who age) sex (keep = who sex);
set everyone;
run;
8. 使用 proc sql 完成方法七:程式如下:
proc sql;
create table age as
select who, age
from everyone;
create table sex as
select who, sex
from everyone;
quit;
感覺行數沒有減少很多,只是寫法比較接近口語。
。合併資料
這回使用兩筆資料,如下所示:
其中 EVERYONE 這筆和之前用的一樣,而新的 ACTIVITY 資料有一點要特別注意的是它並沒有排序過。為什麼要特別強調這一點,理由是在 SAS 的合併過程中,一定要設定一個 index variable,這樣 SAS 才有辦法依照那個 index variable 來進行資料合併。而那個被設定成 index variable 的變數一定要經過排序,否則 SAS 在合併的過程中會錯亂掉。這個錯亂有時候還是會給你 output,只是結果是錯的。如果一時忽略沒有看到 log 視窗上面的警告訊息,就完蛋了。因此若要依照「who」這個變數來合併這兩組資料,則必須要先用 PROC SORT 把該變數排序:
proc sort data=activity;
by who;
run;
而通常要養成一個好習慣就是所有合併的資料最好都給他排序一下,免得有漏網之魚。反正 PROC SORT 的程式碼很簡單,多寫幾行比較安心:
proc sort data=everyone;
by who;
run;
然後用 merge 和 by 來合併:
data combined_11;
merge everyone activity;
by who;
run;
結果如下:
從上表得知,Annie, Bill, Chandra, Igor, Jose 和 Karen 在 ACTIVITY 裡面有資料,所以合併時會顯示在 activity 這個變數底下,但 David 和 Eleanor 則沒有出現在 ACTIVITY 裡面,所以合併後他們兩人的 activity 變數就變成 missing data 了。Age 也是同樣的道理。
如果只想顯示同時出現在兩個資料的觀測值,則必須啟用 in 這個指令。程式碼如下:
data combined_12;
merge everyone (in = in_a) activity (in = in_b);
by who;
if in_a and in_b;
run;
使用 in 這個指令會讓 SAS 在合併的過程中,於兩組資料裡面各加上一個隱藏的變數,分別名為 in_a 和 in_b,其數值都預設為 1。合併之後在程式裡面加上「if in_a and in_b;」來讓 SAS 挑出同時具有 in_a=1 和 in_b=1 的觀測值(要打成「if in_a=1 and in_b=1;」也可以),缺少任一個變數的觀測值則會自動被剔除。結果如下:
如果想要知道哪些觀測值被剔除,可使用下列程式碼:
data combined_14;
merge everyone (in = in_a) activity (in = in_b);
by who;
if in_a and in_b then output;
else if in_a then put "In A only: " Who=; *or output to another dataset ;
else if in_b then put "In B only: " Who=;
run;
最後那兩個 else if... 會讓程式在 log 視窗印出下列字樣:
同樣地,proc sql 也可達成同樣效果:
proc sql;
create table combined_15a as
select a.*, b.activity, b.sex
from everyone as a, activity as b
where a.who = b.who
order by activity
;
quit;
或者
proc sql;
create table combine_15b as
select a.*, b.activity, b.sex
from everyone as a inner join activity as b
on a.who = b.who
;
quit;
但由於 proc sql 的指令比較麻煩,所以還是建議使用 data step 來完成。
CONTACT INFORMATION
Your comments and questions are valued and encouraged. Contact the author at:
Emmy Pahmer
MDS Pharma Services
St. Laurent, Québec
Work Phone: (514) 333-0042 ext. 4222
E-mail: emmy.pahmer@mdsinc.com
沒有留言:
張貼留言
要問問題的人請在文章下方的intensedebate欄位留言,請勿使用blogger預設的意見表單。今後用blogger意見表單留言的人我就不回應了。