2018年5月24日木曜日

SASユーザー間の情報交換サイト「Japan SAS Discussion」





ついにオープンしました!待ってました!

Japan SAS Discussion
https://communities.sas.com/t5/Japan-SAS-Discussion/bd-p/ja_forum



https://communities.sas.com」はSAS社のサイトで前から存在してましたが、やり取りは全部英語でした。そこに今回日本語用のページが用意された感じです。

質問や回答したい場合は、アカウントを登録します。

いやー、うれしいです。
あとはちゃんと回答者がついて軌道に乗ってくれるかですね。。私も微力ながら応援していけたらと思います。



ちなみにデータステップ100万回のSASYAMAさんが運営されてるSAS質問掲示板に、SAS社の方からサイトオープンのお知らせの投稿があって知りました。
http://tumesas.progoo.com/bbs/tumesas_topic_pr_302.html



2018年5月18日金曜日

ODSグラフを画像ファイルとして保存する際、ファイル名の末尾の開始番号を指定する




ODSグラフを画像ファイルとして保存する方法自体は、以下過去記事をご覧下さい。
http://sas-boubi.blogspot.jp/2015/09/odssg.html


そして今回は、SAS9.4の多分メンテナンスリリース3辺りから追加になった機能の紹介です。
まず以下のプログラムをご覧ください。

ods graphics  /  reset=index  imagename="SAMPLE"  imagefmt=png ;
ods html close;
ods listing gpath="C:\TEST";

      proc sort data=SASHELP.CLASS OUT=CLASS;
         by AGE;
      run;

      proc sgplot data=CLASS;
         scatter x=HEIGHT y=WEIGHT;
         by AGE;
      run;

ods listing close;
ods graphics  /  reset=all;
ods html;


by変数AGEの値毎に以下6個のグラフが作られます。ここまでは今までの機能です。

SAMPE.png
SAMPE1.png
SAMPE2.png
SAMPE3.png
SAMPE4.png
SAMPE5.png



では次に上のプログラムの青字箇所を以下のように変えて再実行してみます。

  reset=index(100)  

すると、ファイル名の末尾の番号が100から始まるようになりました。

SAMPE100.png
SAMPE101.png
SAMPE102.png
SAMPE103.png
SAMPE104.png
SAMPE105.png



SAS9.4の多分メンテナンスリリース3辺りから、ファイル名末尾の開始番号を指定出来るようになりました。


  ODS GRAPHICS / RESET = INDEX( ファイル名末尾の開始値 ); 




なので、開始値を1からにしたい場合、

  reset=index(1)  

とすれば、SAMPLE1.png ~ SAMPLE6.png と作ることが出来ます。




2018年5月10日木曜日

データセット内の各文字変数について、実データの最大長(LENGTH)を求める



タイトルのような質問をよく頂きます。
製薬業界だと、CDISC対応で文字変数のLENGTHを実データの最大長に合わせたい時に必要になりますね。



求め方は、すでにデータステップ100万回のSASYAMAさんが記事を載せています。
データセット内のすべての文字変数に対して、それぞれに入っているテキストの長さの最大値を取得するハッシュオブジェクト

やりたい事とHASHオブジェクトがマッチしていて素晴らしいプログラムです。私も他の解法を考えてみたくなりました。





サンプルデータ作成

data DT1;
input ONE (TWO THREE FOUR)(:$10.);
cards;
1 aaa cc .
2 bbbb ddd .
3 . ee .
;
  ONE 
  TWO 
 THREE 
 FOUR 
  1   aaa  cc   
  2   bbbb  ddd   
  3 
  ee  

今回はこのDT1が、実データの最大長を取得したい対象のデータセットとします。



※ 本題に入る前に
今回紹介するプログラムでは文字の長さを「LENGTH関数」使って調べてますが注意が必要です(以下参照)

何が言いたいかというと、変数が欠損値の場合、LENGTH関数は「1」を返します。
もし「0」と返してほしいなら、プログラム中の「LENGTH関数」を「LENGTHN関数」に変える必要があります。





方法① 配列とPROC SUMMARYのコンボ

まずは平凡なプログラムから。


※諸注意
以下プログラム中で「_VARNAM」「_VARLEN」「_i」という変数を作っていますが、もし対象のデータセットにもこの3つと同じ名前の変数がある場合、処理がうまくいきません。
その場合はプログラム中の「_VARNAM」「_VARLEN」「_i」を別の変数名(対象のデータセットにはない変数名)に変えてください。


data OUT1;
  set DT1;

  * 文字変数を配列に格納 ;
  array ar(*) _char_;

  * 変数名とlengthを出力 ;
  length _VARNAM $32. _VARLEN 8.;

  do _i = 1 to dim(ar);
    _VARNAM = vname(ar(_i));
    _VARLEN = length(ar(_i));
    output;
  end;

  keep _VARNAM _VARLEN;
run;

* 変数毎の最大lengthを求める ;
proc summary data=OUT1 nway;
  class  _VARNAM;
  output  max(_VARLEN)=_VARLEN  out=OUT2(keep=_VARNAM _VARLEN);
run;

 _VARNAM 
 _VARLEN  
  FOUR    1
  THREE  3
  TWO   4




方法② TRANSPOSEとSQLのコンボ

内部処理的には無駄が多いものの、プログラムとしては単純かつ楽に書けるため私はよくこの方法使ってます。


※諸注意
以下プログラムでは「_TRANUM」という変数を作っていますが、もし対象のデータセットにもこれと同じ名前の変数がある場合、処理がうまくいきません。
その場合はプログラム中の「_TRANUM」を別の変数名(対象のデータセットにはない変数名)に変えてください。

* 後のtranspose用にobs毎の連番を持たせる ;
data OUT1;
  set DT1;
  _TRANUM = _N_;
run;

* 全文字変数を縦に転置 ;
proc transpose data=OUT1 out=OUT2 name=_VAR prefix=COL;
  var       _char_;
  format  _char_;
  by   _TRANUM;
run;

* 変数毎の最大lengthを求める ;
proc sql;
  create table OUT3 as
  select _VAR, max(length(COL1)) as _LEN
  from OUT2
  group by 1;
quit;

 _VAR 
 _LEN  
  FOUR    1
  THREE  3
  TWO   4




方法③ LUAプロシジャ(SAS9.4メンテナンスリリース3から追加になったプロシジャ)

個人的に書いてて一番楽しかったです。
LUAの知識は初心者レベルなので、これが最適解なのか不明、、もっといい書き方やこういう書き方も出来るってのがあったら教えてほしいです。

proc lua;
 submit;

    -- データセットをopen
    local dsid = sas.open("work.dt1")
    local tb = {}

    -- 文字変数名をテーブルに格納
    for var in sas.vars(dsid) do
        if var.type == "C" then
            tb[#tb+1] = {col=var.name, len=0}
        end
    end

    -- OBSを読み込んでいって最大lengthを求める
    while sas.next(dsid) do
        for i in pairs(tb) do
            tb[i].len = sas.max( sas.length(sas.get_value(dsid,tb[i].col)), tb[i].len )
        end
    end

    -- データセットを閉じて&テーブルの中身をデータセットに出力
    sas.close(dsid)
    sas.write_ds(tb,"work.out1")

 endsubmit;
run;


 LEN 
COL
  4   TWO 
  3  THREE 
  1   FOUR


2018年5月8日火曜日

SQLプロシジャのDESCRIBE TABLEを使って、変数定義をログに出力する。




以下構文でデータセットの変数定義をログに出力することが出来ます。


構文

   PROC SQL;
        DESCRIBE TABLE データセット名 ;
   QUIT;





proc sql;
   describe table SASHELP.GAS;
quit;


ログ
NOTE: SQLテーブルSASHELP.GASは次のように作成されました:

create table SASHELP.GAS( label='Nitrogen Oxide Emissions from a Single Cylinder Engine' bufsize=●● )
  (
   Fuel char(8),
   CpRatio num label='Compression Ratio',
   EqRatio num label='Equivalence Ratio',
   NOx num label='Nitrogen Oxide'
  );

●●の部分(bufsize)は環境によって異なります。




あと知らなかったんですが、定義を見たいデータセットが複数ある場合は、以下のようにカンマで区切ってまとめて指定することもできるんですね。

proc sql;
   describe table SASHELP.GAS, SASHELP.FISH;
quit;



以上、まとめて指定できるの知らなかったんで記事かきましたが、薄い記事となってしまいました。

2018年4月26日木曜日

NOT演算子で条件の否定をする




突然ですがうなぎと梅干しは食べ合わせが悪いって子供のころ聞いたことありますよね。
けど最近はそんな事ないって言われてますね、実際どうなのか調べてないんでわからないけど。


てことで、以下にとある人の3日間の晩御飯のデータがあったとします。

data DT1;
input DT:yymmdd10. FOOD1:$20. FOOD2:$20.;
format DT yymmdd10.;
cards;
2018/04/05 うなぎ  たくあん
2018/04/06 ラーメン  味噌汁
2018/04/07 うなぎ  梅干し
;
  DT 
  FOOD1  
  FOOD2  
 2018/04/05   うなぎ  たくあん 
 2018/04/06   ラーメン  味噌汁
 2018/04/07   うなぎ  梅干し



FOOD1にはメインとなる食べ物で、FOOD2には漬物とかおまけ的な食べ物が入力されているとします。
ここで食べ合わせが問題ないと思われるレコードのみを抽出したいとします。
つまり「うなぎと梅干しを一緒に食べたレコード以外」を抽出します。



NOT演算子を使えばこんな感じで分かり易いですが、、

data DT2;
  set DT1;
  where not (FOOD1="うなぎ" and FOOD2="梅干し");
run;
  DT 
  FOOD1  
  FOOD2  
 2018/04/05   うなぎ  たくあん 
 2018/04/06   ラーメン  味噌汁


使わないとちょっと分かりにくい感じになってしまいます。

data DT2;
  set DT1;
  where FOOD1^="うなぎ" or FOOD2^="梅干し";
run;
  DT 
  FOOD1  
  FOOD2  
 2018/04/05   うなぎ  たくあん 
 2018/04/06   ラーメン  味噌汁



SAS初心者で、このNOT演算子の書き方を知らずに、分かりにくい方の書き方してる人がたまにいます。

NOT ( 条件 )」という感じで否定したい条件をカッコで囲ってNOTをつけてあげるだけです。「^( 条件 )」という書き方も出来ます。

2018年4月17日火曜日

PROC FORMAT入門7 : フォーマットカタログの保存




以下のようにフォーマットを定義したとします。

proc format;

   value TEST1_
       1 = "MALE"
       2 = "FEMALE"
   ;
   invalue TEST2_
       "M" = 1
       "F" = 2
   ;
run;


定義したフォーマットはライブラリ「WORK」に「FORMATS」という名前で保存されます。
この「WORK.FORMATS」を「SASカタログ(或いはフォーマットカタログ)」といいます。





フォーマットカタログの保存



WORK以外の場所にフォーマットカタログを保存したい場合は「LIBRARY=オプション」を指定します。


   PROC FORMAT  LIBRARY = ライブラリ.カタログ名;
         ~ フォーマット定義 ~
   RUN;



例)
library = MYLIB.FORMATS ……「MYLIB.FORMATS」で保存
library = MYLIB.MYFMT      ……「MYLIB.MYFMT」で保存
library = MYLIB                   ……「MYLIB.FORMATS」で保存

※3つ目の例のようにカタログ名を省略すると「FORMATS」という名前で保存されます。





ここまでで、フォーマットカタログを保存する方法は分かりましたが、
WORK以外に保存したフォーマットカタログはそのままでは利用できません。利用するには以下2つの方法があります。





① ライブラリ参照「LIBRARY」を利用する



例えば、以下のプログラムで「TEST1_」というフォーマットを使用しています。

data DT1;
   format X TEST1_.;
   X = 1;
run;



まずSASは、この「TEST1_」をフォーマットカタログから探しにいきます。
デフォルトでは、「WORK.FORMATS」→「LIBRARY.FORMATS」の順に探しに行きます。



「WORK.FORMATS」の次に検索される「LIBRARY.FORMATS」はSASが決めたやつで、


   LIBNAME LIBRARY  "フォーマットカタログを保存したパス";


と書けば、このパスに格納されている「FORMATS」と名の付いたカタログが検索されるようになります。




② 「OPTIONS FMTSEARCH」 を利用する



「FMTSEARCH」システムオプションでは、参照するフォーマットカタログと、参照する順番を指定できます。


   OPTIONS FMTSEARCH = (  参照する順にフォーマットカタログを列挙  )



例) options fmtsearch=( TEST.MYFMT );

「WORK.FORMATS」→「LIBRARY.FORMATS」→「TEST.MYFMT」の順に検索。
(「WORK.FORMATS」と「LIBRARY.FORMATS」は強制的に最初に検索される。)


例) options fmtsearch=( TEST );

「WORK.FORMATS」→「LIBRARY.FORMATS」→「TEST.FORMATS」の順に検索。
(「TEST」というように、ライブラリ名しか指定しない場合、「ライブラリ.FORMATS」が検索される)


例) options fmtsearch=( TEST WORK );

「LIBRARY.FORMATS」→「TEST .FORMATS」→「WORK.FORMATS」の順に検索。
(「WORK.FORMATS」または「LIBRARY.FORMATS」の検索順を明示的に指定して変えることができる)





長い説明になりましたが、データステップ100万回のSASYAMAさんの記事もあわせて参照下さい。(やっぱSASYAMAさんの記事分かり易い、説明うまいなぁ)
http://sas-tumesas.blogspot.jp/2013/12/libraryfmtsearch.html





PROC FORMAT入門 : 記事一覧

2. 範囲の指定
3. INVALUEステートメント
4. PICTUREステートメント1
5. PICTUREステートメント2
6. PICTUREステートメント3
7. フォーマットカタログの保存
8. CNTLIN=オプション
9. CNTLOUT=オプション
10. その他の小ネタ

2018年4月11日水曜日

WHEREやIFでの範囲指定の書き方




4月からSASを始める方も多いので、入門記事的なのも書いてこうと思います。


では早速、以下をご覧ください。


* Sample data ;
data DT1;
   do X=1, 1.1, 2, 2.1, 3;
      output;
   end;
run;

  X  
 1 
 1.1 
 2 
 2.1 
 3 


たとえば、上のデータからXが「1」「2」「2.1」のいずれかのオブザベーションを抽出したいとします。
なんも考えずに書くと以下のようになりますね。

data OUT1;
  set DT1;
  where X=1 or X=2 or X=2.1;
run;
  X  
 1 
 2 
 2.1 



ですが、この書き方はちょっとメンドイです。以下のようにもっと楽な書き方があります。

data OUT1;
  set DT1;
  where X in (1, 2, 2.1);
run;
  X  
 1 
 2 
 2.1 


こんな感じで、ひとつの変数に対して「この値からこの値までのオブザベーションをWHEREで抽出したい(またはIFで条件式を書きたい)」という場合の、範囲の書き方を以下にまとめました。




範囲の書き方

 指定内容 例 例から抽出されるX  注意事項
 ●以下

・X <= 2
・2 >= X
 2以下


 ●未満

・X < 2
・2 > X
 2未満

 ●●以上

・X >= 2
・2 <= X
 2以上

 ●

・X > 2
・2 < X
 2超

 ●以上、以下  






・1 <= X <= 2 
・X >= 1 and X <= 2
・X between 1 and 2  




 1以上、2以下






・「1 <= X <= 2」みたいなのは
 SAS独特の書き方
 (他言語では使わない事!)

・betweenはwhereだけに対応
 (ifでは使えない)

 ●超、未満



・1 < X < 2
・X > 1 and X < 2


 1超、2未満



・「1 < X < 2」みたいなのは
 SAS独特の書き方
 (他言語では使わない事!)

 ●  or    or  …

・X in (1, 2, 2.1)

 1 or 2 or 2.1

 ●以上、以下
  (かつ整数)
・X in (1:2)

 1以上、2以下の整数

 昔の記事で紹介してます。
 http://sas-boubi.blogspot.jp/2014/01/in.html



注意事項
「1 <= X <= 2」や「1 < X < 2」みたいな書き方は便利ですが、SAS独特の書き方です。他言語では通用しません。

他言語でも同じように書いてしまうとSASとは異なるロジックで解釈をして想定外の結果になってしまうので、ご注意ください!