ラベル プロシジャ: DS2 の投稿を表示しています。 すべての投稿を表示
ラベル プロシジャ: DS2 の投稿を表示しています。 すべての投稿を表示

2018年9月10日月曜日

PROC SQL / FedSQL / DS2 の「STIMERオプション」でログに各ステップ毎のパフォーマンスを表示する





以下はPROC SQLで何個かステートメントを書いて実行した結果

proc sql;
  create table dt1 as select * from sashelp.class;
  create table dt2 as select * from sashelp.cars;
quit;



ログ
 72         proc sql ;
 73           create table dt1 as select * from sashelp.class;
 NOTE: テーブルWORK.DT1(行数19、列数5)が作成されました。

 74           create table dt2 as select * from sashelp.cars;
 NOTE: テーブルWORK.DT2(行数428、列数15)が作成されました。

 75         quit;
 NOTE: PROCEDURE SQL処理(合計処理時間):
       処理時間           0.00 秒
       CPU時間            0.01 秒



続いてSTIMERオプションをつけた場合

proc sql stimer;
  create table dt1 as select * from sashelp.class;
  create table dt2 as select * from sashelp.cars;
quit;


ログ
 74         proc sql stimer;
 NOTE: SQL Statement処理(合計処理時間):
       処理時間           0.00 秒
       CPU時間            0.00 秒
     
 75           create table dt1 as select * from sashelp.class;
 NOTE: テーブルWORK.DT1(行数19、列数5)が作成されました。

 NOTE: SQL Statement処理(合計処理時間):
       処理時間           0.00 秒
       CPU時間            0.00 秒
     
 76           create table dt2 as select * from sashelp.cars;
 NOTE: テーブルWORK.DT2(行数428、列数15)が作成されました。

 NOTE: SQL Statement処理(合計処理時間):
       処理時間           0.00 秒
       CPU時間            0.00 秒
     
 77         quit;
 NOTE: PROCEDURE SQL処理(合計処理時間):
       処理時間           0.00 秒
       CPU時間            0.00 秒


ログにステートメント毎のパフォーマンスが表示されるようになります。
「PROC DS2」や「PROC FedSQL」でも同様にSTIMERオプションを設定できます。
私自身はDS2使う時によくこのオプション指定することはあります。


※「STIMERオプション」を利用するには、システムオプションで「OPTIONS STIMER」か「OPTIONS FULLSTIMER」も設定されている必要があります。


2018年9月4日火曜日

PROC DS2でタイムゾーン関係の処理をする「TZパッケージ」



DS2プロシジャの「TZパッケージ」でタイムゾーン関係の処理ができます
アメリカとかだとタイムゾーンが地域によって違うから、パッケージ化するほどの需要あるんですかね。



以下は簡単な例です。

 proc ds2;
 data _null_;
    method init();
       dcl package tz t();
       dcl double utc tokyo having format nldatm20.;
       dcl double tokyo_offset having format time8.;

       /* UTC timeを取得 */
       utc = t.getutctime();
 
       /* Local timeを取得 */
       tokyo = t.getlocaltime('Asia/Tokyo');

       /* Offsetを取得 */
       tokyo_offset = t.getoffset('Asia/Tokyo');

       put utc=;
       put tokyo=;
       put tokyo_offset=;

    end;
 enddata;
 run;
 quit;

ログ
 utc=2018/09/04 11:56:47
 tokyo=2018/09/04 20:56:47
 tokyo_offset= 9:00:00


メソッドの中で「’Asia/Tokyo’」というように地域を指定していますが、タイムゾーンIDといって、「SAS timezone id」とかググれば地域ごとのタイムゾーンIDが載ってるSASのリファレンスが出てくるはずです。

該当する地域のタイムゾーンIDがない場合は、同じタイムゾーンを持つほかの地域を探して設定しましょう(夏時間を導入している地域では、そこも同じかどうか確認するのを忘れずに)

あとは構文自体も大したことないんで、詳細はリファレンスを見てみてください。




とりあえず私は業務で使う事ないんで、暇つぶし用のプログラムを書いて遊んでます。

 proc ds2;
 data _null_;
    method init();
       dcl package tz t();
       dcl double tokyo newyork honolulu vancouver having format nldatm20.;

       tokyo         = t.getlocaltime('Asia/Tokyo');
       newyork    = t.getlocaltime('America/New_York');
       honolulu    = t.getlocaltime('Pacific/Honolulu');
       vancouver = t.getlocaltime('America/Vancouver');

       put tokyo=;
       put newyork=;
       put honolulu=;
       put vancouver=;

    end;

 enddata;
 run;
 quit;

ログ
 tokyo=2018/09/04 20:51:00 
 newyork=2018/09/04 07:51:00
 honolulu=2018/09/04 01:51:00
 vancouver=2018/09/04 04:51:00


適当な地域の現在時刻を調べて「あーニューヨーカーは今頃寝てるのか、いやむしろ寝てないのかな」とか妄想するために使うものだと思っています。
一応、夏時間も考慮されているっぽい(夏時間が存在する地域でちゃんと夏時間の日時になってたんで)




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年10月26日木曜日

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



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



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




① DataStep
data DT1;
  attrib
     VAR1  length=8.     label="Variable1"  format=yymmdd10.
     VAR2  length=$20. label="Variable2"
  ;
  call missing( of _ALL_ );
  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;


LABELの設定はダブルクォーテーションではなく、シングルクォーテーションで囲む必要があります。



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

2016年4月1日金曜日

DS2プロシジャ入門9:SQLSTMTパッケージ



SQLSTMTパッケージは、DS2プロシジャ内でSQL(FedSQL)を実行したり、その結果を取得したりといった柔軟な処理が出来ます。




構文① SQLの実行


SQLSTMTパッケージを使えるようにする
 DCL PACKAGE SQLSTMT  インスタンス参照名( 'SQL文' );

SQLSTMTパッケージを使用するには、パッケージのインスタンスってのを作って、そのインスタンスを参照するための変数(インスタンス参照名)を定義します。


SQL実行
 インスタンス参照名.EXECUTE();


例1
* サンプルデータ ;
data DT1;
input A B;
cards;
1 100
2 200
;

data DT2;
input X Y;
cards;
1 .
1 .
2 .
2 .
;

* SQLSTMTを使ってデータの更新を行う ;
proc ds2;
   data _NULL_ ;
     dcl package sqlstmt sq( 'update DT2 set Y=1 where X=1' );
     method init();
          sq.execute();
     end;
   enddata;
   run;
quit;

dcl package sqlstmt sq( 'update DT2 set Y=1 where X=1' );
データ更新するSQL文を定義して、、

sq.execute();
実行しています。


📝ポイントと注意点
  • SQL文は「PROC SQL」ではなく「FedSQLステートメント」である点に注意(FedSQLの詳細についてはSAS社のリファレンスを参照下さい)
  • FedSQLステートメントはシングルクオーテーションで囲む
  • EXECUTEメソッドは以下のリターンコードを返します
    • 0 = 実行が成功、1 = エラーがある、2 = データがない(UPDATEやDELETEがどの行にも影響を与えない)
  • 例ではプログラムを見やすくするため、リターンコードに対する処理は省略してます。必要に応じ追加下さい。



例2
proc ds2;
   data _NULL_ ;
     dcl package sqlstmt sq( 'update DT2 set Y=? where X=?' , [B A);
     method run();
          set DT1;
          sq.execute();
     end;
   enddata;
   run;
quit;

dcl package sqlstmt sq( 'update DT2 set Y=? where X=?' , [B A] );
SETしたDT1の変数[B A]をSQL文中の「?」に渡すよう指定してます。

update DT2 set Y=[B] where X=[A]

SETしたDT1の各オブザベーションの変数A, Bを使って、DT2のデータを更新したいので、
RUNメソッドの中でEXECUTEメソッドを記述しています
(SETでDT1のオブザベーションを読み込む毎にSQLを実行している)



構文② SQLの結果を取得する


SQLの結果をDS2プロシジャ内の変数に紐づける
 インスタンス参照名.BINDRESULTS( [変数1 変数2 ・・・] );

「SQLの結果に含まれる変数」と「BINDRESULTSに指定した変数」を、左から順に紐づけます
(同じ変数名同士で紐づけるわけではないので注意)


SQLの結果から1行取ってきて、紐づけた変数にセットする
 インスタンス参照名.FETCH();


例1
proc ds2;
   data OUT1 / overwrite=yes ;
     dcl double A2 B2;
     dcl package sqlstmt sq( 'select A,B from DT1' );
     method init();
          sq.execute();
          sq.bindresults([A2 B2]);

          do while (sq.fetch()=0);
              output;
          end;
     end;
   enddata;
   run;
quit;

dcl package sqlstmt sq('select A,B from DT1');
sq.execute();
SQLでselectしたA,Bを、

sq.bindresults([A2 B2]);
DS2プロシジャ内で定義した変数A2, B2に紐づけます。

do while (sq.fetch()=0);
   output;
end;
SQLの結果を1行ずつ取り出して、紐づけた変数A2,B2に格納します。


📝ポイントと注意点
  • BINDRESULTSメソッドは以下のリターンコードを返します
    • 0 = 実行が成功、1 = エラーがある
  • FETCHメソッドは実行される度にSQLの結果から次の行を読み込んでいき、以下のリターンコードを返します
    • 0 = 実行が成功、1 = エラーがある、2 = データがない(FETCHする行がこれ以上ない)
  • 例ではリターンコードに対する処理は省略してます。必要に応じ追加下さい。



例2
proc ds2;
   data _NULL_ ;
     dcl double A2 B2;
     dcl package sqlstmt sq1( 'select A,B from DT1' );
     dcl package sqlstmt sq2( 'update DT2 set Y=? where X=?',[B2 A2] );
     method init();
          sq1.execute();
          sq1.bindresults([A2 B2]);

          do while (sq1.fetch()=0);
              sq2.execute();
          end;
     end;
   enddata;
   run;
quit;

今回の集大成。

dcl package sqlstmt sq1('select A,B from DT1');
sq1.bindresults([A2 B2]);
SQLでselectしたA,Bを、DS2プロシジャ内で定義した変数A2,B2に紐づけて取り出しながら、その値を使って、、

dcl package sqlstmt sq2('update DT2 set Y=? where X=?',[B2 A2]);
別のデータに更新をかけようというわけです。



DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義
4: データ型①
8: ユーザー定義パッケージ
9: SQLSTMTパッケージ


2015年8月14日金曜日

DS2プロシジャ入門8:ユーザー定義パッケージ




メソッドをパッケージに入れる事で、その中に入れたメソッドはいつでもどこでも使えるようになります。



構文


パッケージの定義
PROC DS2;

     PACKAGE パッケージ名 / OVERWRITE=YES;

          METHOD メソッド1;
               ~メソッド処理~
          END;

          METHOD メソッド2;
               ~メソッド処理~
          END;
          ・・・
     ENDPACKAGE;

     RUN;
QUIT;




定義したパッケージを使えるようにする

 DCL PACKAGE パッケージ名  インスタンス参照名();


定義したパッケージを使用するには、パッケージのインスタンスってのを作って、そのインスタンスを参照するための変数(インスタンス参照名)を定義します。


パッケージで定義したメソッドを使う

 インスタンス参照名.メソッド();






*** パッケージの作成 ;
proc ds2;
   package MYPACK / overwrite=yes;
       method TEST (double VAR1) returns double;
          return VAR1+10;
       end;
   endpackage;
   run;
quit;

ログ
package mypack がデータセット work.mypack に作成されました。



数値を放り込むと、10足した値を返してくれるTESTというメソッドを作って、
これをMYPACKというパッケージに入れています。


パッケージは通常WORKに作成されるので、SASを終了すると消えてしまいますが、
package ライブラリ名.パッケージ名」 というように書けば、そのライブラリにパッケージが作成され、SASを終了しても残ります。


*** パッケージを使う ;
proc ds2;
   data _NULL_;
       dcl package MYPACK mp();
       method init();
           dcl double OUTVAR;
           OUTVAR = mp.TEST(100);
           put '******' OUTVAR '******';    
       end;
   enddata;
   run;
quit;

ログ
****** 110 ******



dcl package MYPACK mp()」 でパッケージMYPACKをmpというインスタンス参照名で使えるようにし、
mp.TEST(100)」 でパッケージで定義したTESTというメソッドを使っています。

FCMPプロシジャに似てる感じですね。




DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義
4: データ型①
8: ユーザー定義パッケージ

2015年7月26日日曜日

DS2プロシジャ入門7:ユーザー定義メソッド




ユーザー定義メソッドとは、自分で関数を作れるみたいなやつと思ってください。


構文
  method  メソッド名 ( 引数の定義1 , 引数の定義2 ・・・ )  returns 戻り値の型 ;
          return 戻り値 ;
  end;

いきなり構文みてもよく分かんないので、まずは簡単な例から。




簡単な例

proc ds2;
   data _NULL_ ;

     method TEST( double VAR1 ) returns double;
         return VAR1+10;
     end;
   
     method init();
         dcl double OUTVAR;
         OUTVAR = TEST(100);
         put '******' OUTVAR '******';
     end;
   
   enddata;
   run;
quit;

ログ
****** 110 ******


解説
TESTというメソッドを作って、これに数値を放り込むと、10足した値を返してくれる仕組みを作ってます。

まずは仕組み作り
method TEST( double VAR1 ) returns double;
    return VAR1+10;
end;

① 「method TEST」で TEST というメソッドを作ります。
メソッドに放り込まれてくる数値を 「( double VAR1 )」 でdouble型の変数VAR1が受け取れるようにします。
returns double」でメソッドの結果をdouble型で返すよう指定。

② 「return VAR1+10」で、VAR1に10足した値をメソッドの結果として返しています。

これで仕組み作り完了!今度はこのメソッドを使ってみます。


method init();
    dcl double OUTVAR;
    OUTVAR = TEST(100);
    put '******' OUTVAR '******';
end;

メソッドの使い方は普段使ってる関数と同じです。
OUTVAR = TEST(100)」でTESTメソッドに「100」を渡し、返された値を変数OUTVARに入れています。




実践例

以下はBMIを計算するメソッドを作っています。

proc ds2;
   data DT1 (overwrite=yes);
     dcl double VAR1;

     method BMI( double CM , double KG) returns double;
     CM :身長(cm) , KG :体重(kg) ;
        dcl double BMI;
        BMI = round( KG / ((CM/100)**2) ,.1);
        return BMI;
     end;
 
     method init();
        VAR1 = BMI(156,60);
        put VAR1;
     end;

   enddata;
   run;
quit;

DT1
  VAR1  
   24.7

実践例とは言いつつも、あんまりメソッドにする意味のない例かな、、




注意事項

メソッドの引数を CHAR, VARCHAR, NVARCHAR などの文字変数にする場合、必ずlengthも設定しましょう。

× method TEST( char X ) ・・・;
○ method TEST( char(20) X ) ・・・;

何故かというと、
引数が文字変数でlengthが設定されていないと自動で「length=8」に設定されます。
なので、メソッドを実行するときに、引数に8バイト以上の文字を指定しても、8バイトで切っちゃいます。




DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義
4: データ型①

2015年7月16日木曜日

DS2プロシジャ入門6:データの結合

今回はDS2プロシジャでデータセットを結合する方法です。

サンプルデータ
data DT1;
input A B$;
cards;
1 aa
2 bb
3 cc
;

 A 
 B
  1  
  aa 
  2  
  bb  
  3  
  cc  

data DT2;
input A C$;
cards;
1 xx
3 yy
;

 A 
  1  
  xx  
  3  
  yy  



① 縦結合
proc ds2;
  data OUT1 (overwrite=yes);
      method run();
         set DT1 DT2;
      end;
  enddata;
  run;
quit;

 A 
B
C
  1  
  aa 
   
  2  
  bb  
   
  3  
  cc  
   
  1  
   
  xx 
  3  
   
  yy 

SETステートメントはDATAステップと書き方も似てますが、内部の動きや結合結果も同じになるかは不明。
各自色々なパターンでテストしてみた上で利用したほうが良さそうです。


②横結合
proc ds2;
  data OUT2 (overwrite=yes);
      method run();
          set { select coalesce(DT1.A, DT2.A) as A, B ,C
                  from DT1 full join DT2 on DT1.A = DT2.A
                  order by A };
      end;
  enddata;
  run;
quit;

 A 
B
 C 
  1  
  aa 
  xx  
  2  
  bb  
   
  3  
  cc  
  yy 

DS2プロシジャでは、現状MERGEステートメントがありません。
ではどうやってデータの横結合を実現させるかというと、上のようにSETの中でSQL文を書きます。
SQL文なので当然、DATAステップのMERGEステートメントとは挙動が異なります。

これ面白いですよね。基本的にDS2プロシジャではSQLがキーワードになってきます。
通常のデータステップでもこの機能ほしいな~。


(2022/11/06: 追記)
SAS9.4M6から以下のように「DATAステップのMERGE」と似た横結合が出来るようです。
しかし、こちらもDATAステップのMERGEとは挙動が異なるようです。

MERGEステートメント + RETAINオプション
proc ds2;
  data OUT3 (overwrite=yes);
      method run();
          merge DT1 DT2 / retain;
          by A;
      end;
  enddata;
  run;
quit;

2015年7月14日火曜日

DS2プロシジャ入門5:データ型②


詳細は割愛して、押さえておくべきポイントに焦点を当てたいと思います。
色んなデータ型が用意されてますが、よくマニュアルを読まないと気づきにくい落とし穴もあるのでご注意ください!


DECIMAL型

「DOUBLE」では扱えない桁の数値を利用できます。

DECIMAL を宣言する構文
 dcl  decimal ( 全体の桁数 , 小数部分の桁数 )  変数名 ;


※ 「全体の桁数 」は整数と小数を合わせた桁数を指定します。

たとえば 「dcl decimal (5,2)」 と宣言すれば、整数部分は3ケタ、小数部分は2ケタの数値を表すことができます。
この時 「1234.56」 とか宣言以上の桁がある値を入れたらダメなのでご注意してください。


ポイント①

桁の多い数値は、その数値の末尾に「n」を付ける必要があります。
以下の例をご覧ください。
proc ds2;
data DT1 (overwrite=yes);

    dcl decimal(16,6) VAR1 VAR2;

    method init();
        VAR1=1111111111.111112;
        VAR2=1111111111.111112n;
        put VAR1=;
        put VAR2=;
    end;

enddata;
run;
quit;

ログ
 VAR1=1111111111.111100
 VAR2=1111111111.111112

VAR1とVAR2には同じ値を代入しているはずなのに、ログを見るとVAR1の方は切られちゃってますね。
一方末尾に「n」を付けたVAR2は正しく値が入ってます。


ポイント②

上記プログラムの実行ログをよくよく見てみると、以下のようなメッセージが表示されているはずです。

BASE driver, creation of a DECIMAL column has been requested, but is not supported by the BASE driver. A DOUBLE PRECISION column has been created instead.

これは結果をSASデータセット化する際、、DS2プロシジャでしか扱えない型について、通常のSAS上でも扱える型に置き換える旨の脅しメッセージ(だと思います)。

試しに、先ほど作ったデータセットの中身をデータステップでログに出力してみます。
data _NULL_;
  format VAR1 VAR2 best32.;
  set DT1;
  put (VAR:)(=/);
run;

ログ
 VAR1=1111111111.1111
 VAR2=1111111111.11111

VAR2の値が無慈悲にも最後切れちゃってますね。
型が置き換えられた影響です。

どうしようもないので、今後の解説では通常のSAS上で扱える DOUBLE と CHAR のみを用いることにします。


DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義
4: データ型①
5: データ型②

2015年6月29日月曜日

DS2プロシジャ入門4:データ型①


DS2プロシジャには沢山のデータ型が用意されています。
詳細は 「sas ds2 data types」 などでググって出てくる海外SAS社の解説ページをご覧ください。

ここでは一部紹介するに留めます。


とりあえず、、

文字変数は「CHAR」、数値変数は「DOUBLE」という型が、いままでSASで使ってた文字型・数値型と同等なので、まずこの2つを覚えましょう。

CHAR と DOUBLE を宣言する構文
 dcl  char(長さ ) 変数名 ;
 dcl  double       変数名 ;

ちなみに 「dcl double VAR1 VAR2 VAR3;」 というように複数の変数をいっぺんに定義することも出来ます。


クオーテーションには注意。

以下で、変数VAR1 に'aa' という値を入れたいとします。

proc ds2;
  data _DT1 (overwrite=yes);

      dcl char(10) VAR1;
      method init();
          VAR1="aa";
      end;
   
  enddata;
  run;
quit;

ログ
WARNING: 行 74: 参照変数 aa は宣言されていません。タイプ double
のグローバル変数として作成しています。

WARNINGが出てしまいます。
問題は、プログラム青字 「VAR1="aa"」 です。

実はDS2プロシジャでは、文字をダブルクオーテーションで囲むと、識別子とみなします。
(識別子?って感じですが、要はここでは変数名として見なされているって事です)

よって "aa" は変数aaと解釈されてしまい、「そんな変数ないんだけど、ちゃんと宣言しろ」と注意されてしまったわけです。

解決策は簡単で、シングルクオーテーションで囲めば値と見なしてくれます。

  VAR1='aa';


マクロとクオーテーション

以下で、変数VAR1 にマクロ変数の値 'aa' を入れたいとします。

%let MACVAR=aa;

proc ds2;
  data DT1 (overwrite=yes);

     dcl char(10) VAR1;
     method init();
         VAR1 = "&MACVAR";
     end;

  enddata;
  run;
quit;

ログ
WARNING: 行 76: 参照変数 aa は宣言されていません。タイプ double
のグローバル変数として作成しています。

プログラム青字部分の "&MACVAR" が展開され、"aa" となります。
ダブルクオーテーションで囲んでるため、やはり変数名として判断されWARNINGが出てしまいます。
かといって、 '&MACVAR' とシングルクオーテーションで囲ってしまうと「&MACVAR」という値として解釈されて、マクロ変数として展開してくれません。


そこで、%TSLITマクロ関数の出番。
このマクロ関数はシングルクオーテーションの付与と、マクロ変数の展開をしてくれます

  VAR1 = %tslit( &MACVAR );

2015年6月23日火曜日

DS2プロシジャ入門3:変数属性と配列の定義



① 属性を付与する方法

変数を宣言するついでに、属性も定義する感じです。

構文
 dcl  データ型  変数名  having   label   'ラベル'
                                                   format  フォーマット
                                                   informat  インフォーマット ;


proc ds2;
   data DT1 (overwrite=yes);
       dcl double VAR1 having  label 'aaa'  format yymmdd10. ;
       method init();
            VAR1 = inputn('2015/06/23', 'yymmdd10');
       end;
   enddata;
   run;
quit;




② 配列の定義方法

一時配列(DS2プロシジャを終了したら残らない)を作る構文
 dcl  データ型  配列名 [要素の数] ;


proc ds2;
   data _NULL_;
       dcl double AR[2];
       method init();
            AR[1] = 10;
            AR[2] = 20;
            put AR[1]=;
            put AR[2]=;
       end;
   enddata;
   run;
quit;

ログ
AR[1]=10
AR[2]=20


通常の配列(DS2プロシジャを終了しても変数は残る)を作る構文
 vararray データ型  配列名 [要素の数]   変数1 変数2 ・・・;


proc ds2;
   data DT2 (overwrite=yes);
       vararray double AR[2] A B;
       method init();
            AR[1] = 10;
            AR[2] = 20;
       end;
   enddata;
   run;
quit;


注意:
「vararray double AR[1] double」みたいな感じで、変数名を指定する部分にデータ型を指定すると特殊な動き(想定外の挙動)をするため、ご注意ください(詳細はリファレンスなどを参照下さい)


DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義

2015年6月1日月曜日

DS2プロシジャ入門2:変数の宣言


今回は重要かつ細かい動きの解説になるので、間違いなどがあればご指摘お願いします。


変数の宣言

DS2プロシジャで変数を扱うには、まず変数の宣言が必要です。

変数を宣言する構文。
 dcl  データ型  変数名 ;

(データ型については別記事で解説予定なので、ここでは気にしないでください。)

例 **********;
proc ds2;
   data _DT1 (overwrite=yes);

       dcl char(10) VAR1;

       method run();
           dcl char(10) VAR2;
       end;
 
   enddata;
   run;
quit;

上の例では、長さ10の文字変数VAR1とVAR2を作成しています。

また変数を宣言する場所によって、その変数の役割も異なってきます。


ローカル変数

メソッドの中で宣言した変数は、そのメソッド内でしか使用できないうえ、
一時変数的な意味合いとなり、自動的に削除されます。

以下の例を見てください。
data DT1;
   do i=1 to 3;
       output;
   end;
run;

proc ds2;
  data DT2 (overwrite=yes) ;
    method run();
        dcl double A;
        set DT1;
        A + 1;
        put A;
    end;
  enddata;
run;
quit;

ログ
1
1
1

オブザベーションを読み込む度に「A+1」でどんどん足し合わせていこうとしてますが、
出力結果のログを見ると、全然足されてません。

これは、ローカル変数Aが一時的な変数のため、前回処理時の値を保持していないからです。
よってRETAINやSUMステートメントの機能も無効になります。
出力データセットにも変数は残りません。


グローバル変数

メソッドの外で宣言した変数は、各メソッドから参照する事ができます。
(ローカル変数と違い、変数もちゃんと残ります)

ここで気をつける事あり。以下を見てください。
proc ds2;
  data DT1 (overwrite=yes) ;

    dcl double A;

    method init();
        A=1;
        put 'INITメソッドからPUT ・・・' A;
    end;
    method run();
        put 'RUNメソッドからPUT ・・・' A;
    end;
    method term();
        put 'TERMメソッドからPUT ・・・' A;
    end;
  enddata;
run;
quit;


ログ
 INITメソッドからPUT ・・・ 1
 RUNメソッドからPUT ・・・ 1
 TERMメソッドからPUT ・・・ .

やってる事
① まずグローバル変数Aを作ります。
② INITメソッド内で変数Aに値「1」を入れます。
③ そして各メソッドからAの値をログに出力してます。

ログを見ると、TERMメソッドの結果だけ欠損値になってる!



これは最後に実行されるTERMメソッドが、
処理を終了するから、グローバル変数達の値はリセットしておくね」、、となるからです。

(リセットされるグローバル変数は以下の場合を除く。)
  • SAS側があらかじめ定義している_N_などの変数
  • RETAINのような値を保持するようにしてる変数
  • パッケージ変数


という事で、TERMメソッドには気を付けましょう!

。。。ちなみに、出力データセットには、RUNメソッド実行時の値が格納されます。
(TERMメソッド実行時の値を出力データセットに格納するなど、こちらで明示的に出力するタイミングを変えることも出来るっちゃ出来る)



同名のグローバル変数とローカル変数を作った場合

proc ds2;
   data _DT1 (overwrite=yes);

       dcl char(10) VAR1;

       method init();
           dcl char(10) VAR1;
           VAR1 = 'aaa';
       end;
 
   enddata;
   run;
quit;


この場合INITメソッド内に書かれた 「VAR1 = 'aaa';」 は、INITメソッド内で宣言されたローカル変数VAR1に対しての処理となります。
(出力データセットにはグローバル変数VAR1が出力されます)


DS2プロシジャ入門記事

1: 基本構文
2: 変数の宣言

2015年5月14日木曜日

DS2プロシジャ入門1:基本構文




SAS9.4から追加されたDS2プロシジャについて、基本的な部分からざっくり解説していきたいと思います。
データステップがパワーアップしたようなプロシジャなので、ある程度データステップの動きを知ってる方が理解しやすいです。

私自身勉強中なので、間違いなどがあればご指摘をお願いします。



基本構文

PROC DS2 ;
     DATA  作成するデータセット ;

         ~ 処理 ~

     ENDDATA ;
     RUN ;
QUIT ;

  PROC DS2;
      ~
  QUIT ;
  • DS2プロシジャは、「PROC DS2」で始まり、「QUIT」で終わる。
  DATA データセット ;
      ~
  ENDDATA ;
  RUN ;
  • DATAステップと同じで「DATA 」で始まる。
  • 「ENDDATA」で終わる。実行するために「RUN」も必要。



まずは簡単な例。

例①

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

  10 
  20  
  30 

*** DS2 Procedure **********;
proc ds2;
  data _NULL_;

      method init();
          put '--- 出力開始';
      end;

      method run();
          set DT1;
          put '---' A;
      end;
      
      method term();
          put '--- 出力終了';
      end;

  enddata;
  run;
quit;

ログ
--- 出力開始
--- 10
--- 20
--- 30
--- 出力終了


「method init()」 とか 「method run()」 とかいうのが出てきて「なんじゃこりゃ、もうやだ!」って感じですが、ここはグッと堪えましょう。

DS2プロシジャでは、基本的にこのメソッドの中に処理を書いていきます。

  method init() ;
     ~
  end;
 最初に1回だけ実行したい処理をこのメソッドの中に書く。


  method run() ;
     ~
  end;
 各オブザベーションを読み込んで実行したい処理をこのメソッドの中に書く。


  method term() ;
     ~
  end;
 最後に1回だけ実行したい処理をこのメソッドの中に書く。



各メソッドの役割を知ったところで、先ほどのプログラムおよびログと照らし合わせて確認すると、理解がしやすいと思います。



では、もう1歩先に進んだ例を紹介。

例②

*** DS2 Procedure **********;
proc ds2;
   data OUT2 (overwrite=yes);

      keep TOTAL ;  

      method init();
         TOTAL = 0;
      end;

      method run();
         set DT1;
         TOTAL + A;
      end;
 
      method term();
         output;
      end;
 
   enddata;
   run;
quit;

 A 
  60  

ログにWARNINGが出ると思いますが、今は放置しといてください。



解説

method init();
   TOTAL = 0;
end;

まず最初、変数TOTALに、初期値「0」を与えます。

method run();
   set DT1;
   TOTAL + A;
end;

データセットDT1をsetして、変数TOTALに各オブザベーションの変数Aの値を足していきます。

method term();
   output;
end;

最後に1回だけ、output。
これで、変数Aの合計値を求めたデータセットOUT2が出来ました。

以上終わり、、と言いたいところですが、ここで2つ重要なポイント。

data OUT2 (overwrite=yes);

「(overwrite=yes)」 というオプションを入れること!
これは、「すでにOUT2というデータセットが存在する場合、上書きしていいよ」、というオプションです。
つまり、これを指定してないと、上書きできずにエラーになってしまいます。

keep TOTAL ;

データステップの場合だと、keepはどこに書いても問題なかったけど、
DS2プロシジャの場合は、メソッドの外に書かなければいけない。
また、どのメソッドよりも上に書く必要があります。




DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義

2015年4月24日金曜日

DS2プロシジャのSQLEXEC関数でSQLを実行する。


SAS9.4で追加されたDS2プロシジャを勉強中です。

データステップがパワーアップした感じのやつなんですが、勝手が違うので、なかなかすんなりと動いてくれないです。。(そこがまた楽しいですが)

今回は、DS2プロシジャらしいダイナミックな関数、SQLEXEC関数を使ってみたいと思います(注意点もあるので最後までご覧ください)



*** サンプルデータ作成 *************;
data DT1;
   A=1;
   output;
   output;
run;

 A 
  1 
  1  

*** DS2でSQL (FedSQLステートメント) を実行する ***;
proc ds2;
   data _NULL_;

      method run();
          dcl double rc;
          rc = sqlexec( 'update DT1 set A=2' );
          if rc^=0 then put '更新失敗しました';  
      end;
       
   enddata;
   run;
quit;

 A 
  2  
  2 


構文
  SQLEXEC( 'FedSQLステートメント' )



ポイントと注意点
  • SQLEXEC関数に記載するSQL文は「PROC SQL」ではなく「FedSQLステートメント」である点に注意(FedSQLの詳細についてはSAS社のリファレンスを参照下さい)
  • FedSQLステートメントはシングルクオーテーションで囲む
  • UPDATEやINSERTなど更新・追加・削除・作成をするFedSQLを指定します(SQLEXEC関数自体はFedSQLを実行するだけで、実行結果のテーブルを取得できるわけではないので、単純なSELECT文を指定しても意味ない)
  • 色々試してみた感じでは実行が成功すればリターンコードとして「0」が返される?(SAS社のリファレンスにこの辺の挙動の記載が見つけられなかったので、プログラムに組み込む場合は要確認)


今回の例では、データセットDT1の変数Aの値を更新するSQLを実行しています。

  rc = sqlexec( 'update DT1 set A=2' );

DS2プロシジャの中で、FedSQLの文を文字列として生成できるところがポイントですね。



自分自身の備忘録も兼ねて、いつかDS2プロシジャ入門の記事を書きたいなと思ってます。
需要ないだろって感じですが。