DS2のtermメソッドの話

ハッシュオブジェクトやってた時もそうなんですが、実際に現場で活用できるプログラムを書けるようになるまで、勉強のため、基礎的で単純な処理のコードを延々と書きました。

正直、やってる時は、今までのデータステップでもやれるようなことを敢えて、別の文法で一人で黙々と書いて、どこに使うわけでもなく、何か意味あんのかなって気持ちにもなりました。
けど、結局そういった不毛に思える練習をやっておこないと、いざ今までのデータステップを超える処理を書こうって時に書けない、というか超えるということを発想することすらできないんですよね。

というわけで、今日もDS2の基礎的で面白くない話をしましょう。

ハッシュパッケージに入る前にtemメソッドについて少しだけ。

おさらいですが、DS2は基本的にメソッドというものをうまく組み合わせてプログラム全体を構築していきます。
メソッドはユーザーが自由に定義できるユーザー定義メソッドと、あらかじめ用意されているメソッドに分けられます。
あらかじめ用意されているメソッドのうち、特定のパッケージに依存するもの(hashパッケージやsqlstmtパッケージ等)を除いた、基本となるメソッドをシステムメソッドというそうです。

システムメソッドはinitruntermsetparmsです。

initとrun、termメソッドの説明については忘備録を参考にしてください。

DS2プロシジャ入門1:基本構文
http://sas-boubi.blogspot.jp/2015/05/ds2.html

setparmsはスレッドのところでちらっとでてきました。
http://sas-tumesas.blogspot.jp/2016/04/ds2threadset.html

で、今回はtermメソッドを取り上げます。

例えば、以下のようなデータセットがあって

data Q1;
do x = 1 , 3 , 4 , 7 , 8  , 9 ;
output;
end;
run;













そこから、平均である






のデータセットを作れと言われたら、まあ方法は山ほどあって

例えばプロシジャで

proc summary data=Q1;
var x;
output out=A1(keep=mean_x) mean=mean_x;
run;

とか

proc sql noprint;
create table A2 as
select mean(x)as mean_x
from Q1;
quit;

でいけますが、もしデータステップでやれと言われたなら

data A3;
set Q1 end=eof;
sum_x+x;
count+1;
if eof then do;
mean_x=sum_x/count;
output;
end;
keep mean_x;
run;

みたいにかけます。少し丁寧に書きましたが、もっと手をぬくなら

data A3;
set Q1 end=eof;
mean_x+x;
if eof then do;
mean_x=mean_x/_N_;
output;
end;
keep mean_x;
run;

でもいけますね。


さて、これをDS2で書くならどうなるかという話です。
データステップでは、setステートメントにend=オプションをつけることで
ファイル終端にフラグ変数を当てることができて、これによって、
最終行の際に処理を分岐することができました。

いっぽうDS2にはendオプションはありません。代わりにtermメソッドが存在し、
そこに書いたものが最後の処理として実行されます。
ただ、後で説明してますが、厳密にはendで書く処理とtermメソッドは実行タイミングがちょっと違うので注意が必要です。

以下のコードを見てください。

proc ds2 libs=work;
data A4(overwrite=yes);
declare double sum_x mean_x count test;
retain sum_x count;
method init();
count=0;
sum_x=0;
test=99;/*いらないけど説明用*/
end;
method run();
set Q1;
sum_x =sum_x + x;
count =count + 1;
end;
method term();
put _ALL_;
mean_x = sum_x / count;
output;
end;
enddata;
run;
quit;


実行すると、目的のデータセットが得られるコードで、正解です。
termとは反対に、処理の一番最初に実行されるinitメソッドで初期値0をいれ、
runメソッドで通常のデータステップと同じ処理を書き、
最後にtermメソッドで計算してoutputしています。

ちょっと説明したいことがあったのでinitメソッドでtestという変数に99を割り当てています。

termメソッドでput _ALL_としているので、その時点で(PDVにって言っていいのかな??)存在する変数をログにだします。
ログを見てみると、




まず、initで値を入れてるのに、testがnullになっているのは何故でしょうか?

それは、terrmメソッドには、グローバル変数をリセットする働きがあるからです。
SAS忘備録でも解説されています
http://sas-boubi.blogspot.jp/2015/06/ds22.html

リセットされないのは以下になります
・ SAS側があらかじめ定義している_N_などの変数
・ RETAINのような値を保持するようにしてる変数
・ パッケージ変数

sum_xとcountがnullになっていないのはretainステートメントで指定していたからなんですね。


で、次に注目なのは、_N_=7ですね。nullじゃないのは、_リセットされない変数ルールの1つめに該当するからですね。Q1は6オブザベーションなので、もしデータステップでend=オプションの分岐で_N_をputしていれば6です。
Termメソッドは、仮想的に最終オブザベーションの次の、存在しないオブザベーションにポイントしている感じになります。

なので、データステップの時のようにsum_x/_N_で平均が取れると思ってると間違ってしまいます。
やるならsum_x/(_N_-1)ですね。

もしretainステートメントを直接書かなくても以下のように
sum_x + x; count + 1;のように合計ステートメントで

proc ds2 libs=work;
data A5(overwrite=yes);
declare double sum_x mean_x count test;
method run();
set Q1;
sum_x + x;
count + 1;
end;
method term();
mean_x = sum_x / count;
end;
enddata;
run;
quit;

としてもretainと同じで、値の引き継ぎ効果ということでtermメソッドのリセットを免れます。

そして、もう一つ、別の方法としてパッケージを使い、そのパッケージ内で定義された変数をメソッドで取得してもOKです。

proc ds2 libs=work;
package pac1/overwrite=yes;
declare double sum_x count;
method 
pac1();
count=0;
sum_x=0;
end;
method msum(double inx);
   count = count + 1;
   sum_x = sum_x + inx;
end;
method avg() returns double;
return(sum_x/count);
end;
endpackage;

data A6(overwrite=yes);
declare double mean_x;
declare package pac1 p1();
method run();
set Q1;
p1.msum(x);
end;
method term();
put _ALL_;
mean_x = p1.avg();
output;
end;
enddata;
run;
quit;

結果は同じになります。
pac1というパッケージを作り、オブザベーション数合計(count)と値合計(sum_x)を格納するグローバル変数を定義、コンストラクタ(method pac1)に値への0セット。
msumメソッドにcountとsum_xへの加算機能を持たせ、avgメソッドはsum_xとcountから平均を計算して戻り値とする機能を持たせています。

パッケージを呼び出しているdata A6のステップだけみると、メソッドにxを与え、最後に値を取得するだけで、処理の中身自体は完全に分離していることがよくわかります。DS2っぽいですよね。

ちなみにオマケですが、DS2はsetにSQLを入れ込めるので、以下のコードも通りますからね。

proc ds2 libs=work;
data A7(overwrite=yes);
declare double mean_x;
method run();
set {select mean(x) as mean_x from Q1};
end;
enddata;
run;
quit;



0 件のコメント:

コメントを投稿