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

proc formatのpictureステートメントの話①

う~ん、proc formatのpictureステートメントについて、、はっきり言って僕もそんなに得意じゃないので、ずっと意識的に避けてきました。そのうち mastuさんやSASNamiさんが、記事だしそうだから、それ見て勉強しよ~と思っていました。

ただ、やっぱりそういう後ろ向きな姿勢は良くないですね。苦手克服のために紹介します。
そのため、もしかしたら、間違いがあるかもしれないので、指摘してください。

pictureステートメントは、かなり自由度の高い、出力形式を創造するステートメントです。
おなじproc formatでも、valueステートメントは、この値(範囲)ならこの文字を表示みたいに、決め打ちチックですが、pictureは違います。

いいから、とっとと例をだせ!という声が聞こえてきそうなので、そうします。

data Q1;
 do X=1 to 20 by 4;
  output;
 end;
run;









例えば、値が15までは入りうる数字なので、そのまま表示するが、15を超えたら異常値というシチュエーションの場合、次のようにフォーマットを定義できます。


proc format; 
 picture FT_1_
 1-15 ='99'
 other= '異常値じゃない?';
run;


で、これを実際当ててみると

data A1;
 set Q1;
 format X FT_1_.;
run;









はい、注目すべきは一ケタの数字はゼロパティング(0がついて二ケタ表示)されていることですね。
pictureステートメントで'99'とすると2ゼロパティングして2ケタ数字でデータの値を表示するということになります。'999'とすれば当然「001」のようになります。

いちいちvalue 1='01' 2='02' 3='03'・・・なんてことをしなくていいわけです。

ゼロパティングなんていらねーよ!という場合は次のようにします。

proc format; 
 picture FT_2_
 1-15 ='00'
 other= '異常値じゃない?';
run;

で当ててみると

data A2;
 set Q1;
 format X FT_2_.;
run;











はい、15以下の値は元のまま表示されます。


さて、このままだと15を超える値は全部「異常値じゃない?」となってしまい、フォーマットを解除しないとデータの値がわかりません。

ので、以下のようにフォーマットを作ります。

proc format; 
 picture FT_3_
 1-15 ='00'
 other= '00は異常値じゃない?';
run;

で、当ててみると














00は異常値じゃない?としたことによって、00の部分に実際の
データの値が入ります。凄くないですか?

でも、まだまだこんなもんじゃないです。

とりあえず今回はここまで。




ハッシュオブジェクトの世界⑧ keyの重複を許容する multidata find_nextメソッド

もうハッシュオブジェクトの話もウンザリかもしれませんので、とりあえず今回で一段落です。

本当はここからさらに、新世界である「ハッシュ反復子オブジェクト」というものがあります
簡単にいうと、ハッシュオブジェクトの進化版で、もはやkey-data構造から解放され、まるでデータステップを並行して走らせているかのような柔軟な処理ができます。
でも僕自身の理解がまだ進んでないので、記事にするのは先になると思います。

ただ今回は、今回はそんな「ハッシュ反復子オブジェクト」の1歩手前の話です。

ハッシュオブジェクトはkeyとdataで構成され、keyによって原則的に一意でなっていなければならないと説明したと思います。

原則ということは例外も作れるということで、今回は重複を許したkeyを持つハッシュオブジェクトの話です。

さて

data Q1;
 X=1;A=2;output;
 X=1;A=3;output;
 X=2;A=3;output;
 X=3;A=1;output;
run;







data Q2;
 X=1;Y=2;Z='A';output;
 X=1;Y=4;Z='B';output;
 X=1;Y=5;Z='C';output;
 X=2;Y=1;Z='D';output;
 X=2;Y=2;Z='E';output;
 X=2;Y=3;Z='F';output;
 X=3;Y=6;Z='G';output;
 X=3;Y=7;Z='H';output;
run;












上記2つのデータセットをXの値で両側外部結合して、Y>Aとなるオブザベーションのみを残すという
処理を考えます。

すなわちSQLで書くなら

proc sql noprint;
 create table A0 as
  select coalesce(Q1.X,Q2.X) as X
         ,A
         ,Y
 ,Z
    from Q1 full outer join Q2 on Q1.X=Q2.X
where Y>A
    order by X,A,Z
;
quit;

となって、以下が求めるべき結果です。










Xの値によって結合するわけですが、両方のデータセットともXで一意にならないという、遭遇すると
うざい状況ですね。
またcross joinやfull outer joinはご存知の通り、オブザベーション数があまり膨大になると、処理時間が、止まってんじゃないのこれ?状態になります。

さて、これをハッシュでやるのはどうしましょうか?
本来、小さいほうのQ1をハッシュに入れた方がいいのですが、今回は説明のためQ2を入れます。

data A1;
if _N_=0 then set Q1 Q2;
if _N_=1 then do;
   declare hash hq1(dataset:'Q2', multidata: 'y');
   hq1.definekey('X');
   hq1.definedata('Y', 'Z');
   hq1.definedone();
 end;
 set Q1;
  rc=hq1.find();
  if rc=0 and Y>A then output;
  do while(rc=0);
   rc=hq1.find_next();
   if rc=0 and Y>A then output;
  end;
 drop rc;
run;

これでさっきのSQLとまったく同じ結果になります。

味噌なのはmultidata: 'y'です。これによって、ハッシュの中で同じXの値が共存できます。
そして大事なのがfindメソッドでXに対応するY Zをとった後に、もしかしたら、まだハッシュに同じXの値に対応するデータがあるかもしれないから次を探索するという処理を書いているということです。

do while(rc=0);rc=メソッド;end;の形は、定跡形ともいえる基本的なテクニックなので覚えてください。つまりメソッドが成功している限り、続けるという意味です。

find_nextメソッドは直前のfindメソッドまたはfind_nextメソッドによって参照されたkeyの次のkeyをfindするメソッドです。

超要注意なのは、上記プログラムのfind_next()をfind()にすると、一つでも一致するkeyがあれば同じキーを何度も参照し、当然、rcは常に0となってしまい、無限ループの世界へようこそになってしまいます。まあ、実験でやってみてもいいですが、大事なものは保存してからにしてください。バツ印アイコン押せば、ステップの終了できるはずなんですが、僕のSASは何故か反応せずにSASごと強制的に閉じるはめになりましたので。





ハッシュオブジェクトの世界⑦ コンペア革命 equalsメソッド

例えば、2つのデータセットが同一の内容であるかを比較し、同一である場合は1、ない場合は0などをマクロ変数にいれてそれを使用したい場合があると思います。

また、データステップ中に動的に値を動かしながら別のデータセットと同一内容になっているかをチェックして、それによってステップ中に処理の条件分岐をかけたい場合もあると思います。

SASのcompareプロシジャは多機能で、2つのデータセットにある差異を洗い出すことができます。またlengthやlabel、format等、定義情報の差も見れるので素晴らしいです。

ただし冒頭であげたように、単に値が同じか違うのかという2値で答えが欲しい場合や、データステップの途中で動的な値に対してコンペアしたい場合などには、あまり向きません。

プロシジャを回すだけで1ステップは必ず手数が掛かるので、そのアウトプットから0,1を導出する処理を書くと2ステップは掛かってしまいます。

またデータステップ中にコンペアは、多分、配列などを駆使すれば可能ですが複雑なデータステップになることは避けられないはずです。

そこでハッシュオブジェクトの出番です。

data Q1;
X=1;Y='A';output;
X=2;Y='B';output;
run;

data Q2;
X=1;Y='A';output;
X=2;Y='B';output;
run;

data A1;
length X 8. Y $2.;
 declare hash hq1(dataset:'Q1');
 hq1.defineKey('X');
 hq1.defineData('Y');
 hq1.defineDone();

 declare hash hq2(dataset:'Q2');
 hq2.defineKey('X');
 hq2.defineData('Y');
 hq2.defineDone();

 call missing(X,Y);


 hq1.equals(hash: 'hq2', result: FL);

 keep FL;

run;

結果は




です。

一致する場合は1、しない場合は0になるので、値を変えて試してみて下さい。

後はこれをcall symputとかでマクロに入れるもよし、このままデータステップのコードをどんどん書いていってもよしです。

ちなみにreplaceメソッドなどと合わせて使って、内容が一致するまでループで、値を変えていくみたいな使い方もみたことがあって、まさに動的なコンペアって感じで面白かった覚えがあります。



しかし、注意点があります。
①あまり大きなサイズのデータセットに使わないこと!
ハッシュオブジェクトは、ハードディスクではなくメモリに展開されるので、サイズがでかいと入らなかったり、処理効率が落ちます。どれくらいの大きさならいいのかというのは、実行するハードのメモリ容量と相談してくださいということです。

②値しかみない!どこが違うかもわからない!
フォーマットや長さ、ラベルの差は見れません。医薬品開発分野で、ダブルプログラミングした解析用データセットのコンペアなどに使うことはお勧めしません。




ハッシュオブジェクトの世界⑥ outputメソッド ordered

続きましては、ハッシュオブジェクトのdataをSASデータセットにする方法です。

data Q3;
 X=3;Y='C';output;
 X=1;Y='A';output;
 X=2;Y='B';output;
run;

というデータセットがあって

以下のコードを実行します。

data _NULL_;
 if _N_=0 then set Q3;
  declare hash hq1(dataset:'Q3');
   hq1.definekey('X');
   hq1.definedata('Y');
   hq1.definedone();
   hq1.replace(key:3,data:'D');
   hq1.output(dataset:'A1');
   stop;
run;

すると A1というデータセットが作成され、中身は







です。

outputメソッドはハッシュオブジェクトのdataを指定した名前でデータセットにしてくれます。
outputされる際のオブザベーションの並び順はランダムです。

で、当然、dataだけじゃなくてkeyも欲しいとなるかもしれませんが、今のところ僕が知っているのは
以下のような方法だけです。

data _NULL_;
 if _N_=0 then set Q3;
  declare hash hq1(dataset:'Q3');
   hq1.definekey('X');
   hq1.definedata('X','Y');
   hq1.definedone();
   hq1.replace(key:3,data:3,data:'D');
   hq1.output(dataset:'A1');
   stop;
run;







keyで指定した変数をdataにも指定できるというわけです。

さて、outputされる際のオブザベーションの並び順にソートをかけるのはorderedというものを使います。

proc sort data=Q3 out=A2;
 by X;
run;

と同じことをハッシュでやる場合

data _NULL_;
 if _N_=0 then set Q3;
  declare hash hq1(dataset:'Q3',ordered:'a');
   hq1.definekey('X');
   hq1.definedata('X','Y');
   hq1.definedone();
   hq1.output(dataset:'A2');
   stop;
run;

でできます。

ordered:'a' のaはascendingのa、要は昇順ですね。ordered:'y' ordered:'yes'でも昇順になります。
逆にordered:'d'なら降順になります。
Keyに指定している値で順にソートされます。

この一度、データセットをハッシュオブジェクトに入れてソートして、データセットに吐きだす方法、データセットの大きさや、ハードの性能など状況によっては結構効率いいという噂を聞きますが、実証していません。
まあ正直、やりたいことがソートだけならわざわざハッシュ使いませんよね、めんどい。



ハッシュオブジェクトの世界⑤ replaceメソッド key dataの指定に変数

ハッシュオブジェクトの中のレコードを増やしたり、減らしたり、消したりする方法を紹介してきました。

流れ的に予想ついているかもしれませんが、次はレコード数を変えずに、内容を変える方法です。

data Q1;
 X=1;Y='A';output;
 X=2;Y='B';output;
 X=1;Y='A';output;
 X=2;Y='A';output;
 X=3;Y='A';output;
run;

data Q2;
 X=1;Y='A';Z='い';output;
 X=2;Y='A';Z='ろ';output;
 X=2;Y='B';Z='は';output;
run;

上記2つのデータセットを作ってから以下のコードを実行します

data A1;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();

   hq1.replace(key:1,key:'A',data:'ほ');
   hq1.replace(key:3,key:'A',data:'へ');

end;

 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;

すると結果は









となります。

hq1.replace(key:1,key:'A',data:'ほ');
によって、もともとX=1 Y=Aに対応するdataは「い」だったのが「ほ」に変えられました。
そのためfindメソッドに結果の1,3オブザベーション目のZの値は「ほ」になっているわけです。

次に
hq1.replace(key:3,key:'A',data:'へ');
ですが、ハッシュオブジェクトに存在しないkeyでreplaceメソッドを行うと、addメソッドと同じ、つまりそのレコードがハッシュオブジェクトに追加されます。
そのため5オブザベーション目に「へ」が入っているのです。



では、ハッシュオブジェクトに存在しないkeyの場合には、addするのではなくて、無視して欲しい場合はどうしましょうか?
これはもしからしたら他にやり方があるのかもですが、僕はcheckメソッドと組み合わせて以下のようにしています。

data A2;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
   
   if hq1.check(key:1,key:'A')=0 then 
    hq1.replace(key:1,key:'A',data:'ほ');
   if hq1.check(key:3,key:'A')=0 then
    hq1.replace(key:3,key:'A',data:'へ');

end;

 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;

こうすれば結果は









でOKです。


最後に、あまり例として意味がない処理ですが、通常のデータステップ中の変数を指定して
replaceする処理を。

奇数の行番号の場合、ハッシュオブジェクトのdataを_N_に変える処理です。

data A3;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
end;

 set Q1;
  if mod(_N_,2)=1 then hq1.replace(key:X,key:Y,data:put(_N_,best. -L));
  rc=hq1.find();
  if rc^=0 then Z='';
run;









上のプログラムはかなり意味不な処理ですが、複数のデータセットを使ってハッシュオブジェクトの処理をする場合、あるデータセットの中身に基づいてハッシュの内容を変更し、変更されたハッシュを使って別のデータセットとマッチングしたりといったことが可能になるわけです。


ハッシュオブジェクトの世界④ clearメソッド 予約語num_items deleteメソッド removeメソッド

データセットのオブザベーション数を数える時に、nobs=を使って取得することができますが
http://sas-tumesas.blogspot.jp/2013/10/sascall-symput.html


それと同じ感じで、「num_items」というキーワードを使ってハッシュオブジェクト内の
オブザベーション数(レコード数といった方がいいのか?)を取得できます。

また今まで、addでハッシュオブジェクトにレコードを追加する方法ばかりやってきましたが、
当然逆もできて、レコードを全部消すことも、1つ消すこともできます。

では、とりあえずコードを見ていきましょう

data Q2;
 X=1;Y='A';Z='い';output;
 X=2;Y='A';Z='ろ';output;
 X=2;Y='B';Z='は';output;
run;

data A1;
if _N_=0 then set Q2;

declare hash hq1(dataset:'Q2');
 hq1.definekey('X','Y');
 hq1.definedata('Z');
 hq1.definedone();

 N1= hq1.num_items;

 hq1.clear();

 N2=hq1.num_items;

 hq1.add(key:9,key:'A',data:'い');
 hq1.add(key:8,key:'B',data:'い');

 N3=hq1.num_items;

 hq1.remove(key:9,key:'A');

 N4=hq1.num_items;

 output;
stop;

keep N:;
run;


結果は、





はい、まずN1= hq1.num_items;で,値は3になっています。
ハッシュオブジェクト名.num_itemsでハッシュオブジェクトのレコード数を取得できます。

Q2のオブザベーション数が3だったので、それを入れたハッシュオブジェクトhq1のレコード数が
3になるのは必然ですね。

次にhq1.clear();としていますが、これはclearメソッドで、ハッシュオブジェクトのレコードを空にします。要注意!!なのは、ハッシュオブジェクトの定義自体は存在するということです。空だけど箱はある状態です。
deleteメソッドというのがあって、それと間違いがちなのですが、deleteメソッドはハッシュオブジェクトの存在ごと消し去ってしまいます。元々ハッシュオブジェクトは、1回のデータステップが終了した時点で消滅するので、deleteメソッドを使うケースは、相当レアです。
つまり、1回のステップ中に同名で、定義の違うハッシュオブジェクトを作成してそれを絡ませた特殊な処理をやりたいようなケースです。

N2= hq1.num_items;で,値は0になっています。直前でclearメソッドされているので、空になったいうことです。

その後、addメソッドで2レコード追加されたためN3は2です。

hq1.remove(key:9,key:'A');としていますが、これはremoveメソッドで、指定したkeyを持つレコードをハッシュオブジェクトから取り除きます。

そのためN4は1です。

いかがでしょうか?ハッシュの感覚がつかめてきたでしょうか??










ハッシュオブジェクトの世界③ checkメソッドと、メソッドの戻り値をそのままIFC IFN関数に使う話

今まではfindメソッドで、ハッシュオブジェクトからdataで指定した変数の値をとってきていましたが、必ずしも値が欲しくない場合はあります。

例えば、与えられたデータセットに、顧客IDのようなものがあったとして、そのIDが別のマスタに既に登録済みか、そうでないかの真偽だけが欲しい場合もあると思います。
mergeステートメントならinデータセットオプションを使いますが、ハッシュならどうしましょうか?

別にfindメソッドで、dataも取得した上で、dataを何にも使わず無視して、dropしちゃえばいいんですが、dataに指定されている量が多いと無駄でしかなく、ハッシュの良さを殺しちゃいます。

単に、ある値と同じものがハッシュオブジェクトのkeにあるのかないのかが知りたい、或いはそれを条件分岐にしたい場合はcheckメソッドを使います。

data Q1;
 X=1;Y='A';output;
 X=2;Y='B';output;
 X=1;Y='A';output;
 X=2;Y='A';output;
 X=3;Y='A';output;
run;


data A1;
length X 8. Y Z $2.;
 if _N_=1 then do;
  declare hash hq1();
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
   hq1.add(key:1,key:'A',data:'い');
   hq1.add(key:2,key:'A',data:'ろ');
   hq1.add(key:2,key:'B',data:'は');
   call missing(Z);
   end;
 set Q1;
  C1=ifc(hq1.check(),'該当なし','該当あり');
  C2=ifc(hq1.check(key:3,key:'A'),'X=3,Y=Aは該当なし','X=3,Y=Aは該当あり');
 keep X Y C1 C2;

 run;







こんな感じです。C1の結果は、データセットQ1のX Yがハッシュにあるかどうかを見ていて、
C2は直接値を指定して検索するやり方のサンプルとしてだしました(全オブザベーション分同じことやってるので無駄ですが)

ちなみに今までは、一旦「rc=」とメソッドの戻り値を変数に割り当ててからifで分岐していましたが、
ダイレクトにifc関数やifn関数に入れれます。
0以外の値は真で 0なら偽となるSASの真偽ルールを利用しています。

脱線:
「ダイレクト向かい飛車」という戦法があって、言葉の響きがとても好きで、最近「ダイレクト」という言葉をやたら使いたがってます。




ハッシュオブジェクトの世界② addメソッド

まず、この記事の前に「ハッシュオブジェクトの世界①」を読んでください。
http://sas-tumesas.blogspot.jp/2014/07/blog-post_11.html

さて、前回は作成したハッシュオブジェクトの中に、データセットを突っ込んで、それをマッチングに使いました。

しかし、ハッシュオブジェクトに情報(データっていうとハッシュオブジェクトのkey,dataのデータと混同しちゃうので)を入れる場合、別にそれがデータセットでなくても大丈夫です。

ハッシュオブジェクトに情報(keyとデータ)を挿入するのがaddメソッドです。

ハッシュオブジェクトのメリットの1つは、データステップの途中でハッシュオブジェクトの中身を増やしたり減らしたり変えたりできること、そしてデータステップから吸い上げた情報と、直接メソッドで挿入した情報を一緒に扱える柔軟性です。
それは、いわばハッシュオブジェクトが縦持ち構造だから可能なわけです。
例えばARRAY配列の場合、一度定義した後に、要素数をステップの途中で変えながら処理はできないはずです(中身は変更できますが)

では、「ハッシュオブジェクトの世界①」の処理と同じ処理を、Q2からデータを吸い上げずに、直接データを打つ形でやってみましょう。


data Q1;
 X=1;Y='A';output;
 X=2;Y='B';output;
 X=1;Y='A';output;
 X=2;Y='A';output;
 X=3;Y='A';output;
run;


data A1;
length X 8. Y Z $2.;
 if _N_=1 then do;
  declare hash hq1();
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
   hq1.add(key:1,key:'A',data:'い');
   hq1.add(key:2,key:'A',data:'ろ');
   hq1.add(key:2,key:'B',data:'は');
  end;
 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;

です。

前回のコードと見比べてみてください。
まず、「if _N_=0 then set Q2;」が「length X 8. Y Z $2.;」になってます。
これは今回はQ2を使わないので、変数の初期化問題を解決するためにlengthステートメントで
変数情報を定義しているわけです。

続いて「declare hash hq1(dataset:'Q2');」が「declare hash hq1();」になっています。そりゃそうだ。
注意点は、内容がない場合でも空括弧は必須だということです。普段のデータセットオプションのノリで、指定するものがないからって省くとエラーです。

次に「hq1.definedone();」の後に3行

   hq1.add(key:1,key:'A',data:'い');
   hq1.add(key:2,key:'A',data:'ろ');
   hq1.add(key:2,key:'B',data:'は');

とあります。これがaddメソッドです。
まあ、見れば何となく何しているかはわかるはずです。
挿入する内容の前にkeyであるのかdataであるのかを必ずつけます。コロン:を使うのが意表を
ついた感じするので注意です。
keyやdataが複数ある場合、順番はdefinekeyやdefinedataメソッドで指定した順と同期します。

結果は前回と同じなので省きます。



ちなみに、この辺がハッシュの柔軟なところでもあり、混乱するところでもあるのですが、次のようにも書けます。

data A2;
length X 8. Y Z $2.;
 if _N_=1 then do;
  declare hash hq1();
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
   X=1;Y='A';Z='い';
   hq1.add();
   X=2;Y='A';Z='ろ';
   hq1.add();
   X=2;Y='B';Z='は';
   hq1.add();
  end;
 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;

のようにhq1.add();と空指定する直前に対応する変数が通常のデータステップ領域(PDV)にあれば
変数名で対応するハッシュオブジェクトのkey dataに情報を格納してくれます。

これでも結果は同じです。


最後に、①で使ったQ2と、addメソッドで加えた情報を両方ハッシュオブジェクトに入れた上でマッチングする例であれば次のようにできます。

data Q2;
 X=1;Y='A';Z='い';output;
 X=2;Y='A';Z='ろ';output;
 X=2;Y='B';Z='は';output;
run;


data A3;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
   hq1.add(key:3,key:'A',data:'に');
  end;

 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;


とすると、今までnullだった組み合わせが追加されたので結果は









となります。


この辺で、ハッシュオブジェクトがただ単に高速化のための技術ではなく、むしろ高速化は
おまけで、柔軟性がやばいんじゃないの?と感じてくれればうれしいです。


さて、最近更新頻度が落ちていましたが、
ふと気がつくと今まで投稿した記事の数が200を超えていました。

正直ここまでネタが続くとは思っていませんでした。これも色んな方から日々刺激をいただいているおかげです。コメントやメッセージは僕自身の勉強と、次の記事のネタになるにで常に大歓迎です。
またリンクサイト様からも日々刺激とアイデアを受けて、内容をパクらせていただくことで、ネタ切れせずに生きながらえています。

なので、今さらですが、このブログはリンクフリーです。ただ、こちらからもぜひリンクさせていただきたいのでご連絡はいただけるに越したことはないです。
また何か論文等の参考先に記載していただくのも全然構いません。


僕は、これからもまたあまり役に立たないコードを垂れ流していこうと思います。







ハッシュオブジェクトの世界①

少し前に、ユーザー総会で発表するハッシュオブジェクトの前振りに、何個かコードを載せたところ、意外に反響があって、メールいただいたりしました。

基本から紹介して欲しいといったメッセージをいただいたので、できる範囲で少しずつやってみます。

我流で感覚的に勉強してきたので、きちんとした用語や正しい説明はできないと思いますのでご容赦ください。間違いもあるかと思うので、ガチで実践プログラムに入れる場合は、きちんと自分で検討検証してからにしてください。


まず、ハッシュオブジェクトは乱暴にいうと、ルックアップテーブルです。KeyとDataという要素で成り立ってます。
構造的に、普通のSASデータセットをそのまま、ぶちこめる形をしているので、使い勝手がよいです。
また、ハードディスクではなく、メモリ上に展開されるため、そこからデータを出し入れするのが、鬼のように速いです。

例えばですが、Arrayステートメントで作成される配列は、要素番号という、いわば実データには存在しない「仮の番号」を付与することで、データを管理することをしています。
データの出し入れに際して、ユーザーは要素番号を指定することで、目的のデータにアクセスできるわけです。

脱線して超個人的かつ感覚的な話で、共感してくれる人は皆無だと思いますが、僕はそのことに、ずっと、SASで初めて配列を学んだ時から、疑問を感じてきました。
実際の世界にはどこにも1とか2とかいう要素番号はないのに、効率よく処理するために、1手間かけてわざわざ作成しているわけです。
将棋の「後手番一手損角換わり戦法」の発想と同じで、その後、効率よく進めるために敢えて手損をしているのです(個人的感想)。
それはそれで立派な戦法なのですが、どちらかというと変則的な戦法だと思うのです。もっと、より自然なやりかたはないのだろうかとずっと思っていました。

ハッシュオブジェクトは、KeyとDataという構造で、Key自体もデータであるわけです。つまり実際に存在するものだけを使って、そのままの形で、参照用のものを作っているわけです、
ユーザーはkeyと指定することで、それに紐づくdataにアクセスできるのです。

抽象的な話はこのへんまでにします。

さて、以下に二つのデータッセットがあります。

data Q1;
 X=1;Y='A';output;
 X=2;Y='B';output;
 X=1;Y='A';output;
 X=2;Y='A';output;
 X=3;Y='A';output;
run;









data Q2;
 X=1;Y='A';Z='い';output;
 X=2;Y='A';Z='ろ';output;
 X=2;Y='B';Z='は';output;
run;








で、やりたいことは、Q1のデータッセットに対して、XとYをキーにしてQ2から
変数Zをひぱってくるという処理です。

SASやっている人なら、マージやSQLで反射的に書ける処理ですが、今回はハッシュ使います。


まずは、あと一歩のプログラムから

data A1;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
  end;
 set Q1;
  rc=hq1.find();
run;

結果は









おしい!4オブザベーション目までは完ぺきなのに、5つめ
X=3、Y='A'の組み合わせはQ2には存在しないので、ここはnullになるはずなのに、
どこの値とってきとんねん!というところです。
(ネタばれすると、実は前のオブザベーションの値が引き延ばされている)

この5行目への対応は後で説明するとして、そこまでの処理を解説します。

まず、

if _N_=0 then set Q2;
これは、データセットQ2の変数情報を先に読み込ませておくための儀式です。
後ほどQ2の中身をハッシュオブジェクトの中にぶちこむんで、その後、データステップ中で
取り出すのですが、SASはハッシュオブジェクトの中身のことなんか知らないので、急にでてくるQ2にびっくりして処理をやめてしまいます。
具体的にいうと、PDV(プログラムデータベクトル)の外側から突如出現するZという変数に対して
「宣言されていないハッシュオブジェクトの data シンボル Z が、 行 177 カラム 4 にあります。
」とエラーを出してしまいます。

ので、いつもの、1オブザベーションも実行されないけど、データセットの情報は読み込んでます状態をつくりだすif _N_=0 then set ;を使っているわけです。

if _N_=1 then do;
これは、この後endまでの間に、ハッシュオブジェクトというものを作るのですが、1回作ればいいのです。そのままだと1オブザベーション読み込むごとに、毎回同じハッシュオブジェクトを再作成してしまい、余計遅くなります。
なのでこの条件をいれます。

③  declare hash hq1(dataset:'Q2');
declear hash の後に任意の名前をつけると、その名前でハッシュオブジェクトの作成が開始されます。この場合はhq1というのが、僕が勝手につけた名前です。
(dataset:'Q2')というのは、このハッシュオブジェクトの中にぶちこむデータはQ2から作りますよという指定です。実は、必ずしもデータセットから読み込まずに作れるので、ここは状況によって変わります。また、ここで使えるオプションも多くあるのですが、今回は紹介しません。
要注意なのは、シングルコーテーションを忘れがちなことです。

hq1.definekey('X','Y');
ハッシュオブジェクトには、メソッドという、予め決まっている方法でしか一切操作できません。
そしてメソッドは
ハッシュオブジェクト名.メソッド名(内容);の構造をとります。
つまり、ここでhq1.definekey('X','Y');としているのは hq1というハッシュオブジェクトに対して
definekeyメソッドを使い、内容は('X','Y')ということです。
でdefinekeyメソッドとは、その名の通り、ハッシュオブジェクトにキーを設定するメソッドです。
今回、Q2はXとYで一意になるので、それをキーにしています。
一意にならないのを指定するとエラーになります
(実はわざと一意にせずにエラーを回避して、それを利用する方法もあるが、いずれ)

hq1.definedata('Z');
definedataメソッドはデータを設定するメソッドです。今回、Zが欲しいのでデータはZです。

hq1.definedone();
definedoneメソッドを入れるまでハッシュオブジェクトの定義は完了しません。キー作って、データ作って、やること全部終わったら、忘れずにdefinedone

set Q1;
普通のデータステップです。Q1のデータをsetすれば、1オブザベーションずつ読み込まれて
outputされます。

rc=hq1.find();
rc=はリターンコード吸収用の割り当てステートメントです。SCL関数のところでもでてきましたね。
実はここだけじゃなくて、あらゆるメソッドにrc=とつけれます(名前はrcじゃなくてもよい)
で、ここに入る値は、メソッドがうまくいけばかならず0が入り、それ以外の場合、つまり何かしらメソッドにエラーが生じた場合は、そのエラーを示す数字が入ります。
hq1.find()はfindメソッドで、指定したハッシュオブジェクトのkeyとデータステップ中の同名の変数をマッチングして、マッチする場合、ハッシュオブジェクトのdataで指定されている値を全部もってきてくれます。
今回の場合ではQ1のXとYの値と、ハッシュオブジェクトのXとYの値を比較し、同じ場合はハッシュオブジェクトのZの値を持ってきてくれます。

ふ~、やっと説明終わり!
とはいかないのですね。5オブザベーション目ですね。。

そこのrcの値を見て下さい。0じゃないですよね、つまりfindメソッドは失敗した。
なぜ失敗したのか?マッチするキーがハッシュオブジェクトになかったから。
ハッシュオブジェクトはあくまでPDVの外側の世界なので、猪突猛進のSASデータステップは
我関せず、そのままZの値をretainし続けます。

そこで猪頭のSASに、わかる形で分岐条件を加えます。
rc=は割り当てステートメントでデータステップの世界に作られた値なので
ここは単純に

data A2;
 if _N_=0 then set Q2;
 if _N_=1 then do;
  declare hash hq1(dataset:'Q2');
   hq1.definekey('X','Y');
   hq1.definedata('Z');
   hq1.definedone();
  end;

 set Q1;
  rc=hq1.find();
  if rc^=0 then Z='';
run;

と最後にひとつ加えてやるだけで結果は









後はお好みでrcをドロップしてください。

やっと終わった。

ここまで読んで、なんて面倒なんだと思われたかもしれませんが、
慣れればすらすら書けます。


また続きはいずれ






中間テーブルを挟んだマージをハッシュオブジェクトを使って一撃で片づける話

またハッシュオブジェクトの話です。
今回も、実際に僕がどういったシチュエーションでハッシュを使ったかの実践例の紹介です。

さて、関係性データベースの正規化とは、乱暴解釈すると、各テーブルがユニークなキーで管理できる状態になるまで、細切れにすることだと言えます(DB論は詳しくないですが)

例えばデータセットA B Cがあって、AにCの情報をくっつけたいのに、AとCには共通のマージキーが存在せず、まずAとBとマージしてから、取得したBの情報でCとマージする必要があることって、普段結構あるはずです。

具体的には

data Q1;
A=1;B='A';output;
A=2;B='B';output;
A=3;B='A';output;
A=4;B='C';output;
run;








data Q2;
B='A';C='は';output;
B='B';C='い';output;
B='C';C='ろ';output;
run;







data Q3;
C='い';D='α';output;
C='ろ';D='β';output;
C='は';D='θ';output;
run;







という3つのデータセットがあって、

作りたい結果は








のように、最初のデータセットQ1のAという変数と最後のデータセットQ3のDの変数だけ
だとします。

しかし一直線にQ1とQ3を結合することができず、まずQ1のBとQ2のBでマージして
次にCとQ3のCを結合しなければ、Aに対応するDを取得できません。

順番にマージすればいいわけですが、いちいちキーでソートし直して、マージするのって
とても面倒です。


そこで、ハッシュオブジェクトの出番です。

data A1;
 if _N_=0 then do;
  set Q1;
  set Q2;
  set Q3;
 end;
 if _N_=1 then do;
  declare hash hQ2(dataset:'Q2');
   rc=hQ2.definekey('B');
   rc=hQ2.definedata('C');
   rc=hQ2.definedone();
  declare hash hQ3(dataset:'Q3');
   rc=hQ3.definekey('C');
   rc=hQ3.definedata('D');
   rc=hQ3.definedone();
  end;
   set Q1;
   rc=hQ2.find();
   rc2=hQ3.find();
   if rc+rc2^=0 then D='';
 keep A D;
run;

これで終わりです。

特筆すべきは、これはあくまで1回のデータステップで終了していること。キーによるソートも
必要としていないことです。

このようにデータセットの結合において、かなりいい仕事をしてくれます。
今回は単純な結合ですが、ここに色々条件をつけたり、途中データステップで合成した
値を使ってさらにべつのデータセットと結合したりとか、かなり特殊なこともできます。

柔軟性、応用可能性、自由度がほんと半端ないです。

僕も初心者でもっと勉強したいので、日本でハッシュオブジェクトが盛んになって
いっぱい色んな人の応用例が見たいです。

ちなみにユーザー総会での発表時間が変わって
ハッシュの発表はDay1の15:10-15:40になりました。
数独の発表はDay2の15:20 - 16:10になりました。
発表が2日間に分かれたのでよかったです。

ハッシュの方、ルームCは僕の発表が最後なので、ちょっと?ぐらい時間が足でても、次の発表者に迷惑掛かったりしないので、まあ大丈夫かなと悪いこと思ったりしてます。
また、質問とかあったら終了後いくらでもお答えできそうな気がします。






詰めSAS12回目_カラーパレット問題:組み合わせと結果がデータセットで与えられ、それに基づいて処理する場合

僕が実際に、最近悩んだ処理を、詰めSAS問題にしました。
答えは何種類もありえますが、とりあえず、現実に僕が採用したものを回答例としてあげます。
カラーパレット問題とか適当に名前つけていますが、勝手に僕が作った名前なので気にしないでください。


まず、以下のデータセットを見て下さい。

data RULE;
C1='赤';C2='青';COLOR='紫';output;
C1='青';C2='黄';COLOR='緑';output;
C1='黄';C2='赤';COLOR='橙';output;
run;

【データセット:RULE】






これは、2色の絵の具を混ぜた時に、できる色を示したパターン表です。
例えば1オブザベーション目は、赤と青を混ぜると紫ができるよということを示しています。


次にもう一つデータセットを見て下さい。

data Q1;
X='黄';Y='青';output;
X='赤';Y='赤';output;
X='黄';Y='赤';output;
X='赤';Y='青';output;
X='青';Y='青';output;
X='青';Y='黒';output;
run;

【データセット:Q1】








6オブザベーションあって、変数XとYそれぞれに色が入っています。

さて、今回の問題は、先に提示されたデータセット「RULE」の内容に基づいて
データセット[Q1]の6オブザベーション、全てに対して、混ぜたら何色になるかを
値として持たせなさいというものです。

ただし、以下の条件があります。
①同じ色を混ぜても、同じ色にしかなりません。つまり赤と赤を混ぜたら結果は赤です。
②RULEの中に存在しない組み合わせの色に対しては、結果が不明なので「×」といれます
③赤と青を混ぜるということと、青と赤を混ぜるということは同じとします。混ぜる際の色の指定順序に意味はありません。

さて、あなたなら、こういった処理をどのように解きますか?
ポイントは与えられたパターン表と色の順序が逆の場合の対応ですね。
順番が固定ならマージすりゃ終わりです。

なので、データセットRULEをいじって、C1とC2が逆の場合のデータも作って、6オブザベーションの
パターンが格納されたデータセットにしてからマージするのも、正解です。

でもなんかステップ数がかさむし、嫌だなと思って、以下のようにしました

data A1;
informat X Y COLOR;
 if _N_=0 then set RULE;
 if _N_=1 then do;
  declare hash pallet(dataset:'RULE');
   pallet.definekey('C1','C2');
   pallet.definedata('COLOR');
   pallet.definedone();
  end;
 set Q1;
  if X=Y then COLOR=X;
  else do;
   C1=X;
   C2=Y;
   rc=pallet.find();
    if rc^=0 then do;
     C1=Y;
     C2=X;
     rc=pallet.find();
     if rc^=0 then COLOR='×';
end;
   end;
keep X Y COLOR;
run;

結果は










こんな感じで合ってます。

はい、つまりハッシュオブジェクト使いました。
このケースでは、使うべき処理でハッシュオブジェクト使えた気がします。

いや、1ステップだけど、こんな長いわけわからんコード書くぐらいなら細かくステップ分けるわ!
っていうご意見もごもっともです。

でも、SQLもそうだと思うのですが、最初、読み書きに抵抗があっても、
慣れればどうってことないっていうより、むしろ普通のデータセステップより読みやすいし、すらすら書けるってなります。

僕としては、多次元配列のARRAYで、深いループ書かれるより、ハッシュで書かれたコードの方が
行数があっても読みやすいと思っています。


で、実は今回は、今度のユーザー総会で発表する、ハッシュオブジェクトの前ふりでした。
上記コードの詳しい意味が知りたい方は是非、発表を聞いていただけると嬉しいです。
(もしからした発表時間のスケジュールが変わって、ハッシュはDAY1になるかも、、)

ただ、そのうち、このブログでもハッシュオブジェクトについて書いていきます。





SASのAudit Trail(監査証跡)機能について

今回は、いや、今回もマニアックな話です。

以前、「世代管理機能でデータセットの更新履歴(更新前後のDS)を残す_GENMAX=オプション」
http://sas-tumesas.blogspot.jp/2013/11/dsgenmax.html

で世代データセットを作る話をしました。

世代データセットが作られるようにしておけば、確かにそのデータセットが同名で再作成されるような場合に、世代データセットが作成されるので、更新内容を追うことができますが、例えば


data DS1(genmax=10);
 X=1;
run;

data DS1;
 modify DS1;
 X=2;
run;

のような処理をした場合、DS1の中身のXの値は2になりますが、世代データセットは一切
作成されません。

何故か?modifyステートメントは、元のデータセットを更新するものであり、
一度消して再作成するdata XX;set XX;とは違うからです。
データセットが同名で上書き作成された場合のみ世代データセットの対象になります。

その理屈からsqlプロシジャでupdateをしたときも作成されず、proc appendでデータセットを再作成せずに追加処理した場合も世代データセットは作られません。

では、そういった更新処理に対して、変更内容を確認することはできないのでしょうか?
当然できます。

今、以下の2つのデータセットがあって、Q1について、内容に変更がある度にそれを記録し、後で確認する必要があったとします。

data Q1;
X=1;Y='A';output;
X=2;Y='B';output;
run;






data Q2;
X=3;Y='C';output;
run;






ここでQ1に対して何か処理をする前に、

proc datasets ; 
 audit Q1; 
 initiate; 
quit; 

とします。これによってQ1に対してauditが作成されるようになります。
ただし、世代データセットと違い、ユーザーの目には直接触れない、見えない箇所でSASが勝手に記録をとります。

さて、それでは、一気に以下のコードを実行します。

data Q1;
 modify Q1(where=(X=1));
 X=9;
 Y='Z';
run;

proc append base=Q1 data=Q2;
run;

data Q1;
 modify Q1;
 where X=2;
 remove;
run;


関係のないクイズ、Q1の中身は一体どうなっているでしょうか?

答え






で、ここで、どうやってQ1に対する監査証跡を確認するのかというと、

data A1;
 set Q1(type=audit);
run;

として、A1の中身を見てみると






こんなことになってます。
見方を簡単に説明すると

_ATDATETIME_がデータに何かしら操作が加えられた日時です。
_ATOBSNO_は対象となったオブザベーション番号です。
_ATUSERID_は変更を行ったログインユーザー名です。
_ATOPCODE_は操作内容でDRはそのオブザベーションが読み込まれたこと、DWはそのオブザベーションに何かデータが書かれたことDAはオブザベーションの追加、DDは削除です。

以上のことを踏まえてA1を解読してみると

2014年の6月29日の深夜2時20分12秒に、1オブザベーション目が読み込まれ、同時間に
X=9 Y=Zに書き換えられ、そしてその4秒後、3オブザベーション目にデータが追加され、
間髪いれず2オブザベーション目が削除され、全ての処理はSASYAMAというユーザーによって
行われたんだということが丸裸になります。

さらに詳しくはドキュメントを読んでください。

これは、うまく利用すれば凄いことです。これと同じことをするマクロとかを自作しようとしたら
頭抱えちゃいそうです。それがproc datasetsでちょろっと指定するだけでできてしまうので。

とても大事で、変更履歴を残したいものがあるなら、同名での再作成ができないようにしたうえで
AUDITを作るようにしておいて、変更は全てmodify等の更新処理で行えばよいですね。

実践でやったことないですけどね。

でもなんか、これ、本来の用途と違うことに応用できそうな気がしていて、ちょっと面白いことできないか考え中です。

無償公開SASと、関係ないおまけ話としてSASからEXCEL VBAを実行する方法

リンクさせていただいているサイト様で、取り上げられていて、重複する話題なのですが、ぜひ日本中に拡散したいニュースですので、うちも乗っかってしまいます。

非商用目的に限り(研究や学習とか)、SAS University Edition というものを無料で使っていいよというびっくりな話です。これで家でもSASいじりができます。

インストール方法等等については、リンクサイト様の記事がわかりやすいので、紹介させていただきます。


リンクサイト[Traid.sou]での紹介記事

2014-06-06 SAS Analytics Uについて」




リンクサイト[僕の頁 <SASと臨床試験と雑談と>]での紹介記事


SAS University Edition - ついにSASが無料で利用できる時代に -





ついでにまったく関係のない話

今、アクティブなエクセルファイルに
「MACRO_A」というマクロがある場合、

filename cmds dde 'EXCEL|SYSTEM';

data _null_;
  file cmds;
  put'[RUN("MACRO_A")]';
run;


でSASから実行できます。

それだけです




「SAS® 認定プロフェッショナルのための Base Programming for SAS® 9 完全ガイド」のサンプルデータは公開されてます

以前、書籍案内で「SAS® 認定プロフェッショナルのための Base Programming for SAS® 9 完全ガイド」を紹介しましたが、
何人の方からか、本の中で紹介されているコードを実際実行したいが、使用するデータセットがないので実行できずにいて悔しいといったメッセージをいただきました。

この本は米国SASが出している書籍「SAS Certification Prep Guide: Base Programming for SAS 9」の翻訳版で、基本プログラムもデータも同じなので、米国SAS社のHPから入手できます

http://support.sas.com/publishing/cert/index.html

の「Sample Data」をクリックすると
















長いコードがばっとでてきますので




























全部選択してコピーして、SASのエディタにペーストして実行すれば
SASUSERライブラリに、書籍中にでてくる多分全てのサンプルデータが作成されます。

Advancedの方は無いようですが、基本、米国SASが出している書籍には、その書籍を紹介しているSASのページから、サンプルプログラムやデータを落とせることが多いので便利です。


ぶっちゃけた話、中身が気になるけど、買うほどじゃないなぁって本はサンプルプログラム落として
ざーっとみるだけで充分勉強になります。





目に見えない改行コードが邪魔をしてくる話

今、








こういう、どうってことないデータセットがあったとします。
Xは文字型です。

そして、

data A1;
 set Q1;
 Y=input(X,8.);
run;

というなんてことない処理(Xを数値化してYに割り当てる)を実行します。


結果は








え?

見た目は同じなのに2オブザベーション目だけ欠損値です。

ログにも、確かに欠損値が生成されたNoteはでていますが、






引数が無効と言われても、、。といった感じです。

これ、知らないと一生解決できないんじゃないか系のトラブルです。
以前紹介した「背筋が凍る、厄介でおっかない小数点誤差の話」
http://sas-tumesas.blogspot.jp/2014/03/blog-post_14.html
に似てますね。



実は冒頭のデータセットは

data Q1;
length X $10.;
 X='1';output;
 X=cats('0A'X,'1');output;
run;

というコードで生成したものです。

ん?2個目のXで、1にくっつけている'0A'Xってなに?

これ、実は改行コードの一部(ラインフィールド)を指定する意味をもった記述なんです。
通常、こいつと'0D'x(キャリッジリターン)を合わせて、改行をさせます。

こういう意味をもった記号があって(タブ('09'x)とか)、そいつらが往々にして
僕らの大切な時間と精神力を奪っていきます。

対策は簡単で

data A2;
 set Q1;
 Y=input(compress(X,'0A'X || '0D'X),8.);
run;

取り除いてからinputです。

例えば、エクセルからデータを読み込んでデータセットを作る場合なんかによくこの問題が
浮上します。

見栄えをよくするために、結構セル内改行つかったりしていることが多いんですよね。






横方向に重複を取り除く。配列内の変数から重複している値を消す方法

しばらく更新できないか、頻度が落ちると思います。すみません。

最近、迷った処理について

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

data Q1;
ID='001';TERM_1='頭痛';TERM_2='腹痛';TERM_3='頭痛';TERM_4='頭痛';output;
ID='002';TERM_1='下痢';TERM_2='腹痛';TERM_3='下痢';TERM_4='';output;
ID='003';TERM_1='腰痛';TERM_2='';TERM_3='';TERM_4='';output;
ID='004';TERM_1='';TERM_2='骨折';TERM_3='';TERM_4='骨折';output;
run;









ここから、








のようなデータセットを作れと言われたとします。

要は横方向に重複を除いてから連結しろってことで、
変数の数は可変、発生する重複の個数は可変で、どことどこが重複するかも
不明だとします
(必ず連続して2個重複とかなら、配列で要素番号-1を捉える条件式で対応できますね)


いいアイデアがなかったので、もう一端transposeで縦にデータ起こしてから、sortにnodupつけて
重複殺してから、再度、transposeして、と思ったのですが、それもちょっとイマイチだなと思って
以下の方法でやってみました。

data A1;
set Q1;
array AR{*} $ TERM_:;
 do i=1 to dim(AR);
  if AR{i}^='' and whichc(AR{i},of AR{*})<i then AR{i}='';
 end;
 ALLTERM=catx('、',of TERM_:);
run;






となって、IDとALLTERMだけkeepすれば、最初の作るべきデータセット同じになります。

青字の部分がミソで、要するに、自分の値で、自分自身を含む配列を検索して、最初に
見つかった位置番号が自分の要素番号より小さければ、自分の値は既に前の変数で
出現しているので、自分は要らない子なので消えようってことを順番に全変数分やって
生き残った値がユニークな値ってことです。

横方向に重複を取り除くのって、ぱっと思いつかないですね。
他に方法があれば教えてください。
配列は苦手なので、もっといい方法がある気がしてます。

call sortcルーチンにnodupオプションみたいなのがあれば一発解決な気がしますが、、。
追加されないかな






データセットオプションの意図的な空指定

どうってことないことかもしれませんが。

data Q1;
 X=1;Y=2;Z=3;
run;

のデータセットに

data A1(drop= rename=());
 set Q1;
run;

を実行すると、問題なく実行されます。

(drop= rename=())の部分は完全に意味のない空指定として無視されます。

それがどうした?といった感じなんですが、マクロ化する時なんかに
この性質を知っていると書きやすいです。


%macro ma(dr,rn);
 data A1(drop=&dr rename=(&rn));
  set Q1;
 run;
%mend;

とのようなマクロを作った時、
何も操作を加えず、データセットQ1をA1として作成したい場合は

%ma(,)

でOKです。僕はずっと、こけると思ってました。

それで例えば

%ma(X Y,%str(Z=A))

で実行すれば

変数X Yが落ちて、ZがAにリネームされます。


ただし、注意点として

data A2(where=());
 set Q1;
run;

は駄目です、エラーになります。これ、通るようにならないかなぁ。





WEBページのデータ(HTML)を直接SASデータセットにする方法

WEBページのデータ(HTML)を直接SASデータセットにする方法です。

例えば、例えばの話で、実際やったことはない.....?ですが

仕事中にここ1週間の将棋の棋戦の結果を知りたいなって時に、

http://www.shogi.or.jp/kisen/week/kekka.html


















みたいなページを長々と見てたら、てめぇ仕事してないなって即バレなわけです。


なので filenameステートメントでURLをファイル指定して
infileステートメントでとりあえずそのまま読み込みます。


filename KISEN url 'http://www.shogi.or.jp/kisen/week/kekka.html';
       
data A1;
   infile KISEN ;
   input COL $2000. @@;
run;


すると


















のようにWEBページを構成しているHTMLがそのままデータセットになってます。

あとは必要な部分の性質がわかっているなら

data A2;
 set A1;
 where COL contains ('○');
run;

(基本、勝敗がつくと考えて)




















これで、行われた対局と、その結果をぱっと知れます。
正規表現とか駆使すれば、余計なタグを取り除いて綺麗にできます。




データセットにパスワードをかける話とPWENCODEプロシジャで、コード内に直接パスワードを記載しなくてもよくする話

data Q1(pw=SASYAMA) ;
 X=1;
run;

とすると、データセットQ1に「SASYAMA」というパスワードを書けることができます。

このデータセットを更新・削除・その他操作しようとすると

data A0;
 set Q1;
run;







で、ここでSASYAMAって打てば、処理が実行されます。


で、いちいち手で入力しなくても

data A1;
 set Q1(pw=SASYAMA);
run;

とすればOKです。

でも、人にあげるプログラムで、その人にパスワードに「SASYAMA」って値を使っているって
しられたくない場合、つまり、ようするにコードの中に直接パスワードの文字を記述したくない
場合にどうするかという話です。

まず、あげるプログラム以外のエディタで
proc pwencode in="SASYAMA";
run;

と、パスワードをin=で指定して実行します。

するとログに











謎の文字列が出現します。

実は、これが暗号化された「SASYAMA」です。

なので

人に渡すプログラムは

data A2;
 set Q1(pw="{sas002}BCED1140421B96950FEE34A400661C44");
run;

とすればSASの内部で、実行する際に{sas002}BCED1140421B96950FEE34A400661C44が
復号されて「SASYAMA」として処理されます。
ログ等、いくら探しても、実行者はパスワードを知ることはできません。





SASからクリップボードにデータを出力する

コピーやカットしたした時に、一時的に保存される領域がクリップボードです。
ペーストしたら、そこから張り付けられるわけですね。

そのクリップボードにSASからデータをぶち込むことができます。

filename clip clipbrd;

data _null_;
file clip;
put '新手一生';
run;


として、ペーストすると

putした値がでてくるはずです。

エクセルなどに強引に値を渡す場合、
SASから値をクリップボードにつっこんでエクセルの方でペーストで展開するなんてことも
できます。