2017年6月30日金曜日

プロシジャ実行時に、「結果ビューア」等への出力をしないで、「データセット」に出力する。



以下をご覧ください。

ods output Moments=OUT1;

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

ods output close;


ODS OUTPUT を使って、プロシジャの結果をデータセットに出力しています。
「ODS OUTPUT」については以下記事で紹介済です。


ここから本題で、
「データセットに出力したいだけで、結果ビューアにプロシジャの結果が出るの邪魔くさい!」っていう時ありますよね。



そんな時は「ODS SELECTステートメント
ODS出力先(HTML, LISTING, RTF等)に対して、プロシジャ等の結果を送らないように出来ます。

(少し独特な挙動なので、以降の解説も見ていただいて理解して使った方がよいです)

ods select none;
ods output Moments=OUT1;

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

ods output close;
ods select all;


解説

ODS SELECT NONE:
ODS出力先に対して、出力オブジェクト(プロシジャ等の結果)を送らないようにします。

ODS SELECT ALL:
ODS出力先に対して、出力オブジェクト(プロシジャ等の結果)を送る設定をSASのデフォルトの状態に戻します。


ODS SELECT NONE」を実行すると、
通常「ODS SELECTステートメント」または「ODS EXCLUDEステートメント(説明省略)」で設定を変更しない限り、ODS出力先にプロシジャ等の結果を送ることは出来ません。

ただし「ODS OUTPUT」だけは例外で、上の例のように「ODS SELECT NONE」の直後でもプロシジャの結果等をデータセットに出力することが出来ます。



ちなみに、ちょっと前に以下の記事で「ODS RESULTS」を紹介しました。
「ODS RESULTS」で結果ウインドウへの表示を抑制する

「ODS SELECT」と「ODS RESULTS」は似たような事が出来るので混同しがちですが、機能としては全然別物なのでご注意を。




2017年6月22日木曜日

INPUT関数の落とし穴


input関数で文字値を数値に変換する際に間違いやすいところ。


Q. 下のプログラムを動かすと、変数Bはどんな値になるでしょうか?
data DT1;
   length A $10.;
   A ="10.2"; output;
   A ="25"  ; output;
run;

 A 
  10.2 
  25

data DT2;
   set DT1;
   B = input(A, 16.2);
run;

 A 
B
  10.2  
  ?  
  25
  ? 





答え.
 A 
B
  10.2  
  10.2  
  25
  0.25  

小数はそのまま数値化されますが、
整数は、小数になってしまいます。


これは、input関数に指定しているインフォーマットが影響しています。
今回の例では「input(A, 16.2)」なので2桁シフトして「25」から「0.25」になります。

「25」のまま数値化されるのかな、と思いやすいところです。

今回の場合、「input(A, 16.)」とか「input(A, best16.)」とか、データに応じた長さのインフォーマットを指定すればそのままの値「25」で数値化できます。



以下にもう少し例をあげておきます。

input(A, 16.1)
"10.2 " →  10.2
"25"     →  2.5

input(A, 16.3)
"10.2"  →  10.2
"25"     →  0.025

なんでそんな変換しちゃうんだよ!ってなると思いますが、インフォーマットの性質上のもので、そういうものって思うしかないです。


2017年6月21日水曜日

【SAS入門】データセットの中身を表示する【PROC PRINT】




SASデータセットの中身を見るには、、

  • データセットを直接開いて見る
  • PRINTプロシジャ等でデータセットの中身を出力して見る

といった方法があります。
今回は、PRINTプロシジャを使った方法を紹介します。(沢山機能があるので、基本の部分のみ紹介)



例① 基本構文

proc print data=SASHELP.CLASS;
run;

「data=」に表示したいデータセット名を指定します。
今回は「SASHELP.CLASS」を表示対象としています。

ここからステートメントやオプション等を追加して、出力をカスタマイズしていきます。
以下で紹介する機能は、オプションや設定等の組み合わせで、機能しなかったり挙動が変わる場合がある点だけ注意です。



例② VARステートメント
proc print data=SASHELP.CLASS;
   var NAME SEX AGE;
run;

VARステートメントで表示したい変数を指定することができます。




例③ LABEL、NOOBSオプション
* オプションなし ;
proc print data=SASHELP.BASEBALL;
   var NAME TEAM;
run;



* オプションあり ;
proc print data=SASHELP.BASEBALL label noobs;
   var NAME TEAM;
run;

LABELオプション・・・
通常、「オプションなし」の例のように列ヘッダーには変数名が表示されますが、LABELオプションを指定すると、変数ラベルを表示できます。

NOOBS・・・
通常、「オプションなし」の例のように一番左側に「OBS」という列が表示されますが、NOOBSオプションを指定すると、この列を非表示にできます。




例④ WHEREステートメント
proc print data=SASHELP.CLASS ;
  where AGE = 13;
run;

WHEREステートメントでオブザベーションの抽出条件を指定できます。
(上の例では、AGE=13のオブザベーションのみを表示しています)




例⑤ OBS=データセットオプション
proc print data=SASHELP.CLASS(obs=3);
run;

OBS=データセットオプションで、処理を停止するタイミングを指定できます。
(上の例では、3オブザベーションまでで処理を停止しています)

データセット名の後ろにカッコで囲んで指定します。

ちなみに、入門の範疇を少し超えますが、以下記事の通り、他の機能と組み合わせて使用する際は、その挙動にも注意です。

FIRSTOBS=, OBS=, WHERE を組み合わせたときの挙動



注意点

出力の設定がテキスト形式(ODS LISTING)になっている場合、変数に格納されている文字が長いと、文字切れを起こして途中までしか出力されません(ログにWARNINGが出ます)



PRINTプロシジャ利用時のヒント

PRINTプロシジャには不向きなデータがあります。

例えば、変数の数が多かったり、オブザベーション数が多い場合、
表示されたものが縦や横にながく見づらかったり、そもそもプロシジャの実行にめちゃくちゃ時間が掛かります。

素直にデータセット直接開いて中身見た方が早いです。

ただ、そういった場合でも、VAR、WHERE、OBS=などを組み合わせて、表示する変数やオブザベーションを絞るなどすれば、ある程度見やすくなります。



2017年6月14日水曜日

My Favorite SAS Functions (6) VVALUE / VVALUEX関数



第6回目は、VVALUE / VVALUEX関数。
変数に割り当てられているFORMATを使って、その変数を文字変換した値を返してくれる関数です。

VVALUE関数については以下記事で紹介済みです。
http://sas-boubi.blogspot.jp/2013/12/put-vs-vvalue.html


では、例を見ていきましょう。





例えば、以下のようにフォーマット「YYMMDD10.」を割り当てた変数Xがあったとします。

data DT1;
  format X yymmdd10.;
  X = '13jun2017'd;
run;

  X  
 2017-06-13 


この変数XをYYMMDD10.形式のフォーマットで文字変換したい場合、普通以下のようにPUT関数を使いますよね。

Before
data DT2;
  set DT1;
  length Y $20.;
  Y = put( X, yymmdd10.);
run;
  X
Y
 2017-06-13 2017-06-13 


ここで、VVALUEまたはVVALUEX関数を使えば少しスッキリとした文になります。

After
data DT2;
  set DT1;
  length Y1 Y2 $20.;

  /* vvalue関数を使った例 */
  Y1 = vvalue( X );

  /* vvaluex関数を使った例 */
  Y2 = vvaluex( "X" );

run;
  X
Y1
Y2
 2017-06-13 2017-06-13  2017-06-13 




解説

Y1 = vvalue( X );

「 vvalue( X ) 」で変数Xに割り当てられているFORMAT「YYMMDD10.」を使って文字変換した値「2017-06-13」を返しています。


Y2 = vvaluex( "X" );

vvaluex も vvalue と同じ機能を持っているのですが、違いは以下の通り。

・vvalue( X )      … 変数名を指定
・vvaluex( "X" )  … 変数名を表す文字値を指定

つまり、「 vvaluex( "X" ) 」で変数Xに割り当てられているFORMAT「YYMMDD10.」を使って文字変換した値「2017-06-13」を返しています。


また、以下のような書き方も可能です。

  length VARNAME Y2 $20.;
  VARNAME = "X";
  Y2 = vvaluex( VARNAME );

変数名を格納した変数VARNAMEをVVALUEX関数の引数に指定することでも、
「vvaluex( VARNAME )」 → 「vvaluex( "X" )」 と展開されます。



注意点


PUT関数と挙動が似ています。
例えば、PUT関数で数値の欠損値を以下のようにFORMAT変換すると、、

  length Y3 $20.;
  Y3 = put( . , yymmdd10.);

→ 変換後の値は「         .」という感じで先頭に余計な空白が入ります。


また、フォーマットによっては、、

 length Y4 $20.;
 Y4 = put( 10, best12. );

→ 変換後の値は「          10」という感じでこちらも先頭に余計な空白が入る場合があります。


VVALUE / VVALUEX関数も同様に先頭に空白が入る場合があるのでご注意下さい


2017年6月8日木曜日

変数に含まれる特定の文字の数をカウントする:COUNT関数編




例えば「this is a pen」という文字列に「is」という文字が何個含まれるかカウントしたいような場合、COUNT系関数が使えます。



COUNT、COUNTC関数


シングルバイト(半角の英数など)の文字を対象とする場合、上記2つの関数を状況によって使い分ける
(わりと気難しい関数なので、後段の解説や注意点も参照下さい)

data DT1;
     A  = "this is a pen" ;
     B  = count( trimn(A) , "is" );
     C  = countc( trimn(A) , "isp" );
run;
  A  
  B  
  C  
  this is a pen    2  5

📝関数に指定した変数は、場合によって上記のように「TRIMN関数」で包む必要があります。理由はあとで解説します



解説

COUNT( trimn(A) , "is")
第2引数の "is" という文字列が変数Aに何個あるかを返す(見つからなかった場合は0を返す。)

ただし、カウントする文字の出現位置が重なっている場合、、
例えば「count( "PenPenPen", "PenPen" )」みたいなやつは、関数の結果が「1」か「2」どちらかになる可能性があって挙動が怪しいようなので注意。


COUNTC( trimn(A) , "isp" )
第2引数の "isp"を一字ずつ分解し、「i」「s」「p」という文字が変数Aに何個あるかを返す(見つからなかった場合は0を返す。)



注意点
  • 日本語などのマルチバイト文字を含む場合は、以下記事の通りうまく動作しないのでご注意ください。
  • 以下で解説している問題がCOUNT関数にもあるので、場合によって「TRIMN関数」を併用する必要があります。
    • 文字値の末尾にある空白問題
    • 上記リンク記事で解説している通り、変数値の長さがLENGTHより短い場合、末尾に半角スペースが入るので、以下のように想定外の結果を返す場合があります。

/* 失敗例 */
data DT1;
     length A $20.;
     A  = "this is a pen" ;
     B  = count( A , " " );
     C  = countc( A , " " );
run;
  A  
  B  
  C  
  this is a pen   10 10


てことで「TRIMN関数」で一時的に末尾の半角スペースを取り除いてやります。

/* 成功例 */
data DT1;
     length A $20.;
     A  = "this is a pen" ;
     B  = count( trimn(A) , " " );
     C  = countc( trimn(A) , " " );
run;
  A  
  B  
  C  
  this is a pen    3  3




KCOUNTX、KCOUNTC関数(SAS9.4M5から)


マルチバイト(日本語などの全角文字など)を含む文字を対象とする場合、頭に”K”がつく上記2つの関数を使います
(こちらも気難しい関数なので、後段の解説や注意点も参照下さい)

data DT2;
     A  = "わたしはペンギンではありません" ;
     B  = kcountx( trimn(A) , "ペン" );
     C  = kcountc( trimn(A) , "はン" );
run;
  A  
  B  
  C  
  わたしはペンギンではありません    1  4

📝COUNT関数のところでも解説した通り、関数に指定した変数は、場合によって「TRIMN関数」で包む必要があります。



解説

KCOUNTX( trimn(A) , "ペン")
第2引数の "ペン" という文字列が変数Aに何個あるかを返す(見つからなかった場合は0を返す)

ただし、カウントする文字の出現位置が重なっている場合、、
例えば「kcountx( "ペンペンペン", "ペンペン" )」みたいなやつは、関数の結果が「1」か「2」どちらかになる可能性があって挙動が怪しいようなので注意。

あと間違えやすいですが、KCOUNTではなく、KCOUNTXです(KCOUNTは全く別のことをやる関数です)


KCOUNTC( trimn(A) , "はン" )
第2引数の "はン"を一字ずつ分解し、「は」「ン」という文字が変数Aに何個あるかを返す(見つからなかった場合は0を返す)



注意点

本当にどんな仕様だよって感じなんですが、
第1引数がシングルバイトの文字のみで構成されている場合、マルチバイト非対応の関数に変化して間違った結果を返す場合があります。

例えばshift_jis環境で以下を実行すると、、
  • 「kcountc( "\", "表" )」→ 第1引数がシングルバイトのみのため間違った結果「1」を返す
  • 「kcountc( "\あ", "表" )」→ 第1引数にマルチバイトを含むため正しい結果「0」を返す。



2017年6月2日金曜日

指定した時間プログラムを中断する「SLEEP関数 / CALL SLEEP」と落とし穴



「SLEEP関数」または「CALL SLEEP」で指定した時間だけ、呼び出し元のプログラムを中断出来ます。
ひとつずつ例をみていきましょう。



SLEEP関数


構文

   SLEEP(  中断する秒数 , 秒の単位  )


第2引数「秒の単位」の指定は省略しないこと!(理由は記事の最後に説明)


例)
  • SLEEP( 5 , 1 ) = 5秒
  • SLEEP( 5000 , 0.001 ) = 5秒
  • SLEEP( 1 , 5 ) = 5秒
  • SLEEP( 0.25 , 0.001 ) = 0秒 ← 設定ミスの例


使用例
data DT1;
  A=1;
run;

data _NULL_;
  rc = sleep(5,1);
run;

data DT2;
  A=2;
run;

データセットDT1を作った後、5秒SLEEPさせています。
5秒後に、続きのプログラム(データセットDT2を作る処理)が行われます。



CALL SLEEP


構文

   CALL SLEEP(  中断する秒数 , 秒の単位  )


第2引数「秒の単位」の指定は省略しないこと!(理由は記事の最後に説明)


例)
  • CALL SLEEP( 5 , 1 ) = 5秒
  • CALL SLEEP( 5000 , 0.001 ) = 5秒
  • CALL SLEEP( 1 , 5 ) = 5秒
  • CALL SLEEP( 0.25 , 0.001 ) = 0秒 ← 設定ミスの例


使用例
data DT1;
  A=1;
run;

data _NULL_;
  call sleep(5,1);
run;

data DT2;
  A=2;
run;

SLEEP関数の例と同様、途中で5秒プログラムを中断しています。



落とし穴


リファレンスに記載されていないので注意すべき点。
第2引数に設定する「秒の単位」は省略可ですが指定推奨です。理由は、、

  • 環境によって「秒の単位」のデフォルトが異なる。
  • また、同じ環境内でも「SLEEP関数」と「CALL SLEEP」の間で、「秒の単位」のデフォルトが異なっている場合がある。
    • 例えば、ある環境ではSLEEP関数のデフォルトが「1」で、CALL SLEEPのデフォルトが「0.001」みたいなケースがある。

てことで、第2引数の「秒の単位」は指定したほうが良いです。