2015年7月6日月曜日

COMPAREプロシジャでデータセット間に不一致があった事をログに表示するオプション




COMPAREプロシジャの基本については以下をご覧ください。
【PROC COMPARE入門】データセット間の変数値の比較



さて、以下のような2つのデータセットがあったとします。

data DT1;
   length A $2.;
   A="aa";
run;

 A  
 aa 

data DT2;
   length A B $3.;
   A="aaa"; B="bbb"; output;
   A="ccc"; B="ddd"; output;
run;

 A  
 B  
 aaa 
 bbb 
 ccc 
 ddd 



この2つのデータセットをCOMPAREプロシジャで比較してみます。

proc compare base=DT1 compare=DT2 ;
run;

出力結果(一部)




この出力結果は見るところが多いので、たまに不一致箇所を見落としてしまう事があります。
そこで、WARNINGオプションをつけると、不一致があった旨のWARNINGメッセージをログに表示してくれます。

proc compare base=DT1 compare=DT2  warning ;
run;

ログ
WARNING: データセット WORK.DT2 にある 1 変数が WORK.DT1 にはありません。
WARNING: 1 変数の属性が 2 つのデータセットで一致しません。
WARNING: データセット WORK.DT2 にある 1 オブザベーションが WORK.DT1 にはありません。
WARNING: 次の 1 変数の値は不等です : A
WARNING: データセット WORK.DT1 と WORK.DT2 には不等な値があります。


WARN と略してもいけます。
また、ERROR と書けばERRORメッセージになります。


これで、不一致の見落としを軽減できそうです。


2015年6月29日月曜日

DS2プロシジャ入門4:データ型①


DS2プロシジャには沢山のデータ型が用意されています。
詳細は 「sas ds2 data types」 などでググって出てくる海外SAS社の解説ページをご覧ください。

ここでは一部紹介するに留めます。


とりあえず、、

文字変数は「CHAR」、数値変数は「DOUBLE」という型が、いままでSASで使ってた文字型・数値型と同等なので、まずこの2つを覚えましょう。

CHAR と DOUBLE を宣言する構文
 dcl  char(長さ ) 変数名 ;
 dcl  double       変数名 ;

ちなみに 「dcl double VAR1 VAR2 VAR3;」 というように複数の変数をいっぺんに定義することも出来ます。


クオーテーションには注意。

以下で、変数VAR1 に'aa' という値を入れたいとします。

proc ds2;
  data _DT1 (overwrite=yes);

      dcl char(10) VAR1;
      method init();
          VAR1="aa";
      end;
   
  enddata;
  run;
quit;

ログ
WARNING: 行 74: 参照変数 aa は宣言されていません。タイプ double
のグローバル変数として作成しています。

WARNINGが出てしまいます。
問題は、プログラム青字 「VAR1="aa"」 です。

実はDS2プロシジャでは、文字をダブルクオーテーションで囲むと、識別子とみなします。
(識別子?って感じですが、要はここでは変数名として見なされているって事です)

よって "aa" は変数aaと解釈されてしまい、「そんな変数ないんだけど、ちゃんと宣言しろ」と注意されてしまったわけです。

解決策は簡単で、シングルクオーテーションで囲めば値と見なしてくれます。

  VAR1='aa';


マクロとクオーテーション

以下で、変数VAR1 にマクロ変数の値 'aa' を入れたいとします。

%let MACVAR=aa;

proc ds2;
  data DT1 (overwrite=yes);

     dcl char(10) VAR1;
     method init();
         VAR1 = "&MACVAR";
     end;

  enddata;
  run;
quit;

ログ
WARNING: 行 76: 参照変数 aa は宣言されていません。タイプ double
のグローバル変数として作成しています。

プログラム青字部分の "&MACVAR" が展開され、"aa" となります。
ダブルクオーテーションで囲んでるため、やはり変数名として判断されWARNINGが出てしまいます。
かといって、 '&MACVAR' とシングルクオーテーションで囲ってしまうと「&MACVAR」という値として解釈されて、マクロ変数として展開してくれません。


そこで、%TSLITマクロ関数の出番。
このマクロ関数はシングルクオーテーションの付与と、マクロ変数の展開をしてくれます。

  VAR1 = %tslit( &MACVAR );

2015年6月26日金曜日

データステップ内で、色々なデータセットのオブザベーション数を取得する


(注意!)
今回の方法で正しくobs数を取得できないケースもあるので、記事の最後に貼っているリンク記事も参照下さい。



まずは適当なサンプルデータを用意。

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

data DT2;
   do i=1 to 20;
      output;
   end;
run;

data DT3;
   A="aa";  output;
   A="bb";  output;
   A="cc";  output;
run;

DT1 は10obs、DT2 は20obsを持つデータセットです。



ここで、DT3を読み込むときに、DT1とDT2のobs数も取得したいとします。

* 各データセットのOBS数を取得 ;
data OUT1;

   set DT3;

   if 0 then set DT1(drop=_ALL_)  nobs= _OBS1;
   if 0 then set DT2(drop=_ALL_)  nobs= _OBS2;

   DT1_OBS = _OBS1;
   DT2_OBS = _OBS2;

run;

  A  
 DT1_OBS 
 DT2_OBS 
  aa 
  10  
  20 
  bb
  10  
  20 
  cc
  10  
  20 


解説

プログラム青字部分がポイントです。
「if 0 then」 の 「0」 は 「false」 の意味になり、then以降の 「set DT1(drop=_ALL_) ・・・」 の部分は実行されません。
。。のはずですが、たとえ実行されなくてもデータセット名の指定があると、コンパイル時にそのデータセットの定義情報だけが読み込まれます。

この性質を利用し、
・まず 「if 0 then set DT1」 でDT1の定義情報だけを読み込みます。
・変数定義も読み込まれてしまうので 「DT1(drop=_ALL_)」 で読み込まれる全変数をdropする。
・「nobs = 適当な一時変数名」で、定義情報に格納されたobs数を一時変数に格納。
といった流れです。


※ 以下記事の通り、特殊な処理をしてる場合は、正しくobs数を取得できないのでご注意ください。
行削除の落とし穴


2015年6月23日火曜日

DS2プロシジャ入門3:変数属性と配列の定義



① 属性を付与する方法

変数を宣言するついでに、属性も定義する感じです。

構文
 dcl  データ型  変数名  having   label   'ラベル'
                                                   format  フォーマット
                                                   informat  インフォーマット ;

例
proc ds2;
   data DT1 (overwrite=yes);
       dcl double VAR1 having  label 'aaa'  format yymmdd10. ;
       method init();
            VAR1 = inputn('2015/06/23', 'yymmdd10');
       end;
   enddata;
   run;
quit;




② 配列の定義方法

一時配列(DS2プロシジャを終了したら残らない)を作る構文
 dcl  データ型  配列名 [要素の数] ;

例
proc ds2;
   data _NULL_;
       dcl double AR[2];
       method init();
            AR[1] = 10;
            AR[2] = 20;
            put AR[1]=;
            put AR[2]=;
       end;
   enddata;
   run;
quit;

ログ
AR[1]=10
AR[2]=20


通常の配列(DS2プロシジャを終了しても変数は残る)を作る構文
 vararray データ型  配列名 [要素の数]   変数1 変数2 ・・・;

例
proc ds2;
   data DT2 (overwrite=yes);
       vararray double AR[2] A B;
       method init();
            AR[1] = 10;
            AR[2] = 20;
       end;
   enddata;
   run;
quit;


注意:
「vararray double AR[1] double」みたいな感じで、変数名を指定する部分にデータ型を指定すると特殊な動き(想定外の挙動)をするため、ご注意ください(詳細はリファレンスなどを参照下さい)


DS2プロシジャ入門記事

1: 基本構文
2: 変数の宣言
3: 変数属性と配列の定義

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;

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


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



例②

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

A 
  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 
B 
   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 
B 
B2
   1 
 100  
 100  
   2  
  . 
 100  
   3  
 200 
 200  
   4  
  .
 200  
   5  
 400
 400  
   6 
  .
 400  



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

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

 A 
B 
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機能が無効になる)、というわけです。



2015年6月9日火曜日

複数の変数から、欠損値以外の値を左にシフトしたい。



やりたい事

* サンプルデータ ;
data DT1;
input (SUBJID A B C D) ($);
cards;
001 .  bb .  aa
002 .  .  cc .
003 aa .  cc bb
004 . . .  .
;







上のデータで変数A, B, C, D のうち、非欠損値を左にシフトしたい。




では解決法。と、その前に前提条件
  • 左にシフトしたい変数はすべて同じ型であること。


解決法

data DT2;
   set DT1;
   length COL1-COL4 $5.;

   array AR1(*) A B C D;
   array AR2(*) COL1-COL4;

   NO = 0;

   do i=1 to dim(AR1);
       if not missing(AR1(i)) then do;
           NO + 1;
           AR2(NO) = AR1(i);
       end;
   end;
run;

概要
まず変数A, B, C, D を配列に入れる。
配列の値をループで順番に見ていき、もし非欠損値だったら新しい変数COL1~COL4へ順番に放り込んでいく。




なんか他にいい方法あったら教えてほしいです。