2024年6月2日日曜日

【PROC FORMAT】数値フォーマットの落とし穴



FORMATプロシジャの「FUZZ=オプション」のデフォルト設定が悪さをすることがあります。
まずは「FUZZ=オプション」の説明を交えて紹介していきます。



落とし穴①

proc format;
value x (fuzz=1)
3 - 5  = "aaa"
;
run;

data test;
 format x x.;
 do x=1 to 7;
  output;
 end;
run;


「3~5」の値を  "aaa" と表示するフォーマットですが、「fuzz=1」というオプションも指定しています。
この「FUZZ=」に指定した値を誤差として「- ~ +」して範囲を拡張することが出来ます。
今回だと「3~5」に「-1 ~ +1」した範囲なので、「2~6」の範囲を "aaa" と表示するフォーマットになります。


では次。ヘンテコな結果になります。

proc format;
value x (fuzz=1)
3 <-< 5  = "aaa"
;
run;

data test;
 format x x.;
 do x=1 to 7;
  output;
 end;
run;


「3より大きく、5より小さい」値に対して "aaa" と表示するフォーマットですが「fuzz=1」によっておかしな結果になります。
FORMATプロシジャで範囲の指定に使う「<」は「大なり、小なり」の意味ではなく「除外」の意味合いがあるようですね。
つまり今回の例では「3<-<5」と「fuzz=1」の組み合わせによって、「2~6」の範囲で「3」と「5」を除く値に "aaa" と表示するフォーマットになっているようです。



📝 まとめ
「FUZZ=」と「<」の組み合わせは危険。




落とし穴②


まず、前提知識として「浮動小数点誤差」の理解から。
以下、2オブザベーションとも値が「0.1」になるはずなのに、、

data test;
 x=0.1; output;
 x=0.3-0.2; output;
run;

data test2;
 set test;
 if x=0.1 then y=1;
run;


「if x=0.1 then y=1」の結果、2オブザベーション目の値が何故か「0.1」ではない事が分かります。
これが有名な浮動小数点誤差ってやつで、2オブザベーション目は「0.09999999...」みたいな値が格納されてしまっているわけですね。


データステップ100万回や私の過去記事でも紹介済です。


そしてここから本題。
FORMATを定義した時の浮動小数点誤差の取り扱いを見てみます。

proc format;
 value x
 0.1 = "0.1です"
 0.2 = "0.2です"
 0.3 = "0.3です"
 ;
run;

data test;
 format x x.;
 x=0.1; output;
 x=0.3-0.2; output;
run;


あれ?浮動小数点誤差のデータも「0.1」としてフォーマットをあてられてる??
実は、VALUEまたはPICTUREフォーマット、かつ数値フォーマット(1 = "aaa"のような左辺が数値のフォーマット)では「fuzz=1E-12」という非常に小さな値がデフォルトで設定されています。
浮動小数点誤差で「0.09999999...」みたいに格納されてる値が「0.1」のプラスマイナス「1E-12」の中に入っているため「0.1です」と表示することが出来たわけです。

いい感じじゃん!と思いきや、非常にヘンテコな挙動をする場合があります。



例1

「0.1以上、0.2未満」の範囲のFORMATを作って、それを今回の浮動小数点誤差が起きているデータに割り当てると、、

proc format;
 value x
 0.1 -< 0.2 = "0.1以上、0.2未満"
 ;
run;

data test;
 format x x.;
 x=0.1; output;
 x=0.3-0.2; output;
run;

これは想定通りですね。フォーマットの範囲「0.1 -< 0.2」と「fuzz=1E-12」の組み合わせによって浮動小数点誤差で「0.09999999...」みたいに格納されてる値も「0.1以上、0.2未満」として表示することが出来ました。



例2

例1のフォーマットに「0 -< 0.1」という範囲を追加してみます。

proc format;
 value x
 0 -< 0.1 = "0以上、0.1未満"
 0.1 -< 0.2 = "0.1以上、0.2未満"
 ;
run;

data test;
 format x x.;
 x=0.1; output;
 x=0.3-0.2; output;
run;

あれ?今度は「0以上、0.1未満」の値としてフォーマットがあてられてる??
これは今回フォーマットに追加した「0 -< 0.1」による範囲に、浮動小数点誤差が起きている「0.09999999...」が入ってしまうため。先にこのフォーマットが適用されてしまったというわけですね。



例3

今度は、例2のフォーマットに「0.2 -< 0.3」という範囲を追加してみます。

proc format;
 value x
 0 -< 0.1 = "0以上、0.1未満"
 0.1 -< 0.2 = "0.1以上、0.2未満"
 0.2 -< 0.3 = "0.2以上、0.3未満"
 ;
run;

data test;
 format x x.;
 x=0.1; output;
 x=0.3-0.2; output;
run;

あれ?また「0.1以上、0.2未満」の値としてフォーマットあてられてる??
実は変数値がどのフォーマットの範囲に入っているか内部で検索する際、特殊な順番で検索が行われています。
今回は1個目の「0 -< 0.1」ではなく、2個目の「0.1 -< 0.2」の方が先に検索されたためです。



📝 まとめ
範囲を指定するフォーマットは「FUZZ=」の挙動に注意!
「fuzz=0」とすれば範囲の拡張がされなくなりますが、浮動小数点誤差が考慮されなくなってしまうので、そこのケアが必要になります。
解決策は時と場合によって異なるので、適宜ご留意下さい。



2023年5月7日日曜日

「PROC REPORT」の「COMPUTE」がうまく動かないんだけど②



以下記事の続き。



失敗例1

data test;
input x y;
cards;
. 10
. 20
1 30
1 40
2 50
2 60
;
proc report data=test missing spanrows;
  column x y z;
  define x / order order=internal;
  define y / display;
  define z / computed;
  compute z / character length=1;
    if x=. then z="Y";
  endcomp;
run;
😕


  • まず、XをORDER変数にしています。またORDER変数が欠損値のオブザベーションも出力対象とするため、MISSINGオプションを指定。
  • 次にCOMPUTEで「if x=. then z="Y"」として、Xが欠損値のオブザベーションに"Y"のフラグを立てたんですが、、想定では1、2オブザベーション目だけに"Y"が入るはずなのに、4、6オブザベーション目にも"Y"が入っている?!


XをORDER変数にしたことが影響してると思うんですが、リファレンスにこの辺の挙動の説明見つけられませんでした。

ORDER変数(GROUP、ANALYSIS、ACROSS変数とかも?)を参照した処理(IFステートメントや関数など)は想定通りの結果にならないことがあるので、使用は避けたほうが良さそうです。




失敗例2

proc report data=test missing spanrows;
  column x y;
  define x / order order=internal;
  define y / display;
  compute x;
    if x=. then x=2;
  endcomp;
run;
😕


「if x=. then x=2」として、「Xが欠損値」の場合は「X=2」のオブザベーションとして以下のように一塊で出力したかったのに、、


あと、失敗例①を知っちゃうと、なんとなく以下のようになるのでは?と思っちゃいません?(失敗例①で「X=.」と判定されたオブザベーションが、今回「X=2」になると思ってしまった)



ORDER変数(GROUP、ANALYSIS、ACROSS変数とかも?)に対して、COMPUTEで値をいじると、うまく絡み合ってくれなくて、想定外の結果になる場合があるのでご注意ください。

そもそも元の変数値をPROC REPORT上でいじるのって、あまりやらない、というか良くないのかも?




あと未検証ながら


以下リファレンス内の「Restriction」の記載によると、、

よく分からないですが、LAG, DIF, RANUNI, DATETIMEなどの「non-deterministic関数」については、特定条件のときに使わない方が良いらしいです。


COMPUTE、難しいよーー!



2023年5月4日木曜日

PROC SQLでの変数ラベルの挙動


あまり知られていない&需要があるのか分からん機能。
まず例から。

data test;
 label x="AA#AA" y="#BB#BB";
 x=1;
 y=2;
run;

proc sql;
 select * from test;
quit;




PROC SQL限定で働く機能で、
ラベルの先頭をアンダーライン「_」以外の半角記号にすると、その文字をその変数ラベルの改行用文字にできるようです。

📝ポイント
  • ラベルだけで、データ部分には影響しない
  • PROC SQLで見たときに改行されてるように見えているだけで、実際のデータセットの変数ラベルに改行が入るわけではない。
  • 日本語等のマルチバイト文字は改行用文字に設定出来ないと思いますが、リファレンスに書かれていないため不明。

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変数」で、グループ毎の最初と最後のオブザベーションを特定する。


2023年1月1日日曜日

SASで年賀状2023

 



SASで年賀状をつくりました(ほぼ「SASで年賀状」の焼き直し)
2023年もどうぞよろしくお願いいたします!





年賀状をつくるプログラム (SAS9.4M7で動作確認)

*** 描画用のパーツ ;
data rabbit;
  x1= 0;     y1= 0;
  x2= 0;     y2=-6.5;
  x3=-0.31;  y3= 1.2;
  x4= 0.31;  y4= 1.2;
  x5= 0;     y5= 0.15;
  x6= 0;     y6= 0.7;
  x7=-0.5;   y7= 5;
  x8= 0.5;   y8= 5;
  x9=-0.5;   y9= 5.5;
  x10=0.5;   y10=5.5;
  x11=-0.8;  y11=0.1;
  x12=0.8;   y12=0.1;
run;

*** 描画 ;
title ;
ods graphics on / height=10cm width=14.8cm;

proc sgplot data=rabbit noautolegend;
 styleattrs wallcolor=khaki;
   
 /* Text */
 inset "HAPPY NEW YEAR"  /  position=top textattrs=(color=brown size=31cm);
 inset "20"  /  position=bottomleft textattrs=(color=brown size=140cm );
 inset "23"  /  position=bottomright textattrs=(color=brown size=140cm );
   
 /* rabbit */
 scatter x=x1 y=y1  /  markerattrs=(symbol=circlefilled  size=3cm color=white);
 scatter x=x2 y=y2  /  markerattrs=(symbol=circlefilled  size=4cm color=white);
 scatter x=x3 y=Y3  /  markerattrs=(symbol=circlefilled  size=0.2cm color=black);
 scatter x=x4 y=Y4  /  markerattrs=(symbol=circlefilled  size=0.2cm color=black);
 scatter x=x5 y=y5  /  markerattrs=(symbol=ArrowDown  size=0.5cm color=black);
 scatter x=x6 y=y6  /  markerattrs=(symbol=TriangleDownFilled  size=0.2cm color=black);
 scatter x=x7 y=y7  /  markerattrs=(symbol=CircleFilled  size=1cm color=white);
 scatter x=x8 y=y8  /  markerattrs=(symbol=CircleFilled  size=1cm color=white);
 scatter x=x9 y=y9  /  markerattrs=(symbol=CircleFilled  size=1cm color=white);
 scatter x=x10 y=y10  /  markerattrs=(symbol=CircleFilled  size=1cm color=white);
 scatter x=x7 y=y7  /  markerattrs=(symbol=CircleFilled  size=0.7cm color=mistyrose);
 scatter x=x8 y=y8  /  markerattrs=(symbol=CircleFilled  size=0.7cm color=mistyrose);
 scatter x=x9 y=y9  /  markerattrs=(symbol=CircleFilled  size=0.7cm color=mistyrose);
 scatter x=x10 y=y10  /  markerattrs=(symbol=CircleFilled  size=0.7cm color=mistyrose);
 scatter x=x11 y=y11  /  markerattrs=(symbol=CircleFilled  size=0.4cm color=pink);
 scatter x=x12 y=y12  /  markerattrs=(symbol=CircleFilled  size=0.4cm color=pink);

 xaxis min=-3 max=3  display=none;
 yaxis min=-5 max=1  display=none;
run;


2022年11月29日火曜日

SQLプロシジャのINTO句の注意点


SQLのINTO句を使用する際の注意点を2つ紹介したいと思います。

 

1. 変数にフォーマットが割り当てられている場合

data DT1;
  format X yymmdd10.;
  X=1;
run;

proc sql noprint;
  select X into :X trimmed
  from DT1;
quit;
%put &X;

ログ
1960-01-02

マクロ変数にはフォーマットをあてた値が格納されるようです。この挙動、SASのリファレンスに書いてない気がする。。


もし、フォーマット値ではなく、変数値をマクロ変数に格納したい場合、
事前にフォーマットを除いておくか、もしくは数値変数ならデータに応じた適切な長さのBESTフォーマットを「FORMAT=」で便宜的にあてるなどの対応が必要になります。

proc sql noprint;
  select X format=best16. into :X trimmed
  from DT1;
quit;
%put &X;

ログ
1


2. 数値変数かつフォーマットが割り当てられていない場合

data DT2;
  X=123.456789;
run;

proc sql noprint;
  select X into :X trimmed
  from DT2;
quit;
%put &X;

ログ
123.4568

デフォルトの挙動として「BEST8.」の出力形式で格納されるようです。

状況によっては、もとの数値がだいぶ削られちゃいますね(これはINTO句に限ったことではないですが。。)


2022年7月9日土曜日

【PROC FORMAT】CNTLIN=オプションの落とし穴



まず、前提知識として「PROC FORMAT入門9 : CNTLIN=オプション 」をご参照下さい。


そして、ここから本題。以下の例をご覧ください。


失敗例

①まず「データセット」から「フォーマット」を生成

data FMT;
input FMTNAME:$10. START:$10. LABEL:$20.;
cards;
$TEST_ OTHER その他
;


proc format cntlin=FMT;
run;


②実データに先ほどのフォーマットを当ててみる。

data TEST;
input X:$20.;
cards;
OTHER
.
;


data TEST2;
 set TEST;
 format X $TEST_.;
run;


あれ?!

フォーマット当てた結果、2行目の欠損値に「その他」が当てられてしまってる?!


解説

「CNTLIN=」に使用するフォーマット生成用のデータセットに注目


実は上の "OTHER" が、範囲指定としての「OTHER」として解釈されてしまってます(範囲指定?という方は「PROC FORMAT入門3 : 範囲の指定」を参照)

要は、以下のようにFORMATプロシジャで①みたいな定義をしたつもりが、②の定義をしてしまった、ということ。

proc format; /* ① */
 value $TEST_
 "OTHER" = "その他"
 ;
run;

proc format; /* ② */
 value $TEST_
 OTHER = "その他"
 ;
run;

余計なことをしてますね。。


ちなみに、"OTHER" 以外にも、FORMATプロシジャの予約語的なキーワードは気を付けた方が良いです(色々実験してみたところ、"LOW", "HIGH" なども同様に範囲指定の意味として解釈されてしまいました。たぶん他にもあると思います)



解決策

今回の例では "OTHER" を範囲指定の意味ではないと明示してあげる必要があります。
なので、以下のようにHLOという変数を作ってNullに設定しておけば良いはず。
(HLO?という方は「PROC FORMAT入門9 : CNTLIN=オプション」を参照)

data FMT;
input FMTNAME:$10. START:$10. LABEL:$20. HLO:$1.;
cards;
$TEST_ OTHER その他 .
;


proc format cntlin=FMT;
run;



上でフォーマットを作り直したので、再度このフォーマットをあてたデータセットを見てみましょう。

はい!さっきまで2行目の欠損値が「その他」になっちゃってましたが、今度は問題なさそうです。


最後に

私が把握しているのは "OTHER", "LOW", "HIGH" のみで、その他にFORMATプロシジャの予約語的なキーワードに引っかかるものがあるかもしれません。

今回の場合だと"OTHER"に対してHLOという変数を作ってNullに設定することで解決しましたが、その他の私が把握していないキーワードに対して、各自SASのリファレンスや「PROC FORMAT入門9 : CNTLIN=オプション」などを参考にして、他の必要な変数を設定してあげる必要があります。



2022年6月29日水曜日

SASで花火を描く




暑中お見舞い申し上げます。




SASで花火描いてみました。三角関数をつかってるだけの単純なプログラムです。

/* 花火描画用データ */
data fireworks;
 rad = constant('pi')/180;
 call streaminit(777);
 do i = 1 to 5;
  xpos = rand("uniform",-30,30);
  ypos = rand("uniform",-30,30);
  do rmax = 0, 36;
  do r = 0 to rmax by 4;
  do a = 0 to 360 by 15;
    x=cos(a*rad)*r+xpos;
    y=sin(a*rad)*r+ypos;
    output;
  end;
  end;
  end;
 end;
run;

/* 花火描画 */
title;
ods graphics / height=20cm width=20cm;

options animation=start animduration=1.2 printerpath=gif;
ods noresults;
ods printer file="出力するGIFファイルのフルパスをここに指定。ファイル名は「fireworks.gif」とか適当に" ;

%macro m_fireworks(i=,color=);
 proc sgplot data=fireworks aspect=1 noautolegend;
  styleattrs wallcolor=midnightblue;
  scatter x=x y=y / markerattrs=(symbol=circlefilled color=&color size=0.1cm );
  xaxis min=-50 max=50 display=none;
  yaxis min=-50 max=50 display=none;
  inset "Summer Greetings to All."/ position=bottom textattrs=(color=lightblue size=25cm family="Arial");
  by rmax;
  where i=&i;
 run;
%mend;
%m_fireworks(i=1,color=yellow);
%m_fireworks(i=2,color=skyblue);
%m_fireworks(i=3,color=lightgreen);
%m_fireworks(i=4,color=yellow);
%m_fireworks(i=5,color=lightpink);

ods printer close;
ods results;
options animation=stop;

ods graphics / reset=all;


2022年3月31日木曜日

「PUT "WARNING-"」みたいな書き方。



今回は、以下で紹介した記事のおまけ的内容。


最初に

上の記事のおさらい。

以下サンプルのように「NOTE」「WARNING」「ERROR」のいずれかのキーワードとコロン「:」をつけてPUTすると、ログ上に色つきの独自メッセージを表示できる。

data _null_;
  put "NOTE:    独自メッセージ1";
  put "WARNING: 独自メッセージ2";
  put "ERROR:   独自メッセージ3";
run;

ログ


本題 

上の例でコロン(:)ではなくハイフン(-)に変えると、、

data _null_;
  put "NOTE-    独自メッセージ1";
  put "WARNING- 独自メッセージ2";
  put "ERROR-   独自メッセージ3";
run;

ログ

「NOTE」「WARNING」「ERROR」のキーワード無しで色つきのメッセージを出力できます。


活用例

これは、以下のような使い方が出来ます。

data _null_;
  put "WARNING: 独自メッセージ1";
  put "WARNING- 独自メッセージ1の続き";
run;

ログ

WARNINGメッセージを改行しているように見せることが出来ます。


ただし、1つだけ残念ポイント。

%PUTで同様のテクニックを使おうとすると、、

%put WARNING: 独自メッセージ1;
%put WARNING- 独自メッセージ1の続き;

ログ

ログを見てみると、間にプログラムの文が挟まっていて、WARNINGメッセージが分割されて見えてしまうところ。残念よ。


2022年2月11日金曜日

「PUT 変数名=」という書き方


PUTステートメントの基本的なテクニックの紹介。


基本的なテクニック

data test;
  set sashelp.class;
  if age=13 then put height=;
run;

ログ
 height=56.5
 height=65.3
 height=62.5

「変数名=」という書き方で、例のように先頭に「変数名=」という書式のテキストを追加できます。



以下のように、ちょっと勿体ない書き方をしがち。


ちょっと勿体ない例

data test;
  set sashelp.class;
  if age=13 then put "height=" height;
run;

ログ
 height=56.5
 height=65.3
 height=62.5

勿体ない!


2021年12月5日日曜日

「ODS OUTPUT」で変数属性が自動調整される件



「PROC SQL」と「ODS OUTPUT の PERSIST=RUN」のコンボ技 の記事内で


ODS OUTPUTで出力すると変数属性(変数名、length、formatなど)が自動調整される場合がある

と言ったんですが、じゃあ実際どう自動調整されるのか。


結論からいって、

  • 変数属性は自動調整されるけど、データの中身は無事のよう(リファレンスに詳しい挙動が書いてないので、環境やバージョンによって挙動が同じかは不明。。各自で挙動確認してみてください)
  • 私はこのコンボ技を単純な頻度集計で使う程度で支障ないですが、変数属性が自動調整されてしまうのがまずい状況下では、ご注意下さい。




「ODS OUTPUT 」で変数属性がどう自動調整されるのか見てみる。

実行日: 2021/12/03, 実行環境: SAS Ondemand for Academics [SAS 9,4M6])


テストデータ
/*  d1 */
data d1;
  length a b $10. c e 8.;
  label c = "test1";
  format c yymmdd10.;
  a="aaa"; b="bbb"; c=1; e=1;
run;

/*  d2 */
data d2;
  length a $20. b c d e 8.;
  label c = "test2" d="test3";
  format c d time5.;
  a="aaa"; b=1; c=1; d=1; e=1;
run;



「PROC SQL の SELECTステートメント」の結果を「ODS OUTPUT」を使ってデータセットに出力してみます。


例①「データセットd1」を使ってODS OUTPUTで出力
ods select none;
ods output SQL_Results=out1;
proc sql;
  select * from d1;
quit;
ods output close;
ods select all;

各データセットの変数属性
データセットd1
データセットout1 (ODS OUTPUTで出力したやつ)


例②「データセットd2」を使ってODS OUTPUTで出力
ods select none;
ods output SQL_Results=out2;
proc sql;
  select * from d2;
quit;
ods output close;
ods select all;

各データセットの変数属性
データセットd2

データセットout2 (ODS OUTPUTで出力したやつ)


例③「データセットd1とd2」を使って「ODS OUTPUT の PERSIST=」でプロシジャ内の結果を結合して出力
ods select none;
ods output SQL_Results(persist=run)=out3;
proc sql;
  select * from d1;
  select * from d2;
quit;
ods output close;
ods select all;

各データセットの変数属性
データセットout1 (例①で、d1を使ってODS OUTPUTで出力したやつ)
データセットout2 (例②で、d2を使ってODS OUTPUTで出力したやつ)
データセットout3 (例③で、「ODS OUTPUT の PERSIST=」で出力したやつ)



結果の考察


  • formatを割り当てていない数値変数はなぜかbestフォーマットが割り当てられてた(これはどちらかというとSQLプロシジャ側のODS出力時の設定によるものと思われる。このようにODS出力時に勝手にformatが割り当てられる場合がある。割り当てられるformatはプロシジャによって様々)
    • 例①の変数Eなどを参照
  • 「ODS OUTPUT の PERSIST=」で出力結果を結合すると、
    • 同じ変数名(かつ文字変数)でlengthが違う場合 = 大きい方に合わせられた
      • 例③の変数Aなどを参照
    • 同じ変数名で型が違う場合 = 片方の変数がrenameされた。
      • 例③の変数Bなどを参照(結合後、変数Bと変数B2に分かれてる)
    • 同じ変数名でラベルが違う場合 = 最初のデータセットのラベルになった
      • 例③の変数Cなどを参照
    • 同じ変数名でフォーマットが違う場合 = formatが削除された
      • 例③の変数Cなどを参照
    • 片方のデータセットにしか変数がない場合 = 変数がkeepされた。あとformatを割り当てた変数はちゃんとformatが残った。
      • 例③の変数Dなどを参照

他にも私が把握しきれていないだけで「変数属性が自動調整」されるパターンはあると思います。


同じ変数名(かつ文字変数)のlengthが大きい方に合わせられたのと、同じ変数名で型が違う場合に勝手にrenameして両方保持してくれるのは、便利っていえば便利ですね。


2021年11月15日月曜日

「PROC SQL」と「ODS OUTPUT の PERSIST=RUN」のコンボ技



まず今回のコンボ技を理解するには「ODS OUTPUTのPERSIST=RUN」の知識が必要なので、先に以下リンク記事をご覧ください。

ODS OUTPUTで出力結果を結合する②PERSIST=RUN

 

それでは本題。以下のプログラムをご覧ください。

ods select none;
ods output SQL_Results(persist=run)=out1;

proc sql;
 select count(*) as c1 from sashelp.class;
 select count(*) as c1 from sashelp.cars;
 select count(*) as c1 from sashelp.baseball;
quit;

ods output close;
ods select all;


一応、細かく説明しておくと、

  • PROC SQLは対話型プロシジャなので、1つのプロシジャで複数の集計をいっぺんに行ってます。
  • 「ODS OUTPUTのPERSIST=RUN」で対話型プロシジャ内のそれぞれの集計結果について、出力オブジェクトの名前が共通するものを縦結合しています。(今回の場合はすべての集計で、出力オブジェクトの名前が「SQL_Results」という名前で共通している)
  • 「_RUN_」という変数が勝手に作られました。RUNグループのIDと推測されます。SQLの場合は「RUNステートメント」は記述不要なので、実行グループ(セミコロン「;」で終わる文)のIDですね。
  • ちなみにPROC SQLで「NOPRINTオプション」を設定するとODS OUTPUTが使えない(データセットに出力されない)ので注意



ID振って縦結合してくれるのが便利。他の方法でも同じこと出来るけど、こっちのほうが文が短く済む場合があります。


ただし私自身、単純な集計でしか使ったことないので、各自利用する際は結果が希望通りのものになるか、ご確認下さい。

また、ODS OUTPUTで出力すると変数属性(変数名、length、formatなど)が自動調整される場合があるのでご留意ください。(以下リンク記事参照)

「ODS OUTPUT」で変数属性が自動調整される件



2021年11月1日月曜日

ODS OUTPUTで出力結果を結合する②PERSIST=RUN



前回の続きで、今回はODS OUTPUTの「PERSIST=RUN」というオプションを紹介します。


まずは以下の例をご覧ください。

データセット「SASHELP.CLASS」と「SASHELP.BMT」の定義情報をデータセットOUT1に出力しています。特に問題なく出力できています。

ods output Variables = out1;

   proc datasets lib=sashelp;
      contents data=class;
      contents data=bmt;
   run;
   quit;

ods output close;

データセット OUT1



ここから本題。DATASETSプロシジャは対話型プロシジャです(対話型プロシジャについては以下参照)

https://sas-boubi.blogspot.com/2018/01/runquit.html


そして今度は、説明のため、以下の通りあえて対話型プロシジャを意識した書き方に変えて実行してみます。

😟失敗例

ods output Variables = out1;

   proc datasets lib=sashelp;

      /* RUNグループ1: データセットに出力される */
      contents data=class;
      run;

      /* RUNグループ2: データセットに出力されない */
      contents data=bmt;
      run;

   quit;

ods output close;

データセットOUT1

対話型プロシジャの結果のうち、最初のRUNグループの結果しかデータセットに出力されませんでした。




😄解決策1(グループ毎にODS OUTPUTを記述

proc datasets lib=sashelp;

   /* RUNグループ1 */
   ods output Variables = out1;
   contents data=class;
   run;

   /* RUNグループ2 */
   ods output Variables = out2;
   contents data=bmt;
   run;

quit;
ods output close;

データセットOUT1

データセットOUT2

上の例では「RUNグループ」毎にODS OUTPUTを記述して、「RUNグループ」毎にデータセットを出力しています。プロシジャの中にODS OUTPUTを記述しているので、ちょっと違和感を感じるかもしれませんが、ちゃんと動きます。



もうひとつ解決策。これが紹介したかったんですが「PERSIST=RUN」というオプション。

以下のように、対話型プロシジャ内で、出力オブジェクトの名前が一緒であれば(今回の場合は「Variables」)、結合してデータセットに出力することが出来ます。


😄解決策2(PERSIST=RUN

ods output Variables(persist=run) = out1;

   proc datasets lib=sashelp;

      /* RUNグループ1 */
      contents data=class;
      run;

      /* RUNグループ2 */
      contents data=bmt;
      run;

   quit;

ods output close;

データセットOUT1

注意点:
ODS OUTPUT内に対話型プロシジャを複数書いても、出力できるのは最初のプロシジャのみ。




ちなみに、SQLプロシジャも対話型プロシジャですが「RUNグループ」ではなく、以下のように実行グループ(セミコロン「;」で終わる文)毎に実行されます。そこが違うだけでSQLプロシジャについても今回紹介した2つの解決策を適用できます。

ods output SQL_Results(persist=run) = out1;

   proc sql;

      /* 実行グループ1 */
      select count(*) as c1 from sashelp.class;

      /* 実行グループ2 */
      select count(*) as c1 from sashelp.bmt;

   quit;

ods output close;

データセットOUT1

PERSIST=オプションはSQLプロシジャと相性が良いので、今度その辺りも紹介したいと思います。



2021年10月26日火曜日

ODS OUTPUTで出力結果を結合する①PERSIST=PROC



ODS OUTPUTの「PERSIST=オプション」について、2回に分けて詳しく紹介していきます。

まず1回目は「PERSIST=PROC」について。 



以下の失敗例をご覧ください。

ODS OUTPUTの中にプロシジャを複数記述したけど、最初のプロシジャの結果しかデータセットに出力されませんでした。


😟失敗例

ods output Moments = out1;

    /* データセットに出力される */
    proc univariate data=sashelp.class;
        var height;
        where age=13;
    run;

    /* データセットに出力されない */
    proc univariate data=sashelp.class;
        var weight;
        where age=14;
    run;

ods output close;



そこで「PERSIST=PROC」というオプションの出番です。

以下のように、プロシジャ間で出力オブジェクトの名前が一緒であれば(今回の場合は「Moments」)、結合してデータセットに出力することが出来ます。


😄成功例(PERSIST=PROC

ods output Moments (persist=proc) = out1;

    proc univariate data=sashelp.class;
        var height;
        where age=13;
    run;

    proc univariate data=sashelp.class;
        var weight;
        where age=14;
    run;

ods output close;



ただし、以下のようにPERSIST=オプションと一緒に「BYステートメント」が使われていると、WARNINGが出て、正しい結果が得られないようです。


😞注意(BYステートメントは使えないっぽい)

ods output Moments (persist=proc) = out1;

    proc univariate data=sashelp.cars;
        var msrp;
        by make;
    run;

    proc univariate data=sashelp.cars;
        var invoice;
        by make;
    run;

ods output close;

--- LOG --------------
WARNING: ODSでは、データセットWORK.OUT1をRUNグループ間で保持できません。
BY変数が含まれているからです。データセットは第1 RUNグループが終了すると終了します。


To be continued...

https://sas-boubi.blogspot.com/2021/11/ods-outputpersistrun.html


2021年7月22日木曜日

Graph Template Language(GTL)入門:凡例の設定 (DISCRETELEGEND)


GTLで凡例を設定する「DISCRETELEGEND」と「MERGEDLEGEND」を紹介します。



凡例の表示

まずは例を示して、その後構文を紹介したいと思います。



proc template ;
  define statgraph MYTEMP ;
     begingraph;

          layout overlay;
               scatterplot x=HEIGHT y=WEIGHT / group=SEX name="sca" ;
               regressionplot x=HEIGHT y=WEIGHT / group=SEX name="regr" ;
               discretelegend "sca" "regr" / title="凡例";
          endlayout;

     endgraph;
  end;
run;

proc sort data=SASHELP.CLASS out=CLASS;
  by SEX;
run;
proc sgrender data=CLASS template=MYTEMP ;
run;




  • 「SCATTERPLOT」で凡例の参照名 "sca" を定義しておく
  • 「REGRESSIONPLOT」で凡例の参照名 "regr" を定義しておく
  • 「DISCRETELEGEND」で参照名 "sca" と "regr" を指定してこれらの凡例を表示しています
  • それぞれのPLOTステートメントで「GROUP=SEX」としているので、グループ変数をSEXとして凡例が表示されています。

構文

PROC TEMPLATE ;
   DEFINE STATGRAPH テンプレート名;
   BEGINGRAPH ;
         LAYOUTステートメント;

                PLOTステートメント  /  NAME = "参照名";
                DISCRETELEGEND  "参照名"  / オプション;

         ENDLAYOUT;
   ENDGRAPH;
   END;
RUN;

  • PLOTステートメントの「NAME=」で凡例の参照名を設定 (任意の名前でOK)
  • DISCRETELEGENDで参照名を指定して、凡例を表示します。



DISCRETELEGENDのオプション  (随時追加予定)

注意:
他のオプション・ステートメントとの組み合わせで、オプションが無効になることがあるため、トライ&エラーで正しく動くか確認が必要です。

設定内容設定
凡例タイトル
title = "タイトル"
凡例タイトルのボーダー
titleborder = true | false
凡例タイトルの書式
titleattrs = (
         family = 'フォント'
         size = サイズ
         color = 
         weight = bold  /* 太字 */
)
凡例の書式

valueattrs = (
         family = 'フォント'
         size = サイズ
         color = 
         weight = bold  /* 太字 */
)
凡例の書式で文字の大きさを変えたときに
凡例のマーカー等も大きくするか
autoitemsize = true | false
凡例のボーダーborder = true | false
凡例の表示順 (横順か縦順か)order = rowmajor | columnmajor
凡例の縦位置
valign = top | bottom | center
凡例の横位置halign = right | left | center
凡例の背景色backgroundcolor = 
「order = rowmajor」指定時に
水平方向に表示する凡例エントリの数
across = 数
「order = columnmajor」指定時に
垂直方向に表示する凡例エントリの数
down = 




凡例の結合


MERGEDLEGENDステートメントは使える条件が限定されますが、凡例を結合 (融合?) させることが出来ます。


proc template ;
  define statgraph MYTEMP ;
     begingraph;

          layout overlay;
               scatterplot x=HEIGHT y=WEIGHT / group=SEX name="sca" ;
               regressionplot x=HEIGHT y=WEIGHT / group=SEX name="regr" ;
               mergedlegend "sca" "regr" / title="凡例";
          endlayout;

     endgraph;
  end;
run;

proc sort data=SASHELP.CLASS out=CLASS;
  by SEX;
run;
proc sgrender data=CLASS template=MYTEMP ;
run;



「SCATTERPLOT」と「REGRESSIONPLOT」はそれぞれグループ変数をSEXにしてPLOTしていますが、凡例のマーカーと線をグループごとにくっつけてしまおう、ということでMERGEDLEGENDを使っています。


MERGEDLEGENDが使える条件
  • PLOTステートメントに「GROUP=」で、グループ化した値を凡例に表示している場合
  • マーカーと線を結合する場合のみ?に有効らしい
  • MERGEDLEGENDで指定可能なオプションはDISCRETELEGENDとほぼ同じ


凡例の設定