2018年1月31日水曜日

INFORMATの落とし穴【デフォルト長】




以下のプログラムと結果をご覧ください。結果は想定通りでしょうか?

proc format;
    invalue MYFMT
    "abc"   = 1
    ;
run;

data DT1;
    X = "abcdefg";
    Y = input( X , MYFMT. );
run;

  X    Y  
  abcdefg   1 

変数Y が 1 という結果はおかしいですよね。これって

・INFORMATの値   …  "abc"
・変数X の値           …  "abcdefg"

といった感じで、値が一致してないのに、一致してるかのように変換が行われています。




原因

これは、INFORMATの「デフォルト長 (Default Length)」というものが関係してます。
例えば、今回の例では、、

proc format;
    invalue MYFMT
    "abc"   = 1
    ;
run;

左側に定義した値 "abc" の文字の長さは3バイトです。
文字値から数値に変換するINFORMATの場合、この長さがそのINFORMATの「デフォルト長」というものになります。
(定義した値が複数ある場合は、文字の長さが最も大きいものがそのINFORMATの「デフォルト長」になる)



そしてこのINFORMATは、以降の処理でデフォルト長となった3バイトしか視界に入らなくなります。
どういう事かというと、、

data DT1;
    X = "abcdefg";
    Y = input( X , MYFMT. );
run;

変数X の値 "abcdefg" から先頭の3バイトの値 "abc" だけを見てINFORMATによる変換が行われるという事です。



ちょうど PROC FORMAT で"abc" を定義してたので、変換しちゃったってわけですね。

proc format;
    invalue MYFMT
    "abc"   = 1
    ;
run;



解決策

変数X の値 "abcdefg" の文字の長さは7バイトなので、7バイト全部読み込んで変換する必要があります。
解決策としては以下の2つの方法があります。


解決策①
proc format;
    invalue MYFMT (default=20)
      "abc"   = 1
    ;
run;

data DT1;
    X = "abcdefg";
    Y = input( X, MYFMT. );
run;

   Y  
  abcdefg   . 

「DEFAULT=オプション」によってこのINFORMATのデフォルト長を 20 に広げています。


解決策②
proc format;
    invalue MYFMT
      "abc"   = 1
    ;
run;

data DT1;
    X = "abcdefg";
    Y = input( X, MYFMT20. );
run;

   Y  
  abcdefg   . 

INPUT関数による変換時に、INFORMAT名の後ろに読み込む幅を指定することも出来ます。




See also

フォーマットでも同様の落とし穴があります。
FORMATの落とし穴【デフォルト長】


ポイント
・文字値から数値に変換するインフォーマットの場合、「左側」に定義した文字の長さがデフォルト長になる。
・フォーマットの場合、「右側」に定義した文字の長さがデフォルト長になる。

2018年1月25日木曜日

RUNとQUITの違い【対話型プロシジャ】





プロシジャには「RUN」で終わらせるものと、「QUIT」で終わらせるものがあります。



RUNで終わる例
proc print data=SASHELP.CLASS;
run;

proc means data=SASHELP.CLASS;
   var HEIGHT;
run;


QUITで終わる例
proc datasets nolist;
  copy in=SASHELP out=WORK;
  select CLASS;
run;
quit;


proc sql;
  select *
  from SASHELP.CLASS;
quit;




「RUN」と「QUIT」どちらで終わるのかは、プロシジャが対話型かどうかで決まります。
対話型について、以下SAS社FAQを見ると分かり易いと思います。

「対話型プロシジャの終了方法」
https://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/ba022.html


つまり、
通常は「RUN」でプロシジャを終了させることが出来ますが、対話型プロシジャの場合は「RUN」で実行されるものの、プロシジャは終了しません。
「QUIT」と書くか、別のDATAステップ・PROCステップが現れるまで、プロシジャは起動したままになります。



対話型プロシジャの例



・DATASETSプロシジャの場合

以下を実行したとします。
proc datasets nolist;
  copy in=SASHELP out=WORK;
  select CLASS;
  run;


確かに「RUN」のタイミングで実行はされるんですが、「QUIT」がないのでプロシジャは開いたままです。なので以下のように続けて書くことが出来ます。

  copy in=SASHELP out=WORK;
  select BASEBALL;
  run;
quit;




・SQLプロシジャの場合

以下を実行したとします。
proc sql;
  select * from SASHELP.CLASS;


「QUIT」でプロシジャを終了させていないので、以下のように続けて書くことが出来ます。

  select * from SASHELP.CLASS where AGE = 13;
quit;


(※SQLプロシジャの場合は「RUN」ではなく「;」で実行されるので、他の対話型プロシジャとはちょっと異なる)


SQLプロシジャの対話型機能をつかった書き方については、以前ちょっと紹介してます。
複数のSQL文は、ひとつのPROC SQLにまとめて書くことが出来る




対話型プロシジャかどうかの見分け方


私の場合、
「RUN」の実行後に以下のようなLogメッセージが出ない場合は、プロシジャの処理が完了していないという事になるので、「対話型」として考えてます。

NOTE: PROCEDURE ●●処理(合計処理時間):
      処理時間           X.XX 秒
      CPU時間            X.XX 秒


2018年1月23日火曜日

PROC FORMAT入門2 : 範囲の指定




まずは以下の例をご覧ください。



*** フォーマットを定義 ;
proc format;
   value TEST1_
      1 - 3 = "1~3"
      other = "その他"
   ;
run;

*** フォーマット変換 ;
data DT1;
   length X 8. Y $20.;
   do X=1 to 5;
      Y = put(X,TEST1_.);
      output;
   end;
run;
  X   Y 
  1  1~3 
  2  1~3
  3 1~3
  4 その他  
  5 その他



FORMATプロシジャの部分でやっているのは、

proc format;
   value TEST1_
      1 - 3 = "1~3"
      other = "その他"
   ;
run;

1~3 の値だったら、"1~3" というフォーマットをあて、
それ以外の値だったら、"その他" というフォーマットをあてるように定義しています。


このように範囲に対してフォーマットをあてることができます。
範囲の指定方法は以下のようなものがあります。



範囲の指定方法

  指定例    意味   解説
  1, 2, 3

  1 or 2 or 3

  「,」で区切って含める値を指定

  1-10

  1~10 

  「-」で範囲を指定

  low-10

  10以下 

  「low」は欠損値以外の最小値という意味になる

  1-high

  1以上

  「high」は欠損値以外の最大値という意味になる

  1-<10 


  1以上,10未満  


  「<」で「未満」や「超」の意味を持たせることができる。

   例えば、以下のような感じ。
            1 -< 10 = "1以上,10未満"
            1 <- 10 = "1超,10以下"
            1 <-< 10 = "1超,10未満"

  other


  その他


  いずれの条件にも一致しない場合のFORMAT値を指定
  例えば、フォーマットの指定で、

             1 - 3 = "1~3"
             other = "その他"

  とすると、1~3以外の値に"その他"を割り当てます。






PROC FORMAT入門 : 記事一覧


2018年1月19日金曜日

HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法【DO_OVER編】




ちょっと前に以下の記事を書きました。
HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法



で、マニュアルをよくよく見ると、SAS9.4から追加になった 「DO_OVERメソッド」を使えばもっと楽に書けたんですね。勉強不足すぎ・・

データステップ100万回でも取り上げられてて、そういえばこの記事読んだとき「おー!!」と思った記憶があった。いつこの感動を忘れてしまったんだろう・・
ハッシュオブジェクト感動物語(おまけ付き)



とういわけで、前書いた記事の訂正、というか追記です。
なので、サンプルデータの作成部分とか諸々の説明などは前書いた記事を参照下さい。


以下が訂正したプログラム

data OUT;

    set DT1;

    /* 購入データをHashオブジェクトに格納 */
    if _n_=1 then do;

        length BUYDATE 8.;
        call missing( BUYDATE );

        dcl hash buy( dataset:"DT2", multidata:"y" );
        buy.definekey("NO");
        buy.definedata("BUYDATE");
        buy.definedone();

    end;


    /* Hashオブジェクト内をループして、顧客IDごとの購入日の最小値を取得 */
     do while ( buy.do_over()=0 );
          MINDATE = min( MINDATE, BUYDATE );
     end;

     format MINDATE yymmdd10.;
     keep NO SEX AGE MINDATE;
run;

かなりシンプルになりました!


「DO_OVERメソッド」は、「FINDメソッド」「FIND_NEXTメソッド」を組み合わせたようなやつです。
このメソッドを使う場合は一応マニュアルも見といた方がいいです。

(do whileのループの途中で検索するKey値を変えるような場合は、Hashオブジェクト内の検索位置を先頭に戻すために「RESET_DUPメソッド」を使う必要があったり、などよく動きを理解する必要があるので)


📝注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。

2018年1月10日水曜日

PROC FORMAT入門1 : VALUEステートメント




複雑に入り組んだFORMATプロシジャに鋭いメスを入れ、謎や疑問を徹底的に究明したいと思います。
FORMATプロシジャはザックリいうと、

1→男、2→女」 や 「Y→YES、N→NO

のような読み替え・変換を行う事が出来るプロシジャです。
今回はもっとも基本的な「VALUEステートメント」から解説をしていきます。




ではまず読み替えを行うフォーマットの定義から。


フォーマットを定義する例

proc format;

   value TEST1_
      1 = "MALE"
      2 = "FEMALE"
   ;

   value $TEST2_
      "Y" = "YES"
      "N" = "NO"
   ;
run;


上の例では 「PROC FORMAT」を使って、以下を定義しています。

・「TEST1_」というフォーマットを定義し、「1→MALE、2→FEMALE」 という読み替えを定義
・「TEST2_」というフォーマットを定義し、「Y→YES、N→NO」 という読み替えを定義



構文
 PROC FORMAT;

      VALUE フォーマット名
         値  =  割り当てる文字
         値  =  割り当てる文字
         ・・・
     ;
 RUN;



ポイント

・フォーマット名の最後は数字じゃダメ(例えば「TEST1」など)
・「"Y"→"YES"」のような文字から文字への読み替えを行うフォーマットは、フォーマット名の先頭に「$」をつける(例えば「$TEST」など)





では、先程つくったフォーマットを使ってみましょう。


変数にフォーマットを割り当てる例

* Sample data ;
data DT1;
   SEX=1; YN="Y"; output;
   SEX=2; YN="N"; output;
run;
SEX  YN 
 1 Y
 2 N


* 変数にフォーマットを割り当てる ;
data DT2;
   set DT1;
   format SEX TEST1_. YN TEST2_.;
run;
SEX  YN 
 MALE YES 
 FEMALE  NO



上の青字部分では 「FORMATステートメント」 を使って以下を行なっています。

・変数SEX に フォーマット「TEST1_」を割り当て。
・変数YN に フォーマット「TEST2_」を割り当て。

結果を見ると分かりますが、SEX と YN がそれぞれ対応するフォーマット値に読み替えられていますね。



構文
 FORMAT  変数名  割り当てるフォーマット名. ;


ポイント

・割り当てるフォーマット名の後ろにドット「.」を入れる必要があります。
・あくまでも変数にフォーマットを割り当てているだけです。
(例えば上の例でSEXの値は「MALE」「FEMALE」に置き換わったように見えますが、それは見た目だけで、内部では元の値「1」「2」のままです。)





変数値をフォーマット変換する例

* Sample data ;
data DT1;
   SEX=1; YN="Y"; output;
   SEX=2; YN="N"; output;
run;
SEX  YN 
 1 Y
 2 N


* フォーマット変換する ;
data DT3;
   set DT1;
   length SEX2 YN2 $20.;
   SEX2 = put( SEX, TEST1_.);
   YN2  = put( YN , $TEST2_.);
run;
SEX 
 YN 
SEX2
 YN2 
 1
 Y 
 MALE
 YES 
 2 
 N
 FEMALE 
 NO



上の青字部分では 「PUT関数」 を使って以下を行なっています。

・変数SEX の値を フォーマット「TEST1_」を使って変換し、結果を変数SEX2 に格納。
・変数YN の値を フォーマット「TEST2_」を使って変換し、結果を変数YN2 に格納。



構文
 新規変数名 = PUT(  変数名 ,  フォーマット名.  );


ポイント
フォーマット名の後ろにドット「.」を入れる必要があります。
・先程の例では見た目だけフォーマット値にしていましたが、今回は値そのものをフォーマット値に変換しています。




注意事項




PROC FORMAT入門 : 記事一覧

1. VALUEステートメント