2014年1月24日金曜日

条件式(IF/WHERE)のちょっと効率的な書き方。


IF/WHEREステートメントには以下の真偽ルールが存在する。

変数値または式の結果が

 ・ 0、NULLの場合 ・・・ 「0.false
 ・ 上記以外 ・・・ 「1.true

として評価される。




解説

これはどういう事なのか、以下のサンプルプログラムを実行してみると分かる。

*** サンプルデータ作成 ***********;
data TEST1 ;
  input V1 @@;
cards ;
1 3
;








*** サンプルプログラム ***********;
data TEST2;
  set TEST1;
  if  V1  then  V2=1;
run;



真偽ルールに従い、変数「V1」が「true」となったものに対して、「V2=1」となっている事が分かる。



活用例①

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

data DT2 ;
  do A=2 to 4 ;
     output ;
  end ;
run ;

data DT3 ;
  do A=1 to 3 ;
     output ;
  end ;
run ;

DT1

DT2
 
DT3


上記3データセットを変数「A」をキーにマージしたいとする。
その後たとえば、、
「A」の値が、「DT2」にあって「DT3」にないものを抽出したい場合、
IN=オプションを使って、通常以下のように書く。

data OUT1;
  merge DT1
            DT2 (in=INDT2)
            DT3 (in=INDT3)
  ;
  by A;
  if  INDT2=1 and INDT3=0 ;
run;




これは真偽ルールを元に書き直すと、
 if  INDT2  and  ^INDT3 ;
となる。


「^」は「NOT」の略で、真偽ルールが逆になる。つまり、、
  ・0、NULLの場合 ・・・ 「1.true
  ・上記以外 ・・・ 「0.false
となる。

この真偽ルールと論理演算子「^」の組み合わせは便利で、
使用する場合は以下記事を参照下さい。
論理演算子「^」「^^」によるフラグ変数の作成


活用例②

*** サンプルデータ作成 ******;
data DT4 ;
  input B$ ;
cards;
ABCD
EFGH
;
run;



上記データセットに対して、
変数「B」に"C"という文字が含まれているレコードを抽出したい場合、
INDEX関数を使って、通常以下のように書く。

data OUT2 ;
  set DT4 ;
  if index(B, "C") > 0 ;
run ;



これを真偽ルールに従って書き直すと、
 if index(B, "C") ;
と書くだけでよい。


まとめ

「0/1」や「NULL/1」と入ってる有無変数やフラグ変数に対して有効的に働き、
論理演算子「^」と組み合わせることでスマートに書けるので便利です。

2014年1月21日火曜日

条件式(IF/WHERE)におけるINオペレータの小技




以下のようなデータセットがあったとする。

data DT1;
    do V=1 to 6 ;
        output;
    end;
run;



そこから、変数「V」が「1, 3, 4, 5」のどれかだったら、
レコードを抽出したり何らかの処理を行いたい場合、以下のように書くことが出来ます。

data DT2;
    set DT1;
    if  V in (1, 3, 4, 5) ;
run;



さらに「コロン」を使うと便利です。
「3 : 5」とコロンを挟むことで、「3~5の整数」と範囲指定することができます。

data DT2;
  set DT1;
  if  V in (1, 3 : 5) ;
run;


IFじゃなくて WHERE V in (1, 3 : 5);
とWHEREに置き換えて実行すると、どのような内部処理が行われてるかログに表示されます。

それによると 「WHERE  (V=1)  or  ( (V=INT(V))  and  (V>=3 and V<=5) ) ;
みたいな長ったらしい文に置き換えて内部処理してることが分かります。

「1または3~5の整数」 という条件になってますね。



注意

コロンによる指定は整数限定(「0.1 : 0.5」みたいな小数指定は無理)。





2014年1月16日木曜日

オブザベーション数を取得する自動マクロ変数「&SYSNOBS」




SAS9.3から、かなり使える自動マクロ変数「SYSNOBS」が追加されました。

このマクロ変数にはDATAステップやPROCステップで最後に閉じられたデータセットから読み取られたOBS数が格納されます。

以下に具体的な動作をまとめてみました。



マクロ変数「NOBS」にOBS数を格納する例。

*** 具体例① **************;
data DT1;
  V1=1; output;
  V1=2; output;
run;

%let NOBS=&SYSNOBS;

解説
データセット「DT1」が作成され、OBSは2行。
よって、「SYSNOBS」は「2」となる。



*** 具体例② **************;
proc sort data=DT1 out=DT2;
  by V1;
  where V1=1;
run;

%let NOBS=&SYSNOBS;

解説
データセット「DT1」をもとに「where V1=1」の1OBSが抽出されてソートされ、「DT2」が作成される。
よって、「SYSNOBS」は1」となる。



*** 具体例③ **************;
data _NULL_;
  set DT1;
  where V1=1;
run;

%let NOBS=&SYSNOBS;

解説
注意が必要。
データセット「DT1」をもとに「where V1=1」の1OBSが抽出される。
しかし、「data _NULL_」としているので、データセットは作成されない。
よって、ここで最後に閉じられたデータセットは「DT1」なので、「SYSNOBS」は「DT1」のOBS数である「2」となる。



*** 具体例④ **************;
proc sql noprint;
  select *
  from DT1
  where V1=1;
quit;

%let NOBS=&SYSNOBS;

解説
注意が必要。
こちらも上記同様、SQL文の中で「create table」を記述していないので、最後に閉じられたデータセットは「DT1」となり、「2」となる。



*** 具体例⑤ **************;
data V1 / view=V1;
  set DT1;
run;

data _NULL_;
  set V1;
run;

%let NOBS=&SYSNOBS;

解説
注意が必要。
viewは特殊なデータなので、OBS数は取得できず、「-1」が返される。




まとめ

あと仕様なのか、環境やバージョンによって挙動は異なるかもですが、データセットをダブルクリックで開いてすぐ閉じてから、
「%put "&SYSNOBS";」
と書いて実行しログに出力された値を見ると「1090」という謎値が返される。

独特な動きをするけど、そこを理解することでかなり使える。

2014年1月10日金曜日

最小値・最大値を求めるMIN・MAX関数



各オブザベーションごとに「指定した変数の中での最小値・最大値を求めるMIN・MAX関数」をご紹介



構文


最小値を求める
MIN( 変数1, 変数2, 変数3 ・・・)

最大値を求める
MAX( 変数1, 変数2, 変数3・・・)






/* テストデータ作成 */
data test1;
input a b c;
cards;
. 3 2
5 1 4
;

/* MIN, MAX関数を使ってみる */
data out1;
  set test1;
  vars_min = min( a, b, c );
  vars_max = max( a, b, c );
run;




2013年12月27日金曜日

グループ内連番をふる方法。


グループ内連番をふる機会はすごく多いと思うので、まとめてみました。


サンプルデータ

data DT1;
input SUBJID$ V$;
cards;
001 a
001 b
001 c
002 x
002 y
;


求めたい結果
「SUBJID」毎に「V」の順番で連番をふる。





データステップで連番をふる

*** 方法1 **********;
proc sort data=DT1; by SUBJID V; run;

data OUT1;
  set DT1;
  by SUBJID V;

  if first.SUBJID then SEQ=0;
  SEQ+1;
run;

*** 方法2 **********;
proc sort data=DT1; by SUBJID V; run;

data OUT1;
  set DT1;
  by SUBJID V;

  SEQ+1-first.SUBJID*SEQ;
run;


・方法1が一般的なやり方だと思う。
・方法2は仕組みを理解すれば1行で書けるのでおススメ。


📝注意点

今回のテクニックの中で使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かない事があります。
(解説記事:「サブセット化IFでありがちな落とし穴」)


2013年12月26日木曜日

SQL「再マージ」入門


「再マージ」は、SAS特有の機能っぽい。
AccessやMySQLで試してみたけど、ダメだった。

以下まとめてみてみました。


①サンプルデータ作成

data DT1;
  input A B;
cards;
1 10
1 20
1 30
2 40
2 50
;
run;




②まずは通常のSQL文

* Aのグループ毎に、Bの最大値を求める ;
proc sql;
  create table  DT2 as
  select  A, max(B) as B2
  from    DT1
  group by  A;
quit;




③「再マージ」を利用したSQL文

proc sql;
  create table DT3 as
  select  A, max(B) as B2, B
  from    DT1
  group by  A
  order by  A, B;
quit;

※ 赤字が②から追記した文。



解説
(1) まず②と同様、Aのグループ毎に、Bの最大値を求めにいきます。





(2) がしかし、select文にはBも指定されています。
そのため以下の元データ「DT1」からBを取ってきて、上記(1)とマージします。








(3)Aのグループ毎に(1)での計算が(2)にマージされます。
出来上がりは、DATAステップの「merge (1)(2); by A」とした時みたいな感じ。(内部処理の動きは違うと思うけど。)








ちなみに、この「再マージ」が行われると、並び順が狂うので、「order by A、B」と並び替えをしています。



ちょっと説明が分かりづらいかもしれないけど、
この仕組みは便利なので、今後活用法を小出ししていこうと思います。

2013年12月24日火曜日

ログに独自のエラーメッセージを表示する。


ログのメッセージは、
「NOTE」「WARNING」「ERROR」の3種類あり、

以下サンプルのように上記キーワードとコロン(:)をつけてPUTすると
ログ上に色つきの独自メッセージを表示できます(キーワードは大文字で記述すること)


サンプルプログラム
data _null_;
  put "NOTE:    独自メッセージ1";
  put "WARNING: 独自メッセージ2";
  put "ERROR:   独自メッセージ3";
run;


ログ



ただし、あくまでNOTE・WARNING・ERRORメッセージっぽい表示をしているだけです。
例えば、この方法でERRORメッセージを出したからといって、データステップを中止させるとか、処理に影響を与える事が出来るわけではありません。



使用例として、以下のように条件によって注意喚起を促したい場合に使える。

*** サンプルデータ ;
data DT1;
input SUBJID$ AGE;
cards;
001 17
002 21
003 16
;
run;

*** 年齢が18歳未満だったら、WARNINGメッセージを表示 ;
data DT2;
 set DT1;
 if AGE<18 then put "WARNING: 18歳未満です。" SUBJID= AGE=;
run;


ログ



自分の書いたメッセージがログに色つきで出るのがなんか楽しい。