2016年9月28日水曜日

変数の順番を変える小技




たとえば変数がA、C、Bの順に並んでいるデータセットがあり、、

data DT1;
   A=1;  C=1;  B=1;
run;
  A  
  C  
  B  
  1  1  1

これをA、B、Cの順に並び替えたいとします。


この場合、以下のように
「属性定義をするステートメント」をデータステップの先頭に入れることで順番を変える事ができます。

data DT2;
   length A B C 8.;
   set DT1;
run;
  A  
  B  
  C  
  1  1  1

ただし、LENGTHステートメントの場合、変数の型とか長さも入れなきゃいけないので面倒です。


そんな時は、FORMATステートメントを使うと楽です。
(必ずデータステップの先頭に入れてください)

data DT2;
   format A B C;
   set DT1;
run;

  A  
  B  
  C  
  1  1  1



2016年9月26日月曜日

PROC LUAによる変数作成と行追加のはなし



PROC LUA独自の関数でデータセットの加工をしたいと思い色々いじってましたが、詰まった部分がありSAS社に問い合わせをしました。 今回その辺の情報共有をしたいと思います。

(PROC LUAをご存じない方は先に構文のルール等についてリファレンスをご確認下さい)



データセットを作成して変数を追加する例
proc lua;
submit;
   local dsid = sas.open( "work.test", "o" )
   sas.add_vars( dsid, {{name="VAR1", type="N"},
                                    {name="VAR2", type="C", length="20"}} )
   sas.close( dsid )
endsubmit;
run;

  VAR1  
  VAR2  

データセットTESTを作成し、変数VAR1とVAR2を作成しています。
sas.open("work.test","o") の"o" は 「作成モード」 みたいな意味合いで新たにデータセットを作る場合に指定します。



行を追加して値を格納する例
proc lua;
submit;
   local dsid = sas.open("work.test","u")
   sas.append(dsid)
   sas.put_value(dsid, "VAR1", 1)
   sas.put_value(dsid, "VAR2", "aaa")
   sas.update(dsid)
   sas.close(dsid)
endsubmit;
run;

  VAR1  
  VAR2  
  1  aaa

sas.appendsas.update で sas.put_value を囲むと行追加と値の設定ができます。
SAS9.4M5以上じゃないと sas.put_value の部分が正しく動かないかもしれないので注意。

sas.open("work.test","u") の"u" は「編集モード」 みたいな意味合いで既存のデータセットを編集する場合に指定します。



ここまではいいけど、じゃあ 「既存のデータセットに変数を追加するには?」 と思って以下のように「sas.open("work.test","u") 」として変数を追加しようとするとエラーが出てしまいました。
proc lua;
submit;
   local dsid = sas.open("work.test","u")
   sas.add_vars(dsid, {{name="VAR3", type="N"}})
   sas.close(dsid)
endsubmit;
run;

ログ
ERROR: SASシステムにバグが発生しました。SAS導入担当者に次のメッセージを報告してください:

かといって「sas.open("work.test","o") 」とすると、既存のデータセットを上書きしてしまうので、使えません。

この点をSAS社に問い合わせてみたところ、ざっと以下のような回答を頂きました。

・ 上記のような方法で既存のデータセットに変数を追加する事はサポートされていない。
・ この点を今後ドキュメントに明記予定。
・ 代替案として sas.submit が使える。



sas.submitだと以下のような感じでしょうか。

proc lua;
submit;
   sas.submit([[ data TEST; set TEST; length VAR3 8.; call missing(VAR3); run; ]])
endsubmit;
run;
  VAR1  
  VAR2  
  VAR3  
  1  aaa  .

ただし、この書き方だとデータセットに設定されているデータセットオプション(データセットラベルなど)が消えてしまうので注意。


PROC LUA独自の関数でデータ加工がしたかったので、ちょっと残念。
今後サポートされたら記事更新したいと思います。


2016年9月21日水曜日

SETやMERGEをした時、オブザベーション毎に結合元のデータセットがどれなのか判別する方法 【IN=オプション】



SETやMERGEで使えるIN=オプションについて解説をしたいと思います。



構文

SET   データセット1  ( IN = 一時変数1 )
          データセット2  ( IN = 一時変数2 )

          ・・・
;


・ データセット1 から持ってきたオブザベーションの場合「1」、それ以外「0」が 一時変数1 に入る
・ データセット2 から持ってきたオブザベーションの場合「1」、それ以外「0」が 一時変数2 に入る

・ 一時変数名は適当な名前でOK。またこの一時変数は出力データセットには残らない。







サンプルデータ作成
data DT1;
   length A 8. B $10.;
   A=1; B="aa"; output;
   A=2; B="bb"; output;
run;
  A  
  B  
  1 aa
  2 bb

data DT2;
   length A 8. C $10.;
   A=2; C="cc"; output;

   A=3; C="dd"; output;
run;
  A  
  C  
  2 cc
  3 dd


例① SETステートメントでの例
data OUT1;
  set  DT1 (in=FLG1)   DT2 (in=FLG2) ;

  /* 別の変数に格納してデータセットに残す */
  X = FLG1;
  Y = FLG2;
run;
  A  
  B  
  C  
  X  
  Y  
  1 aa  1  0
  2 bb
  1  0
  2 cc  0  1
  3 dd  0  1



例② MERGEステートメントでの例
data OUT2;
  merge  DT1 (in=FLG1)   DT2 (in=FLG2) ;
  by A;

  /* 別の変数に格納してデータセットに残す */
  X = FLG1;
  Y = FLG2;
run;
  A  
  B  
  C  
  X  
  Y  
  1 aa  1  0
  2 bb cc  1  1
  3 dd  0  1




実践例

上の例②で、DT1とDT2の両方に存在するBY値のオブザベーションを特定したい場合、以下の条件を書けばいい事が分かりますね。

if FLG1=1 and FLG2=1 then COMMON_FLG=1;


また、DT1になくて、DT2にのみ存在するBY値のオブザベーションを特定したい場合、以下の条件を書けばいけますね。

if FLG1=0 and FLG2=1 then DT2ONLY_FLG=1;


2016年9月16日金曜日

文字欠損値の表し方 「' '」 vs 「''」



文字欠損値を表す際、
クォーテーションの間に半角スペースを入れるか(' ')、入れないか('')という話。

リファレンスに書かれてるのは前者(' ')ですが、後者('')の書き方しか知らない方も多いです。


以下の例をご覧ください。

data X;
   /* 変数Aに欠損値をセット */
   length A $5.;
   call missing(A);

   /* 文字変数Aが欠損値ならFLG=1 */    
   if A=' ' then FLG=1;
run;

青字の通り、クォーテーションの間に半角スペースを入れていますが、正しく文字欠損値として認識して処理されています。



間に半角スペースを入れるかどうかは基本どちらでもいいんですが、以下の場合は注意が必要です。

data Y;
   label A='aaa';
   A = 1;
run;

上記では変数Aにラベル「aaa」を設定しています。
この変数ラベルを消したいと思い、以下のように書くと、、、

data Z;
   label A='';
   set Y;
run;

「'」というラベルになってしまいます。なんでなのかよく分かりませんが、
ラベルを消したいのであれば、以下の通りクォーテーションの間に半角スペースを入れてあげればうまくいきます。

data Z;
   label A=' ';
   set Y;
run;

他にもこういうケースがあるかもしれないのでご注意下さい。


2016年9月1日木曜日

「ODS RESULTS」で結果ウインドウへの表示を抑制する



Windows版SASでの説明になります。
SAS Studioなど「結果ウインドウ」が存在しない環境では今回紹介する機能は動作しません。



まずは「結果ビューア」と「結果ウインドウ」を混同しないように違いの説明から。
以下を実行すると、

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


設定によって挙動は異なりますが、
  • 結果ビューア」にHTML形式でUNIVARIATEの結果が出力されます
  • 結果ウインドウ」は画面左側などにあって、UNIVARIATEの結果に対するリンクツリーが表示されます(ちなみにSAS Studioだと結果タブにある「目次」が結果ウインドウに似ているけど別物)

詳細は以下リファレンスを参照下さい。



そして今回は「結果ウインドウ」の制御を行う「ODS RESULTSステートメント」を紹介します。
環境・設定によって挙動が違うので、Windows版SAS、かつ結果の出力先が「結果ビューア」になっている前提で説明します。



「ODS RESULTS」とは?


以下を実行すると、、

ods results off;
proc univariate data=SASHELP.CLASS;
  var AGE;
run;
ods results on;

「ODS RESULTS~」で囲ったUNIVARIATEの結果に対するリンクツリーが「結果ウインドウ」に表示されなくなります。
ODS RESULTS OFF;」で表示を抑制し、「ODS RESULTS ON;」で表示するように戻しています。




「ODS RESULTS」を使う利点


利点①
プログラムを長時間実行する場合などに、「結果ウインドウ」への表示を抑制することで、プログラムの実行時間が格段に早くなる場合がある。



利点②
結果ウインドウ」だけでなく、「結果ビューア」の表示も抑制されます。


役立つ例として、集計結果を「結果ウインドウ」や「結果ビューア」に表示させずに、データセットに出力したい場合、「ODS OUTPUT」と併用して以下のように書いたりします。

ods results off;
ods output Moments=OUT1;

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

ods output close;
ods results on;

参考:集計結果をデータセットに出力する「ODS TRACE」と「ODS OUTPUT」




注意点


利点の②として紹介した内容について、注意があります。

「ODS RESULTS OFF」は「結果ウインドウ」への表示を抑制させているだけで、裏ではガッツリ集計結果が作られています。
以下のプログラムを実行してみてください。


ods results off;
proc freq data=SASHELP.CLASS;
  tables SEX;
run;
ods results on;


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


PROC FREQを「ODS RESULTS ~」で囲ってるので、
  • 「結果ウインドウ」にはMEANSのツリーのみが表示されますが、
  • 「結果ビューア」にはFREQとMEANS両方出力されてしまいました。

これは先ほど説明した通り、FREQは「ODS RESULTS ~」で囲っていても出力結果は裏で作られています。
なのでMEANSを実行したときに、裏で作られていたFREQの結果も一緒に出てきてしまうわけです。