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とほぼ同じ


凡例の設定


2021年5月15日土曜日

LAG関数の動き



LAG関数は「nオブザベーション前の値を取得したい」という場合に役立ちます。

ただし、よくある落とし穴もあるので、最後まで(リンクページも含めて)読んで仕組みを理解したほうが安全です。


簡単な例で見ていきましょう。

*** サンプルデータ作成 ;
data DT1;
input A;
cards;
20
30
10
40
;

data OUT1;
   set DT1;
   B = lag(A);
   C = lag2(A);
   D = lag3(A);
run;

 A 
B
C
D
  20  
  .   
  .   
  .   
  30
  20
  .   
  .   
  10
  30
  20
  .
  40
  10  
  30  
  20  



何が起こっているのか、イメージで説明したいと思います。

(自分なりの解釈になっている可能性があるかもしれませんので、あしからず・・)


今回のサンプルプログラムの「C = LAG2( A )」の時の処理のイメージです。LAG関数によって内部では「キュー」と呼ばれる仕組みを利用しています。


  • 上の例は、ラーメン屋にできた行列みたい。
    • ラーメン屋の行列=「キュー」
    • 行列に並ぶ人=「変数A」
    • ラーメン食べれた人=「変数C」
    • 最初のn杯は店員の味見用に作ったラーメンでお客さんには出さないやつ。
    • 最後に並んだ何名かはスープ切れで食べれなかった、、みたいな

  • 「LAG1」の場合だけ「LAG」と書いてもOK



2021年4月4日日曜日

SASで囲碁盤を再現。



ステイホーム中に囲碁を勉強し始めました。

ルールやっと覚えたところで、コンピュータの一番弱い設定のやつに勝ったり負けたりのレベルですが。。強くなって碁盤の上で繰り広げられる熱い戦いを理解できるようになっていきたいです。


てことで、SASで囲碁を再現してみたくなったので勢いで書いてみました。プログラムは勢いで適当に書いたので、ひどい感じです。


/*** 碁石の位置 (y軸は碁盤に合わせて逆に指定[上から1,2,3...]) *******/
data pos;
input black_x black_y_reverse white_x white_y_reverse;
cards;
2 2 8 2
2 8 3 8
3 7 . .
;

/*** 碁盤の線 *********/
data line;
 do line_x=1 to 9;
 do line_y_reverse=1, 9;
   line_grp=10+line_x;
   output;
 end;
 end;
 do line_y_reverse=1 to 9;
 do line_x=1, 9;
   line_grp=20+line_y_reverse;
   output;
 end;
 end;
run;

data igo;
 set line pos;
run;

/*** PLOT *******************/
proc template ;
 define statgraph tmpl_igo ;
   begingraph;

     layout overlay /
        wallcolor=CXFFC864 /* 碁盤の色 */
        aspectratio=1
        xaxisopts=( display=none displaysecondary=(line ticks tickvalues)
                    linearopts=( viewmin=1 viewmax=9
                                 tickvaluesequence=(start=1 end=9 increment=1) ) )
                                  
        yaxisopts=( reverse=true /* y軸の数値の順番を逆 [上から下] にする) */
                    display=(line ticks tickvalues)
                    linearopts=( viewmin=1 viewmax=9
                                 tickvaluesequence=(start=1 end=9 increment=1)
                                 tickdisplaylist=( "一" "二" "三" "四" "五" "六" "七" "八" "九") ) );

        /* 碁盤の線 */
        seriesplot x=line_x y=line_y_reverse / group=line_grp lineattrs=(color=black pattern=solid);
        
        /* 碁石(黒)を配置 */
        scatterplot x=black_x y=black_y_reverse /
                       filledoutlinedmarkers=true
                       markerfillattrs=(color=black) 
                       markeroutlineattrs=(color=black thickness=1)
                       markerattrs=(symbol=circlefilled size=40 )
                       dataskin=matte;

        /* 碁石(白)を配置 */
        scatterplot x=white_x y=white_y_reverse /
                       filledoutlinedmarkers=true
                       markerfillattrs=(color=white) 
                       markeroutlineattrs=(color=gray thickness=1)
                       markerattrs=(symbol=circlefilled size=40 )
                       dataskin=matte;
        
     endlayout;

   endgraph;
 end;
run;

proc sgrender data=igo template=tmpl_igo;
run;