ラベル Hashオブジェクト の投稿を表示しています。 すべての投稿を表示
ラベル Hashオブジェクト の投稿を表示しています。 すべての投稿を表示

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=, VARLEN= );
    /*-----------------------------------------------------------------------
       DS     =   対象のデータセット ,
       WH    =   WHERE条件 ,
       VAR   =   引っ張ってくる変数
       VARLEN   =   引っ張ってくる変数に設定するLENGTH
   -----------------------------------------------------------------------*/

   %local  _index;
   %let  _index = &sysindex;

   _runobs&_index + 1;
   drop _runobs&_index;

   if _runobs&_index = 1 then do;
      length &VAR &VARLEN;
      dcl hash _hash&_index( dataset:"&DS( where = (" || %sysfunc(quote(&WH)) || "))", duplicate:"e" );
      _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;

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

   length SAFFL $1.;
   SAFFL = QVAL;
run;



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



2018年1月19日金曜日

HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法【DO_OVER編】




以前、以下の記事を書きました。
HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法



で、マニュアルをよくよく見ると、SAS9.4から追加になった「DO_OVERメソッド」を使えばもっと楽に書けたんですね。勉強不足すぎ・・

というわけで、前書いた記事の訂正、というか追記です。
サンプルデータの作成部分とか諸々の説明などは前書いた記事を参照下さい。


以下が訂正したプログラム

data OUT;

    set DT1;

    /* 購入データをHashオブジェクトに格納 */
    if _n_=1 then do;

        length PURDATE 8.;
        call missing( PURDATE );

        dcl hash purhash( dataset:"DT2", multidata:"y" );
        purhash.definekey("NO");
        purhash.definedata("PURDATE");
        purhash.definedone();

    end;

    /* Hashオブジェクト内をループして、顧客IDごとの購入日の最小値を取得 */
     rc = purhash.reset_dup();
     do while ( purhash.do_over()=0 );
           if PURDATE^=. then MINDATE = min( MINDATE, PURDATE );
     end;

     format MINDATE yymmdd10.;
     keep NO SEX AGE MINDATE;
run;

かなりシンプルになりました!
「DO_OVERメソッド」は、「FINDメソッド」「FIND_NEXTメソッド」を組み合わせたようなやつです。

例では「RESET_DUPメソッド」というものも使用していますが、実は今回の例では不要。
どういう時に使用するかは、以下に記載しています。


📝DO_OVERの注意点

今回の例のように、

do while ( purhash.do_over()=0 );

DO WHIILE & DO_OVERで、検索中のkey値がHashオブジェクト内になくなるまでループする必要があります。

もし、検索中のkey値がHashオブジェクト内にまだある状態で、途中からkey値を手動で変えてしまうと、DO_OVERの結果がバグります。
バグらないようにするには「RESET_DUPメソッド」を使って、Hashオブジェクト内の検索位置をリセットする必要があります。

詳細はマニュアルを参照下さい。



📝その他注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。


2017年12月12日火曜日

HASHオブジェクトで、Key変数毎に最小値・最大値・合計値などを取得する方法


(コピペ利用するための、ほとんど個人的なメモ)



例

* Sample Data ;
data DT1;
input NO:$3. SEX:$1. AGE:8.;
cards;
001 F 39
002 M 25
;
 NO・・・顧客ID
 SEX・・・性別
 AGE・・・年齢


data DT2;
input NO:$3. FOOD:$10. PURDATE:yymmdd10.;
format PURDATE yymmdd10.;
cards;
001 ORANGE  2017/12/11
001 APPLE   2017/12/13
001 CHERRY  2017/12/10
002 ORANGE  2017/12/15
002 APPLE   2017/12/05
;
 NO・・・顧客ID
 FOOD・・・購入食品
 PURDATE・・・購入日




上のデータから、顧客ID毎に購入日の最小値を取得したいとします。

data OUT;

    set DT1;

    /* 購入データをHashオブジェクトに格納 */
    if _n_=1 then do;

        length PURDATE 8.;
        call missing( PURDATE );

        dcl hash purhash( dataset:"DT2", multidata:"y" );
        purhash.definekey("NO");
        purhash.definedata("PURDATE");
        purhash.definedone();

    end;


    /* Hashオブジェクト内をループして、顧客IDごとの購入日の最小値を取得 */
     rc = purhash.find();
     if rc = 0 then MINDATE = PURDATE;

     do while (rc = 0);
          rc = purhash.find_next();
          if rc = 0 then MINDATE = min( MINDATE, PURDATE );
     end;

     format MINDATE yymmdd10.;
     keep NO SEX AGE MINDATE;
run;




解説

まずHashの触りは以下記事を読むと理解しやすいです。

http://sas-boubi.blogspot.jp/2015/02/hash.html
(SAS忘備録:Hashオブジェクトを使おう)




かいつまんで解説

  dcl hash purhash( dataset:"DT2", multidata:"y" );
  purhash.definekey("NO");
  purhash.definedata("PURDATE");
  purhash.definedone();

まずはHashオブジェクトの定義から。
データセットDT2をHashオブジェクトに格納し、「multidata:'y'」で、Key値の重複を許しています。
変数NOをKey値、変数PURDATEをKey値に関連づける変数として定義しています。



  rc = purhash.find();
  if rc = 0 then MINDATE = PURDATE;

findメソッドでHashオブジェクト内をKey値で検索。
該当するKey値があったら、取得したPURDATEをMINDATEに格納。



  do while (rc = 0);
    rc = purhash.find_next();
    if rc = 0 then MINDATE = min( MINDATE, PURDATE );
  end;

find_nextメソッドで、Hashオブジェクト内の次のレコードを検索。
該当するKey値があったら、取得したPURDATEをMINDATEと比較し、最小日付をMINDATEに格納。

これをHashオブジェクト内に該当するレコードがなくなるまで、do whileでループしています。




一応今回やってることは、SQLやMEANSとかの方が楽に求められるけど、DATAステップ内で完結できるのが最大のメリットだと思います。


📝注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。



2016年3月29日火曜日

オブザベーション数を取得してマクロ変数に格納する方法 【まとめ】


ぱっと思いつく限りあげてみました。他に面白い方法があれば教えてほしいです。


*** サンプルデータ ;
data DT1;
  do i=1 to 100;
    output;
  end;
run;


① SQL INTO
proc sql noprint;
  select count(*) into :OBS1 trimmed
  from DT1;
quit;

%put &OBS1;


② NOBS
data _NULL_;
  if 0 then set DT1(drop=_ALL_)  nobs=NOBS;
  call symputx("OBS2", NOBS);
  stop;
run;

%put &OBS2;

参照…http://sas-boubi.blogspot.jp/2015/06/blog-post_26.html(過去記事)
※ 注意事項があるので過去記事参照


③ SQL DICTIONARY
proc sql noprint;
  select NLOBS into :OBS3
  from DICTIONARY.TABLES
  /* ライブラリ名とデータセット名を大文字で指定する */
  where LIBNAME="WORK" and MEMNAME="DT1";
quit;

%put &OBS3;

参照…http://sas-boubi.blogspot.jp/2015/08/dictionary.html(過去記事)


④ OPEN, ATTRN関数
data _NULL_;
  DSID = open("DT1");
  OBS  = attrn(DSID, "NLOBSF");
  call symputx("OBS4",OBS);
run;

%put &OBS4;

まず、OPEN関数でデータセットを開く(戻り値としてデータセット識別子というIDが返される)
次に、ATTRN( データセット識別子, "NLOBSF" ) でオブザベーション数を取得しています。


⑤ HASH
data _NULL_;
  if 0 then set DT1;
  dcl hash hs(dataset :"DT1", multidata:"y");
  hs.definekey("i");
  hs.definedone();
  call symputx("OBS5", hs.num_items);
  stop;
run;

%put &OBS5;

参照…http://sas-tumesas.blogspot.jp/2014/07/clearnumitemsdeleteremove.html(データステップ100万回)

ポイント
multidata:"y" で重複キーも保持するようにしています(ハッシュオブジェクトはデフォルトだと、キーが重複するレコードに対して、最初のレコードしか保持しないので)


⑥ SYSNOBS
参照…http://sas-boubi.blogspot.jp/2014/01/blog-post_8902.html(過去記事)


⑦ SQLOBS
参照…http://sas-boubi.blogspot.jp/2014/09/blog-post_8.html(過去記事)

2015年2月16日月曜日

Hashオブジェクトを使おう。



複数のデータセットをこねくり回して、1個のデータセットを作りあげたい時、
ハッシュオブジェクトを使うと、1回のデータステップですべての処理が済んでしまうため、プログラムを書く時間も可読性も大幅に改善できることがあります。

データステップ100万回のSASYAMAさんの得意技で、「ハッシュオブジェクトの世界①」をはじめとして、沢山記事をあげられてるので、かなり参考になります。



では私自身が実際に使ってる簡単な例を紹介したいと思います。

*** サンプルデータ作成 ;
data DT1;
input NO$ SEX$ WEI;
cards;
001 F 50.5
002 M 68.8
;

data DT2;
input NO$ FOOD:$20.;
cards;
001 サンドイッチ
001 みかん
002 おにぎり
002 サンドイッチ
;

DT1
NO
 SEX 
WEI 
  001  
  F  
  50.5  
  002  
  M  
  68.8  

DT2
 NO 
FOOD
  001  
  サンドイッチ   
  001
  みかん  
  002
  おにぎり
  002
  サンドイッチ

DT1は、患者(NO)ごとの性別(SEX)や体重(WEI)などの基本情報が格納されたデータセット、
DT2は、患者(NO)が食べた朝食(FOOD)を表したデータセットだとします。


ここで例えば、DT2にみかんを食べた患者がいたら、DT1に性別・体重と並んでみかんを食べた事を表すフラグ変数を追加した新たなデータセットを作りたいとします。

data OUT1;
    set DT1;

    *** ハッシュオブジェクトを定義 ;
    if _N_=1 then do;
         dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );
         hs.definekey( "NO" );
         hs.definedone();
    end;

    *** みかんフラグを立てる ;
    if hs.check()=0 then FLG=1;

run;

NO
SEX
WEI
 FLG 
  001  
  F  
  50.5  
    1  
  002  
  M  
  68.8  
    . 


仕組みについては、上の方に貼ったSASYAMAさんの記事リンクを見た方が分かり易いかも。

一応、ざっくり解説

if _N_=1 then do;
   dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );
   hs.definekey( "NO" );
   hs.definedone();
end;

まずオブザベーションを読み込むたびに実行する必要がない箇所、
つまり最初の1回実行するだけでいい箇所を「if _N_=1 then do;」と「end;」で囲んであげる。


   dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );

DT2からFOOD='みかん'のデータを抽出してから、


   hs.definekey( "NO" );
   hs.definedone());

変数NOをkey変数としてハッシュオブジェクトに入れてます。
ここまでで、まずはFOOD='みかん'のNOをハッシュオブジェクトに入れることが出来ました。


ちなみに「dcl hash hs~」のところで「multidata:"y"」と書いてる部分は今回は書かなくても問題ないけど説明のために入れました。
ハッシュオブジェクトはデフォルトの動作として、Keyが重複しているレコードがある場合、重複Keyの中から最初のレコードのみハッシュオブジェクトに保持します。「multidata:"y"」で重複Keyのすべてのレコードを保持します。



if hs.check()=0 then FLG=1;

CHECKメソッドで、DT1のNOと同じ値がハッシュオブジェクト内のNOに存在するかチェックを行います。
存在してれば戻り値として0が返されるので、みかんフラグを立てます。


ちょっと応用。

今度は、以下3つのフラグを立ててみましょう。

・ サンドイッチを食べた患者
・ おにぎりを食べた患者
・ みかんを食べた患者

data OUT2;
    set DT1;

    *** ハッシュオブジェクトを定義 ;
    if _N_=1 then do;
         length FOOD $20.;
         call missing( FOOD );
         dcl hash hs( dataset:"DT2", multidata:"y" );
         hs.definekey( "NO", "FOOD" );
         hs.definedone();
    end;

    *** 各フラグを立てる ;
    if hs.check( key:NO, key:"サンドイッチ" )=0 then FLG1=1;
    if hs.check( key:NO, key:"おにぎり" )=0      then FLG2=1;
    if hs.check( key:NO, key:"みかん" )=0        then FLG3=1;

    drop FOOD;
run;


NO
SEX
WEI
 FLG1 
 FLG2 
 FLG3 
  001  
  F  
  50.5  
    1  
    .  
    1  
  002  
  M  
  68.8  
    1
    1 
    . 


ざっくり解説。

dcl hash hs( dataset:"DT2", multidata:"y" );
hs.definekey( "NO","FOOD" );
hs.definedone());

DT2のNOとFOODをkey変数としてハッシュオブジェクトに入れて、


if hs.check( key:NO, key:"サンドイッチ")=0 then FLG1=1;

CHECKメソッドで、検索をかける値を指定して、うまくヒットしたらフラグを立てています。
(DEFINEKEYメソッドに指定したkeyの順に検索値を指定する)


1つ注意することは、以下のメソッドで、

hs.definekey( "NO","FOOD" );

ハッシュオブジェクトに入れるNOとFOODは、データステップ内にも同じ名前の変数NOとFOODが存在している必要があります。(存在してないとエラーがでる)

NOとFOODのうち、NOはDT1に存在してるのでokですが、FOODはありません。なので、

length FOOD $20.
call missing( FOOD );

こんな感じでまずはデータステップ内の変数としてFOODを定義してあげます。
ここがちょっと独特な仕様ですね。


📝話がそれますが1つ注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。




便利さは使い込んでみてよく分かるので、もっともっと普及してほしいです。
私もSASYAMAさんに続き、ハッシュオブジェクトを推していきたいです。