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

同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;


結果は


















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



データステップで0,1のフラグ変数を簡単に作成する

問題です。次のプログラムを実行すると、どんなデータセットができるでしょうか?

data A;
 X=5=5;
run;

最近、こういう系の話ばっかりしていて、飽きているかもしれませんが。

エラーになってデータセットができない or X=5でデータセットができると考えた方は不正解です。

正解は






X=1でデータセットができるでした。

これは実は
X=(条件式);の形で、条件式の中が真である場合は、1、偽である場合は0を返します。
つまり先程のコードはX=(5=5);ということで、5と5は等しいので真でありX=1です。

要するに、これを利用することで簡単に0,1のフラグ変数を作れて、条件で集計する場合などに
役立ちますという話です。

今、以下のようなデータセットがあったとします。

data Q1;
 X=2;Y=1;output;
 X=3;Y=2;output;
 X=5;Y=2;output;
 X=4;Y=8;output;
run;








ここで、X>Y and X^=2の条件で0,1変数を作りたいなと思ったら

data A1;
 set Q1;
  FLAG=X>Y and X^=2;
run;








で、詰みなんです。簡単です。

ちなみに恥ずかしい話なんですが、僕はずっと上記のコードで済むところを
以下のように

data A2;
 set Q1;
  FLAG=ifn(X>Y and X^=2,1,0);
run;

ifn関数を使っていました。
条件によって戻り値を加工したい場合はifn(戻り値が文字型ならifc)ですが
0,1の戻り値なら普通に抽出式書くだけなんですね。


ちなみにSQLプロシジャにおいても、ほぼ同じ話があって、そちらの詳細は
ブログ「SAS忘備録」の「SQLプロシジャでフラグ変数を簡単に作成。 」という記事に書かれていて目から鱗ものですよ。

http://sas-boubi.blogspot.jp/2013/12/sql.html

ちなみSQLプロシジャでIFN IFC関数を利用して、0,1のフラグ変数ではなく、
条件式によって戻り値を変えるという方法については、本ブログの以下の記事で僕も書いています。
SQLのCASE式がどうしても苦手な場合、IFC、IFN関数で代用してみる
http://sas-tumesas.blogspot.jp/2013/11/sqlcaseifcifn.html



SQLのCASE式がどうしても苦手な場合、IFC、IFN関数で代用してみる

SQLにIF文はないのかは、よく尋ねられる話です。MySQLなど特定の製品の独自関数として定義されている場合を除いて、基本的にSQLにIFはありません。

SQLでの条件分岐はCASE式を使うのですが、普段書いているSASのIFステートメントと少し違うので、なかなか書き方がぱっとでてこず、調べたりしてしまうことが多い方もいると思います。
条件分岐が面倒だからSQLは使わないという方もいたりするので、邪道かもしれませんが、SASでのSQLの使用を普及させたい僕としては、どうしてもCASE文が嫌いならとりあえずIFC,IFN関数を使ってみればと提案しています。

戻り値が数字の場合はIFN、文字の場合はIFC関数になります。
この関数はEXCELのIF関数と同じで

IFN(条件式,真の場合の戻り値,偽の場合の戻り値)といった感じで使います。

まず通常のデータステップで説明します。

以下のデータセットがあったとします。

data Q1;
X=1;Y='い';output;
X=2;Y='ろ';output;
X=3;Y='は';output;
run;







data A1;
 set Q1;
 _X=IFN(X=1,X*10,X*20);
 _Y=IFC(Y='い','A','B');
run;

を実行すると






こうなります。

関数を入れ子にすることで、階層的な条件分岐を表現できます。

data A2;
 set Q1;
 _X=IFN(X=1,X*10,IFN(X=2,X*20,999));
 _Y=IFC(Y='い','A',IFC(Y='ろ','B','Z'));
 run;

を実行すると







となります。


これをSQLのCASE式でかくなら

 proc sql noprint;
  create table A3 as
   select X,Y
    ,case X
     when 1 then X*10
     when 2 then X*20
     else 999
     end as X_
     ,case Y
     when 'い' then 'A'
     when 'ろ' then 'B'
     else 'Z'
     end as Y_ length=200
   from Q1;
  quit;

となります。改行の仕方によりますが、やはりぱっと見、ボリュームが、もさっとしがちに感じてしまします。

そこで、IFC IFN関数を使用してみます。

 proc sql noprint;
  create table A4 as
   select X,Y
    ,IFN(X=1,X*10,IFN(X=2,X*20,999)) as _X
    ,IFC(Y='い','A',IFC(Y='ろ','B','Z')) as _Y
   from Q1;
  quit;

こざっぱりします。ただ条件分岐が多くて入れ子にすると深すぎるなら、
CASE式の方が見通しやすいと思います。

まあ好みの問題ですが、無理してでもCASE式の書き方に慣れた方が、他のシステムで
SQL書くときにも使えるので、いいとは思います。