2017年12月25日月曜日

SASでクリスマスカードを作る。



ぎりぎり間に合った・・・。
SASでクリスマスカード作ってみました。やっつけ仕事みたいにすごいシンプルですが。



























以下がプログラムです。
SAS9.4で動作します。あとHTML出力がONになっている前提です。

*** 描画用データ作成 *********;
* ツリー部分 ;
data DT1;
input X Y;
cards;
0 30
50 30
50 50
10 50
35 80
20 80
40 100
30 100
45 120
40 120
60 150
80 120
75 120
90 100
80 100
100 80
85 80
110 50
70 50
70 30
125 30
210 30
;
run;

* 星部分 ;
data DT2;
  call streaminit(201712);
  do X2=0 to 210 by 10;
    STAR = rand('uniform')*180;
    output;
  end;
run;

data DT3;
  set DT1 DT2;
run;


*** 描画 ******************;
proc sgplot data=DT3 noautolegend ;
   styleattrs wallcolor=black;
   series x=X y=Y / lineattrs=(thickness=7 color=white);
   scatter x=X2 y=STAR / markerattrs=(symbol=star size=0.6cm color=yellow);
   xaxis min=0 max=210;
   yaxis min=0 max=180;
   inset "Happy Holidays" / position=right textattrs=(color=white size=30cm);
run;



今年はあと1回記事書くか、書かないかって感じですが、あとちょっとで2018年ですね。
お疲れさまでした。

ブログに立ち寄っていただいたり、直接アイディアを頂いたり、みなさん有難う御座いました。
良い年末年始をお過ごしください!!

2017年12月15日金曜日

PROC DS2 の MATRIXパッケージ による行列計算


以前、行列計算をFCMPプロシジャで実現する方法を紹介しました。
FCMPプロシジャと行列計算


同様に、PROC DS2 の MATRIXパッケージというものを使って行列計算を行う事も出来ます。


簡単な例として、以下の連立方程式をMATRIXパッケージによる行列計算で求めてみたいと思います。








行列で解を求める式はこんな感じでしょうか。










proc ds2;
    data OUT1 / overwrite=yes;

    /* 配列を定義 */
    dcl double m1  [3,3];
    dcl double m2  [3,1];
    vararray double out [3,1];

    /* matrixを定義 */
    dcl package matrix _m1;
    dcl package matrix _m2;
    dcl package matrix _inv;
    dcl package matrix _mult;

    method run();

        /* 計算対象の行列をいったん配列に格納 */
        m1 := (1 1 1 2 4 3 5 3 2);
        m2 := (6 19 17);

        /* 配列を行列にSET */
        _m1   = _new_ matrix( m1, 3, 3 );
        _m2   = _new_ matrix( m2, 3, 1 );

        /* 逆行列 と 行列の積 を求める */
        _inv  = _m1.inverse();
        _mult = _inv.mult(_m2);

        /* 結果を配列に格納 */
        _mult.out(out);

    end;

    run;
quit;

データセットOUT1
 out1  out2  out3 
 1 2 3

 x=1, y=2, z=3 という結果が得られました。


いちいち配列をかませないといけないのがちょっと面倒。

2017年12月12日火曜日

HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法


(コピペ利用するための、ほとんど個人的なメモ)





* Sample Data ;
data DT1;
input NO:$3. SEX:$1. AGE:8.;
cards;
001 F 39
002 M 25
;
 NO・・・顧客ID
 SEX・・・性別
 AGE・・・年齢


data DT2;
input NO:$3. FOOD:$10. PURDATE:yymmdd10.;
format PURDATE yymmdd10.;
cards;
001 ORANGE  2017/12/11
001 APPLE   2017/12/13
001 CHERRY  2017/12/10
002 ORANGE  2017/12/15
002 APPLE   2017/12/05
;
 NO・・・顧客ID
 FOOD・・・購入食品
 PURDATE・・・購入日




上のデータから、顧客ID毎に購入日の最小値を取得したいとします。

data OUT;

    set DT1;

    /* 購入データをHashオブジェクトに格納 */
    if _n_=1 then do;

        length PURDATE 8.;
        call missing( PURDATE );

        dcl hash purhash( dataset:"DT2", multidata:"y" );
        purhash.definekey("NO");
        purhash.definedata("PURDATE");
        purhash.definedone();

    end;


    /* Hashオブジェクト内をループして、顧客IDごとの購入日の最小値を取得 */
     rc = purhash.find();
     if rc = 0 then MINDATE = PURDATE;

     do while (rc = 0);
          rc = purhash.find_next();
          if rc = 0 then MINDATE = min( MINDATE, PURDATE );
     end;

     format MINDATE yymmdd10.;
     keep NO SEX AGE MINDATE;
run;




解説

まずHashの触りは以下記事を読むと理解しやすいです。

http://sas-boubi.blogspot.jp/2015/02/hash.html
(SAS忘備録:Hashオブジェクトを使おう)




かいつまんで解説

  dcl hash purhash( dataset:"DT2", multidata:"y" );
  purhash.definekey("NO");
  purhash.definedata("PURDATE");
  purhash.definedone();

まずはHashオブジェクトの定義から。
データセットDT2をHashオブジェクトに格納し、「multidata:'y'」で、Key値の重複を許しています。
変数NOをKey値、変数PURDATEをKey値に関連づける変数として定義しています。



  rc = purhash.find();
  if rc = 0 then MINDATE = PURDATE;

findメソッドでHashオブジェクト内をKey値で検索。
該当するKey値があったら、取得したPURDATEをMINDATEに格納。



  do while (rc = 0);
    rc = purhash.find_next();
    if rc = 0 then MINDATE = min( MINDATE, PURDATE );
  end;

find_nextメソッドで、Hashオブジェクト内の次のレコードを検索。
該当するKey値があったら、取得したPURDATEをMINDATEと比較し、最小日付をMINDATEに格納。

これをHashオブジェクト内に該当するレコードがなくなるまで、do whileでループしています。




一応今回やってることは、SQLやMEANSとかの方が楽に求められるけど、DATAステップ内で完結できるのが最大のメリットだと思います。


📝注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。



2017年11月30日木曜日

【SAS QUIZ】第1問:関数を無効にする。




たいした答え用意してるわけじゃないし、SASマスターにとっては簡単なクイズかも。



問題

以下「???」にプログラムを追加することによって、SUM関数の結果をERRORにさせて、データステップを中止させて下さい。

data TEST;
   ???
   Y = sum(1,2,3);
run;








回答例










私の思いつく正解としては、「ARRAYを使う」です。
以下をご覧ください。

data TEST;
   array sum(1) _temporary_;
   Y = sum(1,2,3);
run;

ログ
NOTE: 配列sumの名前がSAS提供またはユーザー定義関数の名前と同一です。この名前の後ろのかっこは、関数の参照ではなく、
      配列の参照として処理されます。

ERROR: 配列sumに指定した添字の数が多すぎます。
NOTE: エラーが発生したため、このステップの処理を中止しました。



(ARRAYの構文は例なので適当です、やりたい事が出来ていればOK)

ログに出てるメッセージの通り、sumという配列を定義しちゃって、sum関数を認識できないようにしちゃってます。


他に方法ってあるのかな?
思いついた方いたら、コメントお願いします。


2017年11月20日月曜日

共有マクロを作る【MSTORED, SASMSTORE編】



以前、共有マクロについて、以下の記事を書きました。
共有マクロをつくって、プログラミングを効率的にする1


上の記事では、「sasautos=」を使ってマクロプログラムを呼び出す方法を紹介しました。
今回は、「sasmstore=」を使ってコンパイル済みのマクロを呼び出す方法を紹介します。





コンパイルしたマクロを保存する構文

options mstored sasmstore=コンパイルしたマクロを格納するライブラリ;

%macro マクロ名 / store;
  マクロの中身;
%mend;


store」をつけたマクロは、「sasmstore=」に指定したライブラリに保存されます。






libname mymac "C:\MyMacro";
options mstored sasmstore=mymac;

%macro test1 / store;
  data aa;
  run;
%mend;

%macro test2 / store;
  data bb;
  run;
%mend;

C:\MyMacro の中に、コンパイルしたマクロ test1, test2 を格納しています。
(マクロの中身は適当)



ではここで、一旦SASを再起動します。
普通はSASを閉じるとマクロも消えてしまいますが、上のように保存したマクロは mstored・sasmstore=オプションを指定するだけで、また使えるようになります。

libname mymac "C:\MyMacro";
options mstored sasmstore=mymac;

%test1;




注意①


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

libname mymac2 "C:\MyMacro";
options mstored sasmstore=mymac2;

%macro test1;
   %put ----- macro (1) ----;
%mend;

%macro test1 / store;
   %put ----- macro (2) ----;
%mend;

%test1;

ログ
----- macro (1) ----

同名のマクロ test1 を2つ作っています。
1つ目はライブラリ work に、2つ目はライブラリ mymac2 に作っています。

この状況下で「%test1;」でマクロを呼び出すと、work のほうのマクロが実行されてしまいました。
内部でマクロを検索する順番が決まっていて、今回は work のほうが優先されてしまったわけですね。
他にもSAS側で用意しているマクロや、ユーザー側で設定した自動呼出しマクロとかの名前と被るようなマクロを作ってしまうと、優先的に実行されるマクロがどれなのか分からん!ってなってしまうので、ご注意ください。



注意②


注意①のプログラムを実行した後、以下を実行したらERRORが出てしまいました。

libname mymac2 "C:\MyMacro";

ログ
ERROR: ライブラリMYMAC2は使用中のため、クリアまたは再割り当てはできません。 ERROR: LIBNAMEステートメントのエラーです。

これは注意①で「sasmstore=」に指定したライブラリ mymac2 内のコンパイル済マクロカタログが開いた状態になっているため。


これを以下で閉じる事が出来ます。

%sysmstoreclear;


ただし、閉じるだけで「sasmstore=」の設定が消されるわけではありません。
引き続き「sasmstore=」に指定したライブラリにマクロを保存したり、呼び出すことが可能です(保存・呼び出し時にまたマクロカタログが開く)



2017年11月16日木曜日

【小ネタ】INDEX関数 vs FIND関数




以下のプログラムをご覧ください。

  Y1 = index( trimn(X), "SAS");
  Y2 = find( trimn(X), "SAS" );


どちらも、変数X に "SAS" という文字が何バイト目にあるかを取得しています。
(なんでTRIMN関数も使ってるの?という疑問は、最後に注意点として解説しています。ちなみに今回の例ではTRIMN関数なしでも問題なし)



この2つの関数の違いですが、FIND関数は、INDEX関数の進化版というイメージです。
(FIND関数には第3引数と第4引数があり、これらの引数を使うとちょっと便利)


たとえば、
変数X から大文字・小文字区別せずに "SAS" という文字が何バイト目にあるかを取得したいとします。
この場合、FIND関数では、第3引数に "i" を指定すると大文字・小文字区別せずに検索してくれます。

  Y2 = find( trimn(X), "SAS", "i" );



INDEX関数が使われがちだけど、FIND関数の方もレパートリーに加えてあげて下さい。
(「INDEX」は5文字タイピングしないといけないけど、「FIND」は4文字で済んで、ちょっとだけ楽だし)



注意点

  • 日本語などのマルチバイト文字を含む場合は、以下記事の通りうまく動作しないのでご注意ください
  • 以下で解説している通り、場合によって「TRIMN関数」を併用する必要があります。
    • 文字値の末尾にある空白問題
    • 上記リンク記事で解説している通り、変数値の長さがLENGTHより短い場合、末尾に半角スペースが入るので、想定外の結果を返す場合があります。

2017年11月14日火曜日

【変数属性の変更】ALTER TABLE vs PROC DATASETS



(※今回は「ALTER TABLE」が主役なので、PROC DATASETS の説明はあんまりしてません)




PROC DATASETS を使うと、オブザベーションを読み込むことなく直接データセットの変数属性を変えることができますが、PROC SQL の「ALTER TABLE」を使っても同様のことができます。



構文
PROC SQL;
    ALTER TABLE データセット名
    MODIFY  変数属性を変更する文
    ADD       追加する変数と属性を指定
    DROP     削除する変数を指定
    ;
QUIT;

📝 注意
文字変数を元の LENGTH より短く変更してしまい文字切れしてしまったり、変数を誤って削除してしまったり、という事があり得るので取扱い注意。適宜バックアップをとっておく等ご対応下さい。




* Sample Data ;
data DT1;
   V1 = 1;
   V2 = 2;
   V3 = 3;
run;

* ALTER TABLE ;
proc sql;
   alter table DT1

   modify  V1 label="aaa" format=yymmdd10.  ,
               V2 label="bbb"

   add      V4  char(10)  label="xxx" ,
              V5  num        label="yyy"  format=yymmdd10.

   drop    V4, V5;
quit;



一応、ADDでやっていることを少し補足しておきます。

   add      V4  char(10)  label="xxx" ,
              V5  num        label="yyy"  format=yymmdd10.

・V4 という文字変数を長さ10で、変数ラベルを"xxx"で追加
・V5 という数値変数で、変数ラベルを"yyy"、formatをyymmdd10で追加




ALTER TABLE でしか出来ない事
「変数追加」や「変数削除」は ALTER TABLE だけ。PROC DATASETS は出来ない(はず)

proc sql;
   alter table DT1
   add  V4 char(10)
   drop V4;
quit;

また、LENGTH の変更も ALTER TABLE でしか出来ません。



PROC DATASETS でしか出来ない事
「変数名の変更」は PROC DATASETS だけ。PROC SQL の ALTER TABLE は出来ない(はず)

proc datasets lib=WORK nolist;
   modify DT1;
   rename V2=V10;
run;
quit;


ちなみに、どちらの方法も変数の型(数値・文字)を変更することはできないのであしからず。