?初學(xué)SAS的時候,往往注重程序語法結(jié)構(gòu),而忽略了SAS編譯和執(zhí)行階段如何處理數(shù)據(jù)。所以很多時候,寫的程序看起來沒有問題,但是創(chuàng)建出的數(shù)據(jù)集觀測數(shù)和預(yù)期的卻不一致,一些變量也沒有按照預(yù)期retain下來,嘗試另起一個data步或者用一個新變量名問題就解決了,但是究竟問題出在哪里,也無從得知。今天就一起研究下SAS處理數(shù)據(jù)的機制吧!?
SAS data步包含包含兩個階段:
- 編譯階段
- 執(zhí)行階段
編譯階段
這個階段sas主要做:
- 掃描code是否存在語法錯誤;
- 結(jié)束后創(chuàng)建數(shù)據(jù)集的描述信息;
- 會產(chǎn)生兩個對象:input buffer(輸入緩沖區(qū))、PDV(program data vector)
具體講,就是確保program滿足語法規(guī)則,例如sas數(shù)據(jù)集、變量名命名規(guī)則,關(guān)鍵拼寫是否有誤,引號括號不配對,每個語句是否以分號結(jié)束等等。編譯階段完成之后,sas數(shù)據(jù)集的描述信息就創(chuàng)建好了,例如變量名、變量屬性。
語法錯誤核查完之后,SAS會創(chuàng)建input buffer(只在讀取raw data時產(chǎn)生)。input buffer只是一個邏輯概念,并沒有實際的物理儲存位置,可以理解為一個臨時儲存記錄的位置。
input buffer創(chuàng)建之后,PDV也隨著創(chuàng)建,和input buffer類似,PDV也是一個邏輯概念。
PDV的變量:data步中的所有變量,以及自動生成的變量 by-group創(chuàng)建的變量,選項生成的變量等等。
| variables | description |
|---|---|
| _n_ | Data步執(zhí)行次數(shù)的計數(shù)變量 |
| _error_ | 錯誤信息 表示當(dāng)前觀測執(zhí)行時發(fā)生了錯誤 |
| first.variable | 同一by組第一個觀測 |
| last.variable | 同一by組最后一個觀測 |
| _all_ | 所有變量 |
| indsname= | 數(shù)據(jù)集名稱 |
| nobs= | 數(shù)據(jù)集總觀測數(shù) |
| curobs= | 數(shù)據(jù)集當(dāng)前觀測數(shù) |
| end= | 布爾值 最后一個數(shù)據(jù)集最后一條觀察為1,否則為0 |
| in= | 布爾值 可以標識數(shù)據(jù)集 |
| point= | 取指定觀測 等號右邊為變量 不能設(shè)置為數(shù)字 需和stop連用 |
執(zhí)行階段
創(chuàng)建數(shù)據(jù)部分,_N_初始化為1,_ERROR_初始化為0,F(xiàn)irst.var, Last.var初始化為1,其它的自動變量為缺失。
input語句執(zhí)行,將raw data第一行copy進input buffer, 執(zhí)行其他語句,最后輸出該條觀測到數(shù)據(jù)集中,然后返回DATA步繼續(xù)執(zhí)行,讀取下一條觀測。
在執(zhí)行階段,DATA步看起來更像是一個循環(huán),讀取一行數(shù)據(jù),執(zhí)行所有語句,創(chuàng)建一行觀測記錄,并重復(fù)這個操作。每次循環(huán)就稱作一次迭代。
并非所有的語句都是在執(zhí)行階段執(zhí)行,DATA步中的語句可以分為:聲明語句、可執(zhí)行語句。聲明語句:在編譯階段就開始生效了,可以放在DATA步中的任何位置。比如LENGTH、FORMAT、LABEL、DROP、KEEP等。
可執(zhí)行語句:必須要按照預(yù)期執(zhí)行的順序進行放置,比如input之前要先infile。
example

data total_points (drop=TeamName);
input TeamName $ ParticipantName $ Event1 Event2 Event3;
TeamTotal + (Event1 + Event2 + Event3);
datalines;
Knights Sue 6 8 8
Kings Jane 9 7 8
Knights John 7 7 7
Knights Lisa 8 9 9
Knights Fran 7 6 6
Knights Walter 9 8 10
;
run;
Input Buffer

PDV

Output語句
讀進PDV的數(shù)據(jù),默認是會被output到數(shù)據(jù)集中的,這稱為隱式輸出。如果使用了output語句(顯式輸出)來輸出記錄,隱式輸出就不會起作用了。所以,如果使用了有條件的output語句,只有滿足的條件的記錄才會被輸出到數(shù)據(jù)集中,其它記錄不會再被默認輸出,如果不同條件的記錄要以不同的要求output到數(shù)據(jù)集,可以使用多個output語句。
Reading raw data v.s. Reading sas dataset
Raw data:每次迭代時,除了自動創(chuàng)建的變量、Retain語句中的變量、SUM語句創(chuàng)建的變量、_TEMPORARY_數(shù)組中創(chuàng)建的元素、infile/file選項中創(chuàng)建的變量,其余變量在PDV中都會被設(shè)置為缺失。
SAS data set:只在執(zhí)行的第一次迭代時將PDV中的變量置為缺失,變量將會retain值直到被新的值代替。對于不是來自input dataset而是在data步中創(chuàng)建的新變量,會在執(zhí)行的每一次迭代的開始被置空。如果想要使新創(chuàng)建的變量retain值,可以使用retain語句。
drop/keep語句和選項
drop/keep分別是刪除和保留變量
- drop/keep語句和drop/keep選項作用在輸出數(shù)據(jù)集是一樣的;
- drop/keep選項如果作用在輸入數(shù)據(jù)集,那么相應(yīng)drop的變量或者沒被keep的變量就不會進入PDV。當(dāng)數(shù)據(jù)列非常多的時候,在輸入數(shù)據(jù)集時就keep或者drop變量,能夠提高運行效率;
- drop/keep同時使用,drop先起作用,然后再進行keep;如果drop和keep沖突的話(drop或者keep不存在的變量),那么SAS會報錯。
where/if
where在數(shù)據(jù)進PDV之前就進行篩選,只能篩選輸入數(shù)據(jù)集中的變量,在data步創(chuàng)建的新變量不可以篩選。
- if是在數(shù)據(jù)讀進PDV后才進行篩選,可以篩選輸入數(shù)據(jù)集中的變量,也可以篩選在數(shù)據(jù)步創(chuàng)建的新變量。
- 如果存在by語句,那么where是在by語句之前執(zhí)行,if語句是在by語句之后執(zhí)行。by語句可以產(chǎn)生first.var, last.var的PDV變量,所以where first.var?,if first.var?;
where語句的優(yōu)勢就是可以提高效率,因為在讀取數(shù)據(jù)之前已經(jīng)進行篩選減小了數(shù)據(jù)量,而if是每行都讀入PDV再篩選是否輸出到數(shù)據(jù)集中。
example
數(shù)據(jù)集Long如圖:

運行如下程序:

data rewide(keep=sid programming stats english);
format Sid Programming Stats English;
array course{3} programming stats english;
do i = 1 to 3;
set long;
course{i}=score;
put _all_;
end;
run;
得到的數(shù)據(jù)集Rewide如圖:

這段程序中,set語句被放置在do循環(huán)內(nèi)。set語句每次只讀取一行記錄,到run結(jié)束,返回至開頭讀取下一條記錄,循環(huán)往復(fù)。
第一條do end循環(huán)結(jié)束后,PDV會讀取三行記錄:

遇到Run;之后將當(dāng)前的PDV記錄輸出至Rewide數(shù)據(jù)集中,然后指針+1,繼續(xù)進行第二個Do end循環(huán),輸出S02的一條記錄。
當(dāng)我們對sas讀取數(shù)據(jù)階段有困惑時,可以使用put all語句,將PDV的所有變量都輸出在Log中,查看每次執(zhí)行時每個變量的賦值情況。