2014年9月29日月曜日

変数名をいっきに指定する方法 [まとめ]


変数名を省略する便利な書き方をまとめます。

*** サンプルデータ ;
data DT1;
   A1 = 1;
   A2 = 1;
   A3 = 1;
   B   = "abc";
   C   = 2;
run;


***  A1~A3まで連番になってる変数 ;
data DT2;
   set DT1;
   keep A1 - A3;    /* 「keep A3 - A1」と逆にしてもok */
run;


*** 変数名がAで始まる変数 ;
data DT3;
   set DT1;
   keep A: ;
run;


*** A3からCまで順番に格納されてる変数 ;
data DT4;
   set DT1;
   keep A3 -- C;
run;


*** A3からCまで順番に格納されてる変数の中での数値変数 ;
data DT5;
   set DT1;
   keep A3 -numeric- C;
run;


*** A3からCまで順番に格納されてる変数の中での文字変数 ;
data DT6;
   set DT1;
   keep A3 -character- C;
run;


*** 数値変数のみ ;
data DT6;
   set DT1;
   keep _numeric_;
run;


*** 文字変数のみ ;
data DT6;
   set DT1;
   keep _character_;
run;


*** 全変数 ;
data DT6;
   set DT1;
   keep _all_;
run;



今回はKEEPステートメントでの例でしたが、さまざまなステートメントで使えます。
気をつけなきゃいけないのは以下のように関数中に指定するケース。

× sum(A1-A3);
○ sum(of A1-A3);

「A1-A3」だと引き算の意味になってしまう!この場合は必ず「of」を先頭につけましょう。


あとは、変数の格納順をコロコロ変えるかもしれないデータセットに対して
A3 -- C」、 「A3 -numeric- C」、 「A3 -character- C」といった省略文を書くのは避けましょう。

(変数の格納順が変わるたびに、この省略文を修正しなきゃいけなくなって、プログラムとして不安定になる)


2014年9月18日木曜日

変数値をマクロ変数に格納する方法「SQL編」



前回の続きで、今度はSQLプロシジャを使った方法を紹介。
(最後に注意点の記事リンクも貼ったので、そちらも要参照)


* サンプルデータ ;
data DT1;
input A$ B$ @@;
cards;
001 AA 002 BB 003 CC
run;

 
B
 001
 AA
 002
 BB 
 003
 CC 


上記変数A,Bの値をマクロ変数に格納します。

変数値をマクロ変数に格納 ;
proc sql noprint;
   select A, B  into :A1-, :B1- 
   from DT1;
quit;

%let OBS = &SQLOBS;

* ログにマクロ変数値を展開 ;
%macro MAC;
   %do i=1 %to &OBS;
       %put  &&A&i  &&B&i;
   %end;
%mend;
%MAC;

ログ
001  AA
002  BB
003  CC


SQLのINTOを使った方法(「こちら」を参照)

ただし「into :A1-, :B1-」という書き方はSAS9.3より前のバージョンでは使えないので
データステップ100万回「SQLでマクロ変数に値を格納する into: separated by」で紹介されている「into :A1-:A9999, :B1-:B9999」と値を大きくとっておくテクニックが使えます。

そしてもうひとつ重要な役割をするのが自動マクロ変数SQLOBS。
直前のSQLプロシジャが処理した行数が自動で入ってきます。(詳細は「こちら」を参照)
これを%DOループで利用してます。



一般的に使われるのはCALL SYMPUTXですが、SQLのINTOと&SQLOBSのコンボ技の方がスッキリとした文になるので個人的に好きな方法ではあります。


📝以下、落とし穴についての記事も要確認!

・数値をマクロ変数に格納すると丸められる可能性がある
・フォーマットが割り当てられた変数だと、フォーマットを当てた値がマクロ変数に格納される。

SQLプロシジャのINTO句で、前方に余計な半角スペースが入る場合がある。
・余計な半角スペースが入ってしまう場合の小技を紹介してます


2014年9月17日水曜日

変数値をマクロ変数に格納する方法「CALL SYMPUTX編」




SASを覚えはじめたという方から、この質問を頂くことが多いので解説したいと思います。
(最後に注意点の記事リンクも貼ったので、そちらも要参照)


* サンプルデータ ;
data DT1;
input A$ B$ @@;
cards;
001 AA 002 BB 003 CC
;

 
B
 001
 AA
 002
 BB 
 003
 CC 



上記の変数A,Bの値をマクロ変数に格納してみましょう。

* 変数値をマクロ変数に格納 ;
data _null_;
    set  DT1  end=_EOF;
    if  _EOF then call symputx("OBS", _N_);
    call symputx( cats("A",_N_) , A );
    call symputx( cats("B",_N_) , B );
run;

* ログにマクロ変数値を展開 ;
%macro MAC;
    %do i=1 %to &OBS;
         %put  &&A&i  &&B&i;
    %end;
%mend;
%MAC;

ログ
001  AA
002  BB
003  CC


上記青字部分の解説

end = 一時変数名 
最後のオブザベーションを読み込んだ時、一時変数に「1」が入る。
一時変数名は任意の名前でOK
call symputx
構文・・・call symputx("マクロ変数名", 格納したい値 )
値をマクロ変数に格納する。
_N_
SASの自動変数で、データステップの反復回数が格納されている。
つまり、読み込んだオブザベーションに対して、1~連番をふったような値が入る。
cats関数
構文・・・cats(値1値2 … )
値の両端の半角スペースを取り除いて結合する。
%doループ
%do マクロ変数名 = 開始値  %to 終了値;
%end;
マクロ変数の値を開始値から終了値まで変化させながら、ループさせる。


処理の流れ

data _null_;
    set  DT1  end=_EOF;
    if  _EOF then call symputx("OBS", _N_);
最後のオブザベーションを読み込んだ時に、_N_の値をマクロ変数OBSに格納している。
(つまりオブザベーション数をマクロ変数に格納)

    call symputx( cats("A",_N_) , A );
    call symputx( cats("B",_N_) , B );
run;
変数Aについて
1オブザベーション目の値をマクロ変数A1に
2オブザベーション目の値をマクロ変数A2に
・・・と次々マクロ変数に格納していく。変数Bも同様。

%macro MAC;
    %do i=1 %to &OBS;
         %put  &&A&i  &&B&i;
    %end;
%mend;
%MAC;
%DOでマクロ変数 i の値を1,2,3…と変化させながら、オブザベーションの数だけループさせる。
%PUTを使って、マクロ変数A1,B1,  A2,B2, ・・・の値をログに展開していく。



📝注意点

「CALL SYMPUTX」で数値をマクロ変数に格納すると丸められる場合があるので注意
今回のテクニックの中で使用している「_N_」と「END=オプション」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。
(解説記事:「サブセット化IFでありがちな落とし穴」)


次回はSQLプロシジャを使った方法を紹介。


2014年9月15日月曜日

共有マクロをつくって、プログラミングを効率的にする2




前回の続き。SASのバージョンによって挙動が変わるかもしれないけど、
SASAUTOSオプションを使った時に、以下のようなエラーが出て困ったなという事はないでしょうか?

ERROR: ファイル参照名 TEST に関連付けたファイルの中に、現在使用されているファイルがあります。
ERROR: FILENAME ステートメントのエラーです。



まず、このエラーを再現してみましょう。


①前回と同様に共有マクロを作る。

C:\SAS_MACROS\mac_SORT.sas
*** SORTマクロ ;
%macro  mac_SORT( DS1, DS2, BYVAR );

    proc sort data=&DS1 out=&DS2;
        by &BYVAR ;
    run;

%mend;


②上で作った共有マクロを使えるようにするのですが、
前回と違って、以下のようにファイル参照名をSASAUTOSオプションに設定します。

filename TEST "C:\SAS_MACROS";
options sasautos = ( SASAUTOS  TEST ) ;

data DT1;
  do A=10 to 1 by -1;
    output;
  end;
run;

%mac_SORT(DT1, OUT1, A);

このプログラムを2回実行してみましょう。
1回目は普通に動くけど、2回目はエラーが出てしまうはずです!



原因


SASAUTOSオプションで使われたファイル参照名は、SASAUTOSオプションに守られるようになります。
よって、そのファイル参照名を消そうとしたり、再定義しようとすると、SASAUTOSオプションに守られてるので、突っぱねられてエラーが出てしまうのです。


今回の例では、1回目の実行で、ファイル参照TESTがSASAUTOSオプションに守られることになる。
よって2回目実行したとき、
filename TEST "C:\SAS_MACROS";
の部分で「ファイル参照TESTを再定義しようとしてるな!」と目をつけられてエラーになるというわけです。

このエラーを手っ取り早く回避するには、ファイル参照名ではなく直接パスを入れてやります。
options sasautos = ( SASAUTOS  "C:\SAS_MACROS" ) ;





2014年9月11日木曜日

共有マクロをつくって、プログラミングを効率的にする1





標準的なマクロを作って、みんなで共有する方法を紹介。




①まず共有するマクロ達を入れるフォルダを作ってやります。
今回は適当に「C:\SAS_MACROS」というフォルダを作ってそこに入れたいとします。




②上で作ったフォルダに、マクロプログラムを入れる。
たとえば以下のようなSORTマクロを作って入れます。

C:\SAS_MACROS\mac_SORT.sas
*** SORTマクロ ;
%macro  mac_SORT( DS1, DS2, BYVAR );

   proc sort data=&DS1 out=&DS2;
       by &BYVAR ;
   run;

%mend;

マクロを作る際のルール
・1つのファイルに作れるマクロは1つだけ。
・ファイル名とマクロ名は同一にする。




③SASAUTOSオプションで、共有マクロを使えるようにする。


 OPTIONS SASAUTOS  = ( SASAUTOS "共有マクロのパス"またはファイル参照名 );

(パスまたはファイル参照名は複数指定可。)


今回の例に当てはめて実行してみると、見事SORTマクロを読み込んで実行してくれるはず。

options sasautos = ( SASAUTOS  "C:\SAS_MACROS" );

data DT1;
  do A=10 to 1 by -1;
    output;
  end;
run;

%mac_SORT(DT1, OUT1, A);




ここで注意点
・上で青字にしたファイル参照SASAUTOSを併記するのを忘れずに。

何故かというと、
SAS起動時は「options sasautos = SASAUTOS;」という設定になってて、
このファイル参照SASAUTOSによって、SASがもともと用意してるマクロ(%TRIMや%LEFTなど)が使えるようになっている。
そのため、オプションを再設定する時もこのファイル参照名をいれとかないと、SASが用意してるマクロ達が使えなくなってしまう。



もうひとつ注意点
もし共有マクロにプログラムを追加・修正した場合、その時起動してるSASは一旦再起動するようにしましょう。
(SAS上に、コンパイルした修正前のマクロが残ってるとうまく動いてくれないので。。)




次回 は更なる注意点を解説。



2014年9月8日月曜日

自動マクロ変数「&SQLOBS」で、SQLが処理した行数を取得する



自動マクロ変数「&SQLOBS」には「SQLプロシジャが処理した行数」が格納される。
以下のようなイメージです。
  • select, from, where, group by, havingによって生成した結果の行数
  • updateによって更新した行数
  • deleteによって削除した行数
  • insertによって追加した行数
  • noprint, view, intoなどを使用している場合は挙動に変化あり。

具体例を見ていきましょう。


* サンプルデータ作成(obs数=10)  ;
data DT1;
   do A=1 to 10;
        output;
   end;
run;

*** 例① **************;
proc sql;
  select A
  from DT1;
quit;

%put --- &SQLOBS ---;
select, from句によって選択された10行が結果ビューアに出力される。
よって、SQLOBSには「10」が入る。
%putステートメントでログにSQLOBSの値を展開してるので、確認してみましょう。


*** ② **************;
proc sql;
  create table DT2 as
  select A
  from DT1;
quit;

%put --- &SQLOBS ---;
select, from句によって選択された10行がデータセットDT2に出力される。
よって、SQLOBSには「10」が入る。


*** ③ **************;
proc sql noprint;
  select A
  from DT1;
quit;

%put --- &SQLOBS ---;
注意が必要。
noprint結果ビューアへの出力を抑制。またcreate tableを指定していないので、データセットへも出力せず。
この場合、内部的には1行だけしか処理していないようで、SQLOBSには「1」が入る。
ちなみに、fromやwhere等によって選択される行が存在しない場合、SQLOBSに「0」が入る。


*** ④ **************;
proc sql noprint;
  select A into :A1-:A3
  from DT1;
quit;

%put --- &SQLOBS ---;
noprintが指定されてるけど、into句がある。
ここで指定してるinto句は、「変数Aを3行分、マクロ変数A1~A3に格納する」という指定。
ということでSQLOBSには「3」が入る。


*** ⑤ **************;
data DT3; set DT1; run;

proc sql noprint;
  delete from DT3
  where A>=8;
quit;

%put --- &SQLOBS ---;
noprintが指定されてるけど、delete句がある。
ここでは「A>=8」の3行を削除しています。
ということでSQLOBSには「3」が入る。


*** ⑥ **************;
data DT4; set DT1; run;

proc sql noprint;
  update DT4 set A=10 where A>=8;
quit;

%put --- &SQLOBS ---;
noprintが指定されてるけど、update句がある。
ここでは「A>=8」の3行を更新しています。
ということでSQLOBSには「3」が入る。


*** ⑦ **************;
proc sql ;
  create view DT5 as
  select A
  from DT1;
quit;

%put --- &SQLOBS ---;
注意が必要。
viewは特殊なデータなので、処理行数は取得できない。
ということでSQLOBSには「0」が入る。



オブザベーション数を取得する方法として、よく使われるので覚えておくと便利です。


2014年9月4日木曜日

変数名を取得する方法 [まとめ]




変数名を取得する方法は沢山ありますが、知ってる方法すべてあげてみようと思います。

取得した変数名をマクロ変数に入れたい場合は
変数値をマクロ変数に格納する方法「CALL SYMPUTX編」
変数値をマクロ変数に格納する方法「SQL編」
などが参考になります。



data DT1;   * サンプルデータ ;
   label A="var1"  B="var2"  C="var3";
   A=1; B="AAA"; C=100; output;
   A=2; B="BBB"; C=200; output;
run;


*** ① CONTENTSプロシジャ ;
proc contents data=DT1 out=VARS1 noprint;
run;

一番一般的なのが、この変数定義情報を取得できるCONTENTSプロシジャ。



*** ② SQLのDICTIONARYテーブル ;
proc sql;
   create table VARS2 as
   select *
   from DICTIONARY.COLUMNS
   /* whereに"ライブラリ名"と"データセット名"を大文字で指定 */
   where LIBNAME = "WORK" and MEMNAME = "DT1";
quit;

  • SQLプロシジャで参照することが出来るDICTIONARY.COLUMNS(全データセット, VIEWの変数定義情報が入ってる)を使う方法。
  • DICTIONARYテーブルの詳細は「こちら」を参照。


*** ③ OPEN, ATTRN, VARNAME関数 ;
data VARS3;
   length NAME $40.;
   DSID = open( "DT1" );
   VARN = attrn( DSID, "NVARS" );

   do i=1 to VARN;
       NAME = varname( DSID, i );
       output;
   end;
run;

  • まずOPEN関数でデータセットを開く。戻り値としてデータセット識別子というIDが返される。
  • 次に ATTRN( データセット識別子, "NVARS" ) で変数の数を取得。
  • 最後に変数の数だけループをまわして、VARNAME( データセット識別子, 変数の位置番号 ) で変数名を取得しています。



*** ④ CALL VNEXT ;
data VARS4;
   length _NAME $40.;
   set DT1;

   do until ( _NAME="" );
       call vnext( _NAME );
       /* ^in の後のカッコの中は大文字で指定 */
       if upcase( _NAME ) ^in ( "_NAME", "_ERROR_", "_N_", "" ) then output;
   end;

   keep _NAME;
   stop;
run;

  • CALL VNEXTで変数名を取得。詳細は「こちら」を参照。
📝---------------
    • プログラム中で作成している「_NAME」と同じ名前の変数がSETするデータセットにもあると正しく動かなくなるので、その時は上記プログラム中の「_NAME」を別の名前に変えて下さい。


    *** ⑤ TRANSPOSE ;
    proc transpose data=DT1 (obs=0) out=VARS5;
       var _ALL_;
    run;

    海外のユーザー会で紹介された
    「Atypical Applications of Proc Transpose(John King, Ouachita Clinical Data Services Inc., Hopper, AR)」
    にある面白いテクニック。
    transpose本来の使い方とは異なり、面白いアイディアです。



    *** ⑥ ARRAY ;
    data VARS6;
       length _NAME $40. _ITER 8.;
       set DT1;

       array AR1(*) _numeric_;
       do _ITER = 1 to dim( AR1 );
           _NAME = vname( AR1(_ITER) );

           /* ^= の直後のテキストは大文字で指定 */
           if upcase(_NAME) ^= "_ITER" then output;
       end;

       array AR2(*) _character_;
       do _ITER = 1 to dim( AR2 );
           _NAME = vname( AR2(_ITER) );

           /* ^= の直後のテキストは大文字で指定 */
           if upcase(_NAME) ^= "_NAME" then output;
       end;

       keep _NAME;
       stop;
    run;

    • ARRAYで全数値変数と全文字変数を配列に割り当てて、VNAME関数で変数名を取得してます。
    • VNAME関数は、変数名を文字として取得することができ、僕の頁「VNAME関数」で詳しく紹介されています。
    📝---------------
        • プログラム中で作成している「_NAME」「_ITER」と同じ名前の変数がSETするデータセットにもあると正しく動かなくなるので、その時は上記プログラム中の「_NAME」「_ITER」を別の名前に変えて下さい。


        2014年9月2日火曜日

        変数定義書を自動でチェックするプログラムを作ってみる。


        SASデータセットの変数定義書を、以下のような感じでExcelで作ってたとする。



        今回、SASを使ってこの変数定義におかしなところがないかチェックしてみたいと思います。

        準備: まずEXCELのDB定義書を読み込む

        読み込むプログラムは割愛。
        (Excelを読み込む方法はなんでも良いのですが、文字切れやデータの欠落などが起きやすく、確実に読み込める方法がないので要注意)

        仮にExcelファイルを読み込んで、以下SASデータセットDEFINEを作成したとします。

        データセットDEFINE
        F1 
        F2 
        F3
        F4
        F5
        F6
        F7 
         1
         DEMO    
         PATNO    
         被験者No    
         C    
         10    
             
         2
         DEMO    
         1BIRTH    
         生年月日    
         N    
         8    
         YYMMDDS10 
         3
         DEMO    
         AGE    
         年齢 
         N    
         8    
             
         4
         DEMO    
         AGE    
         発症年月日    
         N    
         8    
         YYMMDDS10 
         5
         AE    
         PATNO    
         被験者No    
         C    
         10    
             
         6
         AE    
         AENO    
         有害事象No    
         N   
         8   
             
         7
         AE   
         AETERM  
         有害事象名   
         C    
         20    


        *** チェック①:変数名がおかしい ;
        data CHECK1;
           set DEFINE;
           CHECK = NVALID(F3, "V7");
           if CHECK=0 then  put "WARNING: 変数名がおかしいです No." F1 F2 F3 ;
        run;

        ログ
        WARNING: 変数名がおかしいです No.2 DEMO 1BIRTH
        WARNING: 変数名がおかしいです No.7 AE AETERM


        *** チェック②:データセット内で変数名が重複してる(SAS9.3以上でのみ動作) ;
        proc sort data=DEFINE out=CHECK2 nouniquekey;
           by F2 F3;
        run;

        data _NULL_ ;
           set CHECK2;
           put "WARNING: 変数名が重複しています No." F1 F2 F3;
        run;

        ログ
        WARNING: 変数名が重複しています No.3 DEMO AGE
        WARNING: 変数名が重複しています No.4 DEMO AGE


        解説

        チェック①
        NVALID関数は、指定した文字がSAS変数名として有効かどうか検証をしてくれる関数です (こちら参照)

        今回の結果で最初のWARNINGは変数名が「1BIRTH」となってて、変数名の先頭は数値を入れちゃダメというルールを破ってます。
        2つ目のWARNINGは変数名が「ETERM」で、よく見ると先頭が全角の「A」です。
        変数名に全角はダメというルールだけど、たまにやっちゃうミスなんですよね。


        チェック②
        SORTプロシジャのNOUNIQUEKEYはSAS9.3から追加された機能で、BY値が重複しているレコードを抽出してくれる (こちらを参照)



        もっと細かくチェックするプログラムを組むことが出来るかと思いますが、、
        長くなりそうなので今回はNVALID関数の紹介も出来たことだし、とりあえずここまでとしたいと思います。