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

同IDグループの中で、n個前のオブザベーションを参照したり、n個先のオブザベーションを参照したりする処理がハッシュオブジェクトなら割と簡単にできる話

※ちょっとコード修正しました。1obsごとにハッシュオブジェクト定義するようになっていて、効率悪い書き方してました。

SASで、1つ前のオブザベーションが変数の値が欲しい!とか、1つ先が欲しいとかってよくある話です。n個前のということであれば、retainステートメントや、或いはlag関数などで実現できます。

また、n個先であれば、例えばSAS公式の
「次オブザベーションの値を参照する方法」
http://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/ba212.html

や、他にpoint=オプションを使った方法(いずれ紹介するかも)などが利用できます。

ただ、例えば、同じIDグループの中で次のオブザベーションといった制限がつくと、少し面倒になります。
(retainであればbyの部分と合わせて、first.で値をリセットしていけばいいし、先をとるためのmergeなら、予めマージキーとしてID内連番を作成して、それに細工をすればできます)

しかし、扱うデータセットのサイズが何万obsとかの巨大なものでないのであれば、ハッシュオブジェクトを使うとかなり楽にでき、さらにより複雑な条件であっても容易に組み込むことができるので、個人的にはお勧めです。

ただし、できれば、ハッシュオブジェクトを勉強して、理屈をある程度理解した上で利用するようにした方がいいと思います。
何をしているのか全くわからずに、コピペからカスタマイズすると、少し危険に思います。

過去のハッシュオブジェクト関連の記事
http://sas-tumesas.blogspot.jp/search/label/ハッシュオブジェクト


それではやってみましょう。

data Q1;
ID='001';X=1;output;
ID='001';X=3;output;
ID='001';X=5;output;
ID='001';X=7;output;
ID='001';X=9;output;
ID='001';X=11;output;
ID='002';X=2;output;
ID='002';X=4;output;
ID='002';X=6;output;
ID='002';X=8;output;
run;


















というデータがあって、同じIDの中で、1obs前のXの値をX_m1に、1obs先のXを_p1、2つ先をX_p2という変数にいれるとします。

data A1;
 if _n_ = 1 then do;
   declare hash hx();
    hx.defineKey('ID','NO');
    hx.definedata('_X');
    hx.definedone();
  do while (^FL);
   set Q1 end=FL;
   by ID;
   if first.ID then NO=0;
   NO+1;
   _X=X;
   hx.add();
  end;
 end;
   set Q1;
   by ID;
   if first.ID then RNO=0;
   RNO+1;
   
   /*同ID内の一つ前のデータを参照*/
   NO=RNO-1;
   rc=hx.find();
   X_m1=ifn(rc=0,_X,.);

   /*同ID内の一つ先のデータを参照*/
   NO=RNO+1;
   rc=hx.find();
   X_p1=ifn(rc=0,_X,.);

   /*同ID内の二つ先のデータを参照*/
   NO=RNO+2;
   rc=hx.find();
   X_p2=ifn(rc=0,_X,.);

 keep ID X X_m1 X_p1 X_p2;
run;


結果は


















このように、ハッシュオブジェクトは、データステップ中の処理と独立したルックアップテーブルを作成して、データステップ中に値のやりくりができるので、使いこなせば、プログラミングの自由度がかなりアップします。



変数ごとの転置結果を再マージするような処理について、変数がいくつあろうが必ず2ステップでケリをつける方法。do until(last.変数)ループを使って

例えば、以下のデータセット

data Q1;
X='い';Y=1;Z='A';W=1;output;
X='い';Y=2;Z='B';W=1;output;
X='い';Y=3;Z='C';W=2;output;
X='は';Y=4;Z='D';W=2;output;
X='は';Y=5;Z='E';W=.;output;
X='ろ';Y=6;Z='E';W=3;output;
run;
proc sort data=Q1;
 by X;
run;














を使って









のようにXを起点にしてその他の変数を全て転置したデータセットを作成せよと言われたらどうしますか?

多分、すぐに思いつくのが

proc transpose data=Q1 out=_Y(keep=X Y_:) prefix=Y_;
 var Y;
 by X;
run;
proc transpose data=Q1 out=_Z(keep=X Z_:) prefix=Z_;
 var Z;
 by X;
run;
proc transpose data=Q1 out=_W(keep=X W_:) prefix=W_;
 var W;
 by X;
run;

data A0;
 merge _Y _Z _W;
 by X;
run;

だと思います。

ただし、この方法だと、転置する変数分だけtransposeを書くため、どうしても処理に無駄があるように僕はずっと感じてきました。

特に以前、仕事で、臨床検査値と臨床検査基準値を、検査日と基準値適応開始日を比較しながらマージすることが多かったため、その過程で上記のコードをよく書いていたため、もっと改良できないかと常に思っていました。

それに対する一つの答えが、今年ユーザー総会で発表したハッシュオブジェクトに関する論文で、ハッシュオブジェクトを利用することで、そもそも上記のようなデータセット加工を行わずに、ダイレクトにマッチングができるという内容でした。

それで少しは気が晴れたのですが、それはあくまで、連続transposeと再マージ処理を避けるテクニックなので、まだひっかかりがありました。

そこで今回、2ステップにはなりましたがdo untilの終了条件にlast.変数を使う特殊なループ法でやってみました。

ちなみにこのループ法、海外ではDow loopと呼ばれていて、「SAS dow loop」で検索すると、その応用法がたくさんでてきます。

Dowの「w」の文字はIan Whitlockという方の名前からつけられました。2000年にこの方が、初めてSAS-Lというコミュニティで、この方法を発表し、それがあまりに画期的であったため、リスペクトを込めて、ループの終了条件にfirstやlast等を使用するような変則ループ全般をDOWループと呼ぶようになったそうです。いい話だな~、うらやましいなぁ。

僕はa matsuさんにこのブログのコメントで初めDowの書き方を教えていただきました。

さて、前置きが長くなりましたが、コードです。

proc sql noprint;
 select max(A)into:TNMAX from
 (select count(*) as A from Q1 group by X);
quit;

data A1;
informat X Y_: Z_: W_:;
 array Y_(&TNMAX) 8. ;
 array Z_(&TNMAX) $2.;
 array W_(&TNMAX) 8. ;
 do until(last.X);
  set Q1;
  by X;
  if first.X then i=0;
  i+1;
  Y_{i}=Y;
  Z_{i}=Z;
  W_{i}=W;
 end;
drop Y Z W i;
run;

で、結果は先述の通りになります。

まあ、満足。

ただ、まだ、もうちょっと面白い手順があるんじゃないかと思うのでもう少し考えてみます。

SASのデータステップについて深く勉強すると、思いついた面白い構想や、突飛なアイデアを強引に実現できる力がついてきて、結局それがモチベーションになってきてる気がします。