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

2023年4月22日土曜日

サブセット化IFでありがちな落とし穴


SASプログラマ歴が長いと、みんなこれ経験してるんじゃなかろうか。


「サブセット化IF」自体は以下記事で解説しています。


😕失敗例1: 「_N_」と組み合わせて失敗しがち


各オブザベーションに1~連番をふった変数を作りたくて、以下のように書いたのですが、、

data dt1;
 set sashelp.class;
 if age=12;
 x = _n_;
 keep age x;
run;


変数Xに1~連番を入れたいのに、なんかおかしい。


解説

  • まずデータステップは内部の動きとして、
オブザベーションの数だけ、データステップをぐるぐるとループ(反復)させています。
ループのたびに1オブザベーションずつ読み込んでいるイメージで、「_N_」はそのループ毎に「+1」した番号が入ります。

  • 次に「_N_」は内部の動きとして、
まずSET文・WHERE文で読み込むオブザベーションに対して、データステップがループ(反復)されますが、自動変数「_N_」は、各反復内でいち早く番号がふられます

番号をふった後で、サブセット化IFにより処理が継続されずに出力もされなかったオブザベーションがあると、番号が連番にならなくなります。



📝特に「IF _N_=1 THEN なんかの処理;」みたいな書き方で失敗しがち。

例えば、以下プログラム(中身はめっちゃ意味のないことやってるのであんまり見ないでください)

data dt2;
 set sashelp.class;
 if age=12;
 if _n_=1 then do;
  dcl hash hs(dataset:"sashelp.class");
  hs.definekey("name");
  hs.definedone();
 end;
 if hs.check()=0 then x2=1;
run;


_N_=1 のオブザベーションがたまたまサブセット化IFによって「処理継続・出力」の対象外になっているので、「IF _N_=1 THEN DO; ~END;」の中の処理も動いていません。

上の例ではエラーが出て分かりやすいですが、書き方によってはエラーが出ない場合もあって失敗に気づかない可能性もあるので要注意です。


😕失敗例2: 「END=オプション」と組み合わせて失敗しがち


最後のオブザベーションにフラグを立てたくて、以下のように書いたのですが、、

data dt3;
 set sashelp.class end=_eof;
 if age=12;
 if _eof then y=1;
 keep age y;
run;


フラグ立ってないじゃないか!


これは「END=オプション」の挙動を勘違いしていると起こりやすいです。

「END=オプション」はSET文・WHERE文で読み込まれる最後のオブザベーションに作用しますが、その最後のオブザベーションがサブセット化IFによって「処理継続・出力」の対象外になっているためです。


あと思いつくのが、以下で紹介している「FIRST.BY変数」「LAST.BY変数」も、サブセット化IFと組み合わせると、同様の原理で意図しない結果になりやすいですね。

「FIRST.BY変数」と「LAST.BY変数」で、グループ毎の最初と最後のオブザベーションを特定する。


2016年5月25日水曜日

最後のオブザベーションかどうかを判定する方法【END=オプション】





今日たまたまタイトルにあるような質問を3回別の方から頂いたので記念に。



やりたい事


以下のデータで、最後のオブザベーションを読み込んだ時に何かしたい。

サンプルデータ ;
data DT1;
  do X=1 to 5;
      output;
  end;
run;

 X  
   1  
   2 
   3  
   4  
   5  




方法


SETステートメントに END=オプションを指定する事で、
  •  最後のオブザベーションだったら「1」、それ以外は「0」が一時変数に入る。
  •  一時変数は出力データセットに残りません。


構文

  SET データセット名  END = 適当な一時変数名;








以下は最後のオブザベーションだったら変数FLGに「1」を入れる例。

data DT2;
   set DT1 end=EOF;
   if EOF=1 then FLG=1;
run;

 X  
 FLG 
   1  
  .  
   2 
  .  
   3  
  .  
   4  
  .  
   5  
  1





注意


以下のようにWHEREステートメントで絞ったうえで最後のオブザベーションを判定することは出来ますが、、

data DT3;
   set DT1 end=EOF;
   where X < 3;
   if EOF=1 then FLG=1;
run;

 X  
 FLG 
   1  
  .  
   2 
  1  


サブセット化IFを使ってる場合は、判定できないのでご注意ください。

data DT3;
   set DT1 end=EOF;
   if X < 3;
   if EOF=1 then FLG=1;
run;

 X  
 FLG 
   1  
  .  
   2 
  .  


後、POINT=オプションによるランダムアクセスを使用してデータセットを読み取っている場合、END=オプションは使用できない(正しく動作しない)ようなので注意。


2014年9月17日水曜日

変数値をマクロ変数に格納する方法「CALL SYMPUTX編」




SASを覚えはじめたという方から、この質問を頂くことが多いので解説したいと思います。
(最後に注意点の記事リンクも貼ったので、そちらも要参照)


* サンプルデータ ;
data DT1;
input A$ B$ @@;
cards;
001 AA 002 BB 003 CC
;

 
B
 001
 AA
 002
 BB 
 003
 CC 



上記の変数A,Bの値をマクロ変数に格納してみましょう。

* 変数値をマクロ変数に格納 ;
data _null_;
    set  DT1  end=_EOF;
    if  _EOF then call symputx("OBS", _N_);
    call symputx( cats("A",_N_) , A );
    call symputx( cats("B",_N_) , B );
run;

* ログにマクロ変数値を展開 ;
%macro MAC;
    %do i=1 %to &OBS;
         %put  &&A&i  &&B&i;
    %end;
%mend;
%MAC;

ログ
001  AA
002  BB
003  CC


上記青字部分の解説

end = 一時変数名 
最後のオブザベーションを読み込んだ時、一時変数に「1」が入る。
一時変数名は任意の名前でOK
call symputx
構文・・・call symputx("マクロ変数名", 格納したい値 )
値をマクロ変数に格納する。
_N_
SASの自動変数で、データステップの反復回数が格納されている。
つまり、読み込んだオブザベーションに対して、1~連番をふったような値が入る。
cats関数
構文・・・cats(値1値2 … )
値の両端の半角スペースを取り除いて結合する。
%doループ
%do マクロ変数名 = 開始値  %to 終了値;
%end;
マクロ変数の値を開始値から終了値まで変化させながら、ループさせる。


処理の流れ

data _null_;
    set  DT1  end=_EOF;
    if  _EOF then call symputx("OBS", _N_);
最後のオブザベーションを読み込んだ時に、_N_の値をマクロ変数OBSに格納している。
(つまりオブザベーション数をマクロ変数に格納)

    call symputx( cats("A",_N_) , A );
    call symputx( cats("B",_N_) , B );
run;
変数Aについて
1オブザベーション目の値をマクロ変数A1に
2オブザベーション目の値をマクロ変数A2に
・・・と次々マクロ変数に格納していく。変数Bも同様。

%macro MAC;
    %do i=1 %to &OBS;
         %put  &&A&i  &&B&i;
    %end;
%mend;
%MAC;
%DOでマクロ変数 i の値を1,2,3…と変化させながら、オブザベーションの数だけループさせる。
%PUTを使って、マクロ変数A1,B1,  A2,B2, ・・・の値をログに展開していく。



📝注意点

「CALL SYMPUTX」で数値をマクロ変数に格納すると丸められる場合があるので注意
今回のテクニックの中で使用している「_N_」と「END=オプション」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。
(解説記事:「サブセット化IFでありがちな落とし穴」)


次回はSQLプロシジャを使った方法を紹介。


2014年5月28日水曜日

色々な例数を簡単に出す2



色々な例数を簡単に出す」のデータステップ編です。



サンプルデータ
data DT1;
  label  FOOD     = "購入食品"
          PRICE    = "価格"
          EATFLG = "食べたフラグ"
  ;
  length FOOD $20.;
input  FOOD$  PRICE  EATFLG;
cards;
ジュース  150  1
カップ麺  100  .
ジュース  100  1
ジュース  200  .
カップ麺  200  .
;



求めたい結果
 購入数  ジュース購入数  カップ麺購入数  
  5  3    2



データステップで各例数を計算
data DT2;
   set DT1 end=EOF;           /* ① */

   C1 + (FOOD^="") ;          /* ② */
   C2 + (FOOD="ジュース") ;
   C3 + (FOOD="カップ麺") ;

   if  EOF;                         /* ③ */
   keep C:;
run;



解説
前に紹介した「SUMステートメント入門」と「1行プログラミング6:TRUE/FALSEを計算に利用する。」の合わせ技です。

  • ① まず「end = 一時変数名」と書くと、最終行の一時変数に1が入ります。
  • ② 条件式によって返される「1.true」「0.false」をSUMステートメントで合計していきます。
  • ③ 最終的な合計がでる最終行のみを残します。






応用
グループ毎に色々な例数や合計を出すこともできる。

たとえば、サンプルデータで以下のように「食品毎の例数や合計」を出したいとする。
 食品  購入数  合計金額   食べた食品の合計金額 
 カップ麺 2   300 0
 ジュース 3   450 250


データステップでグループ毎の例数や合計を出す。
proc sort data=DT1; by FOOD; run;

data DT3;
   set DT1;
   by FOOD;
   if first.FOOD then call missing(C1,C2,C3);   /* ① */

   C1 + (FOOD^="");                                 /* ② */
   C2 + PRICE;
   C3 + (EATFLG=1)*PRICE;

   if last.FOOD;                                        /* ③ */
   keep FOOD C:;
run;



解説
  • ① グループの各先頭行の時、計算結果を入れる変数を初期化する(call missingでNULLにする)
  • ② SUMステートメントで合計していきます。
  • ③ グループ毎の最終行のみを残します。


「C3 + (EATFLG=1)*PRICE;」のところの仕組みを補足しておくと、 (EATFLG=1) の条件が
  • 「0.false」なら、C3 + 0*PRICEとなり、
  • 「1.true」なら、C3 + 1*PRICEとなり、
「true/false」によって合計に含めるかどうかのトリガーにしています。



📝注意点

今回のテクニックの中で使用している「END=オプション」「FIRST.BY変数」「LAST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。