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 秒




注意


環境や設定によって挙動が異なります。
例えば、SAS Ondemand for Academicsについては、私の環境下では対話型モードがONになっていないので、

以下のプログラムを実行すると、、まずは、ちゃんと実行されます。

proc sql;
  select * from SASHELP.CLASS;


次に以下を実行すると、対話型モードになっていないため、ERRORになります。

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

ログ
ERROR 180-322: ステートメントが有効でないか、適切な順序で使用されていません。


ただ、上の青文字と赤文字のプログラムをいっぺんに実行した場合、構文としてはおかしくないので、ちゃんと実行はできます。
(ちなみに、SAS Ondemand for Academicsの場合、対話型モードをONにすると、他の機能の挙動が変わってしまって面倒なので、あまりデフォルトの設定をいじらない方がよいかも。。)


2018年1月23日火曜日

PROC FORMAT入門3 : 範囲の指定




📝 前置き

今回はフォーマット(VALUEステートメント)における範囲の指定方法を説明します。
インフォーマット(INVALUEステートメント)では、使用されるケースが少ないので説明は割愛。もし使用する場合は別途SAS社のリファレンスを参照下さい。

あと、ミスしがちな落とし穴についても、最後の方に記事のリンクを載せてるので、そちらも是非参照下さい。




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



*** フォーマットを定義 ;
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」は最小値という意味になる
  ・数値フォーマットでは「low」に欠損値は含まれない
  ・文字フォーマットでは「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以外の値に"その他"を割り当てる

  また、otherには欠損値も含む。
  ただし、以下のように欠損値の定義が既にある場合、otherに欠損値は含まない

             . = "欠損値"
             1 - 3 = "1~3"
             other = "その他"




ちなみに、

文字フォーマットにも「"xxx" - "yyy"」みたいな範囲を設定できますが、実際に使用されるケースが少ないため、詳しい挙動説明は割愛します。
もし、使う場合「文字」の範囲である事に気をつけてください。
例えば、以下のように「文字変数に数字が入っていて、それに文字フォーマットを適用する場合」

*** フォーマットを定義 ;
proc format;
   value $TEST2_
      "1" - "100" = "aaa"
      other = "その他"
   ;
run;

*** フォーマット変換 ;
data DT1;
   length X2 Y2 $20.;
   do X2="1", "2", "10";
      Y2 = put(X2,TEST2_.);
      output;
   end;
run;
 X2  Y2 
  1  aaa 
  2  その他 
  10 aaa

文字としての範囲「"1"~"100"」には「"1"」「"10"」は当てはまりますが、「"2"」は当てはまりません。
繰り返しになりますが「文字フォーマットは、文字の範囲」であることに注意。




注意事項・落とし穴

2018年1月19日金曜日

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




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



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

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


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

data OUT;

    set DT1;

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

        length PURDATE 8.;
        call missing( PURDATE );

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

    end;

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

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

かなりシンプルになりました!
「DO_OVERメソッド」は、「FINDメソッド」「FIND_NEXTメソッド」を組み合わせたようなやつです。

例では「RESET_DUPメソッド」というものも使用していますが、実は今回の例では不要。
どういう時に使用するかは、以下に記載しています。


📝DO_OVERの注意点

今回の例のように、

do while ( purhash.do_over()=0 );

DO WHIILE & DO_OVERで、検索中のkey値がHashオブジェクト内になくなるまでループする必要があります。

もし、検索中のkey値がHashオブジェクト内にまだある状態で、途中からkey値を手動で変えてしまうと、DO_OVERの結果がバグります。
バグらないようにするには「RESET_DUPメソッド」を使って、Hashオブジェクト内の検索位置をリセットする必要があります。

詳細はマニュアルを参照下さい。



📝その他注意

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


2018年1月10日水曜日

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




FORMATプロシジャは「1→男、2→女」や「Y→YES、N→NO」のような読み替え・変換を行う事が出来るプロシジャです。


今回はもっとも基本的な「VALUEステートメント」から解説をしていきます。
※ VALUEステートメントの重要な性質(落とし穴)についても、解説記事のリンクを最後の方に載せてるので、そちらも要確認!



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


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

proc format;

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

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

  • 「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ステートメント
2. INVALUEステートメント
3. 範囲の指定