?初學(xué)SAS的時(shí)候,往往注重程序語(yǔ)法結(jié)構(gòu),而忽略了SAS編譯和執(zhí)行階段如何處理數(shù)據(jù)。所以很多時(shí)候,寫(xiě)的程序看起來(lái)沒(méi)有問(wèn)題,但是創(chuàng)建出的數(shù)據(jù)集觀測(cè)數(shù)和預(yù)期的卻不一致,一些變量也沒(méi)有按照預(yù)期retain下來(lái),嘗試另起一個(gè)data步或者用一個(gè)新變量名問(wèn)題就解決了,但是究竟問(wèn)題出在哪里,也無(wú)從得知。今天就一起研究下SAS處理數(shù)據(jù)的機(jī)制吧!?
SAS data步包含包含兩個(gè)階段:
- 編譯階段
- 執(zhí)行階段
編譯階段
這個(gè)階段sas主要做:
- 掃描code是否存在語(yǔ)法錯(cuò)誤;
- 結(jié)束后創(chuàng)建數(shù)據(jù)集的描述信息;
- 會(huì)產(chǎn)生兩個(gè)對(duì)象:input buffer(輸入緩沖區(qū))、PDV(program data vector)
具體講,就是確保program滿足語(yǔ)法規(guī)則,例如sas數(shù)據(jù)集、變量名命名規(guī)則,關(guān)鍵拼寫(xiě)是否有誤,引號(hào)括號(hào)不配對(duì),每個(gè)語(yǔ)句是否以分號(hào)結(jié)束等等。編譯階段完成之后,sas數(shù)據(jù)集的描述信息就創(chuàng)建好了,例如變量名、變量屬性。
語(yǔ)法錯(cuò)誤核查完之后,SAS會(huì)創(chuàng)建input buffer(只在讀取raw data時(shí)產(chǎn)生)。input buffer只是一個(gè)邏輯概念,并沒(méi)有實(shí)際的物理儲(chǔ)存位置,可以理解為一個(gè)臨時(shí)儲(chǔ)存記錄的位置。
input buffer創(chuàng)建之后,PDV也隨著創(chuàng)建,和input buffer類(lèi)似,PDV也是一個(gè)邏輯概念。
PDV的變量:data步中的所有變量,以及自動(dòng)生成的變量 by-group創(chuàng)建的變量,選項(xiàng)生成的變量等等。
| variables | description |
|---|---|
| _n_ | Data步執(zhí)行次數(shù)的計(jì)數(shù)變量 |
| _error_ | 錯(cuò)誤信息 表示當(dāng)前觀測(cè)執(zhí)行時(shí)發(fā)生了錯(cuò)誤 |
| first.variable | 同一by組第一個(gè)觀測(cè) |
| last.variable | 同一by組最后一個(gè)觀測(cè) |
| _all_ | 所有變量 |
| indsname= | 數(shù)據(jù)集名稱 |
| nobs= | 數(shù)據(jù)集總觀測(cè)數(shù) |
| curobs= | 數(shù)據(jù)集當(dāng)前觀測(cè)數(shù) |
| end= | 布爾值 最后一個(gè)數(shù)據(jù)集最后一條觀察為1,否則為0 |
| in= | 布爾值 可以標(biāo)識(shí)數(shù)據(jù)集 |
| point= | 取指定觀測(cè) 等號(hào)右邊為變量 不能設(shè)置為數(shù)字 需和stop連用 |
執(zhí)行階段
創(chuàng)建數(shù)據(jù)部分,_N_初始化為1,_ERROR_初始化為0,F(xiàn)irst.var, Last.var初始化為1,其它的自動(dòng)變量為缺失。
input語(yǔ)句執(zhí)行,將raw data第一行copy進(jìn)input buffer, 執(zhí)行其他語(yǔ)句,最后輸出該條觀測(cè)到數(shù)據(jù)集中,然后返回DATA步繼續(xù)執(zhí)行,讀取下一條觀測(cè)。
在執(zhí)行階段,DATA步看起來(lái)更像是一個(gè)循環(huán),讀取一行數(shù)據(jù),執(zhí)行所有語(yǔ)句,創(chuàng)建一行觀測(cè)記錄,并重復(fù)這個(gè)操作。每次循環(huán)就稱作一次迭代。
并非所有的語(yǔ)句都是在執(zhí)行階段執(zhí)行,DATA步中的語(yǔ)句可以分為:聲明語(yǔ)句、可執(zhí)行語(yǔ)句。聲明語(yǔ)句:在編譯階段就開(kāi)始生效了,可以放在DATA步中的任何位置。比如LENGTH、FORMAT、LABEL、DROP、KEEP等。
可執(zhí)行語(yǔ)句:必須要按照預(yù)期執(zhí)行的順序進(jìn)行放置,比如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語(yǔ)句
讀進(jìn)PDV的數(shù)據(jù),默認(rèn)是會(huì)被output到數(shù)據(jù)集中的,這稱為隱式輸出。如果使用了output語(yǔ)句(顯式輸出)來(lái)輸出記錄,隱式輸出就不會(huì)起作用了。所以,如果使用了有條件的output語(yǔ)句,只有滿足的條件的記錄才會(huì)被輸出到數(shù)據(jù)集中,其它記錄不會(huì)再被默認(rèn)輸出,如果不同條件的記錄要以不同的要求output到數(shù)據(jù)集,可以使用多個(gè)output語(yǔ)句。
Reading raw data v.s. Reading sas dataset
Raw data:每次迭代時(shí),除了自動(dòng)創(chuàng)建的變量、Retain語(yǔ)句中的變量、SUM語(yǔ)句創(chuàng)建的變量、_TEMPORARY_數(shù)組中創(chuàng)建的元素、infile/file選項(xiàng)中創(chuàng)建的變量,其余變量在PDV中都會(huì)被設(shè)置為缺失。
SAS data set:只在執(zhí)行的第一次迭代時(shí)將PDV中的變量置為缺失,變量將會(huì)retain值直到被新的值代替。對(duì)于不是來(lái)自input dataset而是在data步中創(chuàng)建的新變量,會(huì)在執(zhí)行的每一次迭代的開(kāi)始被置空。如果想要使新創(chuàng)建的變量retain值,可以使用retain語(yǔ)句。
drop/keep語(yǔ)句和選項(xiàng)
drop/keep分別是刪除和保留變量
- drop/keep語(yǔ)句和drop/keep選項(xiàng)作用在輸出數(shù)據(jù)集是一樣的;
- drop/keep選項(xiàng)如果作用在輸入數(shù)據(jù)集,那么相應(yīng)drop的變量或者沒(méi)被keep的變量就不會(huì)進(jìn)入PDV。當(dāng)數(shù)據(jù)列非常多的時(shí)候,在輸入數(shù)據(jù)集時(shí)就keep或者drop變量,能夠提高運(yùn)行效率;
- drop/keep同時(shí)使用,drop先起作用,然后再進(jìn)行keep;如果drop和keep沖突的話(drop或者keep不存在的變量),那么SAS會(huì)報(bào)錯(cuò)。
where/if
where在數(shù)據(jù)進(jìn)PDV之前就進(jìn)行篩選,只能篩選輸入數(shù)據(jù)集中的變量,在data步創(chuàng)建的新變量不可以篩選。
- if是在數(shù)據(jù)讀進(jìn)PDV后才進(jìn)行篩選,可以篩選輸入數(shù)據(jù)集中的變量,也可以篩選在數(shù)據(jù)步創(chuàng)建的新變量。
- 如果存在by語(yǔ)句,那么where是在by語(yǔ)句之前執(zhí)行,if語(yǔ)句是在by語(yǔ)句之后執(zhí)行。by語(yǔ)句可以產(chǎn)生first.var, last.var的PDV變量,所以where first.var?,if first.var?;
where語(yǔ)句的優(yōu)勢(shì)就是可以提高效率,因?yàn)樵谧x取數(shù)據(jù)之前已經(jīng)進(jìn)行篩選減小了數(shù)據(jù)量,而if是每行都讀入PDV再篩選是否輸出到數(shù)據(jù)集中。
example
數(shù)據(jù)集Long如圖:

運(yùn)行如下程序:

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語(yǔ)句被放置在do循環(huán)內(nèi)。set語(yǔ)句每次只讀取一行記錄,到run結(jié)束,返回至開(kāi)頭讀取下一條記錄,循環(huán)往復(fù)。
第一條do end循環(huán)結(jié)束后,PDV會(huì)讀取三行記錄:

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