ラベル end= の投稿を表示しています。 すべての投稿を表示
ラベル end= の投稿を表示しています。 すべての投稿を表示

do until(end変数)set end=を利用して、複数のデータステップを1つにまとめて共存させる方法

例えばあるデータステップを実行して、その最終結果として得られる値を、
次のデータステップで使用したい場合、常識的に考えれば当然2ステップが必要になります。

しかしdo untilループとファイル終端の際に1がたつend変数を利用することで、
複数のステップを繋いだ、かなり柔軟なデータステップを書くことができます。

今回は例として、単純なものを紹介します。

たとえば以下のように2つのデータセットがあったとします。

data Q1;
do X=1 to 5;
 output;
end;
run;








data Q2;
do Y=1 to 10;
 output;
end;
run;













そこでQ2の全オブザベーションを足して求められる値(1から10までの足し算なので55です)を
Q1の各オブザベーションにそれぞれ足したいとします。

その場合、普通はまずQ2の合計を、何らかのプロシージャやあるいはデータステップで導出し
Q1の全obsにマージしたり、或いはマクロ変数を介して足し算したりすることが浮かぶと思います。
(SQLならサブクエリで1発ですが今回はおいといてください)

ところが、なんと以下のように1ステップで書くことができます。

data A1;

 do until(eofQ2);
  set Q2 end=eofQ2;
   Y_TOTAL+Y;
 end;

 do until(eofQ1);
 set Q1 end=eofQ1;
   TOTAL=X+Y_TOTAL;
   output;
 end;

run;

で結果は








となります。
do until(eofQ2)はdo until(eofQ2=1)の略で、set Q2の際にend=でeofQ2と変数をあてているので
Q2が全て読み込まれて処理されるまで、このループが続き、抜ける頃にはY_TOTALを含む最終obsのみが
残っています。

そこからQ1のループに入りますが、今回Q1をベースとして全obs残したいのでこちらにoutputをいれています。


このように書くことで、複数のデータステップを1つにまとめることができ、
前のステップで生成された値を、リレーのバトンタッチのように使用できます。

海外のコミュニティのやりとりなどで、何回も見たことがあるので、向こうでは定跡化されている
方法なのかもしれません。

あ、ちなみに多分今回の投稿で100回目です。
あと1万倍投稿すればデータステップ100万回になるので、がんばります。

もう多分10回目の投稿ぐらいから慢性的にネタ切れな感じなので、何か意見、要望、突っ込み、アイデア、質問、なんでもください、まっています。









SASデータセットのオブザベーション数をマクロ変数に格納する方法_call symput とsql select into: ①単純な1マクロ変数の作成

SASデータセットのオブザベーション数をマクロ変数で取得したいことはよくあります。

data A;
X=1;output;
X=2;output;
run;

という2オブザベーションのデータセットがあったとします。

【解法1】

data _NULL_;
 set A end=eof;
  if eof then call symputx('obs1',_N_);
run;

call symputx ルーチンでSASの自動変数_N_をobs1というマクロ変数に入れています。
_N_は数値型ですがsymputxは余計な空白を含まない文字型に変換してから
格納してくれるのでログに型変換のNoteも出力されません。

end=をつけてifステートメントをかけているのは
これをつけないと、1オブザベーション読み込むたびにcall symputx ルーチンで
マクロ変数を更新してしまうからです。欲しいのは最後のオブザベーションの_N_だけなので
余計な関数処理にかかる時間を短縮するためこのように書いています。

さて、このcall symputxを利用する方法の問題点は、オブザベーション数が0、つまり空であった場合にマクロ変数が作成されないという点です。
後のコードで、作成されたマクロ変数を参照している場合、条件分岐していないと、マクロ変数が存在しないためエラーになる恐れがあります。

また下のように、サブセット化IFステートメントとcall symputルーチンの併用も危険です。

data _NULL_;
 set A end=eof;
  if X<=1;
  if eof then call symputx('obs1',_N_);

run;


【解法2】

proc sql noprint;
 select count(*) into:obs2
  from A;
quit;

SQLの場合、count関数はデータセットが空の場合も0を返してくるので
マクロ変数は必ず作成されます。
select ○○ into : マクロ変数名で、マクロ変数に値を代入します。
ただし、クエリが返すオブザベーション数には注意してください。

また、条件を加える場合も

proc sql noprint;
 select count(*) into:obs2
  from A
  where X<=1;
quit;

こんな感じで、自然にかけば、それが正解です。

SQLでマクロ変数を作成する方法は、まだ奥が深くて
いろいろできるのですが、今回はここまで。

詰めSAS1回目_全変数全obsで最大の値をとる

詰めSASの1回目のテーマは、データセット中の最大の値を1変数1obsのデータセットに格納する最善手を考えたいと思います。1手?詰めです。

まず、問いの内容は

data Q1;
input X Y Z;
cards;
1 4 3
2 9 8
7 6 5
;
run;







のデータセットから最大の値、この場合、Yの2obs目の9という値を
変数Mのみの新しいデータセットに格納するという目的です。

目的局面図は





です。


以下、解法です。

【解法1】
data A1;
 set Q1 end=eof;
 retain M;
 if max(X,Y,Z)>M then M=max(X,Y,Z);
 if eof;
 keep M;
run;

最大の値をだす場合に、第一感で思い浮かぶのはproc univariateやmeans等の
利用かもしれませんが、その場合は変数ごとの最大をだした後にデータステップで
その中からの最大をだすことになり2ステップになってしまうと思います。

なので、データステップ1回でやっつけたいのですが、SASのmax関数は横(行)方向の最大値
をとる関数なので、そこで出した値を次のobsに持ち込まないといけません。
なのでretainを使って、持ち越し、endオプションで最終obsだけ残します。

【解法2】
proc sql noprint;
 create table A2 as
  select max(M) as M
   from 
  (select X as M from Q1
    union
   select Y as M from Q1
    union
   select Z as M from Q1)
;
quit;

SQL内でmax関数は1変数内を縦にみて最大値を返すので、じゃあ全部の変数を縦に
つないでからかけてやればいいんじゃないかと思って書いてみると
思ったより頭悪い感じのコードになりました。また、サブクエリを使うと、あまり一手といえなくなって
しまいますが。これならサブクエリ内でX、Y、Zで最大をとってからCASE文で比較して最大を
とる方がスマートだったかも。

【解法3】
data A3;
 obs1=1;
 obs2=2;
 obs3=3;
  set Q1(rename=(X=X1 Y=Y1 Z=Z1)) point=obs1;
  set Q1(rename=(X=X2 Y=Y2 Z=Z2)) point=obs2;
  set Q1(rename=(X=X3 Y=Y3 Z=Z3)) point=obs3;
   M=max(of X1--Z3);
  keep M;
  output;
 stop;
 run;

これは悪ふざけです。横に最大を返すなら、全部横にしてから、かければ
それで決着という方向で書きました。

1回目はここまで。


====================================================
後日、コメントをいただいて追記
====================================================
SQLでの記述について、突っ込みをいれていただきました。
以下のコードで詰みですね!スマート!!
思いつきもしませんでした!

標準SQLのmax関数は引数が1つなのですが、複数の引数を指定した場合
SASはSQLプロシジャ内でもSASのMAX関数と認識するそうです。
奥が深い、、

proc sql;
create table A4 as
select max(max(X,Y,Z)) as M
from Q1;
quit;