2016年4月18日月曜日

0オブザベーションのデータセットを作る方法 【まとめ】


変数属性だけ定義した0オブザベーションのデータセットを作る方法をまとめてみます。


①STOP
data DT1;
    set SASHELP.CLASS;
    stop;
run;

STOPステートメントでオブザベーションが出来る前に処理を停止させてしまう。


② OBS=
data DT2;
    set SASHELP.CLASS (obs=0);
run;

OBS=オプションで読み込むオブザベーション数を指定できます。


③ IF 0
data DT3;
    if 0 then set SASHELP.CLASS;
    stop;
run;

「if 0 then」 の 「0」 は 「false」 の意味になり、then以降の 「set SASHELP.CLASS」 の部分は実行されません。
。。のはずですが、たとえ実行されなくてもデータセット名の指定があると、コンパイル時にそのデータセットの定義情報だけを読み込みにいきます。
①とほとんど同じ方法ですが、こちらの方が内部的に余計な処理が入らないのでスマートです。


④ SET _NULL_;
data DT4;
    set SASHELP.CLASS;
    set _null_;
run;

http://sas-boubi.blogspot.jp/2016/04/data-null.html
http://sas-tumesas.blogspot.jp/2013/10/1obs.html(データステップ100万回)

新手かも。


⑤ WHERE 0
data DT5;
    set SASHELP.CLASS;
    where 0;
run;

この方法も見かけた事ないかも。


⑥ SQL
proc sql;
    create table DT6 like SASHELP.CLASS;
quit;

http://sas-tumesas.blogspot.jp/2013/10/blog-post.html(データステップ100万回)


2016年4月15日金曜日

「DATA _NULL_」 について。




以下の構文で、データセットを作らずにデータステップが実行できます。

DATA _NULL_;

      ~ なんかの処理 ~

RUN;

「データステップを実行したいけど、データセットへの出力は不要」という場合に使えます。




単純な例ですが、以下はデータセットSASHELP.CLASS の特定の変数値をログに出力する例です。

data _null_;
      set sashelp.class;
      put name sex age;
RUN;

ログに出力するだけで、データセットを作りたいわけじゃないんで、「_NULL_」 を使用しています。



ちなみに以下の文もエラーなく通ります。

* 何もPRINTしない ;
proc print data=_null_;
run;

* 何もSETしない ;
data dt1;
  set _null_;
run;

あんまり役に立つ例が思いつかないですが、マクロに組み込んだりすれば便利そうです。


ひとつ知ってるのが、0オブザベーションのデータセットを作りたい時に大体以下のように書くと思いますが、、

data empty;
   stop;
run;


何かの本で、

data empty;
   set _null_;
run;

と書いてる面白い例を見たことがあります。
なんかかっこいい書き方ですね。


他に何かアイディアとか、こういう時使ったという例があったら、教えてほしいです。



2016年4月14日木曜日

ログに独自の無効メッセージを表示する【ERRORステートメント】



以下のようなログをよく見かけると思います。

data DT1;
    A = "x";
    B = input("x",best32.);
run;

ログ

NOTE: 関数INPUT(行 71 カラム 9)の引数は無効です。
A=x B=. _ERROR_=1 _N_=1

何か無効なデータが読まれた時に、「~が無効です」 というNOTEメッセージとともに、その時のデータがログに表示されます。

ここで独自の条件に基づいて、上記のような無効メッセージを表示したいとします。
そこで使えるのがERRORステートメントです。



構文

  ERROR "ログに表示するメッセージ" ;

以下機能と組み合わせると、よりそれっぽいメッセージになります。





data DT2;
    A = 1;
    if A = 1 then  error "NOTE: 変数Aの値が無効です。";
run;

ログ

NOTE: 変数Aの値が無効です。
A=1 _ERROR_=1 _N_=1

変数Aの値が「1」の時に無効メッセージを表示しています。
メッセージの先頭部分に「NOTE: 」と入れてNOTEメッセージにしてます。


2016年4月11日月曜日

PRINTプロシジャで変数の数が多い時に使えるIDステートメント



以下のプログラムを実行すると変数が沢山出来ます。(変数NO, A1, A2, A3・・・A30)

*  サンプルデータ ;
data DT1;
  do NO=1 to 5;
     retain A1-A30 0;
     output;
  end;
run;


上のようなデータに対して、

「RTFに出力したい。データの中身を確認するだけだから、適当にPRINTプロシジャとかで出したやつでいいよ」

という状況があったとします。そこで以下のように書くと、、、

ods rtf file="出力するファイルのフルパスを指定";
   proc print data=DT1;
   run;
ods rtf close;

変数の数が多すぎて途中で折り返してしまいました。
これだと例えばNO=4のA27の値は・・・とかって見たいときに見辛いです。


そこでIDステートメントを使ってみます。
ods rtf file="出力するファイルのフルパスを指定";
    proc print data=DT1;
        id NO;
    run;
ods rtf close;

途中で折り返しても、IDステートメントに指定した変数が一番左側に表示されるようになるので、多少見やすくなります。


📝ちなみに

IDステートメントに指定した変数の「数が多い」か「格納値が長すぎる」と、
ログに長すぎるぞ!的なWARNINGが出て、IDステートメントが無効になることがあります。
ODS LISTINGでの出力時に発生したことがありますが、RTF等の他の出力時でも同様のことが起こるか不明なので、ログも要確認です。

また、今回の方法以外に、用紙の向きを横にして横幅を広げて見やすくするって方法もあるかと思います。
ODS出力時の用紙サイズや余白などを設定するオプション [まとめ]


2016年4月8日金曜日

CARDS vs CARDS4



今回は「CARDS」と「CARDS4」の違いを見ていきたいと思います。

CARDSの詳しい使い方や注意事項についてはSAS社のリファレンスをご確認下さい
(CARDSと同じ機能の「DATALINESステートメント」の方に詳しい使い方などが書かれてます)



ではまず、以下をご覧ください。
「CARDS;」 と 「;」 の間のテキストを読み込み範囲としています。

data DT1;
   length A $10. B 8.;
   input A B;
cards;
aaa .
. 1
;
run;
  A   
  B   
 aaa   .  
  1  


CARDSではセミコロン「;」が読み込み範囲終了の合図になっていますが、、

data DT2;
   length A $10. B 8.;
   input A B;
cards4;
aaa; .
. 1
;;;;
run;
  A   
  B   
 aaa;   .
   1  

CARDS4はセミコロン4つ「;;;;」が終了合図になっています。そのため読み込むテキスト中にセミコロンを含める事が出来ます。



ちなみに

似た機能で「DATALINES」「DATALINES4」というステートメントがありますが、「CARDS」「CARDS4」と挙動は一緒です。

また、マクロの中でCARDS等は使えない(エラーになる)ので、ご注意下さい。


2016年4月7日木曜日

INPUTステートメントで、空白を含むテキストを1変数に読み込む方法


以下のようなテキストファイルがあったとします。

C:\TEST.txt
 SAS boubi roku
 I'm a.matsu


このファイルを読み込んで以下のデータセットを作りたいとする。
 TEXT  
 SAS boubi roku 
 I'm a.matsu



失敗例

data DT1;
    length TEXT $2000.;
    infile "C:\TEST.txt"  lrecl=32767;
    input TEXT;
run;
  TEXT   
 SAS
 I'm

テキスト中の半角スペース(空白)が区切り文字として解釈されるので文字が切れてしまいます。



解決案 : 自動変数 _INFILE_ を使う


解決案は色々ありますが、今回は自動変数 「_INFILE_」 を使ってみます。
(注意事項を追記したので、記事の最後までご覧ください)

data DT2;
    length TEXT $2000.;
    infile "C:\TEST.txt"  lrecl=32767;
    input ;
    TEXT = _infile_;
run;
 TEXT  
 SAS boubi roku 
 I'm a.matsu


解説
  infile "C:\TEST.txt"  lrecl=32767;

まず読込むテキストファイルを指定。
また「LRECL=」で1レコードに読み込める最大バイト数を指定しています。この設定値が小さいと文字切れの原因になるので、例では最大設定値(環境によって異なる可能性あり)の「32767」を設定。
それと、LENGTHも32767にしてしまうと、ファイルサイズが大きくなってしまうので、実データにあわせた長さに適宜設定します。


  input;

テキストファイルを1レコード読み込みに行きます。
ただしINPUTの後に変数の指定がないので、変数への出力はしません。


  TEXT = _infile_;

自動変数「_INFILE_」には入力バッファの内容が格納されています。
つまりテキストファイルから読込んだ1レコード分全文字が格納されています。
この文字を変数TEXTに代入すれば出来上がりです。


📝注意

テキストファイルで「先頭に半角スペースを含むレコード」がある場合は注意。
「今回の方法で変数に読み込んだら先頭の半角スペースが消えてしまった」とご報告頂きました。
私のほうでテストしたところ、問題なくスペースを保持することが出来ましたが、環境依存の可能性もあるので、各自の環境で挙動確認したほうが良さそうです。

ちなみに、ご報告頂いた内容とは別件かもですが、変数値の先頭に半角スペースがあっても、表示上、スペースが消えてみえることがあります。
例えばPROC PRINTで変数値を表示すると先頭の半角スペースが消えてみえます。
実際の格納値と異なるため、混乱のもとになりがちです。



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


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パッケージ