2017年12月25日月曜日

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



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



























以下がプログラムです。SAS9.4で動作します。

*** 描画用データ作成 *********;
* ツリー部分 ;
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;
  set 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. BUYDATE:yymmdd10.;
format BUYDATE 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・・・購入食品
BUYDATE・・・購入日



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

data OUT;

    set DT1;

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

        length BUYDATE 8.;
        call missing( BUYDATE );

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

    end;


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

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

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





解説

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

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



今回やってる事は、データステップ100万回で解説されてる事なので、以下記事を見て頂くと分かると思います。

http://sas-tumesas.blogspot.jp/2014/07/key-multidata-findnext.html
(データステップ100万回:ハッシュオブジェクトの世界⑧ keyの重複を許容する multidata find_nextメソッド)





かいつまんで解説

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

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



  rc = buy.find();
  if rc = 0 then MINDATE = BUYDATE;

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



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

find_nextメソッドで、Hashオブジェクト内の次のレコードを検索。
該当するKey値があったら、取得したBUYDATEを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;



2017年11月16日木曜日

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




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

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

どちらも、変数X に "SAS" という文字が何バイト目にあるかを取得しています。



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


たとえば、
変数X から大文字・小文字区別せずに "SAS" という文字が何バイト目にあるかを取得したいとします。
この場合、それぞれ以下のような書き方が出来ます。

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

  • INDEX関数では、UPCASE関数で大文字にしてやる必要があります。
  • FIND関数では、第3引数に "i" を指定すると大文字・小文字区別せずに検索してくれるので、非常にシンプルな文になります。



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



注意点
日本語などのマルチバイト文字を含む場合は、以下記事の通りうまく動作しないのでご注意ください。
関数の落とし穴


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;




* 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;

PROC DATASETS よりシンプルな文になる事があるので、個人的好みはこっちですね。



一応、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;



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

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


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



得手・不得手がそれぞれありますが、ALTER TABLE でオブザベーションを読み込まずに変数削除できるってのは魅力的です。

是非プログラムのレパートリーに加えてあげて下さい。


2017年11月10日金曜日

【小ネタ】インデックスを使ってSORTプロシジャを省略する


以下をご覧ください。

data CLASS;
  set SASHELP.CLASS;
run;

proc sort data=CLASS;
  by AGE;
run;

proc transpose data=CLASS out=TRA;
  var NAME;
  by AGE;
run;

プログラム自体は適当に書いたやつですが、
変数AGEごとにTRANSPOSEをするために、予めSORTプロシジャでAGE順に並び替えています。




このSORTが煩わしくて、よく以下のように直前のデータステップでついでにインデックスを定義しておいて、SORTプロシジャを省略させてます。

data CLASS (index=(AGE));
  set SASHELP.CLASS;
run;

proc transpose data=CLASS out=TRA;
  var NAME;
  by AGE;
run;

インデックスとして定義した変数は、事前に並び替えせずにBY変数として使える性質を利用しています。



ただし、SORTプロシジャ使って並び替えたデータの方が、その後の処理では内部的に処理効率がよい場合もある点は気に留めておいてください。

2017年11月8日水曜日

DATAステップビューの作成


前回「SQLビューの作成と落とし穴①」を書いたので、似たような機能のDATAステップビューに関しても触れておきます。



以下をご覧ください

data DSVIEW / view=DSVIEW;
   set SASHELP.CLASS;
   keep NAME AGE;
run;

proc print data=DSVIEW noobs;
run;



上で作成した DSVIEW は一見データセットに見えますが、「DATAステップビュー」というものです。
「DATAステップビュー」には、データセットではなく、コンパイルしたプログラムが保存されます。


構文

   DATA  ビュー名 / VIEW = ビュー名;
      ~ プログラム ~
   RUN;



ビューの定義を見たい場合は以下のように書きます。

data view=DSVIEW;
   describe;
run;

ログ
NOTE: DATAステップビューWORK.DSVIEWの定義:

data DSVIEW / view=DSVIEW;
   set SASHELP.CLASS;
   keep NAME AGE;
run;


このビューを開いたり、参照すると、コンパイルしたプログラムが実行されて結果のデータが返ってくる仕組みになっています。




DATAステップビューのメリット


たとえば、以下のようにデータセットDT1から変数Cを作って、その変数にMEANSをかけたいとします。

data STAT1;
   set DT1;
   C = A + B;
run;

proc means data=STAT1;
   var C;
run;


もしDT1の中身が定期的に更新される場合、更新される度に上のプログラムを実行する必要がありますよね。


ですが、以下のようにDT1を参照して変数Cを作るDATAステップビューを1度だけ実行・保存しておけば、、

data MYLIB.STAT1 / view=MYLIB.STAT1;
   set DT1;
   C = A + B;
run;


DT1が更新されても、以下のようにDATAステップビューを参照するMEANSを実行するだけで済みます。

proc means data=MYLIB.STAT1;
   var C;
run;

(ビューを参照するだけで、内部で最新のDT1に対してビューに保存されたデータステップのプログラムが実行され、その結果に対してMEANSが実行されるため。)



またメリットとして、ビューの中身は定義なのでファイルサイズは小さいです。
ただし、プログラム中に何回も同じ中身のビューを参照するような状況では、参照するたびに内部でデータステップのプログラムが実行される事になるので、処理効率が悪くなります。



データステップ100万回でSQLビューとDATAステップビューについて、あと注意点についても書かれてるので、合わせてご参照下さい。

SASのテーブルビュー(view)について

ビューの生成コードを出力するdescribeについて、SQLビューとデータステップビューだと方法が違うから気を付けてって話


2017年11月6日月曜日

SQLビューの作成と落とし穴②



SQLビューの作成と落とし穴①」 の続きです。



以下をご覧ください。

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

libname TEST "C:\Myfiles";

proc sql;
   create view TEST.PITFALL as
   select *
   from DT1;
quit;

TEST.PITFALL というSQLビューを作成しています。特にエラーも出ていません。
続いて、このSQLビューをPROC PRINTで出力してみます。


proc print data=TEST.PITFALL;
run;

ログ
ERROR: ファイルTEST.DT1.DATAは存在しません。
ERROR:  直接的または間接的に参照されたデータセット、ビューの 1 つがオープンできなかったため、SQL ビュー TEST.PITFALL を処理できませんでした


ERRORが出てしまいました。
実はSQLビューの性質として、

FROM句のデータセットは、デフォルトでSQLビューと同じライブラリを参照しにいきます。

つまり、SQLビューがTESTライブラリにあるので、「from DT1」は「from TEST.DT1」と置き換えて実行されます。



今回の例では、FROM句に WORK.DT1 を適用したいので、以下のように明示的に指定する必要があります。

proc sql;
  create view TEST.PITFALL as
  select *
  from WORK.DT1;
quit;




他の落とし穴についても別記事で書きました。
SQLビューは同じ変数名が複数あってもエラーにならない

2017年11月2日木曜日

SQLビューの作成と落とし穴①


まずはSQLビューについて説明して、次の記事で落とし穴を紹介(記事の最後にリンク貼ってます)。
落とし穴については、ミスを起こしやすい仕様があるので必ず参照下さい!


以下をご覧ください

proc sql;
   create view MYVIEW as
   select NAME, AGE
   from SASHELP.CLASS;
quit;

proc print data=MYVIEW noobs;
run;


SQLプロシジャの CREATE VIEW で作成した MYVIEW は一見データセットに見えますが、「SQLビュー」というものです。
「SQLビュー」には、データセットではなく、定義したSELECT文が保存されます。



構文

   PROC SQL;
          CREATE VIEW ビュー名 AS  SELECT文
   QUIT;





ビューの定義を見たい場合は DESCRIBE VIEW を使います。

proc sql;
   describe view MYVIEW;
quit;

ログ
NOTE: SQLビュー WORK.MYVIEW は次のように定義されています :

        select NAME, AGE
          from SASHELP.CLASS

ビューを開いたり、参照すると、定義したSELECT文が実行されて結果のデータが返ってくる仕組みになっています。




ちなみにビューを削除したい場合は DROP VIEW を使います。

proc sql;
   drop view MYVIEW;
quit;





SQLビューのメリット


便利なのが
たとえば、以下のようにデータセット MYLIB.DT1 を参照している MYLIB.VW というビューを作ったとします。

proc sql;
   create view MYLIB.VW as
   select A+B as C
   from MYLIB.DT1;
quit;



そしてこのビューを使って翌日FREQを実行したいとします。

proc freq data=MYLIB.VW;
   tables C;
run;



しかし翌日、ビューが参照している MLIB.DT1 に値の変更が発生したとします。
この場合、再度ビューを作り直す必要はありません。

なぜなら、以下のように

proc freq data=MYLIB.VW;
   tables C;
run;


ビューを参照するだけで、内部で最新の MLIB.DT1 に対してSELECT文が実行され、その結果に対してFREQが実行されるからです。



またメリットとして、ビューの中身は定義なのでファイルサイズは小さいです。
ただし、プログラム中に何回も同じ中身のビューを参照するような状況では、参照するたびに内部でSELECT文が実行される事になるので、処理効率が悪くなります。



分かり辛い上に長くなりましたが、データステップ100万回の方が分かり易く解説されてるので、こちらもご覧ください。
SASのテーブルビュー(view)について


次回は「落とし穴編

2017年10月31日火曜日

現在のオプションの設定値を確認する【PROC OPTIONS】



OPTIONSプロシジャをつかうと、特定のオプションの

・設定方法
・現在の設定値

をログに表示することが出来ます。


構文

   PROC OPTIONS   OPTION=確認したいオプション名   表示内容;
   RUN;


表示内容は以下に示す通り、DEFINE, LONG, SHORT などがあります。



DEFINE … オプションの詳細な説明と現在の設定値を表示
proc options option=msglevel define;
run;




LONG … オプションの簡単な説明と現在の設定値を表示
proc options option=msglevel long;
run;



SHORT … オプションの現在の設定値を表示
proc options option=msglevel short;
run;



カッコで囲んで複数のオプションを指定することもできます。
proc options option=(msglevel center) long;
run;





2017年10月27日金曜日

テキストファイル(CSVなど)への出力【EXPORTプロシジャ編】



よくある落とし穴なので先に、、

出力対象のデータセットにセル内改行があると、出力したテキストファイルにも改行が入って、おかしな出力結果になってしまうので注意




構文

 PROC EXPORT
        DATA                 =      出力データセット
        OUTFILE           =      "作成するテキストファイル"
        DBMS                =      CSV | TAB | DLM   /* ファイル種類 */
        REPLACE                  /* 既存のテキストファイルを上書く */
        LABEL                        /*  ヘッダーに変数名ではなく変数ラベルを適用 */
        ;
        DELIMITER       =      "区切り文字";   /*  DBMS=DLMの場合に指定 */
 RUN ;


解説

① DBMS=オプション
  指定  設定内容
  CSV    カンマ区切り 
  TAB    タブ区切り
  DLM   自分で 「DELIMITER=オプション」 に区切り文字を指定 



② その他の注意点
変数にフォーマットが割り当てられている場合、フォーマット変換した値がテキストファイルに出力されます。




例 ・・・ CSVファイルへ出力

proc export
   data=SASHELP.CLASS
   outfile="C:\TEST\TEST.csv"
   dbms=dlm
   replace;
   delimiter=",";
run;




2017年10月26日木曜日

変数属性を定義した空のデータセットを作成する方法【まとめ】



思いつく方法をざっとあげてみました。
(他にも方法ありそうなので、知ってたら教えてください)



例として以下の変数と属性をもつ0オブザベーションのデータセットを作成してみます。




① DataStep
data DT1;
  attrib
     VAR1  length=8.     label="Variable1"  format=yymmdd10.
     VAR2  length=$20. label="Variable2"
  ;
  stop;
run;



② PROC SQL
proc sql;
   create table DT2 (
      VAR1  num        "Variable1"  format=yymmdd10. ,
      VAR2  char(20)  "Variable2"
   );
quit;



③ PROC DS2 (SAS9.4~)
proc ds2;
   data DT3 (overwrite=yes);
       dcl  double     VAR1  having  label 'Variable1'  format yymmdd10. ;
       dcl  char(20)  VAR2  having  label 'Variable2';
       method run();
          stop;
       end;
   enddata;
   run;
quit;




④ PROC LUA (SAS9.4~)
proc lua;
submit;
   local dsid = sas.open( "DT4", "o" )
   sas.add_vars( dsid, {{name="VAR1", type="N", label="Variable1", format="yymmdd10."},
                                    {name="VAR2", type="C",  label="Variable2", length="20"}} )
   sas.close( dsid )
endsubmit;
run;

2017年10月24日火曜日

【小ネタ】%SYSFUNC(SLEEP)




割とどうでもいいような話しを小ネタシリーズとして書いていこうと思います。



以下はSASを3秒間待機させるプログラム

data _null_;
   rc=sleep(3, 1);
   put rc "秒間待機しました";
run;



よく上のような書き方を見かけますが、これって%SYSFUNC使えば1文ですっきりとした文になっていいんじゃないでしょうか。

%put %sysfunc(sleep(3, 1)) 秒間待機しました;




でも data _null_ を使う書き方も、様式美として好きなので、好みの問題ですね。



2017年10月18日水曜日

外部ファイルをいっぺんに読み込んで連結する【FILEVAR=】



まえに、以下の記事を書きました。
外部ファイルをいっぺんに読み込んで連結する。


今回は別のやり方(INFILEステートメントの FILEVAR=オプション を使った方法)を紹介します。
まず以下のCSVファイルがあったとします。


C:\sample\TEST1.csv
001,a,11
002,b,22

C:\sample\TEST2.csv
003,c,33
004,d,44



手順1
まずは読み込みたいテキストファイルのフルパスをデータセットに格納します。

data DT1 ;
   length VAR $500. ;
   VAR="C:\sample\test1.csv"; output;
   VAR="C:\sample\test2.csv"; output;
run;
VAR 
 C:\sample\test1.csv 
 C:\sample\test2.csv



手順2
上記のテキストファイルのフルパス情報を使って、テキストファイルを読み込んでいきます。

data OUT1;

  *--- テキストファイルのフルパスが格納されたデータセットをSET ---;
  set DT1;

  *--- フルパスが格納された変数VARを filevar= に指定 ---;
  *--- end=で各テキストファイルの終端を検知する一時変数を作成 ---;
  infile dummy filevar=VAR end=EOF dsd truncover;

  *--- テキストファイルの終端を検知するまでループ ---;
  length A B $10. C 8.;

  do while (not EOF);
     input A B C;
     output;
  end;

run;

 B  
 C  
 001 
 a
 11
 002
 b 
 22
 003
 c
 33
 004
 d
 44


📝注意点

上の例で「do while (not 一時変数名)」の部分を「do until (一時変数名)」と書くと正しく読み込めない場合があります
(読み込む外部ファイルのいずれかが空だと、そこでデータステップが終了してしまい、以降の外部ファイルが読み込まれないという落とし穴がある)


📝その他ポイント

ちなみに、この記事の先頭にも貼ったリンク

の方法だと、読み込むファイルのヘッダー部分を「firstobsオプション」でスキップさせたい場合に、うまく動かないのですが、今回の記事の方法だとうまくスキップ出来ます。



2017年10月12日木曜日

「input &」で空白を含むテキストを1変数に読み込む



「INPUTステートメントで、半角スペース区切りで値を読み込む場合」の小技です。
以下の例をご覧ください。


data DT1;

length  JUICE $20.  YEN 8.;
input    JUICE &       YEN;

cards;
APPLE JUICE  120
ORANGE JUICE  130
;
run;



変数 JUICE の値が「APPLE JUICE」と「ORANGE JUICE」という感じで半角スペース(空白)を含んで読み込まれています。


ポイント

・INPUTステートメントで、変数の後に「&」を入れると、テキストに2つ連続する半角スペースが出てくるまでテキストを1つの変数に読込みます。
この機能は「&」を入れた変数のみに効力があります。


上のプログラムでは分かりづらいですが、
CARDSの中の「APPLE JUICE」と「120」の間には2つ半角スペースが入ってます。
同様に「ORANGE JUICE」と「130」の間にも2つ半角スペースが入ってます。



ちょっと前に紹介した以下の記事の方法もあわせて、状況によって使い分けましょう。
INPUTステートメントで、空白を含むテキストを1変数に読み込む方法


2017年10月10日火曜日

【SAS入門】算術演算子



SASで四則演算や累乗を計算するときの、算術演算子について簡単にまとめておきます。




算術演算子

  演算      演算子    
  加算   + 
  減算   - 
  乗算    *
  除算   /
  累乗    **

間違えやすいのが、累乗の場合。
EXCELだと「^」が累乗の意味になりますが、SASだと「**」です。





data DT1;
   A = 4;
   B = 2;

   C = A + B;    /* 加算 */
   D = A - B;     /* 減算 */
   E = A * B;    /* 乗算 */
   F = A / B;     /* 除算 */
   G = A ** B;   /* 累乗 */
run;





2017年10月5日木曜日

【CDISC】変数 [--DTC] から、ADTM, ADT, ATM への変換



CDISC関連の話。


変数「--DTC」を、ADaM変数「ADTM」「ADT」「ATM」に変換するときの、ISO8601のフォーマットと書き方を載せときます。




[--DTC] が日付のみ (時刻を含まない) の場合


※以下の例は「2017」「2017-10」等の、日付が揃っていないデータは想定していません

data DT1;

    LBDTC = "2017-10-03";

    ADT    =  input( LBDTC, e8601da. );

run;





[--DTC] が日時の場合


※以下は想定していません。
・日時が揃っていないデータ(「2017-10-03」「2017-10-03T09」等)
・秒の小数部分が含まれているデータ(「2017-10-03T09:10:12.345」等)

※以下リンクの記事に紹介してるバグにも注意。
【SAS9.3以前のBug】E8601DTインフォーマットで正しく変換できないことがある。


data DT1;

    LBDTC = "2017-10-03T09:10:00";

    ADTM =  input( LBDTC, e8601dt. );
    ADT    =  input( LBDTC, e8601da. );
    ATM    =  timepart( input( LBDTC, e8601dt. ) );

run;

・ATMだけいったん日時変換してから、TIMEPART関数で時刻部分をとりだしています。




以上、自分用のメモ記事でした。

2017年10月3日火曜日

【PROC REPORT】1つの項目に複数のCALL DEFINEを定義したい




「CALL DEFINEの重ね着って出来ないんですか?」って質問をたまにいただきます。


どういう事かと言うと、以下の例をご覧ください。

proc report data=SASHELP.CLASS nowd;
    column NAME AGE ;
    define NAME / display;
    define AGE / display;
    compute AGE;
       if AGE > 13 then call define( "name", "style", "style=[color=blue]" );
       if AGE > 14 then call define( "name", "style", "style=[background=yellow]" );
    endcomp;
run;

CALL DEFINEで、

① AGE>13の時、変数NAMEの値を青色にする
② AGE>14の時、変数NAMEの背景色を黄色にする

という事をしてます。

よくみると、Janetさんは値を青にした上で、背景色を黄色にしたかったんですが、青くなってません。


CALL DEFINEで定義したSTYLEが、1つのセルに対して1つしか適用されていないようです。
STYLEの適用順は状況によって異なりますが、今回は「①値を青色」→「②背景色を黄色」の順で適用されているようですね。



そこで、小技。
以下のように「STYLE/MERGE」とすれば、複数のCALL DEFINEによるSTYLEを、上書きじゃなくてMERGE(結合)してくれます。

proc report data=SASHELP.CLASS nowd;
    column NAME AGE ;
    define NAME / display;
    define AGE / display;
    compute AGE;
       if AGE > 13 then call define( "name", "style", "style=[color=blue]" );
       if AGE > 14 then call define( "name", "style/merge", "style=[background=yellow]" );
    endcomp;
run;

ちゃんとJanetさんが青くなってますね。


ちなみに、
本題からズレますが、CALL DEFINEを書くために利用しているCOMPUTEステートメントは高頻度でうまく設定できないケースを見かけるので、以下記事と以下記事内にリンクしている記事も要確認です。




📝その他、混乱しそうなポイント

以下みたいに一方が「_ROW_」だと、「STYLE/MERGE」としなくても1つのセルに複数のSTYLEが設定できました。

proc report data=SASHELP.CLASS nowd;
    column NAME AGE ;
    define NAME / display;
    define AGE / display;
    compute AGE;
       if AGE = 14 then call define( "name", "style", "style=[color=blue]" );
       if AGE = 14 then call define( _row_, "style", "style=[background=yellow]" );
    endcomp;
run;


ただ、以下の場合ちょっと混乱しそう。

proc report data=SASHELP.CLASS nowd;
    column NAME AGE ;
    define NAME / display;
    define AGE / display;
    compute AGE;
       if AGE = 14 then call define( "name", "style", "style=[color=blue]" );
       if AGE = 14 then call define( _row_, "style", "style=[color=red]" );
    endcomp;
run;


最後「_ROW_」で行の全変数の文字色を赤にしたはずなのに、NAMEの文字色が青になってる?
プログラムの記述順で、STYLEが適用されると思いがちですが、違うっぽい。
2番目の以下「_ROW_」のSTYLEが先に実行されて、、

 if AGE = 14 then call define( _row_, "style", "style=[color=red]" );


次に1番目の以下STYLEが適用されたような挙動です。

 if AGE = 14 then call define( "name", "style", "style=[color=blue]" );



本題から逸れましたが、こんな感じで動きが独特なのでご注意ください。


2017年9月28日木曜日

「%IF条件」で「IN」を使う。(その2)




以下の記事をご覧ください。
「%IF条件」で「IN」を使う。


上の記事では、システムオプション「minoperator」 と 「mindelimiter」を指定すると、
「%IF条件」で「IN」が使えるようになると紹介しました。




もうひとつ別の書き方として、以下のようにも書けます。

%let MVAR1=1;

%macro TEST  /  minoperator mindelimiter=',' ;
      %if  &MVAR1 in (1,2)  %then %put NOTE: マクロ変数 MVAR1には 1 か 2 が設定されています;
%mend;

%TEST;


  • マクロ定義に直接このオプションを指定すると、そのマクロでのみオプションが有効になります
    • システムオプション「minoperator」「mindelimiter」の設定よりも優先される
  • minoperator」 でINが使えるようになります
    • 「IN」の代わりに「#」でもOK( %if &MVAR1 # (1,2) %then ... )
  • mindelimiter」 には値を区切る時の区切り文字(シングルバイト1文字)を指定
    • % & ' " ( ) ;」といったマクロ等で意味を持ちそうな区切り文字は設定できません
    • 区切り文字はシングルクォーテーションで囲む必要がある(ダブルクォーテーションではダメ)


📝注意



2017年9月25日月曜日

【PROC REPORT】RTF出力時の細かい罫線の設定



PROC REPORTでRTF出力する際に、セル毎の細かい罫線(色・太さ・線種)を設定する方法を紹介します。



まず罫線の設定を表すスタイル属性は以下の3つです。



罫線のスタイル属性

  設定内容  指定   指定できる値
 罫線の色 

 BorderxxColor = 
  
 black, white …など 

 罫線の幅
(太さ)

 BorderxxWidth =
 

 1pt, 2pt …など


 罫線の線種  

 BorderxxStyle =

 dashed, dotted, double, hidden, solid …など 


xx部分にはセルのどの位置の罫線を設定するか指定
「Right」「Left」「Top」「Bottom」が入る




例① 特定の列に罫線を引く


工程1
まずは以下の記事で紹介している方法でざっくりとレポート全体の罫線の引き方を変更します。

(PROC TEMPLATEで全体の罫線やフォントをあらかじめ設定しといてもOK)

ods rtf file='出力するRTFファイルのフルパスを指定';

proc report data=sashelp.class nowd
           style(report)=[rules=groups frame=hsides]
           style(header)=[background=white];
   column name sex age ;
   define name / display;
   define sex / display;
   define age / display;
 run;

ods rtf close;


工程2
次に個別に細かい罫線を引いていきます。
たとえば、変数NAME の右に縦罫線を入れたいとします。

以下の記事も参考に罫線を入れてみます
REPORTプロシジャ入門8:書式の設定【STYLE=】

ods rtf file='出力するRTFファイルのフルパスを指定';

proc report data=sashelp.class nowd
           style(report)=[rules=groups frame=hsides]
           style(header)=[background=white];

   column name sex age ;
   define name / display style(header column)={BorderRightColor=black
                                                                BorderRightStyle=double
                                                                BorderRightWidth=1pt};
   define sex / display;
   define age / display;
run;

ods rtf close;




例② 特定の行に罫線を引く


以下の記事を参考に「AGE > 13」の行だけセルの下に罫線を引いてみます。
REPORTプロシジャ入門9:特定セルの書式設定【CALL DEFINE】

(例で使用しているCOMPUTEステートメントは、高頻度でうまく設定できないケースを見かけるので、上記記事と上記記事内にリンクしている記事も要確認)

ods rtf file='出力するRTFファイルのフルパスを指定';

proc report data=sashelp.class nowd
           style(report)=[rules=groups frame=hsides]
           style(header)=[background=white];

   column name sex age ;
   define name / display;
   define sex / display;
   define age / display;
   compute age;
        if age > 13 then
                call define(_row_,"style","style(calldef)={BorderBottomColor=black
                                                                                BorderBottomStyle=solid
                                                                                BorderBottomWidth=1pt}");
   endcomp;
run;

ods rtf close;




例③ 2段以上のヘッダーの罫線

以下をご覧ください。

ods rtf file='出力するRTFファイルのフルパスを指定';

proc report data=sashelp.class nowd
           style(report)=[rules=groups frame=hsides]
           style(header)=[background=white];
   column name ("Height and Weight" height weight);
   define name / display;
   define height / display;
   define weight / display;
run;

ods rtf close;


上のレポートで、ヘッダーが2段になっている「Height and Weight」の下に罫線を引きたいとします。


以下記事を参考に罫線を入れてみます。
ODS出力時に文字の書式設定をする【インラインフォーマット】

ods rtf file='出力するRTFファイルのフルパスを指定';

proc report data=sashelp.class nowd
           style(report)=[rules=groups frame=hsides]
           style(header)=[background=white];

    column name ("(*ESC*)S={BorderBottomColor=black
                                             BorderBottomStyle=solid
                                             BorderBottomWidth=1pt}Height and Weight"  height weight);
   define name / display;
   define height / display;
   define weight / display;
run;

ods rtf close;