SASでGIFアニメを作る





















先日のユーザー総会で発表されていた「動画による統計表現~新しい統計の要約~」という論文にとても感動した。

面白い、凄い!絶対に読むべき論文です。

ただ、SG系で作成された画像ファイルからGIFアニメーションファイルを作成する際の方法の理屈が、発表を聞いている時は、理解できなかった。
質問までさせていただいたのに、よくわからなかった。

それもこれも、9.2でSG系グラフがでて、GTL使えるようになったから、G系グラフのANNOTATEのこと勉強しなくてもいいや~、全部テンプレートでなんとかできるでしょ、とか甘い考えして、ANNOTATEの勉強しなかったせいだ。

gannoプロシジャの意味がわからなかった。


ただ、その後、実際論文集のコードを全て打って、やってみて、やっとわかった。

gannoプロシジャはANNOTATEデータセットを読み込んで、その指定に基づいて出力を作成するもので、その中でファイルを指定して画像イメージを表示するコマンドを使って、G系出力ではない画像ファイルを無理やりG系出力にしちゃうことで、G系出力からGIFアニメを作成するGIFANUMオプションの網にかけちゃおうって発想なわけね!
凄い!どうやったら、そんな発想が!!

試しに自分も何か作ってみたくなったけど、何も思いつかない。
ので、以前の記事
「ods graphicsで作成される複合グラフを1つ1つのプロットに分解する話」
http://sas-tumesas.blogspot.jp/2014/03/ods-graphics.html

で、分解されたプロットをアニメとして再合成してみる。
わかりやすくするためにベタベタ打ちで。

data Q1;
X=1;Y=4;output;
X=2;Y=5;output;
X=5;Y=8;output;
X=6;Y=9;output;
X=9;Y=12;output;
X=11;Y=10;output;
X=22;Y=26;output;
X=12;Y=15;output;
run;

ods graphics on/reset imagename='P' width=600px height=400px;
proc reg data=Q1 plots(only)=DiagnosticsPanel(unpack);
model Y = X;
run;
quit;
ods graphics off;

data ANNO;
 length imgpath function style $32.;
 retain xysy ysys '3' hsys '3' when 'a';
 function='image';X=100;Y=100;imgpath='P.png';output;
 function='image';X=100;Y=100;imgpath='P1.png';output;
 function='image';X=100;Y=100;imgpath='P2.png';output;
 function='image';X=100;Y=100;imgpath='P3.png';output;
 function='image';X=100;Y=100;imgpath='P4.png';output;
 function='image';X=100;Y=100;imgpath='P5.png';output;
 function='image';X=100;Y=100;imgpath='P6.png';output;
 function='image';X=100;Y=100;imgpath='P7.png';output;
run;


filename animmap "C:\SAMP.gif";
goptions reset=goptions device=gifanim gsfmode=replace gsfname=animmap
xpixels=601 ypixels=401 cback=white iteration=0 delay=100 disposal=background border htitle=13pt;

proc ganno anno=ANNO;
where monotonic()=1;
run;
goptions gsfmode=append;
proc ganno anno=ANNO;
where monotonic()=2;
run;
proc ganno anno=ANNO;
where monotonic()=3;
run;
proc ganno anno=ANNO;
where monotonic()=4;
run;
proc ganno anno=ANNO;
where monotonic()=5;
run;
proc ganno anno=ANNO;
where monotonic()=6;
run;
proc ganno anno=ANNO;
where monotonic()=7;
run;
goptions gepilog='3B'x;
proc ganno anno=ANNO;
where monotonic()=8;

run;


結果は冒頭に貼った図です。


options iconの話と先日のユーザー総会のお礼

options icon;

として実行すると。SASのウインドウが最小化します。

options noicon;とすると元に戻ります。

一体、いつ使うんだろうと思っていましたが、開いているEXCELにSASからデータを出力する際に
出力過程を見たいといった場合に、役立ちました。
つまり、先日のSASユーザー総会で発表した数独のプログラムで初めて使いました。

先週の木曜日と金曜日、SASユーザー総会で2論文の発表をさせていただきました。
見に来て下さった方、声をかけて下さった多くの方について、本当に有難うございました。
また、私の昨年の論文や、本ブログを発表の中で取り上げてくださった方も複数いらっしゃたようで
とても嬉しいです。体がいくつもあれば見に行きたかったのですが、中々全てに顔をだすことができませんでした。

数独の方で最優秀論文賞をいただきました。これについても本当に有難うございます。

さて、今年は表彰式で、賞品の紹介がなかったので、「なに貰ったの?」とよく聞かれました。
別にバラしても問題ないと思うので、発表します。

目録の封筒の中には、紙が一枚入っていてそこには
「キングジム boogie board sync 9.7」と書かれていました。

??ちょっとそれが何を意味するかわからなかった僕は、ジム?
まさか貧弱な体を鍛えるための、筋トレ用具か?これからのSASプログラマーはマッチョであれというメッセージなのか?うわ~、家で場所とりそうだな~とか一瞬で考えたのですが

全然違いました。キングジムって、あの文房具とかのか。
以下の商品でした。
http://www.kingjim.co.jp/sp/boogieboard/bb6.html


おぉ~、なんかよくわからんけど、こういうものが世の中にあるんですね。
自分じゃ絶対買わないのでびっくりです。

紙のノート、すぐ失くす、使いきれない、破いてしまう僕にはいいかも。

普段ぱっと思いついたブログのネタを、まずここに書き留めるようにします。これからは。

なんだか、ユーザー総会の賞品は電化製品という伝統があるんですかね。
ある方は、以前の商品でお掃除ロボットでしたし、確か電子書籍読むやつの年もあったし、今年も別の賞を受賞された方はネスカフェドルチェだったと教えてくださいましたし。

ちなみに昨年いただいた「特別プレゼンテーション賞」の賞品は
USBに接続する小型扇風機でした。
接続すると、かなりの騒音を発生させながら扇風機が回り、しかも自ら発生させた振動と風によって、少しづつ机の上を移動し、最終的には机の下に落下するというエキセントリックな代物でした。

あ、決してケチつけているわけではないですからね!!凄い嬉しかったです!何回もやって、その度に爆笑できたので!








MD5関数 SHA256関数でデータ内容の変更を確認する

データセットの内容が、あるプログラムの処理前と処理後で同一であるかを確認する方法は色々考えることができます。まあ基本compareプロシジャ使えばいいんですけど。

今回はハッシュ値関数を使った話です。

ハッシュオブジェクトの話ではないので、ハッシュオブジェクトに興味ない方でも問題ないです。

ハッシュ値関数とは、ざっくりいうと、ある文字列を与えた時にその文字列から数値を生成してくれるものです。

基本的に暗号化に使われている技術です。ハッシュ値関数による改竄検知の仕組みとはつまり、元のデータセットの内容をハッシュ値関数にかけてハッシュ値をつくります。

そして、まあ何か処理やデータの移動があったとして、再度ハッシュ値関数にかけてハッシュ値をつくってそれが最初のハッシュ値と一致してればOKということです。

で、どういうアルゴリズムでハッシュ値を生成するかというのは、とても難しい問題です。
何せ、暗号化に使われている技術なので、簡単に復号できたらいけないわけで、日進月歩で進歩しているわけです。

でSASにはMD5関数というものがあったのですが、9.4から、さらに頑健なSHA256関数というものが実装されたそうです。

ただ9.4の実行環境がないので MD5で説明します。
9.4環境の人はどうせならSHA256使った方がいいでしょう。

data Q0;
A='い';B='は';C='ろ';D='に';output;
A='い';B='は';C='に';D='に';output;
A='は';B='ろ';C='ろ';D='に';output;
A='に';B='ろ';C='ろ';D='は';output;
A='い';B='は';C='ろ';D='に';output;
run;









というものが与えれたら、まず全変数の値を連結した値をもとにして
ハッシュ値を生成してみます。

data Q1;
 set Q0;
 H1=md5(cats(of A--D));
 format H1 hex32.;
run;








となります。
hex32.フォーマットを当てないと表示が文字化けします。
1オブザベーション目と5オブザベーション目は、実は同じ内容なのですが、ちゃんと
H1の値が同じであることがわかります。

で、今回は3obsベーション目の内容を変えて、再度ハッシュ値を生成して比較してみます。

data Q2;
 set Q1;
 if _N_=3 then D='は';
 H2=md5(cats(of A--D));
 if H1^=H2 then FL='×';
 else FL='○';
 format H2 hex32.;
run;








となって、ちゃんと3オブザベーション目のハッシュ値だけ変わっていることが確認できます。


さて、これの応用の仕方はそれぞれです。
長いテキスト内容そのものや、複数変数の内容をキーにしなければならない時などに、そこから生成したハッシュ値を代理キーにすることで、処理効率や保守性があがることもあるでしょう。

また、他の人にデータセットを送って、それが送り返されてきた際に同一かどうかを確認するのに使うのもよいでしょう。

また変更箇所にもとづいた条件分岐を1つのデータステップでやりたい場合などにも、1回コンペアプロシジャ回してから処理に入るよりスムーズな場合もあるでしょう。






ユーザー総会の資料

SASユーザー総会の発表の資料が公開されました。
SASのプレミアムランジのページからダウンロードできます。(要登録)

http://www.sascom.jp/campaign/usergroups2014/agenda.php


いくつか自分で、発表資料と論文に誤記を発見してしまい、ショック、、、。
ハッシュとの比較にだしたmergeステートメントにbyつけ忘れるとか、、。まあ、あんまり本筋とは関係ないところですが、誤記修正はいずれまとめて、アップします。

また、臨床検査値と基準値をマッチングするコードを公開してますが、これはあくまで、ハッシュオブジェクトの説明用に極度に単純化したものであり、テストデータに対してしか検証していないので気をつけてください。

特に本来、実業務では、想定するべきエラー(検査データは入力されているが、基準値がまだ設定されていない医療機関があるetc)に対しての処理を入れるべきところなども、説明用のコードが長くなりすぎるのも良くないと思い、全く入れてません!!

また同じく臨床検査値と基準値のマッチングで、方法②として示したコードは、かなり実験的な性質のものになります。具体的にいうと、キーの重複したハッシュオブジェクト内での、重複キーに付随するデータの格納順は、リファレンスの記述から、恐らくハッシュオブジェクト定義時に格納したデータ順に基づくという解釈が正しいことを当て込んで作っています。自分である程度、バリエーションのあるデータを使って実験し、想定通りにいくことを確認していますが、仕様の抜け道を通すようなワイルドなコードですので実装は充分注意してください。

基本的に論文を読んでいただいて、コードの意味を理解していただいた上で、自身の環境用にカスタマイズして、充分検証されてから、自己判断・自己責任でお使いください。


それにしても、今年は結構、発表が多い気がします。
他の方の資料を見ていると、わくわくします(内容は全然理解できてないんですが、)


自分用のサンプルプログラムフォルダを作ってる人へ。そこからエディタにマクロでコードをペーストする話

SASには正式なレファレンスにはどこにも載っていないけれども実装されている機能があります。
拡張エディタを操作するEDCMDもその一つです。
http://support.sas.com/kb/10/385.html

さて、話は変わって、結構多くの人が、自分用のサンプルプログラムフォルダのようなものを持っている気がします。特徴的な処理や、しばらく使わないと忘れてしまうプロシジャ、過去作ってきたたくさんのマクロ達などです。部品倉庫みたいな感じで。

そういったものを現在書いているコードに、ぺたっと張り付けたい時ってありますよね?

そういった際に、いちいちフォルダ開いて、目的のサンプルプグロラム探して、コピーして、ペーストして、サンプルプログラム閉じてって動作を面倒に感じませんか?僕は怠惰なので感じます。

なので、比較的短いものは全部キーボードマクロに登録してるのですが
(キーボードマクロとは?http://sas-tumesas.blogspot.jp/2013/10/sas.html

一連の流れごとサンプルにしているものなどは、そもそも登録が面倒です。


そこで、僕が使っているマクロを紹介します。

%macro pgcall(NAME);
dm 'whostedit;
 include "C:\sample\&NAME..sas";
 edcmd selectall;
 edcmd copy;
 edcmd winclose;
 edcmd paste'
;
%mend pgcall;

C:\sample\の部分はご自身のプログラムが入っているフォルダパスに直してください。

そして、実行したい時は、そのプログラムを張り付けたい場所に

%pgcall(merge)

として(例ではmerge.sas)というプログラムをエディタにコピペする









そして上のように行選択した状態で、実行すると、マクロの記述が消え去って、代わりに










とエディタにmerge.sasの内容が出現します。

どうでしょうか?

ちなみに、何人かにこのマクロをあげたところ、全員から「すげー使いにくい」という素敵な感想を
いただきました。

やっぱこういうのって、便利に感じるは作った本人だけってころですね。

そもそも、ちゃんとサンプルプログラムフォルダ整理してないし、名前も適当につけているから、いざ呼び出した時に、謎のコードが展開されて焦ったことも多く、やっぱり作業効率化できてないのかも、、。


ハッシュオブジェクトの世界⑧ 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をドロップしてください。

やっと終わった。

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


また続きはいずれ






Grayコードを利用して、組み合わせデータを作る

Grayコードというものがあります

【wikipedia】
http://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%82%A4%E3%82%B3%E3%83%BC%E3%83%89

こういう話苦手です。

で、このGrayコードを作成する関数とルーチンがSAS9.2から追加されました。
以下のテクニカルニュースの3ページ目で解説されています。

http://www.sas.com/offices/asiapacific/japan/periodicals/technews/pdf/09aut.pdf


これを最初読んだ時、思いました。こういう関数って一体何に使えと言うの?と。

きっと特殊な職業の人が特殊なことに使ってんだろうなぁとか思ってました。
しかし結構前ですが、これを使って面白いことをやっているコードを見かけました。

確か本だったか、海外の論文だったか覚えてないのですが、概要は記憶していたので
ちょっと再現してみました。

今、○と△と□と×を組み合わせて、1文字から4文字の組み合わせを全て列挙しなさい。

つまり、1文字パターンは ○、△、□、×の4通り
2文字パターンは○△、○□、○×、△□、△×、□×の6通り
って、感じですね。
3文字パターンは4通りで
4文字パターンは当然1通りなので

全部で15のはずです。

さて、これをSASで作ってみます。

data A0;
length OUT $10;
array N{4};
do i = 1 to 2**dim(N);
 call graycode (-1, of N{*});
   OUT='';
   do j = 1 to dim(N);
     if N{j}^=0 then OUT = cats(OUT,ksubstr("○×△□",j,1));
     end;
   if OUT^='' then output;
 end;
run;


結果は














となって、変数OUTの中身をみると、確かに15オブザベーション分できてます。

網羅的に0、1が出現するGrayコードを、こういう風に使うわけですか、
ちょうど0,1という値だから、これが文字列連結のための真偽値になってるわけか。

はぁ~、納得すると溜息つきたくなるようなコードですね。
どうやったらゼロからこういうコードを思いついて、表現できるようになるんでしょうか。
僕には一生思いつく気がしません。

わけわからんような、何の役にたたなさそうな関数やルーチンも、きちんと使い道を
研究検討しないと駄目ですね。






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

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

さて、関係性データベースの正規化とは、乱暴解釈すると、各テーブルがユニークなキーで管理できる状態になるまで、細切れにすることだと言えます(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になるかも、、)

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





of で関数の引数に配列

SAS忘備録「関数の中でOFを使うと変数の指定が楽になる例と応用例」
http://sas-boubi.blogspot.jp/2014/07/of.html

を読んで、思い出したのですが、

data A1;
 array A{3} (1,2,3);
 array B{3} (4,5,6);
 S=sum(of A(*) B(*));
run;

のように複数の配列を引数にとれるんですね。
考えてみると自然な感じですが、わりと最近まで知りませんでした。

上記のように結果的に全数値変数が対象であるなら

sum(of _numeric_);

sum(of _all_);

でも結果は同じです。