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 
     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
     
     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. 範囲の指定