2018年2月20日火曜日

PROC FORMAT入門4 : PICTUREステートメント




「PICTUREステートメント」は、
「1→001、12→012」とか「1234→12-34、5678→56-78」のように数値に特定の書式を割り当てます。



📝 前置き


構文

PROC FORMAT;
      PICTURE フォーマット名
          数値  =  '書式の定義'  (オプション)
          数値  =  '書式の定義'  (オプション)
          ・・・
      ;
RUN;

  • フォーマット名の末尾は数字NG(例えば「TEST1」など)
  • 等号の左辺の「数値」の指定方法は、VALUEステートメントの数値フォーマットに対する指定方法と同じ
    • 等号の右辺の「書式の定義」と「オプション」の指定方法については以下詳しく解説していきます。



    例
    * PICTUREフォーマットの定義 ;
    proc format;
       picture NINE
           0 - high = "99-99"
       ;
       picture ZERO
           0 - high = "00-00"
       ;
    run;

    • フォーマット「NINE」を定義して、出力する値の書式を '99-99' としています。9の意味は「先頭の数値がないところを0で埋める」という意味で、今回の場合は「123→"01-23"」や「1→"00-01"」のように変換


    • フォーマット「ZERO」を定義して、出力する値の書式を '00-00' としています。0の意味は「先頭の数値がないところを半角スペース(空白)で埋める」という意味で、今回の場合は「123→" 1-23"」や「1→"    1"」のように変換


    この書式設定に使う 0 や 9 を「数字セレクタ」といいます。
    (1 ~ 8 も 9 と同じ意味を持つ数字セレクタです)

    '00-99' のように 0 と 9 の組み合わせ方によって挙動が変わるので、そこんところは挙動確認必須です!
    また、数字セレクタ設定時は 'xyz99-99' のように先頭を数字セレクタ以外の文字にすることは出来ません。



    ではフォーマットを使ってみます。

    * Sample Data ;
    data DT1;
    input X;
    cards;
    1
    12
    123
    1234
    12345
    ;

    * フォーマット変換してみる ;
    data DT2;
       set DT1;
       length NINE ZERO $10.;
       NINE = put(X,NINE.);
       ZERO = put(X,ZERO.);
    run;
     X  NINE  ZERO 
    1 
     00-01      1
       12 
     00-12    12
      123 
     01-23  1-23
      1234 
     12-34 12-34
      12345 
     23-45 23-45


    気をつけたいのが、今回「数字セレクタ」の部分を「00-00」のように4桁しか定義してないから、最後のオブザベーションの結果が「12345→23-45」って感じで桁が足りなくて「1」が切れちゃってるとこですかね。





    PICTUREフォーマットのオプション


    ※オプションの組み合わせ・使用方法によっては、動作しなかったり動きが変わるため要挙動確認!

    オプション内容プログラム例
     ( NOEDIT )

    0~9を数字セレクタではなく、文字として扱う
    proc format;
      picture test2_
      0 - high = '00-00' (noedit);
    run;

    フォーマット変換の例
    「123」→「00-00」
    「1」    →「00-00」

     ( PREFIX = '文字' )

    フォーマット変換した値の先頭に表示する文字の指定
    ※「default=オプション」を併用しないと文字切れする可能性あり
    詳細は記事の下の方で解説
    proc format;
      picture test3_ (default=5)
      0 - high = '0000' (prefix='-');
    run;

    フォーマット変換の例
    「123」→「 -123」
    「1」    →「   -1」

     ( MULTIPLIER = 数値 )

    フォーマット変換前に変数値に掛ける数値を指定
    ※ただし以下リンクの通り、かなり取扱い注意なオプション
    (http://sas-boubi.blogspot.jp/2018/03/proc-formatpicture.html)
    proc format;
      picture test4_
      0 - high = "0000" (multiplier=10);
    run;

    フォーマット変換の例
    「123」→「1230」
    「1」    →「  10」

     ( FILL = '文字' )

    右の例のように数字セレクタで「00-00」みたいに指定した事で、
    フォーマット変換後に先頭が半角スペース(空白)となった部分を
    指定文字で埋める
    ※「default=オプション」と併用すると
    何故か「fill=」の指定文字が先頭に1つ強制付与される事がある

    proc format;
      picture test1_
      0 - high = '00-00' (fill='*');
    run;

    フォーマット変換の例
    「123」→「*1-23」
    「1」    →「****1」





    PREFIXオプションだけ詳細説明しときます。
    たとえば以下にマイナスの値を含むデータがあります。

    * Sample Data ;
    data DT3;
    input X;
    cards;
    -12
    12
    ;
     X 
    -12 
     12 



    このデータを以下のようにフォーマット変換すると、、

    * Mistake (間違っている例) ;
    proc format;
       picture TEST1_ (default=3)
         low - high = '00'
       ;
    run;

    data DT4;
       set DT3;
       length Y $20.;
       Y = put(X,TEST1_.);
    run;
     X  Y 
    -12 
      12 
     12 
      12 

    数値部分だけになり、マイナス記号が消えてしまいます。



    ならば、'-00' という書式を用意してみますが、、

    * Mistake (間違っている例) ;
    proc format;
       picture TEST1_ (default=3)
         low -< 0 = '-00'
         0 - high = '00'
       ;
    run;

    data DT4;
       set DT3;
       length Y $20.;
       Y = put(X,TEST1_.);
    run;
     X  Y 
    -12 
      12 
     12 
      12 

    やっぱりマイナス記号消えちゃいます。
    PICTUREフォーマットは、'-00' みたいに「書式の先頭に文字を入れてもその文字は無視される」という仕様になっています。



    てことで、先頭に文字を入れたい場合は、prefixオプションを使います。

    * 正しい例 ;
    proc format;
       picture TEST1_ (default=3)
         low -< 0 = '00' (prefix="-")
         0 - high = '00'
       ;
    run;

    data DT4;
       set DT3;
       length Y $20.;
       Y = put(X,TEST1_.);
    run;
     X  Y 
    -12 
     -12 
     12 
      12 


    ちなみに「picture TEST1_ (default=3)」って感じで、defaultオプションというのを指定しないと文字切れするケースがあるのでご注意を!

    これは「デフォルト長 (Default Length)」というものが関係してます。
    例えば、今回の例で defaultオプションがない状態で実行すると、、

    proc format;
       picture TEST1_
         low -< 0 = '00' (prefix="-")
         0 - high = '00'
       ;
    run;

    等号の右側に定義した値 "00" の文字の長さは2バイトです。
    PICTUREステートメントではこの長さが「デフォルト長」というものになります。
    (定義した値が複数ある場合は、右側の文字の長さが最も大きいものがそのフォーマットの「デフォルト長」になる)


    このデフォルト長となった2バイトが、フォーマット適用後の長さになります。
    どういう事かというと、例えば変数値が「-12」の時、適用されるフォーマットは以下太文字部分のやつですが、、

    proc format;
       picture TEST1_
         low -< 0 = '00' (prefix="-")
         0 - high = '00'
       ;
    run;

    フォーマット適用後の文字値 "-12" から「デフォルト長の2バイト」に切られた "12" という文字値になってしまいます。
    このような文字切れを起こさないために defaultオプションでデフォルト長を指定する必要があります。

    ちなみに、defaultオプションにも少し注意が必要で、例えば「default=10」と指定すると
    変数値「12」→ フォーマット変換後の値「        12」
    って感じでdefaultオプションの長さの分だけ幅が確保された上で右詰めにされた値になります。



    2018年2月15日木曜日

    ODS OUTPUT の落とし穴 (2)



    以下記事の続き。
    ODS OUTPUT の落とし穴 (1)



    以下のプログラム、WARNINGが出ちゃいますが、どこがおかしいか分かりますか?

    proc glm data=sashelp.class;
       class sex;
       model height = sex;
    run;

    ods output Summary=OUT1;
    proc means data=sashelp.class;
       var height;
    run;
    ods output close;


    ログ
    WARNING: 出力'Summary'は作成されていません。出力オブジェクト名、ラベル、パスが正しく記述されているかを確認してください。また、要求した出力オブジェクトを作成するために、
             適切なプロシジャオプションが使われているかも確認してください。たとえば、NOPRINTオプションが使われていないことを確認してください。
    WARNING: プロシジャステートメントの終わりを検出したので、現在のODS SELECT/EXCLUDE/OUTPUTステートメントをクリアしました。
             対話型プロシジャ(終了するにはquit;をタイプします)が終了していないかもしれません。



    この落とし穴にはまってる人たまに見かけます。


    これは対話型プロシジャを使う場合に起こる落とし穴です。
    (対話型プロシジャについては、「RUNとQUITの違い」をご覧ください。)


    今回の例で使用しているGLMプロシジャは対話型プロシジャです。
    「QUIT」と書くか、別のDATAステップ・PROCステップが現れるまで、GLMプロシジャは起動したままになります。
    なので、以下の青文字部分がGLMプロシジャに対する処理だと判断されてしまいます。


    proc glm data=sashelp.class;
       class sex;
       model height = sex;
    run;

    ods output Summary=OUT1;
    proc means data=sashelp.class;
       var height;
    run;
    ods output close;



    解決方法はご存じの通り、「quit;」を入れるだけです。

    proc glm data=sashelp.class;
       class sex;
       model height = sex;
    run;
    quit;

    ods output Summary=OUT1;
    proc means data=sashelp.class;
       var height;
    run;
    ods output close;




    そもそもSASのマニュアルにも対話型プロシジャにQUITを入れていない例があるので、
    この問題に気づかなくても無理はないっちゃないんですが。。


    ちなみに、今回紹介した落とし穴は「ods output」だけでなく、「ods select」「ods exclude」とかでも同様に起こる問題です。

    2018年2月6日火曜日

    PROC FORMAT入門2 : INVALUEステートメント




    今回は「INVALUEステートメント」について。
    データを読み込む際、以下の変換をする「インフォーマット」と呼ばれるものを定義するステートメントです。

    • 文字値から数値(例:男→1、女→2)
    • 文字値から文字値(例:Y→YES、N→NO)


    ※ INVALUEステートメントの重要な性質(落とし穴)についても、解説記事のリンクを最後の方に載せてるので、そちらも要確認!



    実際、例を見た方が分かり易いです。まずは変換を行うインフォーマットの定義から。



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

    proc format;

       invalue TEST1_
          "MALE"     = 1
          "FEMALE" = 2
       ;
       invalue $TEST2_
          "Y"    = "YES"
          "N"    = "NO"
       ;
    run;

    • 「TEST1_」というインフォーマットを定義し、「"MALE"→1、"FEMALE"→2」 という変換を定義(数値に変換するインフォーマットを「数値インフォーマット」という)

    • 「TEST2_」というインフォーマットを定義し、「"Y"→"YES"、"N"→"NO"」 という変換を定義(文字値に変換するインフォーマットを「文字インフォーマット」という)



    構文
    PROC FORMAT;

          INVALUE インフォーマット名
             値  =  変換後の値
             値  =  変換後の値
             ・・・
         ;
    RUN;


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



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


    インフォーマットを使う例①

    data DT1;
       length SEX 8. YN $3.;
       input SEX YN;
       informat SEX TEST1_. YN $TEST2_.;
    cards;
    MALE Y
    FEMALE N
    ;
     SEX  YN 
      1  YES 
      2  NO


    CARDSで読み込むデータに対して「INFORMATステートメント」で以下を行なっています。

    • インフォーマット「TEST1_」を使い「"MALE"→1、"FEMALE"→2」に変換した値を変数 SEX に格納
    • インフォーマット「TEST2_」を使い「"Y"→"YES"、"N"→"NO"」に変換した値を変数 YN に格納



    構文
    INFORMAT  変数名  インフォーマット名. ;


    ポイント
    • テキストファイル や CARDS で読み込むデータに対して、インフォーマット変換した値を変数に格納する。
    • インフォーマット名の後ろにドット「.」を入れる必要があります。




    インフォーマットを使う例②

    * Sample data ;
    data DT1;
       length SEX $6. YN $1.;
       SEX="MALE"; YN="Y"; output;
       SEX="FEMALE"; YN="N"; output;
    run;

    SEX  YN 
     MALE  Y
     FEMALE  N


    * 変数値をインフォーマットで変換する ;
    data DT3;
       set DT1;
       length SEX2 8. YN2 $3.;
       SEX2 = input( SEX, TEST1_.);
       YN2  = input( YN , $TEST2_.);
    run;

    SEX 
     YN 
     SEX2 
     YN2 
     MALE
     Y 
      1
     YES 
     FEMALE  
     N
      2 
     NO



    上の例では「INPUT関数」を使って以下を行なっています。
    • 変数 SEX の値を インフォーマット「TEST1_」を使って変換し、変数 SEX2 に格納。
    • 変数 YN の値を インフォーマット「TEST2_」を使って変換し、変数 YN2 に格納。



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


    ポイント
    • 文字変数に対して、インフォーマットを使って数値または文字に変換します。
    • インフォーマット名の後ろにドット「.」を入れる必要があります。




    注意が必要なインフォーマットの細かい挙動

    proc format;

       * (1) 数値インフォーマット1 ;
       invalue X1_ (default=20)
          "1" = 111
          other = 999
       ;
       * (2) 数値インフォーマット2 ;
       invalue X2_ (default=20)
          1 = 111
          other = 999
       ;
       * (3) 文字インフォーマット1 ;
       invalue $X3_ (default=20)
          "1" = "111"
          other = "999"
       ;
       * (4) 文字インフォーマット2 ;
       invalue $X4_ (default=20)
          1 = "111"
          other = "999"
       ;
    run;

    * インフォーマットで変換 ;
    data DT4;
       length X $20. X1 X2 8. X3 X4 $20.;
       X = "+1.0";
       X1 = input(X,X1_.);
       X2 = input(X,X2_.);
       X3 = input(X,$X3_.);
       X4 = input(X,$X4_.);
    run;

    X 
     X1 
     X2 
     X3 
     X4 
     +1.0 
     999  111  999  999 


    上で作成した (2) のインフォーマットだけ、変換した結果が異なります。
    これは以下のような文字値から数値に変換する「数値インフォーマット」かつ青文字部分の定義で「左側に指定した数値をクォーテーションで囲っていない」場合、、

    proc format;
     invalue X2_ (default=20)
      1 = 111
      other = 999
     ;
    run;

    数値としてマッチした値に対して変換することが出来ます。
    例えば、文字変数Xの値「+1.0」を数値化すると「+1.0」→「1」なので、上の青文字部分の定義とマッチするので変換が行われます。

    その他の (1) (3) (4) で作成したインフォーマットでは、左側に指定した数値をクォーテーションで囲う・囲わない関係なく、文字値としてマッチした値のみ変換します。

    (ちなみに、上の例で「default=オプション」を指定しているのは、以下の注意事項の2個目で解説しているデフォルト長による落とし穴予防のためです)




    注意事項・落とし穴

    • インフォーマットで「文字値の欠損値」を表す場合「" " = "欠損値"」のような指定になります(「""」ではなく「" "」というように間に半角スペースを入れないと欠損値の意味にならない)
    • INFORMATの落とし穴【デフォルト長】




    PROC FORMAT入門 : 記事一覧

    2. INVALUEステートメント
    3. 範囲の指定

    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;

    X    Y  
      abcdefg   . 

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


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

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

    X    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」と一緒に使用すると正しく動かなくなる事があります。