2015年3月23日月曜日

PROC MEANSの、COMPLETETYPES・PRELOADFMTオプションの紹介



注意点もあるので最後までご覧ください。
まずは以下のプログラムと結果を見てみましょう。

*** サンプルデータ作成 ;
data DT1;
input A B;
cards;
1 10
1 20
3 30
3 40
3 50
;
 A 
B
  1  
  10 
  1
  20 
  3
  30
  3
  40  
  3
  50  

*** FORMAT作成 ;
proc format;
   value MYFMT
   1 = "aaa"
   2 = "bbb"
   3 = "ccc"
 ;
run;

*** Bの要約統計量を、Aの値毎に出す ;
proc means data=DT1 nway;
   var  B;
   class  A;
   format  A  MYFMT. ;
run;



Bの要約統計量を、Aの値毎に出しています。
その際、Aに以下のフォーマットをあてています。
「1="aaa"」「2="bbb"」「3="ccc"」


OUTPUTを見てみると、「2="bbb"」の要約統計量が出てませんね。
これはデータセットDT1のAに「2="bbb"」の値が存在しないからです。


そこで、以下のように completetypes と preloadfmt オプションを使って再度実行してみると、フォーマットに存在するカテゴリ値をすべて出力してくれます。


proc means data=DT1 nway completetypes;
   var  B;
   class  A / preloadfmt ;
   format  A  MYFMT.;
run;



注意点その1

集計対象が0オブザベーションの場合、今回のオプションは機能しません。


注意点その2

以下のようにフォーマットに欠損値を定義して、MEANSの結果に欠損値のカテゴリも出したい場合、MEANSプロシジャでmissingオプションをつけないと出てくれません。

proc format;
   value MYFMT
   . = "Unknown"
   1 = "aaa"
   2 = "bbb"
   3 = "ccc"
 ;
run;

proc means data=DT1 nway completetypes;
   var  B;
   class  A / missing preloadfmt ;
   format  A  MYFMT.;
run;



注意点その3

以下リファレンスのMEANSプロシジャのCLASSステートメントのページで「PRELOADFMT」>「Interaction」の欄に気になる記述が。


CLASSが文字変数だと、非常に低いまたは高い「sentinel values」が生成されるかも、だそうです。
「sentinel value」はプログラミング用語で「番兵」とかって表現される特別な値らしいです。
SASの内部処理の話なので、ちょっと分かりづらいですね。


SASのテクニカルサポートにも聞いてみました。
聞いてみたうえで、やはり内部的な話なので、ハッキリと分かったわけではないんですが、

こう書いたとき結果がおかしくなるっていう例を以下に示します。

proc format;
   value $x
      "x" - high = "xx"
   ;
run;

data DT2;
   length x $40.;
   x = "x";
   y = 1;
run;

proc means data=DT2 nway completetypes noprint;
   var y;
   class x / preloadfmt;
   format x $x.;
   output out=OUT1 n= mean= / autoname;
run;

OUT1(SAS Ondemand for Academicsで開いたときの画面です😟


おかしいです。
上の例では、まずFORMATプロシジャの「high」に対する「sentinel value」がカテゴリとして扱われてしまうようです。

この「sentinel value」ですが、上の結果のイメージではxの2行目のところで「?」と出ていて、これはSASのセッションエンコーディングで有効な文字ではないため。

上の結果のイメージは、SAS Ondemand for Academicsで見たときのものです。
他の環境によっては、使用しているSASのセッションエンコーディングで有効な文字ではない場合に、見た目上は欠損値に見えるので、余計混乱のもとになりそうです。

あと、色々試してみましたが、この問題に起因した問題がまだあるような気がしました。
ひとまず、こういう使い方をする場合は注意が必要そうです。そもそもリファレンスにこの事について記載されてるってことはバグではなく仕様ってことなんでしょうか。修正してほしい。。



2015年3月13日金曜日

LAG関数とIFN / IFC関数のコンボ技



LAG関数の挙動には注意が必要です。
以下のプログラムを実行してみて、予想した結果になるか見てみましょう。

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

*** 例1: 2個前の値をとってくる ;
data OUT1;
   set DT1;
   Y = lag2(X);
run;
 X 
Y
  1   
  .    
  2
  . 
  3
  1
  4
   
  5
  3  


*** 例2: IFステートメントを含む場合 ;
data OUT2;
   set DT1;
   if X ^= 4 then Y = lag2(X);
run;
 X 
Y
  1   
  .    
  2
  . 
  3
  1
  4
   
  5
  2 




例1は予想通りだと思いますが、例2はどうでしょう?
結果の赤文字で示した部分が「3」になると思ってたら要注意です。


解説


ここから、別記事「LAG関数の動き」で紹介したイメージをもとに説明するので、この別記事を読んでいないと分かりづらい部分あるかも。



まずは以下、「Y = LAG2(X)」と書いたときの内部処理のイメージです。
(自分なりの解釈になっている可能性があるかもしれませんので、あしからず・・)

まず、LAG関数によって内部で「キュー」と呼ばれる仕組みを利用しています。





続いて、「if X ^= 4 then Y = lag2(X)」というように、IFステートメントと組み合わせたときの内部処理のイメージ。

(こちらも自分なりの解釈になってる可能性あり)



LAG関数が実行されないと、キューの中身が更新されないことによるものですね。





ここでもし、「IF条件が通った時だけ、2行前の値を取ってきたい」という場合は、以下のようにLAG関数が毎回実行されるように工夫します。

data OUT3;
   set DT1;
   Y2 = lag2(X);
   if X^= 4 then Y = Y2;
   drop Y2;
run;

 X 
Y
  1   
  .    
  2
  . 
  3
  1
  4
  .  
  5
  3  



ここからちょっとした裏技を紹介。
上記の処理は、LAG関数とIFN・IFC関数を組み合わせることによって、より簡潔に書くことが出来ます。

data OUT3;
   set DT1;
   Y = ifn( X^=4, lag2(X), . );
run;

 X 
Y
  1   
  .    
  2
  . 
  3
  1
  4
  .  
  5
  3  



解説


IFN・IFC関数の構文は以下の通り。

 IFN(  条件式 ,  条件がTRUEの場合の戻り値 ,  条件がFALSEの場合の戻り値  )

 ・・・ 戻り値が数値の場合はIFN、文字の場合はIFC関数を使います。

そしてこの関数、1つ面白い性質があります。

この関数が実行されるとき、
第1引数の条件式の結果がなんであろうと、第2・第3引数は、内部で両方とも処理(計算)が行われているようです。


つまり今回の 「ifn( X^=4, lag2(X), . )」 は、
条件式「X^=4」の結果がTRUEだろうがFALSEだろうが、内部では毎回こっそり第2引数の「lag2(X)」が実行されています。
毎回LAG関数が実行されることで、2行前の値を取ってくることができているわけです。

この性質、他にも使えそうですね。なんか思いつきそうで思いつかないですが。