ラベル CALL EXECUTE の投稿を表示しています。 すべての投稿を表示
ラベル CALL EXECUTE の投稿を表示しています。 すべての投稿を表示

2014年10月15日水曜日

全オブザベーション欠損値の変数を削除する


たとえば、以下サンプルデータで、削除したい変数はAとCです。

*** サンプルデータ ;
data DT1;
   A=.; B=1; C=""; D=1; output;
   A=.; B=1; C=""; D=2; output;
   A=.; B=.; C=""; D=3; output;
run;

 A  
 B  
 C  
 D  
  .
  1
  
  1
  .
  1
  
  2
  .
  .
  
  3

欲しい結果
 B  
 D  
  1
  1
  1
  2
  .
  3

そこで、データステップ100万回「freqプロシジャのnlevelsオプション」をヒントにプログラムを組んでみました。


*** ① 全OBS欠損値の変数を特定する ;
ods output NLevels=O_NLEVEL;

    proc freq data=DT1 nlevels;
       tables _all_ / noprint;
       format _all_;
    run;

ods output close;

*** ② 特定した変数をDROPする ;
data _NULL_;
    length  NNONMISSLEVELS 8. ;
    set  O_NLEVEL  end=EOF;
    if  _N_=1 then
          call execute( "DATA DT2; SET DT1 (DROP=" );

    if  NNONMISSLEVELS=0 then
          call execute( TABLEVAR );

    if  EOF then
          call execute( "); RUN;" );
run;


解説

①まず、FREQプロシジャのNLEVELSオプションによって下のようなOUTPUTとデータセットが出来る。

OUTPUT
変数    水準数    欠損水準    非欠損水準
----------------------------------------------
A              1           1             0
B              2           1             1
C              1           1             0
D              3           0             3

データセット
 TableVar 
 NLevels 
 NMissLevels 
 NNonMissLevels 
 A
 1
 1
  0
 B
 2
 1
  1
 C
 1
 1
  0
 D
 3
 0
  3

注目してほしいのは「非欠損水準(NNonMissLevels)」で、これが0ってことはその変数は全オブザベーションが欠損値ということですよね。


②次にプログラムを生成・実行してくれるCALL EXECUTEを使い、上で取得した非欠損水準が0の変数をDROPするプログラムを生成します。

生成されるプログラムは以下のような感じ。

DATA DT2; SET DT1 (DROP=
A
C
); RUN;


同様の処理を沢山のデータセットにやりたい場合は、このプログラムをちょっと改良してマクロ化しちゃえば楽です。

単純に全オブザベーション欠損値の変数が知りたいだけでも、FREQプロシジャ+NLEVELSの組み合わせでいけちゃうので便利です。



📝注意


・変数に format が割り当てられている場合、
formatがあてられた値に対してNLEVELSが動いてしまい、思った動きをしてくれないことがある。そのため、FREQプロシジャのとこで「format _all_」でformatを解除しておいた方が良いです。


・対象のデータセットが空(0オブザベーション)の場合、FREQプロシジャのとこでつまづいてWARNINGが出ちゃってうまく動きません。

その辺も考慮しなきゃいけない場合は、「オブザベーション数=0なら~」みたいな条件分岐する文を追記する必要があります。


・今回のテクニックの中で使用している「_N_」と「END=オプション」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。
(解説記事:「サブセット化IFでありがちな落とし穴」)


2014年3月14日金曜日

変数名を取得する「CALL VNEXT」。




以下は、CALL VNEXTを使って変数定義をデータセットに書き出す例です。


* サンプルデータ作成 ;
data DT1;
input A1 A2 A3;
cards;
1 1 2
2 1 2
3 1 2
;
* 変数定義をデータセットに書き出す ;
data DT2;

   if 0 then set DT1;   * ① ;
   length  _NAME  $40. 
_TYPE  $1.;   * ② ;

   do until ( _NAME="" );  * ③ ;
       call vnext( _NAME, _TYPE, _LENGTH ); * ⓸ ;
       if  _NAME ^=""  then  output;
   end;

   stop;  * ⑤ ;
   keep _NAME _TYPE _LENGTH ;

run;
結果データ「DT2」



解説

変数定義を取得するデータセットを指定。「IF 0 THEN」の部分は大分特殊な事をしてるので詳細は割愛。ざっくり言うと、変数定義を取得したいだけなので、データセットの読み込みを一部省略したような動きをしています。

変数定義を放り込む変数に、LENGTHを指定。

CALL VNEXTは実行する度に、順に変数定義を読みに行きます。
なので読み込む定義がなくなるまでDO UNTILでループします。

変数名を「_NAME」、型を「_TYPE」、変数長を「_LENGTH」という変数に放り込むよう指定。
もし変数名だけ取得したい場合、「call vnext( _NAME )」としてもOK。

⑤ 変数定義を取得した後は、STOPステートメントでデータステップを強制的に停止。


POINT
  • 「_ERROR_」「_N_」などの自動変数の定義も読み込まれます。
  • プログラム中で作成した変数(今回の例では「_NAME」「_TYPE」「_LENGTH」)の定義も読み込まれます。またこれらと同じ名前の変数がSETで読み込んだデータセットにもある場合は正しく動かなくなるので、その時はプログラム中で作成する変数名を変える必要あり。
  • 型は文字変数であれば"C"、数値変数であれば"N"が入ります。
  • 得られる変数名の順番は、バージョンや環境によって異なる可能性があるので当てにしない方が良い。




応用例

全変数一括で何か処理したい場合、「CALL VNEXT」と「CALL EXECUTE」を組み合わせると便利です。

CALL EXECUTEはデータステップ100万回の以下記事にて分かり易く解説されてるので参照下さい。
http://sas-tumesas.blogspot.jp/2013/11/call-execute.html


* 全変数を一括でRENAME(先頭に"X"を付与)するサンプル ;
data _NULL_;

   if 0 then set DT1 ( keep= drop= ) ;   * ① ;
   length _NAME $40.;

   call execute( "DATA DT3; SET DT1; RENAME " );

   do until ( _NAME="" );
      call vnext( _NAME );

      /* ^in の後のカッコの中は大文字で指定 */
      if upcase( _NAME ) ^in ( "_NAME", "_ERROR_", "_N_", "" ) then
         call execute( strip(_NAME) || " = X" || strip(_NAME) );  * ② ;
   end;

   call execute( "; RUN;" );
   stop;

run;
「CALL EXECUTE」で生成されるプログラム
 DATA DT3; SET DT1; RENAME
 A1 = XA1
 A2 = XA2
 A3 = XA3
 ; RUN;
結果データセット「DT3」


📝
プログラム中の「_NAME」と同名の変数が①の部分でSETしたデータセットにもある場合は正しく動かなくなるので、その際はプログラム中の「_NAME」を別の名前に変える必要があります。



解説

最初のサンプルプログラムをもとに発展させたやり方です。

RENAMEする変数を選択したい場合は、2行目の「if 0 then set DT1 (keep= drop=)」のところで、

if 0 then set DT1 ( keep=A2 A3  drop= );

としたり、RENAMEしたくない変数がある場合、

if 0 then set DT1 ( keep=  drop=A1 );

のようにすればOK。
上2つのように書くと、以下のプログラムが生成されます。

DATA DT3; SET DT1; RENAME
A2                                      =XA2
A3                                      =XA3
; RUN;


自動変数等がRENAME対象に入らないよう除外した上で、CALL EXECUTEでRENAME処理の文を生成しています。
SASのバージョンが上がって、もし自動変数が追加されてたら、ここをちょっと変える必要あり。



工夫すればなんでも出来るので非常に使い勝手がいいです。
変数を取得する方法は他にも沢山あり、「変数名を取得する方法 [まとめ]」でまとめてるので参考までに。