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



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

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