2016年3月24日木曜日

SASのバージョンを取得する方法



SASのバージョン情報が格納されている3つのマクロ変数を紹介。
自宅で遊んでるSAS OnDemandのバージョン情報を出してみます。

   %put  &SYSVER;         *** ⇒ 9.4 ;
   %put  &SYSVLONG;    *** ⇒ 9.04.01M3P062415 ;
   %put  &SYSVLONG4;  *** ⇒ 9.04.01M3P06242015 ;


「SYSVLONG」「SYSVLONG4」 の方がメンテナンスレベル等、詳細な情報が格納されてます。


ちょっと前、「顧客にSASプログラムを納品したらERRORが出て動かないってクレームが来たよ。。」という話しを聞きました。

「顧客とその会社では同じSAS9.4を使ってて、なんでだろうねー」ってとこで話しは終わっちゃいましたが、メンテナンスレベルとかが異なってたのかもですね。
(上の例では 「9.04.01M3P062415」 なのでメンテナンスレベル3)

実は同じSAS9.4でもマイナーバージョンアップが行われて新機能が追加されたりするので、上のような事が起こり得るわけです。
他にもHotfix(不具合に対する修正プログラム)を当ててるかどうかっても挙動に影響します。


また、SAS/STATなどプロダクト毎にカスタムバージョンがあるのでその辺も確認しておく必要があるかと思います。
以下プロシジャでプロダクト毎のバージョンをログに表示できます。

  proc product_status;
  run;

2016年3月23日水曜日

SASで基本統計量を求める 【MEANSプロシジャ】




基本構文

PROC  MEANS  DATA=対象データセット;
      VAR  分析変数 ;
RUN;



基本統計量を出力

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




グループ分けして基本統計量を出力

proc means data=SASHELP.CLASS ;
   var AGE;
   class SEX;
run;


CLASSステートメントで、グループ分けして基本統計量を出力する事が出来ます。
上の例ではSEXの値「F」「M」毎に基本統計量を出しています。


ただし、CLASS変数が欠損値のオブザベーションは集計から除かれます。
欠損値も含めるには「CLASS 分類変数 / MISSING;」と記述します(詳細は以下記事を参照)

http://sas-boubi.blogspot.com/2017/03/missing_21.html




表示する統計量を指定する

proc means data=SASHELP.CLASS n min median max q1 q3 range;
   var AGE;
run;




表示したい統計量を指定できます。
  統計量   内容
    非欠損値の数
  NMISS  欠損値の数 
  SUM  合計
  MEAN  平均値
  MIN  最小値
  MEDIAN   中央値
  MAX   最大値   
  RANGE  範囲
  MODE  最頻値   
  STDDEV  標準偏差   
  STDERR  標準誤差   
  Q1  下側四分位点 
  Q3  上側四分位点  
  QRANGE  四分位範囲  
  KURT  尖度
  SKEW  歪度
  …etc


2016年3月22日火曜日

数値を四捨五入して文字変数に格納したい場合、PUT関数を使うと想定外の結果になる。



数値を四捨五入して文字変数に格納したい場合、PUT関数を使ってませんか?
以下の例を見てみましょう。

想定通りの結果
data DT1;
    length B $10.;
    A = 1.25;
    B = left(put(A, 12.1));
    put B=;
run;

ログ
B=1.3

小数第2位を四捨五入して文字化した変数が出来ました。
次はどうでしょう。


想定外の結果
data DT1;
    length B $10.;
    A = 4.1-4.05;
    B = left(put(A, 12.1));
    put B=;
run;

ログ
B=0.0

4.1-4.05 = 「0.05」 なので、小数第2位を四捨五入すると「0.1」となるはずが、「0.0」になりました。

これは浮動小数点誤差ってやつのせいですね。
http://sas-tumesas.blogspot.jp/2014/03/blog-post_14.html (データステップ)100万回)


つまり「4.1-4.05」は「0.05」ではなく「0.049999999・・・」みたいな値で格納されてます。
だからPUT関数で「0.0」という値が帰ってきたわけですね。



で、これをどうするかは各自(各社)いろいろ考え・やり方があると思います。
よくROUND関数をかませたりしますが、、、
data DT1;
    length B $10.;
    A = 4.1-4.05;
    B = left(put( round(A,0.1) ,12.1));
    put B=;
run;

ログ
B=0.1



📝ROUND関数には注意すべき動きがある

ROUND関数は「第1引数を第2引数の最も近い倍数に丸める関数」ですが、
その独特の動きや制限も重要です。以下にSAS社のドキュメントの記述を抜粋します
(※日本語ドキュメントは誤記がある?のでちょっと分かりにくいですが英語の方を示します。。)


The Effects of Rounding
Rounding by definition finds an exact multiple of the rounding unit that is closest to the value to be rounded. For example, 0.33 rounded to the nearest tenth equals 3*0.1 or 0.3 in decimal arithmetic. In binary arithmetic, 0.33 rounded to the nearest tenth equals 3*0.1, and not 0.3, because 0.3 is not an exact multiple of one tenth in binary arithmetic.

The ROUND function returns the value that is based on decimal arithmetic, even though this value is sometimes not the exact, mathematically correct result. In the example ROUND(0.33,0.1), ROUND returns 0.3 and not 3*0.1.

「第1引数を第2引数の最も近い倍数に丸める」処理において、10進数の算術と2進数の算術では「正確な倍数」の扱いの違いがあって、そこをROUND関数では調整してるような事が書かれているのでしょうか(間違っていたらご指摘をお願いします)


Producing Expected Results
In general, ROUND(argument, rounding-unit) produces the result that you expect from decimal arithmetic if the result has no more than nine significant digits and any of the following conditions are true:
  • The rounding unit is an integer.
  • The rounding unit is a power of 10 greater than or equal to 1e-15. (If the rounding unit is less than one, ROUND treats it as a power of 10 if the reciprocal of the rounding unit differs from a power of 10 in at most the three or four least significant bits.)
  • The result that you expect from decimal arithmetic has no more than four decimal places.

期待される結果を生成する条件が示されています。思ったより扱える範囲が狭い?
ちなみに文中の、significant digits と decimal places の意味を混同してるケースをよく見かけるので注意。



その他の挙動についてもドキュメントに書かれているので、要確認!
ROUND関数を単純に四捨五入するやつと思っていると、想定外の結果になるかもしれません。
例えば以下のように最後の桁が「8」か「9」かで結果が異なっています。
data DT1;
 A = round(1.049999999998,0.1);
 B = round(1.049999999999,0.1);
 put A= B=;
run;

ログ
A=1 B=1.1



というわけで、一概にこうすればいいってのはない気がします。
「私はこうしてます」 とか何かアイディアがあったら教えてほしいです。

2016年3月18日金曜日

SASで頻度集計とクロス集計 【FREQプロシジャ】




構文 : 頻度集計

PROC  FREQ  DATA=対象データセット;
       TABLES  対象変数  ;
RUN;

対象変数が欠損値のオブザベーションは集計から除かれます。
欠損値も含めるには「TABLES  対象変数  /  MISSING;」と指定します。



proc freq data=SASHELP.CLASS;
     tables AGE ;
run;







構文 : クロス集計

PROC  FREQ  DATA=対象データセット;
    TABLES  表側変数 *  表頭変数 ;
RUN;

表側または表頭変数が欠損値のオブザベーションは集計から除かれます。
欠損値も含めるには「TABLES  表側変数 * 表頭変数  /  MISSING;」と指定します。




proc freq data=SASHELP.CLASS;
     tables SEX * AGE;
run;


度数, パーセント, 行のパーセント, 列のパーセントが出力されます


対象変数が欠損値のオブザベーションが集計から除かれる件の詳細は以下参照。
http://sas-boubi.blogspot.com/2017/03/missing_21.html


2016年3月17日木曜日

RTF出力時に透かし文字を設定する 【WATERMARKオプション】


SAS9.3からTAGSETS.RTF の 「WATERMARKオプション」 で、透かし文字を設定できます。


”Sample” という透かし文字を設定してみます。
ods tagsets.rtf file="出力するRTFファイルのフルパスを指定" options( watermark = 'Sample' ) ;

   proc print data=SASHELP.CLASS;
   run;

ods tagsets.rtf close;




IF-THEN-ELSE入門 【条件分岐処理】


IF-THEN-ELSEステートメントは条件によって処理を分岐する場合に使います。




構文1

IF  条件式  THEN  処理 ;

  • 「条件式」を満たす場合に、指定した「処理」を実行する。




data DT1;
   set SASHELP.CLASS;
   if AGE=11 then X=1;
run;
  • 「AGE=11」 だったら、「変数X=1」 にする。





構文2

IF  条件式1  THEN  処理1 ;
ELSE  IF  条件式2  THEN  処理2;
ELSE  処理3;

  • 「条件式1」を満たす場合に、「処理1」を実行。
  • 「条件式1」以外で「条件式2」を満たす場合に、「処理2」を実行。
  • 「条件式1」と「条件式2」以外だったら、「処理3」を実行。




data DT2;
   set SASHELP.CLASS;
   if AGE=11 then X=1;
   else if AGE=12 then X=2;
   else if AGE=13 then X=3;
   else X=99;
run;




構文3

IF  条件式  THEN  DO;
      処理1 ;
      処理2 ;
      ~
END;
ELSE  IF  条件式  THEN  DO;
      処理1 ;
      処理2 ;
      ~
END;
ELSE  DO;
      処理1;
      処理2;
END;

  • DO~ENDで囲って処理を複数指定できます。




data DT2;
   set SASHELP.CLASS;
   if AGE=11 then do;
          X=1;
          Y=1;
   end;
   else do;
          X=99;
          Y=99;
   end;
run;

  • 「AGE=11」 だったら、「変数X=1」「変数Y=1」 にする。
  • それ以外だったら「変数X=99」「変数Y=99」にする。




2016年3月15日火曜日

ODS出力時に文字の書式設定をする【インラインフォーマット】



HTMLやRTFなどのODS出力時に、インラインフォーマットを使って文字の書式設定等が出来ます。
(本機能はSGPLOT・GTLでは、ほとんど動作しません)




インラインフォーマット (一部)

設定内容指定備考
上付き文字にする
{SUPER 文字}  
下付き文字にする
{SUB 文字}  
  
文字のスタイル設定(書式)をかえる 
{STYLE [スタイル設定]文字} 
S={スタイル設定}文字

という書き方もある。
(S=は大文字じゃなきゃダメ)
Unicode記号の表示
{UNICODE unicode値}
  
ノーブレークスペースを挿入
{NBSPACE}

改行 
{NEWLINE 改行数} 
    
ページ番号を表示
(現ページ of 総ページ)
{PAGEOF}
RTF, TAGSETS.RTF で利用可

TITLE・FOOTNOTEステートメント内で使用

ODS RTF で BODYTITLEオプションが設定されていると正しく動作しない
現在のページ番号を表示
{THISPAGE}
RTF, TAGSETS.RTF, PRINTER で利用可

TITLE・FOOTNOTEステートメント内で使用

ODS RTF で BODYTITLEオプションが設定されていると正しく動作しない 
最終ページ番号を表示
{LASTPAGE}
RTF, TAGSETS.RTF で利用可

TITLE・FOOTNOTEステートメント内で使用

ODS RTF で BODYTITLEオプションが設定されていると正しく動作しない


📝
以下の要素によっては、インラインフォーマットの挙動が異なったり、動作しないケースがあるので、リファレンスを確認したり動作確認するなどした方が良いです。
  • 出力先の種類(HTMLやRTFなど)
  • インラインフォーマットの種類
  • エスケープ文字の設定方法



構文1

  (*ESC*){ インラインフォーマット }


前に (*ESC*) を入れると、インラインフォーマットとして解釈させることが出来る。
(「ESC」は大文字で記述する)


*** タイトルを青くする例 ;
title "(*ESC*){style [color=blue]abcde}";

ods rtf file="出力先のパスを指定\test.rtf";
   proc print data=sashelp.class;
   run;
ods rtf close;




構文2

  ODS ESCAPECHAR = 'エスケープ文字 ';
  エスケープ文字{ インラインフォーマット }


(*ESC*) の代わりとなるエスケープ文字を設定することができます。

*** タイトルを青くする例 ;
ods escapechar='~';
title "~{style [color=blue]abcde}";

ods rtf file="出力先のパスを指定\test.rtf";
  proc print data=sashelp.class;
  run;
ods rtf close;


📝注意
  • エスケープ文字を「/」とかありきたりなのにすると、例えば「2016/03/15」のような文字に含まれてる「/」もエスケープ文字として解釈しちゃうので注意(結構ありがち)
  • RTF出力時に「\」(円マーク、バックスラッシュ)をエスケープ文字にしない方が良いようです(RTFで特別な意味を持つ記号のため)




変数値にもインラインフォーマットが利用可能

*** ODS出力時に変数値を途中で改行させる例 ;
data DT1;
   A="SAS(*ESC*){newline 1}BOUBIROKU";
run;

ods rtf file="出力先のパスを指定\test.rtf";
  proc print data=DT1;
  run;
ods rtf close;