RWIはデータステップの美しさを目に見えるようにするために生まれたんだねって話

SASのRWIについて、RTF出力への対応がされたら、解析帳票作るのに使うから
勉強するよって話をよく聞きます。

医薬系の解析帳票についてRWIがProc Report等と比べて、第一選択になりえる
機能を持っているということはSASNAMIさんが解説されてます

「Report Writing Interface (RWI)を試してみる  」
http://sasboku.blog.fc2.com/blog-entry-54.html

はやく次期バージョンとかで対応されないかなぁ。


それはさておき、解析帳票作るのに使えんのか、使えないのかってとこだけに目がいきがちですがRWIの本質はそこではないと思うのです。

データステップ内で動的にレポート生成できるということが本質で、
データステップ内で何が起きているのかをビジュアル化できるという魅力があると思います。

ということで、今回は、ソートアルゴリズムの授業を想定し、バブルソートをSASの配列で表現した時、配列要素がどのように更新されていくかをRWIでビジュアル化してみましょう。

バブルソートは隣り合う二つの要素を大小比較でひたすら並び替えていく方法です。
並び替えが起きなくなるまでループします。もっとも原始的なアルゴリズムですね。

いまの時代、call sornで一撃やないのとか言わないでね。

普通にSASで書くとこんな感じかな

data _null_;
array ar{10} ( 3 5 8 2 1 9 4 7 6 0);
do until(finish=1);
  finish=1;
    do i=1 to dim(ar) - 1;
        tmp=.;
        if ar{i} > ar{i+1} then do;
            tmp = ar{i};
            ar{i} = ar{i+1};
            ar{i+1}=tmp;
            finish=0;
            put ar{*}=;
        end;
    end;
end;
run;

結果は





















う~ん、地味。じっくりみないとピンとこない。

そこで、上記のコードにRWIをたしちゃいます

data _null_;
array ar{10} ( 3 5 8 2 1 9 4 7 6 0);
array num _numeric_;

/*試行回数*/
total=0;

dcl odsout ob();
ob.layout_gridded(columns: 2,column_gutter: '1mm');

/*初期状態描画*/
 ob.region();
 ob.table_start();
  ob.row_start();
ob.format_cell(data: "回数",  style_attr: "background=green color=white");
ob.row_end();
ob.row_start();
        ob.format_cell(data: "初期状態");
    ob.row_end();
  ob.table_end();

 ob.region();
 ob.table_start();
ob.row_start();
  do i=1 to dim(ar);
          ob.format_cell(data: vname(ar{i}),  style_attr: "background=blue color=white");
end;
ob.row_end();
  ob.row_start();
  do i=1 to dim(ar);
        ob.format_cell(data: ar{i});
 end;
   ob.row_end();
    ob.table_end();

/*バブルソート開始*/
do until(finish=1);
  finish=1;/*終了フラグ*/
    do i=1 to dim(ar) - 1;
        tmp=.;
        if ar{i} > ar{i+1} then do;
            tmp = ar{i};
            ar{i} = ar{i+1};
            ar{i+1}=tmp;
            finish=0;
total=total+1;

/*描画*/
ob.region();
ob.table_start();
ob.row_start();
ob.format_cell(data: "回数",  style_attr: "background=green color=white");
ob.row_end();
ob.row_start();
      ob.format_cell(data:cats(total, "回目"));
   ob.row_end();
 ob.table_end();

  ob.region();
    ob.table_start();
ob.row_start();
  do j=1 to dim(ar);
          if j = i  or j = i+1then ob.format_cell(data: vname(ar{j}),  style_attr: "background=red color=white");
else ob.format_cell(data: vname(ar{j}),  style_attr: "background=blue color=white");
end;
ob.row_end();
  ob.row_start();
  do over num;
        ob.format_cell(data: num);
end;
   ob.row_end();
    ob.table_end();

        end;
    end;

end;

run;

結果は、ちょっと長くて画像分割しちゃってますが、以下の感じです










































やっぱRWI、面白い。これに最初に目をつけて体系的に説明して、世に広めた忘備録のa.matsuさんは本当に偉大だと思います。

「レポート作成インターフェイス(RWI)入門1」
http://sas-boubi.blogspot.jp/2014/10/rwi1.html

複数回transposeしてからmergeする前にちょっと考えてみてって話

例えば以下のデータセットから

data Q1;
ID="001";VISITN=1;A=1;B=2;C=3;output;
ID="001";VISITN=2;A=3;B=4;C=5;output;
ID="002";VISITN=1;A=6;B=7;C=8;output;
ID="002";VISITN=2;A=9;B=0;C=1;output;
run;











以下の全転置データセットを作りたい場合










proc transpose data=Q1 out=_A prefix=A_;
  var A;
  id VISITN;
  by ID;
run;
proc transpose data=Q1 out=_B prefix=B_;
  var B;
  id VISITN;
  by ID;
run;
proc transpose data=Q1 out=_C prefix=C_;
  var C;
  id VISITN;
  by ID;
run;
data RES1;
merge _A _B _C;
by ID;
drop _NAME_;
run;

と書いたりします。

もちろん、これで正解です。
変数が10個なら10回transposeかければいいんですが、ちょっと工夫を考えてみます

例えば、今回の場合だと「ID」 と「パラメータ」と「値」で構成される縦積み構造にしてから転置すれば1回の転置でスムーズに結果がだせます。

つまり

data _Q1;
set Q1;
array ch A--C;
do over ch;
VNAME =vname(ch);
VAL=ch;
output;
end;
keep ID VNAME VISITN VAL;
run;
proc transpose data=_Q1 out=RES2(drop= _NAME_) delimiter=_;
  var VAL;
  id VNAME VISITN;
  by ID;
run;








というわけ(IDの複数指定ができるようになったのは9.3から)。

1回途中で作ってる縦積み構造(_Q1)の中身をみてみると




















となるわけです。


別にこっちの方法が正解だというわけではなく、状況に応じて考えましょうという話ですね。対象が文字列変数の場合、縦積み構造にするときに文字切れしないように最大lengthにしなければならないので注意が必要です。

SASの場合、BYやCLASSステートメントが強力なので、インプットやアウトプットの形にちょっと頭を使えば、同じ機能のステップを反復する必要がない状況が多いです。

まあ、言いたいことを一言で言うと、データステップも楽しい世界だよってことです。


影の薄い%syscallの話

例えば以下のようにマクロ変数が3つあったとします。

%let a = 3;
%let b = 1;
%let c = 2;

あんまりやらない処理ですが、a,b,cの中身の値を昇順ソートして
a=1,b=2,c=3と再代入したいとします。

実は1行で書けますが、ぱっとコードが思いつきますか?

知ってるか知らないかですが、以下のように書けます

%syscall sortn(a,b,c)

で終わりです。
確認してみると

%put &=a  &=b  &=c;





というわけ。
%sysfuncはよく知られてるけど、それのコールルーチン版の%syscallはあんまり知られてない。

まあ、確かに使わざるをえない機会はそれほど多くない気がする。



libname excelで参照しているデータセット(エクセルSheet)がダブルクリックで開けない

地味に困ってる人が多いみたいなのでメモ。

libname excelして、ライブラリ参照しているエクセルシートを、ダブルクリックで
データビューから直接見ようとすると開けずにエラーになる現象。

もしSASが9.4で64bitで動いてるなら

options validmemname=extend;

した後にもう一回挑戦してみましょう。たぶん開けるのでは?

以下のProblem Noteで言及されてました

「Problem Note 51691: SAS® VIEWTABLE does not display an Excel sheet that is accessed using a LIBNAME statement」
http://support.sas.com/kb/51/691.html


ODS EPUBで電子書籍を作って遊ぶ話

この季節、新しい人と触れ合ったりすると、これからSASを始める日本全国のSASユーザーに、もっと役に立つ基礎的かつ実用的な記事を書くぞ!って気持ちが湧いたりします。

湧いたりはするんだけど、実際書いてみるとこのザマですよ。
駄目なんですよ、こういう内容しか書けないんです。SAS勉強するならSAS忘備録みましょう、書籍の「統計解析ソフトSAS」読みましょうね。
ここはSASのジョークサイトだと思ってください。

というわけで、SAS9.4から追加されたods epubでepub形式、ようするに電子書籍リーダー用のファイルを作れるようになりました。

自分はタブレット端末持ってないし、本も紙でしか見たことないような遅れ人なんですが
後ろの席に座ってる人がやたらとEPUB形式の魅力を語ってくれるので興味をもっちゃいました。

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

ods escapechar="^";
ods graphics on;

ods epub file="XXXXXXX/sassyama.epub"
title="SASはたのしい"
options(creator="SASYAMA" cover_image="XXXX/美濃囲い画像.jpg");
proc odstext;
p "実行コード"/style=[font_size=8];
p "proc freq data=sashelp.class;^{newline 1} tables age*sex/plot=freq;^{newline 1} run;"
/style=[font_size=8 font_style=Italic];
run;

proc freq data=sashelp.class;
tables age*sex/plot=freq;
run;
ods epub close;

結果、出力されたファイルを何かしらのソフトで開きます。
画像はChromeのアドオンのReadiumってやつで開いてます。

まずは表紙、特に意味もなく美濃囲いの美しい画像を使ってみました

















んで、中身


















たぶん、こだわりだしたらキリがないくらい色々できるみたい。

コード書いてるときは、100%おふざけのつもりだったんだけど、テキストとか画像とか、SAS出力を簡単にまとめれるし、リンクみたいなのも作れそうだから、何か仕事にも使えるかも。

例えばね、実行プログラム自身をproc streamでincludeして、どうにかodstextで表示したりして、実行ログとアウトプット、主要なデータセットのビューを埋め込んだりして
、もしかしたら仕様書の記述も加えたりしたら、なかなか面白い解析メタデータブックみたいなのができそうですね。
それを例えば新人教育の教材として、epub形式で配布するとかさ。

ああ、でもそれならYoshihiro Fukiyaさんが提案されてる「情報共有としてのSAS & Jupyter Notebookの使用」の方がよいのかな

まあ、あと例えば営業の人が全員ipad支給されてたりすることあるじゃないですか、そういう部隊にSASアウトプットを即時的に資料提供するのに使ったりとかね。
ないしは日報とかでその日の売り上げレポートをバッチでばらまくとか。

おお、なんかそれらしくまとまった。
やっぱり新しいアイデアは、遊びから始まるということで。こじつけだけど。







3回話しかけると襲い掛かってくる村人をコルーチンで表現する話

最初にちょっとしたSAS絡みのニュース
①SASユーザー総会のサイトがオープンした。聴講するより発表した方が楽しいので、アブストラクトだしましょうという話
http://www.sascom.jp/sug17/

②生物統計ハンドブックが12年ぶりに新版で内容変わるみたいなので、医薬系ユーザーは会社にねだって買って貰っとこうという話
http://www.scientist-press.com/11_339.html

さて、本題、例えばSASでロールプレイングゲームを作ろうと思います。
ある村人に話しかけると、村の名前を言ってくれるのですが、三回同じように話しかけると会話内容が変わって戦闘になるというイベントを作りたいと思います。
この3回話しかけると処理が変わるという機能をSASで実装するにはどうしますか?
今までは村人の会話挙動をSASマクロで実装して、村人に話しかけた回数をグローバルマクロ変数かSASデータセットに保存する必要がありました。
しかし、たかだか村人一人の挙動制御のためにグローバルマクロ変数を一つ作っているとキリがないです。
何がいいたいかというと、SASマクロの弱点は、ある機能に紐づく値をその機能自信に保持させれないということです。だってローカルマクロ変数は実行終わると消えちゃうんだもん。

そこでProc Luaの出番です。以下のコードで村人の挙動を作成します

proc lua;
submit;
function murabito()
    print("ここはSASむらです。")
    coroutine.yield()
    print("ここはSASむらです。")
    coroutine.yield()
    print("しつこいな、ころしてやる")
    return "----戦闘開始-----"
end
-- コルーチンを作成
co=coroutine.wrap(murabito)
endsubmit;
quit;

で、実際に3回話しかけてみると

proc lua;
 submit;
 -- 1回目話しかける
 print(co())
 -- 2回目話しかける
 print(co())
 -- 3回目話しかける
 print(co())
 endsubmit;
quit;












これがLuaのコルーチンです。色々あって奥が深いんですけど、とりあえず簡単な例です。
ようはcoroutine.yield()と書いたポイントで、一旦処理を中断して、戻すことができるわけです。次に呼び出された際はcoroutine.yield()の次の行から実行されます。
ちなみに戻り値も設定できます。
関数をコルーチンにするにはcoroutine.wrapを使えばOKです。

実は、ある機能自体に状態を記憶させるのは、コルーチン以外にもクロージャという機能で実装できます。
詳しくは過去記事「クロージャってなんじゃろ?」
http://sas-tumesas.blogspot.jp/2016/11/blog-post.html
をどうぞ。

どっちを使えばよいかは、好みとケースバイケースですが、今回みたいな単純な処理の場合、コルーチンの方がわかりやすいかもですね。カウント変数いらないし。