ラベル ステートメント: RETAIN の投稿を表示しています。 すべての投稿を表示
ラベル ステートメント: RETAIN の投稿を表示しています。 すべての投稿を表示

2015年7月7日火曜日

ARRAYステートメントにRETAIN機能を付与する小技




今回は「RETAIN」と「ARRAY」まわりの話になります(この2つよく分からんって方は以下記事を参照)



では本題。以下のようなデータがあったとします。

* サンプルデータ ;
data DT1;
input A B;
cards;
1 10
. .
2 .
. 20
. .
;

 A  
 B  
 1 
 10 
 . 
 . 
 2
 . 
 . 
 20 
 . 
 . 


変数A, Bについて、欠損値であれば前のオブザベーションからRETAINで値をひっぱってきたいとします。↓
(引っ張った結果をそれぞれ変数A2, B2とする。)

 A  
 B 
 A2  
 B2  
 1 
 10 
 1 
 10 
 . 
 . 
 1 
 10 
 2 
 . 
 2
 10 
 . 
 20 
 2
 20 
 . 
 . 
 2
 20 


そこで最初に思いつくのが、以下のように ARRAY と RETAIN を組み合わせる方法。

data DT2;
   set DT1;

   retain A2 B2;

   array AR1(*) A B;
   array AR2(*) A2 B2;

   do i=1 to dim(AR1);
       if AR1(i) ^=. then AR2(i) = AR1(i);
   end;
   drop i;
run;


実はこれ、RETAINを使わなくてもいけちゃいます。
最近マニュアルを読み返して知ったんですが、ARRAYで初期値を与えるとRETAIN機能が付与されるみたいです。

初期値を与える
data DT2;
   set DT1;

   array AR1(*) A B ;
   array AR2(*) A2 B2 ( 2*. ) ;

   do i=1 to dim(AR1);
        if AR1(i) ^=. then AR2(i) = AR1(i);
   end;
   drop i;
run;


 A  
 B 
 A2  
 B2  
 1 
 10 
 1 
 10 
 . 
 . 
 1 
 10 
 2 
 . 
 2
 10 
 . 
 20 
 2
 20 
 . 
 . 
 2
 20 

プログラム青字部分のようにカッコの中に初期値を与えることが出来て、たとえば、
「array AR(*) A B C  ( 10 20 30 );」 と書けば、「A=10 B=20 C=30」 と初期値が与えられます。
「array AR(*) A B C  ( 3*. );」 と書けば、「A=. B=. C=.」 と3つまとめて初期値として欠損値を与えることが出来ます。


ただし!
※ 初期値を与えてRETAIN機能が付与されるのは、RETAINステートメントと同様にデータステップ内で新たに作成した変数のみです。


2015年6月22日月曜日

RETAINステートメント徹底入門





RETAINは、前のオブザベーションの値を保持してくれるステートメントです。



構文

RETAIN  値の保持機能を持たせる変数   初期値 ;

  • 値の保持機能を持たせる変数」… データセット内に存在する変数を指定しても、うまくREATINされません。新しい変数を作って、それを指定しましょう。
    • 値の保持機能を持たせたいのが文字変数の場合は、先にLENGTHステートメント等で長さの設定も忘れずに。
  • 初期値」… 省略化



とりあえず、具体例を見ていきましょう。



サンプルデータ

data DT1;
input A @@;
cards;
1 . 5 . . 10
;
 A  
    1 
    .  
    5  
    .  
    .  
  10  



例①

data OUT1;
   set DT1;
   retain B 0;
run;

  B   
   1 
   0  
   .  
   0  
   5  
   0  
   .  
   0  
   .  
   0  
 10  
   0  


変数Bに初期値「0」を与えて、最後のオブザベーションまでその値が保持され続けていることが確認できます。



例②

data OUT2;
   set DT1;
   retain A2;
   if A^=. then A2=A;
run;

  A2   
   1 
    1  
   .  
    1  
   5  
    5  
   .  
    5 
   .  
    5  
 10  
  10  


  • RETAINで変数A2に値保持機能を持たせます。
  • IF文で、変数Aに何か値があれば、その値をA2に放り込んでいます。


結果として、「変数Aに値がなければ、前のオブザベーションから値を引き継ぐ」という処理をした変数A2を作ることが出来ます。




実践例①


サンプルデータ
data DT2;
input NO$ DAY POINT$;
cards;
001 1 100pt
001 2 .
001 3 .
001 4 150pt
002 1 .
002 2 200pt
002 3 .
002 4 .
;
 NO  
 DAY 
 POINT 
  001
  1  
 100pt 
  001
  2  
  
  001
  3  
   
  001
  4  
 150pt
  002
  1  
  002
  2  
 200pt   
  002
  3  
   
  002
  4  
    

NO      ・・・ 顧客NO
DAY    ・・・ 来店日
POINT ・・・ ポイント


サンプルデータの説明。
とあるお店がポイントカードを導入したとします。
顧客NO毎にポイントを管理していて、来店してない日はポイントをNULLにしています。

(突っ込みどころ満載のデータですが、あくまで説明用のためのデータです。。)


ここで、来店してない日は、前回来店時のポイントで埋めたいとします。

 NO  
 DAY 
 POINT2 
  001
  1  
 100pt 
  001
  2  
 100pt
  001
  3  
 100pt
  001
  4  
 150pt
  002
  1  
  
  002
  2  
 200pt   
  002
  3  
 200pt 
  002
  4  
 200pt


*** 来店してない日を、前回来店時のポイントで埋める。 ;
proc sort data=DT2;
   by NO DAY;
run;

data OUT3;
   set  DT2;
   by  NO ;

   length  POINT2 $8.;
   retain  POINT2;
   if  first.NO then POINT2="";
   if  POINT ^="" then POINT2 = POINT;
run;

以下記事の方法も組み合わせてます。
「FIRST.BY変数」と「LAST.BY変数」で、グループ毎の最初と最後のオブザベーションを特定する。


  • まず「retain  POINT2」で、POINT2に値保持機能を持たせる。(この変数にポイント埋めをします)
  • if  first.NO then POINT2=""」で、RETAIN機能を有したPOINT2を、顧客NOごとに初期化(欠損値に)する。(初期化しないと、前の顧客のポイントがRETAINされちゃうので。)
  • if  POINT ^="" then POINT2 = POINT」で、POINTに何か値があれば、その値をRETAIN機能を有したPOINT2に放り込んでいます。

これで、ポイントを埋めた変数POINT2の完成です。



実践例②


サンプルデータ
data DT3;
input SUBJID$ VISITNUM VAL;
cards;
001 0 100
001 1 200
001 2 .
001 3 .
002 0 300
002 1 .
002 2 400
002 3 .
;
 SUBJID 
 VISITNUM 
 VAL 
  001
  0  
 100
  001
  1  
 200
  001
  2  
  . 
  001
  3  
  .
  002
  0  
 300
  002
  1  
  .   
  002
  2  
 400 
  002
  3 
  .

SUBJID          ・・・ 患者ID
VISITNUM    ・・・ 測定時期  0=投与前、1=投与1日後、2=投与2日後・・・
VAL                ・・・ なんかの測定値


サンプルは、治験薬投与前後にとある検査値を測定したデータだとします。

やりたい事は、測定値が欠損値であれば、以下のように前回測定された値を引っ張ってきたい。
(ただし、前回測定したのが投与前の場合は、投与後の値としては引っ張ってきたくない。)

 SUBJID 
 VISITNUM 
 VAL2 
  001
  0  
 100
  001
  1  
 200
  001
  2  
 200  
  001
  3  
 200
  002
  0  
 300
  002
  1  
  .   
  002
  2  
 400
  002
  3 
 400


* 前回測定値を引っ張ってくる(投与前以外) ;
proc sort data=DT3;
   by SUBJID VISITNUM ;
run;

data OUT4;
   set DT3;
   by SUBJID;

   length VAL2 8.;
   retain VAL2;

   if first.SUBJID then VAL2=.;
   if VAL^=. then VAL2=VAL;

   output;
   if VISITNUM=0 then VAL2=.;
run;


やってる事は、ほぼ「実践例①」と同じ。

ただし投与前の測定値は、投与後にRETAINしたくないので、
特定条件の時だけ、RETAIN機能を無効にする
の方法を組み合わせてます。(プログラム青字部分)


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



2015年6月13日土曜日

特定条件の時だけ、RETAIN機能を無効にする。




以下のデータを見てください。

*** サンプルデータ ;
data DT1;
input A B @@;
cards;
1 100 2 . 3 200 4 . 5 400 6 .
;

 A 
   1 
 100  
   2  
  . 
   3  
 200 
   4  
  .
   5  
 400
   6 
  .



このデータで、まずはBの値を単純にRETAINした変数B2を作ってみます。

*** Bの値をRETAIN ;
data OUT1;
   set DT1;

   retain B2;
   if B ^=. then B2 = B;
run;

 A 
B2
   1 
 100  
 100  
   2  
  . 
 100  
   3  
 200 
 200  
   4  
  .
 200  
   5  
 400
 400  
   6 
  .
 400  



次が問題。
たまに特定条件の時だけ、RETAIN機能を無効にしたい事があります。

たとえば、以下のように A=3 の時だけ、RETAINしたくないとします。
(3オブザベーション目の 「200」 が次のオブザベーション以降に保持されないようにする)

 A 
B2 
   1 
 100  
 100  
   2  
  . 
 100  
   3  
 200 
 200  
   4  
  .
   .  
   5  
 400
 400  
   6 
  .
 400  



*** A=3の時だけ、RETAIN機能を無効にする ;
data OUT2;
   set DT1;

   retain B2;
   if B ^=. then B2 = B;
   output;

   if A=3 then B2=. ;
run;



上の赤文字が追加した文。

データセットDT1の3オブザベーション目に対する処理の動きを説明すると、

①「output;」で3オブザベーション目の現状の処理結果をデータセットに出力してから、
②「if A=3 then B2=.;」で A=3 の場合にB2を初期化(欠損値に)しています。


上記①で3オブザベーション目に対する処理結果をデータセットに出力済みなので、②の処理は3オブザベーション目の処理としてはデータセットに反映されず、PDV (処理が行われる一時的な箱) のみに保持されています。

このPDV内のB2は、「retain B2;」により次のオブザベーションを読み込む際にもその値が引き継がれます。


③ 上記②でPDVのB2が初期化された場合、次のオブザベーションには欠損値になったB2が引き継がれる(=RETAIN機能が無効になる)、というわけです。



2014年8月13日水曜日

RETAIN vs SETステートメント



SETステートメントの落とし穴として紹介した「値の保持」「変数の初期化」の性質を利用したテクニックを紹介したいと思います。



サンプルデータ

data DT1;
   NO="001";  DAY=1;  POINT="100pt"; output;
   NO="001";  DAY=2;  POINT=""; output;
   NO="001";  DAY=3;  POINT=""; output;
   NO="001";  DAY=4;  POINT="150pt"; output;
   NO="002";  DAY=1;  POINT=""; output;
   NO="002";  DAY=2;  POINT="200pt"; output;
   NO="002";  DAY=3;  POINT=""; output;
   NO="002";  DAY=4;  POINT=""; output;
run;

proc sort data=DT1; by NO DAY; run;

 DT1
 NO  
 DAY 
 POINT 
  001
  1  
 100pt 
  001
  2  
 . 
  001
  3  
 .  
  001
  4  
 150pt
  002
  1  
 . 
  002
  2  
 200pt   
  002
  3  
 .   
  002
  4  
 .   


サンプルデータの説明です。
あるドーナツ屋さんがポイントカードを導入したとします。
顧客NO毎にポイントを管理していて、来店してない日はポイントをNULLにしています。

ここで、来店してない日を、前回来店時のポイントで埋めたいとします。

 NO  
 DAY 
 POINT2 
  001
  1  
 100pt 
  001
  2  
 100pt
  001
  3  
 100pt
  001
  4  
 150pt
  002
  1  
 . 
  002
  2  
 200pt   
  002
  3  
 200pt 
  002
  4  
 200pt

プログラム例

*** 1. RETAINを利用 ;
data DT2;
   length  POINT2 $8.;
   retain  POINT2;
   set  DT1;
   by  NO ;
   if  first.NO then POINT2="";
   if  POINT ^="" then POINT2 = POINT;
run;

*** 2. SETの性質を利用 ;
data DT3;
  set  DT1  DT1(obs=0 rename=(POINT=POINT2));
  by  NO ;
  if  POINT ^="" then POINT2 = POINT;
run;

RETAINについては、ここでは詳細を割愛します。
詳細解説は「RETAINステートメント徹底入門」をご覧ください。


そして、今回紹介したいSETの性質を利用した方法。
(まず一番先頭のリンクの内容を理解しておく必要があります。)

ポイントは上のプログラムで赤字にした箇所。
①「obs=0」は0行読み込むという意味、、つまり1行も読み込まない。
②「rename=」でPOINTをPOINT2にRENAMEしておく。

つまりここでやりたい事は、レコードは読み込まず、PDV上にPOINT2という変数の枠を作っておく事です。

③次に、上のプログラムの青字で示したDT1はPOINT2という変数を持っていません。
つまりこのデータセットからレコードを読み込むとき、PDV上のPOINT2は値の保持機能が働きます。



以下PDV処理の一部




















説明が難しいところなので、分かりづらいかもしれないけど、どうでしょうか。
他にも応用がきくので、いろいろ試してみると面白いです。


📝注意点

今回のテクニックの中で使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。
(解説記事:「サブセット化IFでありがちな落とし穴」)