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さんに続き、ハッシュオブジェクトを推していきたいです。


0 件のコメント:

コメントを投稿