2016年6月22日水曜日

【DROP・KEEPステートメント】データセットから特定の変数を削除または残す




※ データステップのMODIFYステートメントなど、特定の機能を用いるとDROP, KEEPステートメントが動作しないことがあるので注意。




DROPステートメント

指定した変数を出力データセットに「含めない」ようにする

data DT2;
   set SASHELP.CLASS;
   drop NAME AGE;
run;

💬 
  • 通常のステートメントはPDVという「一時的な処理スペース」で実行されますが、DROPステートメントは「一時的な処理スペース」の結果を「出力データセットへ書き込む時」に実行します
  • つまり、最終的に適用されるイメージです。また適用されるタイミングが決まっているので、DROPステートメントを記述する位置は関係ありません(プログラムの先頭や最後に書いても挙動は変わらない)




KEEPステートメント

指定した変数のみ」出力データセットに「含める」ようにする

data DT1;
   set SASHELP.CLASS;
   keep NAME AGE;
run;

💬 
  • DROPステートメントと同様、KEEPステートメントは記述する位置に関係なく、「出力データセットへ書き込む時」に実行します(詳細は上のDROPステートメントの説明を参照)




テクニック

過去にいくつかテクニックを紹介しています。

ATTRIBとKEEPを使った基本コンボ
変数名をいっきに指定する方法 [まとめ]



また、上の2つ目のリンク記事内にある「:(コロン)」を使ったよくやるテクニックを紹介。

やりたい事
「データステップ内で作った一時的な変数を最終的にまとめてDROPしたい」


こういった場合、まとめてDROPしたい変数の名前の先頭に 「tmp_」 とかつけておいて、「DROP tmp_: ;」 と書いてしまえば、最後に「tmp_○○」の変数をまとめて削除できちゃいます。
(たまにやっちゃうミスですが、既存の消したくない変数に「tmp_○○」というのがあったらそれも消えちゃうので気をつけてください)



data DT3;
   set SASHELP.CLASS;
   _A = 1;
   _B = 2;
   drop _: ;
run;


上の例では先頭に「_」のつく変数をまとめて削除しています。


2016年6月7日火曜日

「OUTPUT」と「CALL MISSING」のコンボ技


一対多のマージで、一側データ由来の変数値を各BYグループの最初のみに出力したい事ってありませんか?


*** サンプルデータ ;
data DT1;
input A$ B$ C$ @@;
cards;
001 aa cc 002 bb dd
;

A
 B  
 C  
  001    aa     cc   
  002    bb  dd   

data DT2;
input A$ D @@;
cards;
001 11 001 22 001 33 002 44 002 55
;

A
  D  
  001     11   
  001     22
  001     33
  002     44
  002     55


やりたい事

上のデータをMERGEすると、
data OUT1;
  merge DT1 DT2;
  by A;
run;


上記のように一側データ(DT1)由来の黄色部分が繰り返されますが、これを空白にしたい。
A
 B  
 C  
  D  
  001    aa     cc      11   
  001                 22   
  001        33   
  002    bb     dd      44   
  002        55  


前提: 
一対多のマージを前提とします。多対多を含むマージ(一対多対多とか)だと挙動が変わってくるので今回の記事では対象外とします。



方法1 最初に思いつく方法
data OUT1;
  merge DT1 DT2;
  by A;
  if first.A = 0 then call missing( B, C );
run;

以下記事で紹介した「FIRST.BY変数」を利用しています。

1つ注意として、ここで使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。



方法2 OUTPUT と CALL MISSING のコンボ技
data OUT1;
  merge DT1 DT2;
  by A;
  output;
  call missing( B, C );
run;

紹介しといてなんですが、方法1の方が、分かり易くていいと思います。
ですが方法2のようなやり方を知ってると、応用がきいて便利な時があります。


方法2の解説

以前の記事 「MERGEステートメントの落とし穴」 で紹介した内容を理解することで、今回の内容も理解がしやすいです。

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


そこで、1オブザベーション毎に
① 「output;」でデータセットOUT1に出力
② 「call missing( B, C );」 でPDVの中の変数BとCを欠損値にする

PDVに残った一側データ由来の変数BとCの値が保持されるのを②によって抑制しているので、BYグループの最初にしかBとCが入らなくなっているわけです。


一側データの変数の数が多い場合は、「call missing( of _all_ );」と書いてしまえば、いっぺんに欠損値に出来るので楽です。

data OUT1;
  merge DT1 DT2;
  by A;
  output;
  call missing( of _all_ );
run;

ただし、PDV内の全変数を欠損値にしているので、
RETAIN等、値を保持するような処理は全て無効になります。


。。。つづく

2016年6月2日木曜日

マクロ言語入門7:マクロ内でのループ処理【%DO】



マクロ内でループ処理をする「%DO-%ENDステートメント」を紹介します。





構文

%DO  マクロ変数名 = 開始値  %TO  終了値;

           繰り返し実行したい処理

%END;

  • [マクロ変数] の値を [開始値] から [終了値] まで変化させながら、中に書かれた処理を繰り返し実行します
  • マクロの中でのみ利用可能




%macro TEST;

   %do no = 1 %to 5;
        data DT&no ;
        run;
   %end;

%mend;
%TEST;



マクロ変数NOの値を1から5まで変化させながら、中に書かれた処理を繰り返し実行します。
つまり以下のプログラムが実行される事になります。

data DT1;
run;

data DT2;
run;

data DT3;
run;

data DT4;
run;

data DT5;
run;

2016年5月31日火曜日

【%LET Y=%X】 みたいに書いて指定変数の統計量をマクロ変数に直接格納する方法





SASプログラミング掲示板で過去に以下の質問がありました。
http://tumesas.progoo.com/bbs/tumesas_topic_pr_20.html (サイト閉鎖されたためリンク切れ)


%let Y = %X(データセット, 対象変数 );


みたいな書き方で、「マクロ変数Yに統計量などを格納する事は出来ないか?」 という質問です。
この時、FCMPを使う方法を提案したんですが、もっといい方法を思いついたので、また自己満足のために書き留めておきます。



着想としては以下の記事あたり。
マクロで 【%LET Y=%X】 みたいな書き方
データステップ内でプロシジャを実行する。


以下は、SUMMARYプロシジャを実行して、好きな統計量を直接マクロ変数に落とすマクロです。

*** マクロ登録 ;
%macro m_summary( ds, var, stat );
/********************************************************************
   ds       … 対象データセット
   var      … 対象変数
   stat     … 統計量キーワード
********************************************************************/


/* dosubl関数で値を設定するマクロ変数_mputが既に存在していたら削除 */
/* (削除しないと_mputに正しい値が入らない事があったため) */
%symdel _mput / nowarn;


%local _rc _mput;
%let _rc = %sysfunc(dosubl(%str(


       /* SUMMARYプロシジャを実行 */
       proc summary data=&DS;
          output out=OUT1 &stat(&var)=_mvar1;
       run;

       /* 結果をマクロ変数に格納 */
       /* マクロ変数に数値をいれると、元の値と誤差がでる事があるので注意 */
       data _null_;
          set OUT1;
          call symputx('_mput', _mvar1);
       run;
)));

&_mput

%mend;


*** マクロを実行してみる ;
%let val=%m_summary( sashelp.class, age, mean );
%put &val;


*** データステップでも実行出来るようになってます ;
data DT1;
 VAL1 = %m_summary( sashelp.class, age   , mean );
 VAL2 = %m_summary( sashelp.class, height, mean );
run;



💬 注意事項
プログラム中に青字で書いてるように、dosubl関数内ではマクロ変数が正しく作られない場合があるので、実行後正しい結果が返ってくるか、テストが必要です


また「データステップ100万回」にて紹介されている通り、
マクロ変数に数値をいれると、元の値と誤差がでる事があるので注意。

マクロ変数に数値をいれて戻すと誤差がでちゃう場合がある問題について考える話



実用的かどうかは置いといて、個人的に満足したので良しとします。。
(アイディアの段階のため、この方法を利用する際は、上で記載した問題を考慮する必要があるのと、動作テストも行ったほうが良いです)



2016年5月26日木曜日

マクロ言語入門6:クォート処理【%BQUOTE関数】




%STR と %BQUOTE は同じクォート処理系の関数ですが、まったく異なる動きをします。



%STR関数の性質

%STRで囲った範囲の特殊文字をクォートする。



%BQUOTE関数の性質

%BQUOTEで囲った範囲に「クォート処理されていないマクロやマクロ変数」が含まれる場合は可能な限り展開してから、結果に含まれる特殊文字をクォートする。




具体例


以下は、変数Aに値を入れるマクロです。

%macro TEST( VAL );

       data DT1;
           length A $30.;
           A = "&VAL";
       run;

%mend;


このマクロを実行して変数Aに「A,B,C」という値を入れたいとします。

 
 A,B,C  


まどろっこしい書き方ですが、以下のように書いて実行すると、、

%let X= A,B,C;
%TEST( &X );

ログ
ERROR: マクロに定義された数よりも多い定位置パラメータが与えられています。

エラーが出ます。これは 「%TEST( &X );」 → 「%TEST( A,B,C );」という順に展開されるわけですが、
「%TEST(A,B,C)」にはカンマが含まれてるので「3つ引数があるのか」と解釈されてしまうからです。




%BQUOTE関数を使ってみる


マクロ変数Xを展開した後の値「A,B,C」をクォート処理したいので、%BQUOTE関数を使います。

%let X = A,B,C;
%TEST( %bquote(&X) );

 
 A,B,C  




%BQUOTE , %NRBQUOTE関数


%BQUOTE と %NRBQUOTE は同じ性質ですが、クォート対象の文字が異なります。

マクロ関数対象の文字
%BQUOTE






+ - * / < > = ¬ ^ ~ ; , # 半角スペース
AND OR NOT EQ NE LE LT GE GT IN ' " ( )

以下のようにクォートする文字としてカッコを含めて記述すると、SASが混乱してバグってしまうので注意
%bquote(aa(bb)
%bquote(aa)bb)
%NRBQUOTE




%BQUOTE関数と同じ+ & %

このマクロ関数は挙動を勘違いしやすいうえ、扱いづらいため個人的にはあまり使ってません。
以下「%NRBQUOTEの詳しい挙動」も要確認!



💬 %NRBQUOTEの詳しい挙動

例えば、以下2つのマクロ変数があったとします。
  • マクロ変数X: 「&Y」という値がクォート処理されずに格納されている
  • マクロ変数Y: 「&Z」という値がクォート処理されずに格納されている


これに対して、以下を実行すると、、

%put %nrbquote(&X);

%BQUOTE, %NRBQUOTEで囲った範囲の「クォート処理されていないマクロやマクロ変数」は可能な限り展開されるので、

→「&X」を展開すると「&Y」
→「&Y」を展開すると「&Z」
→「&Z」というマクロ変数は存在しないため、これ以上展開できない
→「&Z」を展開できない旨のWARNINGをログに出力


%NRBQUOTEは「&」もクォート対象になっているので、

→「&Z」というマクロ変数として解釈されないようクォート処理




実践例


%SYSFUNC関数を使う時によくクォート処理します。詳細は以下過去記事をご覧ください。



とにかくマクロ関数は複雑で、何故そうなるの?という挙動が沢山あります。
実際に使用する際は、結果をよく確認したほうが良さそうです。


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=オプションは使用できない(正しく動作しない)ようなので注意。


2016年5月19日木曜日

マクロ言語入門5:クォート処理【%STR関数】





クォート処理とは?



以下は、変数Aに値を入れるマクロです。

%macro TEST( VAL );

       data DT1;
           length A $30.;
           A = "&VAL";
       run;

%mend;


このマクロを実行して変数Aに「A,B,C」という値を入れたいとします。

 
 A,B,C  


何も考えず書くと、、

%TEST(A,B,C);

ログ
ERROR: マクロに定義された数よりも多い定位置パラメータが与えられています。

エラーが出ます。これは%TEST(A,B,C)にカンマが含まれてるので「3つ引数があるのか」と解釈されてしまうからです。



解決策
%STR関数で囲ってあげます。

%TEST( %str(A,B,C) );

 
 A,B,C  


%STR関数は、カンマやセミコロンなどSASの構文として意味のある特定の文字を、ただの文字として解釈させる関数です。
このただの文字として解釈させる事を「クォート処理」といいます。





%STR, %NRSTR関数



関数によってクォート対象の文字が異なります。

マクロ関数対象の文字備考
%STR




+ - * / < > = ¬ ^ ~ ; , # 半角スペース
AND OR NOT EQ NE LE LT GE GT IN



' " ( )
上記文字をクォートしたい場合、その文字の前に%をつける(%直後の1文字のみに効果がある)
この方法の落とし穴を記事の下の方の例③に示しているので要確認!
%NRSTR

%STR関数と同じ+ & %

%STR関数と同じ



ちなみに%NRSTR の先頭の「NR」は 「Not Resolved」の意味で、つまり「マクロやマクロ変数を展開させない」という意味合いがあります。
なので&や%がクォート対象となっています。



では最初に書いたマクロTESTを使って、クォート処理の例を紹介します。
(例③④は落とし穴的な挙動なので要注意です!)






例①
欲しい結果
 
 Mr.&Mrs.Smith  

「&Mrs」 というマクロ変数として解釈されないようクォート処理をします。
%TEST( %nrstr(Mr.&Mrs.Smith);




例②
欲しい結果
 
 It's a small world  

シングルクォーテーションをクォート処理します。
%TEST( %str(It%'s a small world);



例③
欲しい結果
 
 100%JUICE  

「%JUICE」 というマクロとして解釈されないようクォート処理をします。
%TEST( %nrstr(100%JUICE);
   または
%TEST( %nrstr(100%%)JUICE );

2個目の例を「%TEST( %nrstr(100%)JUICE )」とすると、、

%NRSTR関数での「%)」は「)」をクォート処理せよという命令になってしまいます(%NRSTR関数の閉じカッコがクォート処理されるのでSASの挙動もおかしくなる)

そこで%STR, %NRSTR関数内に「%%」と書くと、
  • この「%」は直後の文字「'」「"」「(」「)」をクォートするという意味ではない、と命令する
  • 「%%」は「%」に置き換わる



例④
欲しい結果
 
 /* abcd */  

コメントステートメントとして解釈されないようクォート処理をします。
%TEST( %str(/)%str(*) abcd %str(*)%str(/) );

「%TEST( %str(/* abcd */) )」という書き方はNG。%STR関数の中でもコメントステートメントとして認識されるからです。なので「/*」を1文字ずつ%STR関数で分けてクォート処理しています。

ちなみに、コメントステートメントの種類(「/* */」「* ;」「%* ;」)によってマクロ内での挙動が異なっているのが影響してますが、
ほしい結果が「* abcd ;」の場合は「%TEST( * abcd ; )」という書き方でOK。
ややこしい。。コメントステートメントを含む文字をクォート処理する場合は挙動確認したほうが良いです。




(おまけ) シングルクォートとダブルクォート


以下プログラムは、&Mrsというマクロ変数が指定されてると解釈されてしまい、ログにWARNINGが出ます。

data OUT1;
  A = "Mr.&Mrs.Smith";
run;

ログ
WARNING: MRSのシンボリック参照を解決できません。


しかし、以下はWARNINGが出ず期待通りの結果が得られます。

data OUT2;
  A = 'Mr.&Mrs.Smith';
run;


これは「シングルクォーテーションで囲まれた文字に&や%が含まれていても、ただの文字として解釈される」というルールがあるからです。