ラベル SASマクロ の投稿を表示しています。 すべての投稿を表示
ラベル SASマクロ の投稿を表示しています。 すべての投稿を表示

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

SASプログラミング掲示板の方の話題で
http://tumesas.progoo.com/bbs/tumesas_topic_pr_20.html

話題の本筋ではないのですが
p値をマクロ変数に入れて、後でそこから値を取り出すと、本来の値と比べて凄い小さい誤差がでてるっぽいねという話がありました。

まあ、基本コンピューターの小数点表現には限界があるので、みんな、疑わしきはroundみたいな習慣がついてるので、問題ないね!ということです。
そういや、そもそも、僕の場合、何らかの計算結果をマクロ変数に入れるってこと自体、したことないかも。

けど、一応なんかないかな~と気になって調べてみると

hex16フォーマットを使うという記事がいくつか、ひっかかります
http://www.sascommunity.org/wiki/Unintentional_Loss_of_Precision とか)

読んでもあんまりピンとこないから、実際やってみました。

data _NULL_;
call symputx('A',1/2);
call symputx('B',1/3);
call symputx('C',1/7);
call symputx('D',sqrt(2));
call symputx('E',constant('pi'));
run;

と5つの計算結果をマクロ変数に入れてみます

%put A=&A B=&B C=&C D=&D E=&E;

とすると





お~、掲示板の話と違って、これはわかりやすく切れてますね。
そうか、なんもしないとデフォルトのフォーマットの有効桁数分しか文字に入らんわけか。
まあ一応、確認してみますか

data A1;
A=1/2;
B=1/3;
C=1/7;
D=sqrt(2);
E=constant('pi');

FL1=ifc(A=&A,'同じ','違う');
FL2=ifc(B=&B,'同じ','違う');
FL3=ifc(C=&C,'同じ','違う');
FL4=ifc(D=&D,'同じ','違う');
FL5=ifc(E=&E,'同じ','違う');

keep FL:;
run;

とすると





と当然の結果。

ではでは、hex16で文字化してからマクロ変数に入れて

data _NULL_;
call symputx('HA',put(1/2,hex16.));
call symputx('HB',put(1/3,hex16.));
call symputx('HC',put(1/7,hex16.));
call symputx('HD',put(sqrt(2),hex16.));
call symputx('HE',put(constant('pi'),hex16.));
run;

%put HA=&HA HB=&HB HC=&HC HD=&HD HE=&HE;




マクロ変数を戻す時に、hex16.で再び読み込んで比較すると

data A2;
A=1/2;
B=1/3;
C=1/7;
D=sqrt(2);
E=constant('pi');

FL1=ifc(A=input("&HA",hex16.),'同じ','違う');
FL2=ifc(B=input("&HB",hex16.),'同じ','違う');
FL3=ifc(C=input("&HC",hex16.),'同じ','違う');
FL4=ifc(D=input("&HD",hex16.),'同じ','違う');
FL5=ifc(E=input("&HE",hex16.),'同じ','違う');

keep FL:;
run;





となるんですね。

なんか、やっぱよくわかんないですね。
どんなフォーマット当てようが、どっかで切れてんのは一緒じゃないのかって
思ってしまうのですが…。
誰か詳しく解説してください。

掲示板の内容でもチェックしてみます。

proc univariate data=SASHELP.CLASS noprint;
 var AGE;
 output out=U1 probt=PT;
run;
data _NULL_;
 set U1;
 call symputx('VAL',put(PT,hex16.));
run;
data OUT1;
 set U1;
 VAL=input("&VAL",hex16.);
 if PT=VAL then FLG="同じ";
run;

とすると





やっぱり、できましたね。

へ~

シングルコーテーション内のマクロ変数を展開する

下書きのまま、すっかりあげ忘れていた記事があったのでアップします。


今回の話は、マクロ変数の展開についてです。

さて今

%let M=いろは;

data Q;
 X='&M';
run;

といったコードを実行するとどうなるでしょうか?
やりたいこととしては、マクロ変数Mに「いろは」という値を入れて、それを後続のデータステップで
展開したいわけですが、この処理は期待どおりの結果になりません。







このように、マクロ変数が展開されません。
どうしてかというと、シングルコーテーションを使っているからです。
シングルコーテーション内のマクロ変数は展開されないというルールなんですね。
この場合は、ダブルコーテーションを使うのが正解でした。


さて、ここまではSASマクロを少しでも知っている人であれば、常識と言っていいことでしょう。

ただ、SASを長くやっていると、dmステートメント内の記述や、DDE等外部アプリケーションとの連携のため、シングルコーテーションを使う縛りがある上で、尚且つその中でマクロ変数を展開したいといったケースが生じることがあります。

無理。仕様だからしようがないといって諦めるのもそれはそれで一局の将棋ですが、新手一生の升田幸三はこう言いました。
「もう一歩突っこんで、不可能を可能にする努力、―将棋を創作し、また、勝負を勝ちきるには、この“えぐる”という修練が必要である」

というわけで、諦めずに、えぐってみましょう。

まず、思いつくのは

data E;
 X=%bquote('&M');
run;

ですね。シングルコーテーションに、マクロ変数展開を拒絶する能力があるというのならば、それを無効化して、意味のない記号文字に変えてしまえという発想です。
これをクォート処理っていいます。クォート処理を行う関数は山ほどあって、対象とする記号の種類や、処理のタイミングによって使い分けるのですが、詳しく書くと量がやばいことになるので、今回はしません。
%bquoteはシングルコーテーションもクォート対象なので今回使っています。
おまけですが%strは、デフォではシングルはクォート対象外ですが、
data E;
 X=%str(%'&M%');
run;
のように%を対象の前につけることで、同じ効果を得られます。

で、先ほどのコードを実行すると


となって、もれなくエラーになりました。
マクロ変数の展開はできているみたいですが、なぜでしょう?

わかりにくいんですが、ヒントはエラーメッセージです。

例えばですが、

data E;
X= $ABC$;
run;

というコードを実行すると、











あれ、同じメッセージだ!
どういうこと?

これはABCという文字列をくくっている$ドルマークに何の意味もないから、SASが、「お前が何をしたいのかさっぱりわからん、意味のある記号を使って、成立しているコードを書いてくれ」
っていう意味なんですね。


さて、これで先ほどの問題がわかりました。
data E;
 X=%bquote('&M');
run;

は確かに、シングルコーテーションのマクロ展開拒絶能力を無効化したことで、&Mの内容は展開できました。
しかし同時に、シングルコーテーションの、括った値を文字列として扱うという能力も無効化していたのです。
シングルコーテーションが全く意味のない役立たずの無意味文字になりさがったわけです。

なるほど、やっと解決法が見えてみました。

つまりマクロを展開する際は、シングルコーテーションを無効化(クォート)し、展開が終わったら再度有効化(クォート解除)すればいいわけです。

そこで、強制的にクォートを解除する関数%unquoteで

data A;
 X=%unquote(%bquote('&M'));
run;

とすれば






です。

ちなみに、残念ながらこれは別に僕が考えた新手ではなく、割と有名な定跡なんですけどね。


掲示板質問の回答例:現在使用中のFootnoteの最終番号を取得する方法

掲示板の方に、質問をいただきました。有難うございます!掲示板に返事書こうと思ったのですが、結構やってて面白い内容だったので記事にしました。
(掲示板:http://tumesas.progoo.com/bbs/tumesas_topic_pr_6.html

ただ、本当にこの方法でいいのかわからないので、何かアイデアがあればコメントでも掲示板にでもメッセージください。

さて本題。質問は











なるほど、、。そういえば考えたことない、、。

検索しても、それっぽいのが出てこないので、自分の頭で考えてみました。
以下になります。


まず

data Q1;
X=1;
run;

title 'タイトル1';
footnote 'フットノート1';
footnote2 'フットノート2';
footnote3 'フットノート3';
proc print;
run;


を実行すると、当然















こうなるわけですね。
さて次のプロシジャ実行時に、すでにfootnote3まで使ったことを取得し、footnote4が展開されるようにしたいわけですね。仮にfootnote6まで使っていればfootnote7みたいな感じです。

さて、定義したタイトルやフットノートの情報はどこにいるでしょうか?











SASHELPライブラリの中には、SAS社が用意したサンプルデータセットと共に、SASの様々な定義情報がリアルタイムで更新されるビューが用意されてます。
(ちなみにSASHELPのサンプルデータセットを間違えて全部消したおバカさん(僕です)はSAS社のWebページから再ダウンロードできます)



この中のVTITLEを開けてみると










発見!

typeのTはタイトル、Fはフットノートです。

後は、もう簡単ですね

proc sql noprint;
 select max(number) into:fno
 from dictionary.TITLES
 where type='F' and text^='';
quit;

です。

sashelp.VTITLEはSQLの中ではdictionary.TITLESと書けます。そういうものなんです。

これでマクロ変数fnoには3が入ります。

そして以下

footnote%eval(&fno+1) 'プログラマー名:SASYAMA';

proc print;
run;


を実行すると














となって、確かに最後にプログラマー名がでてきます。

こんな感じです。

まあ、改善点としては、定義可能最大値が10なので、footnote10が既に定義されている場合や、全く定義されていない状態で実行した場合エラーにならないような分岐が必要ですが、そこまではやってません。

ODS OUTPUTにmatch_allをつけてデータセットを作成する際に、作成されたデータセット名のリストが格納されるマクロ変数を同時に作成する

以前、「ODS OUTPUTからデータセットを作る場合に複数変数を指定したプロシジャ出力の場合に(MATCH_ALL)で変数ごとにデータセットを自動連番で分けてもらう」という記事で

http://sas-tumesas.blogspot.jp/2014/02/ods-outputmatchall.html

ods outputの出力データセットを複数にわけるという話をしましたが、それに補足です。

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


ods listing close;
ods output basicmeasures(match_all=MA)=AA;
proc univariate data=Q1;
 var X Y;
run;
ods output close;

ods listing;

match_allの後に=で任意の名前を指定すると、作成されたデータセット名がマクロ変数に格納されます。

実行後、

%put 作成されたのは &MA;

とすると






となります。


data OUTPUT;
 set &MA;
run;

とすると、作成されたデータセットが対象となって縦連結されます。
じゃあなんでmatch_allつけて分けたの?って感じですが。


前回の記事では複数変数を指定したらという話でしたが
例えば

proc sort data=Q1;
 by X;
run;

ods listing close;
ods output basicmeasures(match_all=MB)=BB;
proc univariate data=Q1;
 var Y;
 by X;
run;
ods output close;
ods listing;

のようにby変数ごとに結果のデータセットを作成した場合でも

%put 作成されたのは &MB;

とすると






となります。


作成されたデータセットに、マクロループなんかで同一の処理をかけたい場合に
役立ちます。

まあ今まで一度もそんな状況はなかったけど。






SQLでマクロ変数に値を格納する into: separated by

SQLでマクロ変数に値を格納する方法についてご質問いただきましたので、あまり詳しくなくて自信がないですが紹介します。

SELECT INTO:を利用します。

proc sql noprint;
select count(*) into:obs
from DS;
quit;

などでデータセットのオブザベーション数をobsというマクロ変数に格納できます。

他に例えば

data Q1;
X='い';Y='A';Z=1;output;
X='ろ';Y='B';Z=2;output;
X='は';Y='C';Z=3;output;
run;







のようなデータセットがあった場合


proc sql noprint;
 select X,Y,Z
 into  :MX1-:MX3
      ,:MY1-:MY3
      ,:MZ1-:MZ3
 from Q1
 ;
quit;

のようにかけば、マクロ変数MX1にXの一つ目の値、MX2に二つ目の値、以下、同Y同Zに同じように格納できます。

%put &MX1
     &MX2
     &MX3
     &MY1
     &MY2
     &MY3
     &MZ1
     &MZ2
     &MZ3
;

するとログにでるのは




です。


データセットのオブザベーション数が可変だけども、どれくらいになるかある程度わかっていれば

proc sql noprint;
 select X,Y,Z
 into  :MX1-:MX999
      ,:MY1-:MY999
      ,:MZ1-:MZ999
 from Q1
 ;
quit;

と大雑把な方法ですが、大きくとっておけば、オブザベーションがない場合も
よけいなマクロ変数は作成されないので、使えます。


あと、以下のように書くと

proc sql noprint;
 select X,Y,Z
 into  :MXALL separated by '、'
      ,:MYALL separated by '、'
      ,:MZALL separated by '、'
 from Q1
 ;
quit;


%put &MXALL
     &MYALL
     &MZALL
;

ログは





のように、データをseparated by で指定した区切り文字で連結したものを一つのマクロ変数に
格納できるので、マクロのパラメータや関数の引数に渡したい値を作る時などに重宝します。

他にselect into:について技をお持ちの方がいらっしゃれば、むしろ勉強させていただきたいです。
call symputはデータステップの中でやるので、可変的にマクロ変数を作るうえで凄いやりやすい
のですが、SQLでもできないでしょうか、、。

多分、色々できると思うのですが、、いっつも、とりあえずわかる方法でやってしまって、なかなか新手開拓ができてないです。









SYMGET関数でマクロ変数の値を取得する

マクロがあまり得意ではないので、もし誤っている点があればご指摘ください。



%let MA=11;
%let MB=22;
%let MC=33;
%let X=44;

のようにマクロ変数に値が代入されているとします。

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

data Q1;
X='MA';output;
X='MB';output;
X='MC';output;
run;







そこでXの値に対応するマクロ変数の値を展開したい場合、次のように書けます。

data A1;
 set Q1;
  Y=symget(X);
run;








あるいは

data A1_;
 set Q1;
  Y=resolve('&'||X);
run;

でも可です。resolve関数はより広義的機能をもってたりするので興味のある方は
リファレンスやヘルプをひいてください。

でsymgetの戻りはlength$200がデフォで、つまり文字型ですが

data A2;
 set Q1;
  Y=symgetn(X);
run;

とsymgetn関数にすれば数値で得られます。


また

data A3;
 set Q1;
  Y=symget('X');
run;

とすると







単純にマクロ変数Xを展開するので、上記のようになります。


もう一点ポイントなのですが、

以下のプログラムをみてください

data A4;
 set Q1 end=eof;
 call symputx(cats('MV_',_N_),X);
 if eof then Y=cats("&MV_1","&MV_2","&MV_3");
run;









このプログラムでは、最後のY=のところでマクロ変数を展開することができません。
call symputはデータステップ中にマクロ変数に値を代入できますが、
それはそのデータステップが終了した時点で確定し、普通の方法では同データステップ内で
参照することはできません。

%symdel MV_1 MV_2 MV_3 MA MB MC X;
(※順に実行して確かめている方のために一度マクロ変数を消しています)

ところが

data A5;
 set Q1 end=eof;
 call symputx(cats('MV_',_N_),X);
 if eof then Y=cats(symget("MV_1"),symget("MV_2"),symget("MV_3"));
run;







のように書けば、symputで作成されたマクロ変数を同ステップで参照できます。



指定した変数を除いて、それ以外の全ての変数を関数の対象にするマクロ


少し早いですが、PC内のファイルの年末大掃除をしていた際に
まだSASを覚えてそんなにたっていない頃に作ったマクロがでてきて、

僕自身、存在すら完全に忘れてたのですが
これが結構面白い発想のものだったので、自画自賛ながら紹介します。

例えば以下のように変数の多い、横長なデータセットがあった場合を考えます

data Q1;
A=1;B=2;C=5;D='あ';E=8;F=4;G='い';H='A';I=9;output;
A=4;B=3;C=2;D='い';E=6;F=2;G='ろ';H='A';I=5;output;
run;






AとBの合計を出してとかだったら sum(A,B)で余裕ですし、
AとB以外の合計を出してと言われたらsum(of C-numeric-I);でいけますよね。
(※変数名--変数名は変数の格納位置を指定する方法です。
間にnumericやcharacterを挟むことで、数値型か文字型で絞れます)

でも、きっついのが、変数の格納順が定まってなかったり、横に変数が増えていくような
ことが想定される、つまりケツにくる変数名が不明、特定できない場合に
sum(of C-numeric-I);の方法は使えません。

さらに痛いのがEとI以外の合計を出してみたいなケースで、
sum(A,B,C,F);みたいに全部列記して書くか、、それでも変数が可変的に追加される状況では
決め打ち対応できません。

そういった時に昔の僕が捻くり出したのが以下のマクロです。

%macro noarray(INDS,OUTDS,EXLIST,FUNC);
 data &OUTDS;
 informat &EXLIST DUMMYST;
 set &INDS;
  DUMMYST=.;
  DUMMYEND=.;
  RESULT=&FUNC(of DUMMYST-numeric-DUMMYEND);
  drop DUMMYST DUMMYEND;
 run;
%mend noarray;

INDSに流し込むデータセット名
OUTDSに作成するデータセット名
EXLISTに関数の対象から除外する変数リスト
FUNCにはSUMやMEANやMAXなど数値対象の関数を指定します。

=======================
以下、実行例
=======================
%noarray(Q1,A1,A B,sum)
これはAとB以外をsumしています 結果は




%noarray(Q1,A2,E I,sum)
これはEとI以外をsumしています 結果は




%noarray(Q1,A3,C I D,mean) 
これはCとIとD以外をmeanしています 結果は





なかなか面白いのが、関数の指定に変数順を使用して、
対象除外の変数を一番先頭にして、その後ダミーのスタート地点と
ダミーのゴールとなる変数を定義しているところですね。

numericのところを消せば、文字数値共通で引数にとれる関数に使えますし
characterにすれば文字型変数版マクロに早変わりですね。
てか、ここ自体、マクロパラメータにしちゃえばいいですね。

で、思い出深いのが、noarrayというマクロ名です。
array、配列がどうしてもわからなくて、使うのが凄い苦手だったんです。
で、マクロも苦手だったので、変数名をマクロにいれるとか、配列をつかうとかいう発想が
でなかったんです。それでこういう名前にしたんですね。





CALL EXECUTEで、マクロループみたいなことを平コードで実現する_条件に合致するデータセットに対して処理を掛ける例

CALL EXECUTEというと、データステップ内で発生した値をマクロの引数にして実行する時によく使う機能でマクロとセットで捉われがちですが、
本来、CALL EXECUTEの引数を必ずマクロ化した処理にしなければならないわけではありません。


例えば今、以下のようにDS1,DS2,DS3,DS4と適当なデータセットが4つあったとします。

data DS1;
 do X=1 to 10;
  output;
 end;
run;
data DS2;
 do X=10 to 20;
  output;
 end;
run;
data DS3;
 do X=20 to 30;
  output;
 end;
run;
data DS4;
 do X=30 to 40;
  output;
 end;
run;

そして、以下のLISTというデータセットがあったとします

data LIST;
 DSNAME='DS1';FLAG='Y';output;
 DSNAME='DS2';FLAG='N';output;
 DSNAME='DS3';FLAG='N';output;
 DSNAME='DS4';FLAG='Y';output;
run;







FLAG変数がYとなっているDSNAMEのデータセットに対して
要約統計量の入ったデータセットを作成したいとします。

その場合、以下のようにLISTをデータステップでsetし
ifステートメント後にcall executeで、実行したい処理を文字列として記述します。
処理の引数にしたい部分は、データステップの変数なのでコーテーションでくるまないように
気をつけます。

data _NULL_;
 set LIST;
 if FLAG='Y' then
 call execute("proc summary data="||DSNAME||";
               var X;
               output out="||DSNAME||"_RESULT;
               run;");
run;

これで実行するとDS1とDS4に対してのみproc summaryが実行され
DS1_RESULTとDS4_RESULTが作成されます。





















もし、マクロ化してからcall executeするのであれば以下の感じでしょうか。

%macro m_d(ds);
  proc summary data=&ds.;
   var X;
   output out=&ds._RESULT;
  run;
 %mend m_d;

 data _NULL_;
  set LIST;
  if FLAG='Y' then
  call execute('%m_d('||DSNAME||')');
 run;

TRANSPOSEプロシジャのdelimiter=を使ってみる

SAS9.2からtransposeプロシジャのidステートメントに複数変数を指定できることは何回か紹介したのですが、それに付随してdelimiter=が追加されていることを紹介し忘れていました。

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

data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;







idステートメントにXとYの2変数を指定して以下のコードで転置すると

proc transpose data=Q1 out=A1;
 var Z;
 id X Y;
run;





となります。

変数名をみるとXとYの値がそのままくっついているのがわかります。
ただ、これって例えば変数によって入っている値の長さがばらばらで、内容が似ていたりすると
どこからどこまでが何個目の変数から生成された部分か判別できないことがあります。
delimiter=はその間に接続文字を入れることができます(変数の命名規則には注意)
つまり

proc transpose data=Q1 out=A2 delimiter=_;
 var Z;
 id X Y;
run;

とすると





となります


proc transpose data=Q1 out=A3 delimiter=and;
 var Z;
 id X Y;
run;

なら





です。

SQLで削除や更新されたオブザベーション数を取得する。&SQLOBSは守備範囲が広い

以前「SASデータセットのオブザベーション数をマクロ変数に格納する方法」についてcall symputxやSQLのselect into:を紹介したのですが、もしデータセットがSQLのcreate table文で作成されたものであれば、自動マクロ変数の「&SQLOBS」が有効ではないかとコメントをいただきました。

コメント有難うございます!!
(コメントやご意見・ご感想、こういうところを取り上げてほしいとか
こういう処理はどうやっているかなどの相談でもいいので、いつでも募集しています)

その通りだと思います。

data A;
do X=1 to 10;
 output;
end;
run;

今10オブザベーションのデータセット「A」があり
以下のコードでA1を作成した後、&SQLOBSを展開すると
その中には、A1のオブザベーション数が入っています。
特別なコード書いてないのに、勝手に入ってます。
なんと素敵な。

proc sql;
 create table A1 as
  select X
  from A
  where X>3;
quit;

%put &SQLOBS;

それで、ついでに人の褌で今回のネタにさせていただいちゃうのですが
&SQLOBSのいいのは、こいつの性質が「SQLで最後に処理された行数が格納される」という
ちょっと曖昧な感じなところです。

処理されたということは、要は削除したり、更新したりしたすることも含まれるのですね。

ごくごくたまに、何オブザベーションが削除されたのか、また何オブザベーションが条件に合致して更新されたかなどを取得したい時が、ありますが、それがタダで手に入るのであればそんな素晴らしい話はないですよね。

要するに

proc sql;
 delete from A
 where X<5;
quit;

%put &SQLOBS;

なら、4となり。

data B;
do X=1 to 10;
 output;
end;
run;

proc sql;
update B
 set X=X*1.5
 where X<3;
quit;

%put &SQLOBS;

なら、2となるのです。

SQLとSASの機能の合わせ技は、どうしてもこうも心をくすぐるのでしょうか?

放言するならば、
SQLを知らないとSASのデータステッププログラミングの面白さの4割くらい損してるんじゃないかと僕は思います。




%window マクロウインドウが今でも輝きを失っていないという話

マクロウインドウは、マクロで制御することができる、簡易な(しょぼい)インターフェースを作成することができる機能なのですが、ちょとマイナーだったり古いといったイメージがあります。

よくEXCELをインターフェースにしてVBAでSASを動かす簡易なアプリケーションを作成したり、HTMLとJavascriptとSASを組み合わせた簡易ツールの話を目にしますが、しかし

複雑な機能を持っていたり、見栄えを綺麗にする上ではそれらが活きてくると思いますが、例えば1つ2つユーザーがパラメータを入力して、後はSASが走るだけといったシステムなら今でもマクロウインドウは全然活きてくると思います。
(DMでつかうツールで症例を絞ってロジカルやコンペアツール動かしたり程度のツールなど)

例えば、以下のようなデータがあったとします。

data DS1;
ID='T001';SEX='M';VAL=100;output;
ID='T002';SEX='M';VAL=110;output;
ID='T003';SEX='F';VAL=120;output;
ID='T004';SEX='F';VAL=130;output;
run;


それで以下のプログラムがあったとして

proc print data=DS1;
 where SEX="&WH";
run;

&WHに入る「M」「F」などの条件をユーザーに入力させるツールを作成するとします。

%let WH=M;

%window selectselect
#1 @1 "抽出条件の設定"

#3 @8 "男性のデータが欲しい場合はM"
#4 @8 "女性のデータが欲しい場合はF"
#6  @15 WH 20 attr=underline
#8 @8 "入力後にEnterを1回おしてください"
;

%display selectselect;

proc print data=DS1;
 where SEX="&WH";
run;

SASを開いて
このコードを、実行すると












という画面が表示され、デフォルトでMと表示されています。
これは%letでマクロ変数WHにMという値を代入しているからです。

ここはユーザーが消して、Fに変えることもできます。

抽出したい値を入力してエンターキーをクリックすると
その値でマクロ変数WHが更新され、以降のプログラムが実行されます。


%window
でウインドウを定義します
(#が表示される行、@がカラム位置、attrで書式など属性を付与できます)

%displayで定義したウインドウを表示できます。

実行後endsas;等で自動で閉じるようにしておけば、このSASファイルはもう
立派なツールじゃないでしょうか?

詳しくはマクロ言語:リファレンスを参照してください。
簡易なインターフェースと書きましたが、コードを細かく書けば
かなりしっかりしたものも作れるみたいですよ?




マクロ変数の削除

SASマクロ、あまり得意じゃないのですが、ユーザー定義のマクロ変数の削除についてです。

data A;
X=1;output;
X=2;output;
run;

proc sql noprint;
 select count(*) into:obs1
  from A;
quit;

今、値2の入ったマクロ変数obs1が作成されました。


%SYMDEL obs1;

消えました。

めでたしめでたしで終わりなのですが、

例えば、いっぱいマクロ変数を作成していて、それを一括で消したい場合
いちいち%SYMDELで指定するの面倒ですね、というかデータ内容に応じて可変的に
作成している場合、どれだけ作られるかわかりませんしね。

で、SAS社の古いヘルプだと

data symdel;
  set sashelp.vmacro;
  where scope = 'GLOBAL' ;
run;

data _null_;
  set symdel;
  call symdel(name);
run;

としているのです。
sashelp.には現在のSASセッションで最新の情報がテーブルビューとして格納されていて
SQLやデータステップで利用することで、存在する変数・ライブラリ・データセットなどなどの情報を
取得できるのですが
今回はvmacroというテーブルから、存在するマクロ変数の情報を取得します。
scopeという変数に、GLOBALまたはAUTOMATICといった値が入るのですが
GLOBALで絞ることで、自動マクロ変数以外を全部けしてやろうということですね
call symdelルーチンでマクロ変数を消せます。

ただ、いつのバージョンからか調べてないのですが
SQLを使うと
SYS_SQL_IP_ALL SYS_SQL_IP_STMTというマクロ変数が作成され、
カテゴリとしてはGLOBALなのに消そうとすると消せないのでエラーになってしまいます。

ので、

data symdel;
  set sashelp.vmacro;
  where scope = 'GLOBAL' and NAME not in ('SYS_SQL_IP_ALL','SYS_SQL_IP_STMT');
run;

data _null_;
  set symdel;
  call symdel(name);
run;

としてやればOKです。



SASデータセットのオブザベーション数をマクロ変数に格納する方法_call symput とsql select into: ①単純な1マクロ変数の作成

SASデータセットのオブザベーション数をマクロ変数で取得したいことはよくあります。

data A;
X=1;output;
X=2;output;
run;

という2オブザベーションのデータセットがあったとします。

【解法1】

data _NULL_;
 set A end=eof;
  if eof then call symputx('obs1',_N_);
run;

call symputx ルーチンでSASの自動変数_N_をobs1というマクロ変数に入れています。
_N_は数値型ですがsymputxは余計な空白を含まない文字型に変換してから
格納してくれるのでログに型変換のNoteも出力されません。

end=をつけてifステートメントをかけているのは
これをつけないと、1オブザベーション読み込むたびにcall symputx ルーチンで
マクロ変数を更新してしまうからです。欲しいのは最後のオブザベーションの_N_だけなので
余計な関数処理にかかる時間を短縮するためこのように書いています。

さて、このcall symputxを利用する方法の問題点は、オブザベーション数が0、つまり空であった場合にマクロ変数が作成されないという点です。
後のコードで、作成されたマクロ変数を参照している場合、条件分岐していないと、マクロ変数が存在しないためエラーになる恐れがあります。

また下のように、サブセット化IFステートメントとcall symputルーチンの併用も危険です。

data _NULL_;
 set A end=eof;
  if X<=1;
  if eof then call symputx('obs1',_N_);

run;


【解法2】

proc sql noprint;
 select count(*) into:obs2
  from A;
quit;

SQLの場合、count関数はデータセットが空の場合も0を返してくるので
マクロ変数は必ず作成されます。
select ○○ into : マクロ変数名で、マクロ変数に値を代入します。
ただし、クエリが返すオブザベーション数には注意してください。

また、条件を加える場合も

proc sql noprint;
 select count(*) into:obs2
  from A
  where X<=1;
quit;

こんな感じで、自然にかけば、それが正解です。

SQLでマクロ変数を作成する方法は、まだ奥が深くて
いろいろできるのですが、今回はここまで。

%put に_all_,_automatic_,_user_などを指定してみる

%put ステートメントはログ等にマクロ変数を展開して出力するステートメントです。マクロ変数に値を代入するプログラムを書いたあと、ちゃんと想定した値が入っているか確認するときなどにも使います。
ただ一気にマクロ変数を大量作成するプログラムの場合、いちいち%putでマクロ変数名を指定するのはめんどうです。

そこで%put _user_;とすると、ユーザーが作成したマクロ変数とそこに入っている値が全部ざっとでてきます。

また%put _automatic_;とするとユーザーが作成していないマクロ変数、ようするに自動マクロ変数が全部出てきます。SASはバージョンによって、自動マクロ変数の種類の豊富さが結構違うので、あとマクロ変数ここで使えたっけ?という場合や、現在日時が格納されている自動マクロ変数ってなんだっけって時に結構重宝します。

%put _all_で上の二つを合わせた全部がでます。

またユーザー定義マクロ変数のなかでも
%put _global_ または%put _local_でグローバル変数またはローカル変数だけだせるので、
マクロ変数のスコープでわけわからんくなったときは一度全部だして値を確認してみるといいかもしれません。