公告

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

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


2007年3月12日 星期一

Arrays Made Easy: An Introduction to Arrays and Array Processing

原文載點:http://www2.sas.com/proceedings/sugi30/242-30.pdf

陣列(Array)幾乎每個程式語言中都會使用到的一種元素,SAS 也不例外。Steve First 和Teresa Schudrowitz 在 2005 年的 SUGI 30 上發表了一篇介紹陣列用法的文章,是我覺得可以讓初學者在短時間內將陣列上手的一份教材,因此在此做一些心得的分享。

WHY DO WE NEED ARRAYS?

陣列的使用可以將許多重複性的動作用最短的程式碼表現出來。舉例來說,如果我們要將 24 個溫度變數從華氏改成攝氏,最直覺的程式寫法是一個變數一個變數慢慢地改:

data;
input etc.
celsius_temp1 = 5/9(temp1 – 32);
celsius_temp2 = 5/9(temp2 – 32);
. . .
celsius_temp24 = 5/9(temp24 – 32);
run;


整串程式碼寫完要花費 27 行,但利用陣列可以濃縮為短短地 8 行。

data;
input etc.
array temperature_array {24} temp1-temp24;
array celsius_array {24} celsius_temp1-celsius_temp24;
do i = 1 to 24;
celsius_array{i} = 5/9(temperature_array{i} – 32);
end;
run;


上述程式碼的寫作原則是,利用兩個陣列,將原始的 24 個舊變數存入一個陣列,新的 24 個新變數存入另一個陣列,最後用三行的 Do Loop 來重複執行變數轉換工作二十四次。

BASIC ARRAY CONCEPTS

ARRAY STATEMENT

首先,陣列的宣告方式是:

array array-name {n} <$> length array-elements (initial-values);

先用 array statement 讓 SAS 呼叫陣列指令,接下來的設定是:
  • array-name – 設定陣列名稱
  • n – 設定陣列大小
  • $ - 指定字串變數
  • length – 設定變數長度
  • elements – 輸入一連串變數名稱
  • initial values – 設定起始值
如果你有很多數值變數想要放進一個陣列裡面,但又懶得慢慢輸入變數名稱,則可以用 _NUMERIC_ 來請 SAS 一次代入所有數值變數。如果是字串變數,則可以用 _CHARACTER_ 一次全部代入。如果不管型態而只想要全部代入,那用 _ALL_ 即可免去輸入所有變數的麻煩。

此外,界定陣列大小的括弧除了如上所示的 { } 外,還可以用 [ ] 和 ( )。又若變數名稱是像 temp1, temp2, temp3 .... 這樣連串下去,則可用下列表示法來縮短長度:

array temperature_array {24} temp1 – temp24;

如果你有很多個數值(或字串)變數要代入陣列,但搞不清楚到底有幾個,那就在界定陣列大小的括弧內打上個星號(*)即可。這樣 SAS 會自動去設定大小:

array allnums {*} _numeric_;

ARRAY REFERENCES

陣列大小決定後,則每個位置所代入的變數次序就依照陣列後面宣告的變數順序而定。如果:

array temperature_array{13} temp1-temp13;

則表示 temperature_array{1} 等於 temp1,temperature_array{2} 等於 temp2,以此類推。

USING ARRAY INDEXES

當然,陣列的宣告是很有彈性的。如果有些人不想從 temp1 開始設定變數,而想要從 temp6 開始命名到 temp18。同時,也不想從陣列的第一個元素設定成 temp6,而是要從第六個元素開始代入變數,那麼便可以用下列程式碼來限定:

array temperature_array {6:18} temp6 – temp18;

MULTI-DIMENSION ARRAYS

上述的教學都只是將陣列設定成一個一維的 1Xm 矩陣,若用表格來表示即為:

sugi242_30_01

不過我們也可以把陣列設定為二維的 nXm 矩陣,如下圖所示:

sugi242_30_02

要用上圖所示的方法把變數代入陣列裡面需要用到下列程式碼:

array sale_array {3, 12} sales1-sales12 exp1-exp12 comm1-comm12;

利用 {3, 12} 把陣列設定成一個 3X12 的矩陣,然後將三排變數一列一列的代入。我們也可以將陣列設定成三維甚至四維以上,但基本上不鼓勵這種型態的陣列,因為在之後的使用上會有點困難。

TEMPORARY ARRAYS

一旦陣列宣告且設定變數之後,則這些變數就會被紀錄在資料集裡面,無論之後是否有用到這些陣列變數。如果只是想要把一些數字放進變數以利之後的計算,而不想把他們存入陣列裡面的話,笨一點的方法是先製造好再用 delete 語法刪除。不過陣列語法中容許使用者創造布會被紀錄到資料集裡面的「暫時性陣列」,這樣有利於使用陣列上的便利。

舉例來說,假設我們要在 Data step 中進行下列動作:

if month_delinquent eq 1 then balance = balance + (balance * 0.05);
else if month_delinquent eq 2 then balance = balance + (balance * 0.08);
else if month_delinquent eq 3 then balance = balance + (balance * 0.12);
else if month_delinquent eq 4 then balance = balance + (balance * 0.20);
else if month_delinquent eq 5 then balance = balance + (balance * 0.27);
else if month_delinquent eq 6 then balance = balance + (balance * 0.35);


則可以用暫時性陣列將(0.05, 0.08, 0.12, 0.20, 0.27, 0.35)這些值代入,方法就是用 _temporary_ 來指定陣列變數是暫時性的,並將每個變數所代表的值用 ( ) 設定在 _temporary_ 之後:

array rate {6} _temporary_ (0.05 0.08 0.12 0.20 0.27 0.35);
if month_delinquent ge 1 and month_delinquent le 6 then
balance = balance + (balance * rate{month_delinquent});


因此,當 if ... then ... 迴圈跑完後,這個陣列的階段性任務也停止了,使用者將不會在資料集中看到他們。

如果陣列的初始值有些規則可循,那我們也不用一個數字一個數字的輸入,而是用一個 Do loop 來完成。如下所示:

array rateb {6} _temporary_;
do i = 1 to 6;
rateb{i} = i * 0.5;
end;


上述程式碼中,會用 Do loop 自動生成六個初始值(0.5, 1, 1.5, 2, 2.5, 3)。這種方法在設定大量有規則的初始值時具有相當效率!

SORTING ARRAYS

此處介紹一個可以在 Data step 中立刻排序陣列元素的語法:sortn。如果原始的陣列元素和其所代表的數值如下表所示:

sugi242_30_03

想要變成下圖的遞升排列模式(但變數名稱不會跟著變動),

sugi242_30_04

則可以用下列程式碼:

data _null_;
array xarry{6} x1-x6;
set ds1;
call sortn(of x1-x6);
run;


COMMON ERRORS AND MISUNDERSTANDINGS

在使用陣列時,我們經常會犯一些以下的錯誤:

INVALID INDEX RANGE


一旦陣列大小被限定住後,就沒有辦法在之後的程式中做動態修改。因此,有些語法在操作陣列時,經常會發現溢位的情況,亦即要丟入的元素比一開始陣列設定的元素還要多,此時程式就會發生錯誤!下列用 Do-Until loop 來說明這個情況。

data dailytemp;
set tempdata;
array temperature_array {24} temp1-temp24;
array celsius_array {24} celsius_temp1-celsius_temp24;
do until (i gt 24);
i = 1;
celsius_array{i} = 5 / 9 * (temperature_array{i} – 31);
end;
i=0;
drop i;
run;


上列程式碼中,首先設定了兩個 1X24 的陣列: temperature_array 和 celsius_array。接著用一個 Do-Until loop 讓裡面的計算重複跑 24 次(i 每次都會加一)。但當 i=24 時,這個程式不會停止,因為他還沒有違背 Do-Until 所給定的跳出迴圈的條件:i 大於 24。因此,這個迴圈會跑第 25 次,並產生 celsius_array{25} ,溢位便產生了。改善的方法就是去改 Do-Until 括弧內的條件,讓迴圈只會跑 23 次。如下所示:

do until (i gt 23);
i = 1;
celsius_array{i} = 5 / 9 * (temperature_array{i} – 31);
end;


do while (i le 24);
i = 1;
celsius_array{i} = 5 / 9 * (temperature_array{i} – 31);
end;


FUNCTION NAME AS AN ARRAY NAME

SAS 禁止使用者設定一些在 SAS 內設定成語法或者是選項的文字當作陣列名稱。一旦使用的話,SAS 會出現警告訊息,並終止程式執行。如下所示:

8 data dailytemp;
9 set tempdata;
10 array mean {24} temp1-temp24;
WARNING: An array is being defined with the same name as a SAS-supplied or userdefined function. Parenthesized references involving this name will be treated as array references and not function references.
11 do i = 1 to 24;
12 meantemp = mean(of temp1-temp24);
ERROR: Too many array subscripts specified for array MEAN.
13 end;
14 run;

上面程式碼被終止的原因就是因為使用 mean 當作陣列名稱。mean 是 SAS 裡面用來計算平均數的語法之一,因此被限定不得使用於陣列名稱當中。

ARRAY REFERENCED IN MULTIPLE DATA STEPS, BUT DEFINED IN ONLY ONE

每個陣列只能在該 Data step 中使用,一旦離開這個 Data step,則陣列的功能就會消失。下面提供一個範例:

data dailytemp;
set tempdata;
array temperature_array {24} temp1-temp24;
array celsius_array {24} celsius_temp1-celsius_temp24;
do i = 1 to 24;
celsius_array{i} = 5 / 9 * (temperature_array{i} – 31);
end;
run;


data celsius;
set dailytemp;
do i = 1 to 24;
if celsius_array{i} lt 0 then tempdesc = ‘below freezing’;
else if celsius_array{i} gt o then tempdesc = ‘above freezing’;
end;
run;


上述程式中,陣列 celsius_array 被宣告在第一個 Data step 中,因此在第二個 Data step 再次使用陣列 celsius_array 時,SAS 會說找不到這個陣列。最佳的解決方法,就是再一次地在第二個 Data step 宣告陣列 celsius_array。如下所示:

data celsius;
set dailytemp;
array celsius_array {24} celsius_temp1-celsius_temp24;
do i = 1 to 24;
if celsius_array{i} lt 0 then tempdesc = ‘below freezing’;
else if celsius_array{i} gt o then tempdesc = ‘above freezing’;
end;
run;


陣列真的是一個相當實用的功能,希望大家能夠很順利地上手。

CONTACT INFORMATION

Your comments and questions are valued and encouraged. Contact the author at:
Steve First
Systems Seminar Consultants, Inc.
2997 Yarmouth Greenway Dr
Madison, WI 53711
Work Phone: (608) 279-9964 x302
Fax: (608) 278-0065
Email: sfirst@sys-seminar.com
Web: www.sys-seminar.com

Teresa Schudrowitz
Systems Seminar Consultants, Inc.
2997 Yarmouth Greenway Dr
Madison, WI 53711
Work Phone: (608) 279-9964 x309
Fax: (608) 278-0065
Email: tschudrowitz@sys-seminar.com
Web: www.sys-seminar.com
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; }