2014年3月23日日曜日

MERGEステートメントの落とし穴




むかし、MERGEでやってしまったミスです。


サンプルデータ

data A;
input NO$ NO2 V1;
cards;
001 1 1
001 2 1
001 3 1
002 1 1
002 2 1
;

data B;
input NO$ V1 V2;
cards;
001 0 1
002 0 2
;

 AB
 



問題

サンプルデータより以下のプログラムを実行すると、①②どちらのデータが出来るでしょうか?

data C;
   merge A  B;
   by  NO;

   if  V2=1 then  V2=10;
   else  V2=20;
run;

 ①

正解は②です。①だと思ったら要注意!


解説

はじめに、内部処理の説明は自分なりの解釈になってる可能性もあるので、あしからず。。


仕組み①
まずMERGEの仕組みを知る必要があります。
内部では、一旦プログラムデータベクトル(PDV)という入れ物に読み込んで加工してから、データセットに出力します。

以下は、サンプルデータの1行目をPDVに読み込むイメージ。




仕組み②
次が最大のポイントで、一対多のマージにおいて、
一側データセットBの変数は、BYグループ毎に1回しかPDVに読み込まれません。
(BYグループが変わるまで、PDVに残った値が保持される。。。RETAINのようなイメージ)

上記イメージの続きで、2行目をPDVに読み込むイメージ。




もし①のような結果が欲しい場合は、以下のように工夫する必要がある。
data C;
   merge  A(drop=V1)  B;
   by  NO;

   if   V2=1 then _V2=10;
   else  _V2=20;
run;




利用

この動きを面倒なものと思わず、逆に利用してやりましょう。
以下はマージの動きを利用してBYグループ毎に連番をふってます。
data B_;
  set B;
  SEQ=0;
run;

data D;
  merge A B_;
  by NO;
  SEQ+1;
run;




以上、説明がうまく出来た気がしないので、分かりにくい等のコメントを頂ければ、
さらに詳しく解説したいと思います。

7 件のコメント:

  1. はじめまして。いつも参考にさせていただいております。

    MERGEステートメントとIF-thenステートメントを一つのdata step内に書くと、if文が思った通りの動きをしてくれないことがあるのですが(MERGEステートメントとIF-thenステートメントをそれぞれ二つのdata stepに分けて書くと上手くいきます。)、そのような経験はありますでしょうか?

    data stepはあまり増やしたくないという勝手な美学があり(笑)、一つのdata step内にまとめて書くことが多いのですが、上記のようにステートメント自体は間違っていなくても、うまく動いてくれないケースがたまにあり、発生条件もわからないのですが、もし理由をご存知のようでしたら、教えていただけますと幸いです。

    返信削除
  2. はじめまして、コメントありがとうございます。

    私もなるべくステップ数は短くおさめたい派なのでその美学すごい分かります!
    MERGEとIF文で想定外の結果になるというのは例えば以下のような場合でしょうか?

    data A;
    input NO$ V1;
    cards;
    001 1
    002 2
    ;
    run;

    data B;
    input NO$;
    cards;
    001
    001
    002
    002
    ;
    run;

    data C;
    merge A B;
    by NO;
    if V1=1 then V1=10;
    else V1=20;
    run;

    想定
    NO V1
    001 10
    001 10
    002 20
    002 20

    結果
    NO V2
    001 10
    001 20
    002 20
    002 20

    もし上記のようなケースを指されている場合、一応この記事で解説を入れているんですが、説明がちょっと分かりづらかったですかね。。
    なかなか文章だけでは意味不明な説明になってしまいそうなので、この記事をちょっと直してわかりやすく解説をしようかと思っています。

    返信削除
    返信
    1. ご返信ありがとうございます!
      説明がだいぶ足りておらず、すみません。。
      そもそも、こういう書き方をしないで別の方法もあるとはおもうのですが、A、B、Cのデータセットをマージして、変数V5にデータセットA由来の変数V1の値を代入したOBSとデータセットB由来の変数V2の値を代入したOBSをそれぞれ作成したあと、if-thenステートメントを動かすと、V6に値がセットされないという現象でした。

      data A;
      input NO$ V1;
      cards;
      001 1
      002 2
      ;
      run;

      data B;
      input NO$ V2;
      cards;
      001 3
      002 4
      ;
      run;

      data C;
      input NO$ V3;
      cards;
      001 1
      002 3
      ;
      run;


      data D;
      merge A B C;
      by NO;

      V4='A'; V5=V1; output;
      V4='B'; V5=V2; output;

      if V3=1 then V6=10;
      else V6=20;
      run;

      この記事の内容とはずれていますし、PDVのことをきちんと理解していれば簡単なことなのかもしれないので、申し訳ないのですが、上記のプログラムでうまくいかない理由がわかりましたら、教えていただけると幸いです。
      (outputとif-thenを逆にしたところ、うまく動いてくれました^^;)

      削除
    2. なるほど、現象を確認しました!

      これはMERGEというよりOUTPUTステートメントの落とし穴ですね。
      以前書いた記事がヒントになりそうです。

      http://sas-boubi.blogspot.jp/2015/10/output.html


      今回の例だと、

      V4='A'; V5=V1; output;
      V4='B'; V5=V2; output;

      OUTPUTが実行されると、PDVにあるデータをコピーしてデータセットに書き込みます。
      データセットに書き込んでしまったOBSはもう編集することはできませんので、これ以降に書かれた

      if V3=1 then V6=10;
      else V6=20;

      この処理は書き込んだデータセットに対してではなく、PDVに残ったデータに対して実行されます。
      なので解決法はご提示いただいた通り、OUTPUTとIF-THENを逆にするだけでOKです。

      なんか拙い説明になりましたが、わかりづらい部分がありましたらお知らせください。

      削除
  3. ご返信ありがとうございます!
    OUTPUTステートメントでデータセットにすでに書き込んでしまったから、IF-THENが動かなかったということですね。

    とても良くわかりました!ありがとうございます!!

    返信削除
  4. 図解で丁寧なご説明で、とても親切ですね。大変参考になりました。
    新人さんでmergeは同じ変数名の場合は、後に書いたほうで上書きしてくれるからと言ってコーディングしている方がいましたが、
    こういうの説明するの面倒だったので、スルーしてしまいました...。懺悔

    返信削除
    返信
    1. コメント有難う御座います。
      確かにここは説明が面倒ですよね、PDVから理解する必要があるので、私もこの記事でうまく説明できてる気がしてないです。。
      今後も参考になったと言って頂ける記事を目指していきたいと思いますので、また見て頂けると嬉しいです!

      削除