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


結果は


















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



6 件のコメント:

  1. ハッシュオブジェクトは私の能力をかなり超えるので、先日、http://tumesas.progoo.com/bbs/tumesas_topic_pr_20.html
    で教えていただいたデータステップと関数のコラボを使ってやってみました。
    どうもfcmpの戻り値は数値変数でないとだめなのでしょうか、IDが数値でないとだめという問題点があります。
    他にもわからないことがいろいろ発生しました。・・・、一応、同じものが、できました。
    視覚的にはわかりやすいと思いますが、発展性を考えるとハッシュオブジェクトを習得すべきなのでしょうね。

    *-----------------------------;
    *マクロ ;
    *-----------------------------;
    %macro InfNValPart;
    *** FCMPで作られたマクロ変数のクオーテーションを削除 ;
    %let Ds = %sysfunc(dequote(&Ds));
    %let Var = %sysfunc(dequote(&Var));
    %let Row = %sysfunc(dequote(&Row));
    *** データの値をマクロ変数に格納 ;
    data _null_;
    set &Ds(obs=%eval(&Row)) end=EOF;
    if EOF then call symputx("OutVal",&Var);
    run;
    %mend;

    *-----------------------------;
    *マクロ実行関数 ;
    *-----------------------------;
    proc fcmp outlib=Work.functions.MyFunc1;
    function InfNValFunc( Ds$, Var$, Row$ );
    rc = run_macro("InfNValPart", Ds, Var, Row, OutVal);
    return (OutVal);
    endsub;
    run;

    options cmplib=Work.functions; *関数使用宣言;

    data A1; *コードを変えてわかりにくいですが、単に短くしただけです;
    ID="001";
    do X=1 to 11 by 2; output; end;
    ID="002";
    do X=2 to 8 by 2; output; end;
    run;

    data Q1;
    set A1 nobs=NN;
    N0=put(_N_-1, best.);
    if 0<N0 & ID=InfNValFunc( "A1", "ID", N0 )
    then X_m1=InfNValFunc( "A1", "X", N0 );
    N1=put(_N_+1, best.);
    if N1<=NN & ID=InfNValFunc( "A1", "ID", N1 )
    then X_p1=InfNValFunc( "A1", "X", N1 );
    N2=put(_N_+2, best.);
    if N2<=NN & ID=InfNValFunc( "A1", "ID", N2 )
    then X_p2=InfNValFunc( "A1", "X", N2 );
    drop N0 N1 N2;
    run;

    返信削除
  2. こんにちは、amatsuです。
    文字の場合は、
    以下のように戻り値が文字であることを指定してあげるとうまくいきますよ。

    proc fcmp outlib=Work.functions.MyFunc1;
    function InfNValFunc( Ds$, Var$, Row$ ) $200;
    length OUTVAL $200;
    rc = run_macro("InfNValPart", Ds, Var, Row, OutVal);
    return (OutVal);
    endsub;
    run;

    返信削除
  3. amatsuさん、ありがとうございます。さすがです!
    ひとつ教えていただきたいのですが、
    %put %sysfunc( InfNValFunc(A1, ID,8));
    とすると、002と出るのですが、その前に「・・・テキスト式は65534文字に切り捨てられます。」ERRORが出てしまいます。
    このERRORを出さないようにするにはどうすればいいのでしょうか。

    返信削除
    返信
    1. すみません。別件の質問で、この問題について以下の回答をいただきました。
      ------------------------------------------------------------------------------------------------------
      なお、以下のエラーに関しましてお調べ致しましたところ、こちらは不具合
      により発生するもので、SAS 9.3 では修正済みとの情報を確認しております。

      =====================================================================
      ERROR: テキスト式の長さ(96000)は最大長(65534)を超えています。
      テキスト式は65534文字に切り捨てられます。
      =====================================================================

      大変恐れ入りますが、%SYSFUNC関数をご利用いただく場合は SAS 9.3 へ
      バージョンアップして頂くことをご検討いただきたく存じます。

      削除
    2. もし9.2でこの問題を解決するのであれば、以下のように%trim関数を使うと解決すると思います。

      %put %trim(%sysfunc( InfNValFunc(A1, ID, 8) )) ;

      削除
  4. うぉ、ちょっと確認してなかったら、凄く高度な話になってました!matsuさん、scdentさんいつも有難うございます。本当に勉強させていただいてます。scdentさんはマクロをかなり極められている印象なので、うらやましいです。僕がハッシュオブジェクトとか、その他SASの小技が好きなのはマクロが苦手でうまくできないので、オープンコードでどうにかならないかと勉強してるうちに今みたいなスタイルになったっていう感じなので、マクロができる人は尊敬してますし、マクロはうまく書けば全ての処理を恐らく効率的にかけるはずなので、ハッシュオブジェクトを無理に覚えることはないかもしれないです。もちろん僕はハッシュオブジェクト大好き人間なので、使っていただけたら最高ですが。

    返信削除