詰めSAS データステップで新しく作成された変数か、元からデータセットに あった変数かを判定する方法を考えてみる。vinarray関数とか

結構、よく質問されるのが、データステップ中で新しく追加された変数か、
読み込んだデータセットに元からあった変数どうかを判別したいみたいな話です。

いちいち、データセットの仕様書つくるの面倒くさいから
先にプログラム書いちゃって、その実行ついでに新規変数がどうやって生成されているかを
取得して、仕様書を自動生成しちゃえば楽だよなぁみたいな、おバカなこと考えたことありません?
え、いや、僕はないですけどね。

ところが、SASの場合、これが結構ハードで厳しい。
手抜きするつもりが、真面目にやる1000倍くらい時間がかかったりするのもよくある話です

しかも、いまだに完全に納得できる方法が思いついてません
誰か教えてください。

とりあえず、前後のデータセットをコンペアしてその結果を読み込むという
面倒すぎる方法は除外して、1ステップ内で、本処理のついでにできる方法に限定します。

僕が考えた案を2つほど。

以下のデータセットがあります。



変数 a b c dがあります。

data Q1;
a=1;b=2;c=1;d='A';
run;

まず一つ目の方法は以下です。

data A1;
set Q1;
array an _numeric_;
array cn _character_;

/*新しい変数 e f g 作る*/
e=1;
f='b';
g=a+b;

/*チェックしてみる*/
flg_a=ifc(vinarray(a),'元','新');
flg_b=ifc(vinarray(b),'元','新');
flg_c=ifc(vinarray(c),'元','新');
flg_d=ifc(vinarray(d),'元','新');
flg_e=ifc(vinarray(e),'元','新');
flg_f=ifc(vinarray(f),'元','新');
flg_g=ifc(vinarray(g),'元','新');

put (flg_a--flg_g) (/=);

run;

判定結果をputしているので、それを見てみると











追加した3つの変数が正しく判定されているのがわかります。

なにをしているかというと、最初に数値型、文字型それぞれ全変数を
配列に定義しちゃいます。

で、その後、割り当てステートメントがはいるわけですが、
ここで新規に割り付けられた変数は、最初に定義している配列には含まれていない
わけです。

そこで、判定の箇所でvinarray関数という、引数の変数が何かしらの配列に属しているか
どうかを1 , 0で返すニッチな関数で判定します。
配列に入ってないということは、新しい変数だろってノリで。

しかしまあ、お気づきの方もいるでしょうが、これは新しく作る変数を配列に定義しているような
プログラムでは正しく判定できない限定的な方法ですね。
e f gが何の配列にもたまたま定義されていないからそれでいけてるわけです。
同じく、新規変数をarrayで生成したりしても判別不能になります。
(他にも弱点はありますがそれは後で)

次に思いついたのはハッシュオブジェクトです。

data A2;
set Q1;

/*この時点での全変数の情報を一旦ハッシュオブジェクトにいれる*/
if _N_=1 then do;
 declare hash h1();
 h1.definekey('vname');
 h1.definedone();
 array an _numeric_;
 array cn _character_;
 do over an;
  vname=vname(an);
h1.add();
 end;
 do over cn;
  vname=vname(cn);
h1.add();
 end;
 drop vname;
end;

/*新しい変数 e f g 作る*/
e=1;
f='b';
g=a+b;

/*チェックしてみる*/
flg_a=ifc(h1.check(key:vname(a)),'新','元');
flg_b=ifc(h1.check(key:vname(b)),'新','元');
flg_c=ifc(h1.check(key:vname(c)),'新','元');
flg_d=ifc(h1.check(key:vname(d)),'新','元');
flg_e=ifc(h1.check(key:vname(e)),'新','元');
flg_f=ifc(h1.check(key:vname(f)),'新','元');
flg_g=ifc(h1.check(key:vname(g)),'新','元');

put (flg_a--flg_g) (/=);

run;

結果は同じなので割愛。

最初のレコードの時点のPDVの変数名情報をハッシュオブジェクトに格納して
ステップの終わりで、checkメソッドで、存在するかどうかを判定する方法
(さっきと逆に、あれば0,なければエラーコードとして0以外の数字が戻ります)
これならば、ステップの途中で新規変数に配列つかったりしてても問題なしです。


ただ、2つの方法とも、致命的に弱点なのがあくまでステップ開始時の
PDVにある変数を、元からある変数だと考えている点です。

つまり length f $100.;といったようにlengthやformatステートメントなど
データを読み込む前に箱を定義しちゃうようなものがはいると、fは実際は元データセットに無いけど
元からある変数ってことになるんですね。
ここが僕の限界。

う~ん、どうするのが正解なんでしょう。
contentsプロシジャとかsqlとかで、ガチで大元のデータセット情報読み込むコードを
作ってステップ内でdosubl関数で回すとか?

あるいは新規データセットのPDVに影響受けずに変数名をとれるのかな?
if _N_=0の状態でvnextルーチン回したりできんのかな?

誰か教えてください

2 件のコメント:

  1. こんにちは!

    SCL関数使って変数定義を読み込めば、arrayとかの影響を受けないかなと思いました。
    結局contentsプロシジャみたいにガチな方法かもですが。。

    data A2;
    set Q1;

    /*この時点での全変数の情報を一旦ハッシュオブジェクトにいれる*/
    if _N_=1 then do;

    declare hash h1();
    h1.definekey('vname');
    h1.definedone();

    dsid = open("Q1");
    varn = attrn(dsid,"NVARS");

    do i=1 to varn;
    vname=varname(dsid,i);
    h1.add();
    end;
    drop vname;

    end;

    /*新しい変数 e f g 作る*/
    e=1;
    f='b';
    g=a+b;

    /*チェックしてみる*/
    flg_a=ifc(h1.check(key:vname(a)),'新','元');
    flg_b=ifc(h1.check(key:vname(b)),'新','元');
    flg_c=ifc(h1.check(key:vname(c)),'新','元');
    flg_d=ifc(h1.check(key:vname(d)),'新','元');
    flg_e=ifc(h1.check(key:vname(e)),'新','元');
    flg_f=ifc(h1.check(key:vname(f)),'新','元');
    flg_g=ifc(h1.check(key:vname(g)),'新','元');

    put (flg_a--flg_g) (/=);

    run;

    返信削除
    返信
    1. おぁ~、有難うございます!なるほど、綺麗にまとまってますね。こういう風にかけるのか~、勉強になりました!

      削除