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ステートメント」から解説をしていきます。
データを読み込む際、以下の変換をする「フォーマット」と呼ばれるものを定義するステートメントです。

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

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

2017年12月25日月曜日

SASでクリスマスカードを作る。



ぎりぎり間に合った・・・。
SASでクリスマスカード作ってみました。やっつけ仕事みたいにすごいシンプルですが。



























以下がプログラムです。
SAS9.4で動作します。あとHTML出力がONになっている前提です。

*** 描画用データ作成 *********;
* ツリー部分 ;
data DT1;
input X Y;
cards;
0 30
50 30
50 50
10 50
35 80
20 80
40 100
30 100
45 120
40 120
60 150
80 120
75 120
90 100
80 100
100 80
85 80
110 50
70 50
70 30
125 30
210 30
;
run;

* 星部分 ;
data DT2;
  call streaminit(201712);
  do X2=0 to 210 by 10;
    STAR = rand('uniform')*180;
    output;
  end;
run;

data DT3;
  set DT1 DT2;
run;


*** 描画 ******************;
proc sgplot data=DT3 noautolegend ;
   styleattrs wallcolor=black;
   series x=X y=Y / lineattrs=(thickness=7 color=white);
   scatter x=X2 y=STAR / markerattrs=(symbol=star size=0.6cm color=yellow);
   xaxis min=0 max=210;
   yaxis min=0 max=180;
   inset "Happy Holidays" / position=right textattrs=(color=white size=30cm);
run;



今年はあと1回記事書くか、書かないかって感じですが、あとちょっとで2018年ですね。
お疲れさまでした。

ブログに立ち寄っていただいたり、直接アイディアを頂いたり、みなさん有難う御座いました。
良い年末年始をお過ごしください!!

2017年12月15日金曜日

PROC DS2 の MATRIXパッケージ による行列計算


以前、行列計算をFCMPプロシジャで実現する方法を紹介しました。
FCMPプロシジャと行列計算


同様に、PROC DS2 の MATRIXパッケージというものを使って行列計算を行う事も出来ます。


簡単な例として、以下の連立方程式をMATRIXパッケージによる行列計算で求めてみたいと思います。








行列で解を求める式はこんな感じでしょうか。










proc ds2;
    data OUT1 / overwrite=yes;

    /* 配列を定義 */
    dcl double m1  [3,3];
    dcl double m2  [3,1];
    vararray double out [3,1];

    /* matrixを定義 */
    dcl package matrix _m1;
    dcl package matrix _m2;
    dcl package matrix _inv;
    dcl package matrix _mult;

    method run();

        /* 計算対象の行列をいったん配列に格納 */
        m1 := (1 1 1 2 4 3 5 3 2);
        m2 := (6 19 17);

        /* 配列を行列にSET */
        _m1   = _new_ matrix( m1, 3, 3 );
        _m2   = _new_ matrix( m2, 3, 1 );

        /* 逆行列 と 行列の積 を求める */
        _inv  = _m1.inverse();
        _mult = _inv.mult(_m2);

        /* 結果を配列に格納 */
        _mult.out(out);

    end;

    run;
quit;

データセットOUT1
 out1  out2  out3 
 1 2 3

 x=1, y=2, z=3 という結果が得られました。


いちいち配列をかませないといけないのがちょっと面倒。