2016年6月29日水曜日

BY値繰り返しのマージを活用する。


↓前回の続きです。
「MERGEステートメントにBY値を繰り返すデータセットが複数あります。」 とログに出たとき、どんなマージがされているのか。


今回は、BY値を繰り返すMERGEの活用例を紹介します。

例えば製薬業界だと、治験とかで症例の一覧を作成したりしますが、そういった時に使えます。




data DT1;
input NO$ SEX:$10. AGE ;
cards;
001 男 25
002 女 30
;

data DT2;
input NO$ KIO:$200. ;
cards;
001 aaa
001 bbb
001 ccc
002 ddd
002 eee
;

data DT3;
input NO$ GAP:$200. ;
cards;
001 AAA
001 BBB
002 CCC
002 DDD
;

DT1
NO
 SEX  
 AGE  
  001     男    25  
  002     女  30   

DT2
NO
 KIO  
  001     aaa   
  001     bbb
  001     ccc
  002     ddd
  002     eee

DT3
NO
 GAP  
  001     AAA   
  001     BBB
  002     CCC
  002     DDD


上記3つのデータセットをNOをキーにマージして、最終的に以下のような形でEXCELの一覧を作りたいとします。










普通にMERGEすると、、

data OUT1;
   merge DT1 DT2 DT3;
   by NO;
run;


望み通りの形になりません(上記の黄色部分を欠損値にしたい)

厄介なのが、DT2とDT3による多対多のマージにより、BY値の繰り返しが起きて、上記3オブザベーション目のGAPにも値が引き伸ばされてしまっている点。




そこで以下で紹介したテクニックで値が引き伸ばされないようにすると、、
「OUTPUT」と「CALL MISSING」のコンボ技

data OUT1;
   merge DT1 DT2 DT3;
   by NO;
   output;
   call missing(of _all_);
run;


大体、望み通りの形になります。

BY変数にしているNOも繰り返し表示されないように、以下青字のような文を追加すれば完成です。
data OUT1;
   length _NO $10.;

   merge DT1 DT2 DT3;
   by NO;

   if first.NO then _NO=NO;

   output;
   call missing(of _all_);
run;

あとはこれをEXCELにペタっと貼れば完成です。


📝注意

今回のテクニックで使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。


2016年6月27日月曜日

「MERGEステートメントにBY値を繰り返すデータセットが複数あります。」 とログに出たとき、どんなマージがされているのか。


n対nのマージをすると出るログメッセージ。
具体的にどういう動きをするのか見てみます。

*** サンプルデータ ;
data DT1;
input A$ B$ @@;
cards;
001 vv 001 ww 001 xx 002 yy 002 zz
;

data DT2;
input A$ C$ @@;
cards;
001 aa 001 bb 002 cc
;

data DT3;
input A$ D @@;
cards;
001 10 002 20
;

DT1
A
  B  
  001     vv   
  001     ww
  001     xx
  002     yy
  002     zz

DT2
A
  C  
  001     aa   
  001     bb
  002     cc

DT3
A
 D  
  001     10   
  002     20


上記3つのデータセットを変数AをキーにMERGEした結果を以下に示します
data OUT1;
   merge DT1 DT2 DT3;
   by A;
run;

ログには 「NOTE: MERGEステートメントにBY値を繰り返すデータセットが複数あります。」 と出ます。

このログメッセージと出力結果はどういうものなのかというと、、
内部処理(PDVとか)が分かると理解しやすいですが、その辺は抜きで簡単に解説。
(注意: 以下はあくまでイメージなので、厳密な動きを表しているわけではないです)











まずBY変数Aの値毎にMERGEされます。
たとえばA="001"の1行目同士でMERGE、2行目同士でMERGEがされていきます。
ここで、A="001"の2行目はDT1とDT2にはあるけど、DT3にはありません。











なので上記黄色部分のように前のレコードで処理された値が引き継がれます。
(RETAINみたいなイメージですね)












同じ要領で上記のような形が結合結果となります。



BY値を繰り返すマージって、意図的にやる事は少ないと思います。
なので人・組織によっては 「ログにこのメッセージが出てるぞ!確認しろ!」 と怒られます。

もし意図的にやるのであれば、問題ない旨のコメントをプログラム文中に残しておいた方が良いです。

/* BY値の繰り返しメッセージが出てるけど問題なし */  っていうコメントではなく、どういったデータを意図していて、何故問題がないのか詳細を書いた方が良さそうです。


。。。つづく

2016年6月23日木曜日

【SAS入門】 SASデータセットとは?



以下に、AGE(年齢)、SEX(性別)、HEIGHT(身長)、WEIGHT(体重)の情報が入ったSASデータファイルがあるとします。








このデータファイルを 「データセット」 と呼びます。

また、AGE、SEX、HEIGHT、WEIGHTなどの項目の事を 「変数」 と呼びます。

さらに行の事を 「オブザベーション」 と呼びます。



ではSASプログラマーの第1歩として、以下の質問に答えてみましょう。


質問
Q1. 上のデータセットで、3オブザベーション目の変数AGEの値を教えてください。

Q2. 上のデータセットで、変数SEXの値が”女”のオブザベーション数を教えてください。



答え
A1. 19

A2. 2オブザベーション



2016年6月22日水曜日

【DROP・KEEPステートメント】データセットから特定の変数を削除または残す




※ データステップのMODIFYステートメントなど、特定の機能を用いるとDROP, KEEPステートメントが動作しないことがあるので注意。




DROPステートメント

指定した変数を出力データセットに「含めない」ようにする

data DT2;
   set SASHELP.CLASS;
   drop NAME AGE;
run;

💬 
  • 通常のステートメントはPDVという「一時的な処理スペース」で実行されますが、DROPステートメントは「一時的な処理スペース」の結果を「出力データセットへ書き込む時」に実行します
  • つまり、最終的に適用されるイメージです。また適用されるタイミングが決まっているので、DROPステートメントを記述する位置は関係ありません(プログラムの先頭や最後に書いても挙動は変わらない)




KEEPステートメント

指定した変数のみ」出力データセットに「含める」ようにする

data DT1;
   set SASHELP.CLASS;
   keep NAME AGE;
run;

💬 
  • DROPステートメントと同様、KEEPステートメントは記述する位置に関係なく、「出力データセットへ書き込む時」に実行します(詳細は上のDROPステートメントの説明を参照)




テクニック

過去にいくつかテクニックを紹介しています。

ATTRIBとKEEPを使った基本コンボ
変数名をいっきに指定する方法 [まとめ]



また、上の2つ目のリンク記事内にある「:(コロン)」を使ったよくやるテクニックを紹介。

やりたい事
「データステップ内で作った一時的な変数を最終的にまとめてDROPしたい」


こういった場合、まとめてDROPしたい変数の名前の先頭に 「tmp_」 とかつけておいて、「DROP tmp_: ;」 と書いてしまえば、最後に「tmp_○○」の変数をまとめて削除できちゃいます。
(たまにやっちゃうミスですが、既存の消したくない変数に「tmp_○○」というのがあったらそれも消えちゃうので気をつけてください)



data DT3;
   set SASHELP.CLASS;
   _A = 1;
   _B = 2;
   drop _: ;
run;


上の例では先頭に「_」のつく変数をまとめて削除しています。


2016年6月7日火曜日

「OUTPUT」と「CALL MISSING」のコンボ技


一対多のマージで、一側データ由来の変数値を各BYグループの最初のみに出力したい事ってありませんか?


*** サンプルデータ ;
data DT1;
input A$ B$ C$ @@;
cards;
001 aa cc 002 bb dd
;

A
 B  
 C  
  001    aa     cc   
  002    bb  dd   

data DT2;
input A$ D @@;
cards;
001 11 001 22 001 33 002 44 002 55
;

A
  D  
  001     11   
  001     22
  001     33
  002     44
  002     55


やりたい事

上のデータをMERGEすると、
data OUT1;
  merge DT1 DT2;
  by A;
run;


上記のように一側データ(DT1)由来の黄色部分が繰り返されますが、これを空白にしたい。
A
 B  
 C  
  D  
  001    aa     cc      11   
  001                 22   
  001        33   
  002    bb     dd      44   
  002        55  


前提: 
一対多のマージを前提とします。多対多を含むマージ(一対多対多とか)だと挙動が変わってくるので今回の記事では対象外とします。



方法1 最初に思いつく方法
data OUT1;
  merge DT1 DT2;
  by A;
  if first.A = 0 then call missing( B, C );
run;

以下記事で紹介した「FIRST.BY変数」を利用しています。

1つ注意として、ここで使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。



方法2 OUTPUT と CALL MISSING のコンボ技
data OUT1;
  merge DT1 DT2;
  by A;
  output;
  call missing( B, C );
run;

紹介しといてなんですが、方法1の方が、分かり易くていいと思います。
ですが方法2のようなやり方を知ってると、応用がきいて便利な時があります。


方法2の解説

以前の記事 「MERGEステートメントの落とし穴」 で紹介した内容を理解することで、今回の内容も理解がしやすいです。

重要なポイントは、
「一対多のマージにおいて、一側データセットの変数は、BYグループ毎に1回しかPDVに読み込まれない。
(BYグループが変わるまで、PDVに残った値が保持される。。。RETAINのようなイメージ)」


そこで、1オブザベーション毎に
① 「output;」でデータセットOUT1に出力
② 「call missing( B, C );」 でPDVの中の変数BとCを欠損値にする

PDVに残った一側データ由来の変数BとCの値が保持されるのを②によって抑制しているので、BYグループの最初にしかBとCが入らなくなっているわけです。


一側データの変数の数が多い場合は、「call missing( of _all_ );」と書いてしまえば、いっぺんに欠損値に出来るので楽です。

data OUT1;
  merge DT1 DT2;
  by A;
  output;
  call missing( of _all_ );
run;

ただし、PDV内の全変数を欠損値にしているので、
RETAIN等、値を保持するような処理は全て無効になります。


。。。つづく

2016年6月2日木曜日

マクロ言語入門7:マクロ内でのループ処理【%DO】



マクロ内でループ処理をする「%DO-%ENDステートメント」を紹介します。





構文

%DO  マクロ変数名 = 開始値  %TO  終了値;

           繰り返し実行したい処理

%END;

  • [マクロ変数] の値を [開始値] から [終了値] まで変化させながら、中に書かれた処理を繰り返し実行します
  • マクロの中でのみ利用可能




%macro TEST;

   %do no = 1 %to 5;
        data DT&no ;
        run;
   %end;

%mend;
%TEST;



マクロ変数NOの値を1から5まで変化させながら、中に書かれた処理を繰り返し実行します。
つまり以下のプログラムが実行される事になります。

data DT1;
run;

data DT2;
run;

data DT3;
run;

data DT4;
run;

data DT5;
run;