2018年7月27日金曜日

Hashオブジェクトは、CDISCに係るSASプログラミングと相性バッチリ




今回は製薬業界でのお話になります。


CDISC関連のSASプログラミング(SDTMやADaMのデータセット作成)では、よく以下のような変数を導出します。

・他のデータセットから変数値を取得
・他のデータセットに条件に合うレコードが存在する場合「Y」、 のようなフラグ変数の導出
・他のデータセットの特定レコードの最小値・最大値・合計・カウントなどの取得

これらは通常、SQL, MEANS, FREQ, TRANSPOSE, DATAステップ等を組み合わせて導出するんですが、Hashオブジェクトを使うと、すべてまとめて1回のDATAステップで導出する事も可能です。



(Hashオブジェクトがどんなものか知りたい方はデータステップ100万回のSASYAMAさんが詳しく解説をされてるので、そちらをご覧ください)

ハッシュオブジェクトの世界① (データステップ100万回)
http://sas-tumesas.blogspot.com/2014/07/blog-post_11.html




今回は、CDISC対応でのHashオブジェクトの利用例を紹介します。
以下は変数USUBJIDをキーに、他のデータセットから条件に合うレコードの変数を引っ張ってくるマクロです。

%macro m_find( DS= , WH=, VAR= );
     /*-----------------------------------------------------------------------
         DS     =   対象のデータセット ,
         WH    =   WHERE条件 ,
         VAR   =   引っ張ってくる変数
     -----------------------------------------------------------------------*/

     %local  _index;
     %let  _index = &sysindex;

     _runobs&_index + 1;
     drop _runobs&_index;

     if _runobs&_index = 1 then do;
            if 0 then set &DS ( keep=&VAR );
            dcl hash _hash&_index( dataset : "&DS( where = (" || %sysfunc(quote(&WH)) || "))" );
            _hash&_index..definekey( "USUBJID" );
            _hash&_index..definedata( "&VAR" );
            _hash&_index..definedone();
     end;

     if _hash&_index..find() ^= 0 then call missing( &VAR );
%mend;




ではマクロの使用例も見てみましょう。

* 適当なサンプルデータ ;
data DM;
   USUBJID = "001";
run;





data SUPPDM;
    USUBJID = "001";  QNAM = "SAFFL";  QVAL = "Y";
    output;

    USUBJID = "001";  QNAM = "FASFL";  QVAL = "Y";
    output;
run;








* マクロ使用例 ;
data ADSL;
      set DM;
      length SAFFL $1.;

      *** SAFFL ******;
      %m_find( ds=SUPPDM, wh=QNAM="SAFFL", var=QVAL )
      SAFFL = QVAL;
run;



データステップ中に、SUPPDM.QNAM="SAFFL"の時のQVALを取得しています。
このマクロはシンプルで十分使えますが、もっと作りこめば、最強マクロにもなり得ます。



2018年7月25日水曜日

SASのカレントディレクトリのパスを取得する




カレントディレクトリをマクロ変数に格納する例です。

libname _path  "." ;
%let _curr = %sysfunc( pathname(_path) );

  • 「libname _path "."」でカレントディレクトリに_PATHっていうライブラリを割り当ててから、
  •  PATHNAME関数を使って_PATHライブラリのパスを取得し、
  • そのパスをマクロ変数_CURRに格納してます。




ちゃんとマクロ変数に格納できてるか見てみましょう。

%put &_curr;

ログ
/home/amatsu

はい、OKっぽいですね!




また、単純にカレントディレクトリの場所を知りたいだけなら、以下を実行してログを見れば分かります。

libname _path  "." ;

ログ
NOTE: ライブラリ参照名_PATHを次のように割り当てました。
      エンジン: V9
      物理名: /home/amatsu



他にも、SAS9.4のメンテナンスリリース5で追加された「DLGCDIR関数」を使えば、ログにカレントディレクトリのパスを表示できます。

%let rc = %sysfunc(dlgcdir());

ログ
NOTE: 現在の作業ディレクトリは"/home/amatsu"です。


この関数の機能はこれだけじゃないので、また別の記事でも紹介します。



2018年7月19日木曜日

PROC FORMAT入門9 : CNTLIN=オプション



(落とし穴もあるので記事を最後までご覧ください。落とし穴を解説した記事のリンクを貼ってます)


CNTLIN=オプションを使うと、「データセット」から「フォーマット」を生成する事ができます。(つまり前回紹介した「CNTLOUT=オプション」の逆が出来る)




まずは簡単な例から。



例1

たとえば、FORMATを定義したいとき以下のように書きますが、、

proc format;
   value TEST1_
       1 = "MALE"
       2 = "FEMALE"
   ;
   value $TEST2_
       "Y" = "YES"
       "N" = "NO"
   ;
run;



別のアプローチ「CNTLIN=オプション」を使って、同様のFORMATを定義できます。
記事の最後に、この方法の落とし穴についてのリンク記事をのせているので必ず参照下さい

*** 定義したいFORMATの内容をデータセットで持たせておく ;

data FMT;
input FMTNAME:$7. START:$1. LABEL:$6.;
cards;
TEST1_ 1 MALE
TEST1_ 2 FEMALE
$TEST2_ N NO
$TEST2_ Y YES
;









*** 先ほどのデータセットをCNTLIN=オプションに指定してFORMATに変換する ;

proc format lib=work cntlin=FMT;
run;


ログ
NOTE: 出力形式TEST1_を作成しました。
NOTE: 出力形式$TEST2_を作成しました。


ログを見ると分かる通り、見事データセットからフォーマット「TEST1_」と「TEST2_」を生成する事が出来ました。



構文

PROC FORMAT LIBRARY=ライブラリ.カタログ名  CNTLIN=FORMAT変換用のデータセット;
RUN;

  • LIBRARY=ライブラリ名」というようにカタログ名を省略した場合、ライブラリ内の「FORMATS」という名前のカタログがデフォルトで選択されます。
  • FORMAT変換用のデータセットには少なくとも「FMTNAME」「START」「LABEL」という変数が必要です。

あとは以下の中から、定義したいFORMATにあわせて、必要な変数を選択してデータセットに含めます。



CNTLIN = データセットの各変数の説明

 変数名 型 内容 
 FMTNAME  文字   フォーマット名
 START 文字 開始値
 END 文字 終了値
 LABEL 文字 出力値
 MIN 数値 MIN=オプション値 
 MAX 数値 MAX=オプション値
 DEFAULT
 数値 DEFAULT=オプション値 
 LENGTH  数値 LENGTH=オプション値
 FUZZ   数値 FUZZ=オプション値
 PREFIX   文字 PICTUREフォーマットのPREFIX=オプション値 
 MULT 

 数値

 MULTIPLIER=オプション値
 (PICTUREフォーマットの場合、指定してなくても勝手に入る)
 FILL  文字 PICTUREフォーマットのFILL=オプション値
 NOEDIT   数値 PICTUREフォーマットのNOEDITオプションが有効かどうか
 (1=有効 0=無効)
 TYPE 

 文字

 フォーマットの種類
  N = 数値フォーマット
  C = 文字フォーマット
  I = 数値インフォーマット
  J = 文字インフォーマット
  P = PICTUREフォーマット

 SEXCL

 文字

 開始値の取扱い
 ("Y"=開始値超 "N"=開始値以上)

 EEXCL  

 文字

 終了値の取扱い
 ("Y"=終了値未満 "N"=終了値以下)

 HLO

 文字

 開始値・終了値の追加情報
  O = OTHER
  L = LOW
  H = HIGH
  R =「round=オプション」を有効に
  ...等 

 (組み合わせて使用する事も可能 例:Low-High…LH)

 DECSEP  文字 DECSEP=オプション値
 DIG3SEP 文字 DIG3SEP=オプション値
 DATATYPE  文字 PICTUREフォーマットのDATETYPE=オプション値
 LANGUAGE 文字 LAUNGUAGE=オプション値

これ見て頂くと分かる通り、前回紹介したCNTLOUT=で出来るデータセットと同じ構造です。



ここまで特に難しいことはないんですが、、ただですね、、
CNTLIN=オプションは先の例のような簡単なFORMATを定義する分にはいいけど、複雑なFORMATを定義したい場合、それなりの知識が必要になります。


例えば、以下のPICTUREフォーマットをご覧ください。



例2

proc format;
   picture TEST7_ (round)
      low - high = '009.00%'
   ;
run;

ここで問題です。
上のと同様のフォーマットをCNTLIN=で作るには、どんなプログラムを書けばよいでしょうか?




以下が正解の一例。

data FMT2;
length FMTNAME START END LABEL $10. MULT 8. TYPE HLO $10.;

FMTNAME = 'TEST7_';
START   = 'LOW';
END     = 'HIGH';
LABEL   = '009.00%';
MULT    = 100;
TYPE    = 'P';
HLO     = 'LHR';
run;





proc format lib=WORK cntlin=FMT2;
run;

パッとこれが思い浮かべばいいですが、詳しくないと中々難しいと思います。

私の知る限り、実務ではこの記事の最初の方に載せた「例1」程度の簡単なフォーマットを作るのにCNTLIN=オプションを使っているのはよく見かけますね。



最後に、割と問題ありな落とし穴もあります。以下記事をご覧ください。

2018年7月14日土曜日

古くて可愛い PROC CHART と PROC PLOT




PROC CHART と PROC PLOT は簡単にいうとアスキーアートのようなグラフを作る事が出来ます。



今でこそPROC SGPLOTとかで綺麗なグラフを作れるんで、マニュアルに載っててもスルーされがちですが、味があって私は結構好きです。
昔のSASの本にはよく載ってましたね。


何個か例を見ていきましょう。



options ls=100 ps=40;

proc chart data=sashelp.class;
   vbar age /  type=freq discrete;
run;



味がありますね。




proc chart data=sashelp.class;
   block age / type=freq discrete;
run;


可愛いです。



proc chart data=sashelp.class ;
    pie age / type=freq discrete;
run;


狂気を感じるけど可愛いです。



proc plot data=sashelp.class;
   plot height*weight $name;
run;
quit;


PROC PLOTも味がありますね~。


これからも使っていきたいです。



2018年7月4日水曜日

【SAS9.3以前のBug】E8601DTインフォーマットで正しく変換できないことがある。



今回の記事はSAS9.3以前で発生するバグの話です。
(9.4では解消されてます)


CDISC対応でADaMのデータセットをよく作る方なら既にご存知かもしれません。
SAS社の Problem Note にE8601DTインフォーマットのバグについての記載があります。

Problem Note 43743: E8601DT informat might read beyond its intended boundary
http://support.sas.com/kb/43/743.html



再現してみましょう。


data TEST;
    length X1 X2 $16.;
    X1 = "2018-01-01T10:00";
    X2 = ":15";

    format Y e8601dt.;
    Y  = input( X1, e8601dt. );
run;

SAS9.4での実行結果
  X1 
  X2  
  Y  
 2018-01-01T10:00  :15  2018-01-01T10:00:00 


SAS9.3での実行結果(Bug)
  X1 
  X2  
  Y  
 2018-01-01T10:00  :15  2018-01-01T10:00:15 


9.3で実行すると、X1とX2が合体した日時がYに格納されてしまいました。



内部の動きとして、E8601DTインフォーマットはデフォルトで入力データから19文字読み込もうとします。
しかしX1はlengthが16しかないので、残りの3文字をX2から読み込んできちゃうという暴挙にでるわけです。


解決策
このバグが解消されているSAS9.4で実行するのが手っ取り早いです。

9.3なら、変換する変数のlengthを16じゃなくて19に設定しとくとか、
または以下のような感じでINPUT関数で変換する時に、後ろに半角スペースを3文字分くっつけて、無理矢理19文字にするっていうのはどうでしょうか。これらは検証してないんでうまくいくかは保証できないです。

  if X1 ^="" then Y  = input( X1 || "   ", e8601dt. );


よーわからんですね。気をつけましょう。