SASグローバル認定プログラム SAS Certified Statistical Business Analyst Using SAS 9: Regression and Modelingを受けてみた

ブログのネタは溜まってるんですが、なんだかんだとバタバタしてて更新滞ってます。
またそのうち、投下していきたいと思います。

さて、SAS認定資格の話ですが、BASE,ADVANCE,CLINICALに続いて4つ目の資格に挑戦してみました。
「SAS Certified Statistical Business Analyst Using SAS 9: Regression and Modeling」
http://www.sas.com/offices/asiapacific/japan/training/certify/sba9.html

出題範囲(SAS社ページより)
-----------------------------------------------------------------------------
分散分析
 分散分析の仮定の検証
 GLMおよびTTESTプロシジャを使用した母平均の差の分析
 分散分析におけるポストホック検定の実施による処理効果の評価
 因子間の交互作用の検出と分析
線形回帰
 REGおよびGLMプロシジャを使用した線形重回帰モデルの当てはめ
 線形重回帰モデルに対するREGプロシジャの出力の精査
 REGプロシジャを使用したモデル選択の実施
 診断分析と残差分析の使用による回帰モデルの妥当性の評価
ロジスティック回帰
 LOGISTICプロシジャによるロジスティック回帰の実施
 入力の選択によるモデル性能の最適化
 LOGISTICプロシジャの出力の解釈
 LOGISTICおよびSCOREプロシジャを使用した新規データセットのスコアリング
予測モデルの性能に向けた入力の準備
 入力データにおける潜在的な問題の同定
 DATAステップの利用によるループや配列、条件文、関数を用いたデータ・ハンドリング
 予測モデルにおけるカテゴリ変数の水準数の削減
 CORRプロシジャを使用した不要な変数のスクリーニング
 経験ロジットのプロットを使用した非線形性を持つ変数のスクリーニング
モデル性能の計測
 「正直な評価」原理を適用したモデル性能の計測
 混同行列を使用した分類性能の評価
 学習および評価データを使用したモデル選択と評価
 モデルの比較や選択を行うためのグラフ(ROC、リフト、およびゲインチャート)の作成と解釈
 スコアリングを行ううえでの効果的なカットオフ値の確立
 スコアリングのためのカットオフ値の効果的な意思決定
-----------------------------------------------------------------------------

長いので今後、SBAと書かせてください。

日本での受験が可能になってから、もう2年くらいたちますかね。
しかし、現時点で日本での有資格者はわずか21人!!

なんせ、BASE,ADVANCEと違って、テキストも模擬問題もないし、情報もほとんどないんで手をだしにくいですよね。

というわけで、そんな皆様のために、先陣?きってSBAの首獲ってきました。

あんまり、こういう問題でたよってピンポイントでいうのはまずいと思うので、個人的な感想と独り言だけ、ごく簡単にかいて終わります。

・ややマニアック(高度)&システムよりな部分のあるAdvanceに比べて、統計解析の理論・実装・結果の解釈という三点セットが入って、難易度もほどほどな資格なので、解析担当者のスキルアップや理解度の確認に良い資格だと思いました。新人の方が目標とかに据えるなら、Advanceより先にこっち勉強した方が、データサイエンティストを育てるという観点では、真っ当なんじゃないかなぁって思いましたね。
(僕の個人的な趣味趣向としては是非Advanceを先に勉強して、データステップマニアになろうぜ!って思うけど)
別にSASのセールスマンじゃないけど、偉い立場の方向けにアピールしときます。会社でサポートする価値のある資格だと思いますし、もし面接にこれ持ってる人がいたら、少なくとも「なんちゃって統計プログラマー」ではないと判断していいはずですよ。
資格の名前にBusinessとか入ってますけど、解析の素材が売り上げ予測だったり、倒産有無なだけで、治療効果の予測とか疾患の有無とかに変えれば、そのまま医薬や他業界で使える内容なんで、分野あんま関係ないです。別にビジネスに特化した知識は一切出ませんでした。
・出題範囲に偽りはない。ちゃんとその通りにでたよ。トピックごとの出題比率はさておき。
※ただ、英語試験での記述「Apply the principles of honest assessment to model performance measurement」を「正直な評価」原理を適用したモデル性能の計測って訳すのはどうなんでしょう。そんな言葉あるんでしょうか…。一般的な表現ならごめんなんさい。
普通に、複数のモデルの比較評価の仕方や、モデルのバリデーションについて出題されてた箇所のこと言ってるんだと思いますが。
・どっかの誰かさんみたいに、データステップで点数稼ごうとかって愚かな考えはやめた方がよい。出題比率的に。
・普段からSAS Outputはちゃんとみよう。解析プロシジャまわすと一杯色々でますけど、どういう統計量なのかとか知っておくのは大事。
・ods graphicsでだしてくれてるプロットも大事、
・個人的感触として、統計解析の理論部分についてはそんなに難しくないはず。
・出題範囲にあがっている GLM REG LOGISTICの基本的なアウトプットの解釈と、基本的なステートメント・オプションは押さえておいた方が。
・挙げられている(ROC、リフト、およびゲインチャート)について、ROCはともかく、リフトとかゲインは医薬であんまり書いたりしないかもなので調べておこう。
・広域をカバーする統計の教科書や社内資料があるなら復習しておくとよいかも。


まあ色々、自分で考えて広く勉強すればいいと思います。
結果、テストにでなくて、対テストとしては無駄になることも多いけど、勉強するのは悪いことじゃないですしね。

以上、頑張って!


裏シージャ proc spellでスペルチェックだ

マニュアルに載ってないけど実は存在している「UNDOCUMENTED SAS PROCEDURES」、いわゆる裏シージャや闇シージャと呼ばれるプロシジャの話です。

その中でも、誰か使い道考えてくれないかなぁってやつをご紹介。

まずテキストファイルで

「I am SAS YAMA.」

とだけ書かれたものを用意します。

そして、以下のようにProc Spellにかけると

filename text 'ファイルのフルパス';
proc spell in=text verify suggest;
run;

アウトプットウインドウに

 ファイル: "TEXTDATA"

   認識されないワード               度数     行

   SAS                              1       1
         候補: ASS, AS, GAS, HAS, PAS, WAS, SAD, SAG, SAP, SAT, SAW,
                      SAY, SEAS, SPAS, SAGS, SAPS, SAWS, SAYS, SASH

   YAMA                             1       1
         候補: YAM, MAMA, YAMS



と出力されて、修正候補まで出してくれます。

辞書データを読み込ませて賢くすることもできるみたいなので
誰か面白いことに使ってください。リファレンスないけど。


proc scaprocの世界

最近、面白そうだなと思って注目してるプロシジャを紹介します。
その名もProc SCAPROCです。なんて読むのでしょうね。

9.2からいるので、さして新しくもないですね。知ってました?
こいつの機能はずばりSAS Code Analyzerです。

ちょっと奥深いので、何回かにわけるかも。
とりあえず今回はさわりだけ。

実行されたSASコードが何をしているのかを分析し、その結果を出力してくれます。

「proc scaproc;record "★出力先指定" オプション;run;」
として、そこから
「proc scaproc; write; run;」までの間に実行されるSASコードが分析対象になります。

使い方的にはproc printtoに似てますね。

例えば
proc scaproc;
record "★出力先指定\record.txt"
attr
;
run;

data OUT;
set sashelp.class;
 Z=999;
run;

proc summary data=sashelp.class(keep=age);
var age;
output out=summary mean=mean;
run;


proc scaproc; write; run;

とすると、
どんなデータセットが読み込まれ、どんなデータセットが出力されたのか?
ステップに何秒かかったか?動きのあったマクロ変数は?などの情報を、ちょっと人間には優しくない形式ですが、だしてくれます。オプションの種類と出力の解読方法についてはまた今度くわしく。


以下が出力されたファイルの中身です(一部抜粋)


* JOBSPLIT: DATASET INPUT SEQ #C00003.CLASS.DATA */
/* JOBSPLIT: DATASET OUTPUT SEQ WORK.OUT.DATA */
/* JOBSPLIT: ATTR #C00003.CLASS.DATA INPUT VARIABLE:Name TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CLASS.DATA INPUT VARIABLE:Sex TYPE:CHARACTER LENGTH:1 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CLASS.DATA INPUT VARIABLE:Age TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CLASS.DATA INPUT VARIABLE:Height TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CLASS.DATA INPUT VARIABLE:Weight TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Make TYPE:CHARACTER LENGTH:13 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Model TYPE:CHARACTER LENGTH:40 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Type TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Origin TYPE:CHARACTER LENGTH:6 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:DriveTrain TYPE:CHARACTER LENGTH:5 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:MSRP TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Invoice TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:EngineSize TYPE:NUMERIC LENGTH:8 LABEL:Engine Size (L) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Cylinders TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Horsepower TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:MPG_City TYPE:NUMERIC LENGTH:8 LABEL:MPG (City) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:MPG_Highway TYPE:NUMERIC LENGTH:8 LABEL:MPG (Highway) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Weight TYPE:NUMERIC LENGTH:8 LABEL:Weight (LBS) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Wheelbase TYPE:NUMERIC LENGTH:8 LABEL:Wheelbase (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR #C00003.CARS.DATA INPUT VARIABLE:Length TYPE:NUMERIC LENGTH:8 LABEL:Length (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Name TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Sex TYPE:CHARACTER LENGTH:1 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Age TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Height TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Weight TYPE:NUMERIC LENGTH:8 LABEL:Weight (LBS) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Make TYPE:CHARACTER LENGTH:13 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Model TYPE:CHARACTER LENGTH:40 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Type TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Origin TYPE:CHARACTER LENGTH:6 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:DriveTrain TYPE:CHARACTER LENGTH:5 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:MSRP TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Invoice TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:EngineSize TYPE:NUMERIC LENGTH:8 LABEL:Engine Size (L) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Cylinders TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Horsepower TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:MPG_City TYPE:NUMERIC LENGTH:8 LABEL:MPG (City) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:MPG_Highway TYPE:NUMERIC LENGTH:8 LABEL:MPG (Highway) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Wheelbase TYPE:NUMERIC LENGTH:8 LABEL:Wheelbase (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Length TYPE:NUMERIC LENGTH:8 LABEL:Length (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA OUTPUT VARIABLE:Z TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ELAPSED 5  */
/* JOBSPLIT: SYSSCP LIN X64 */
/* JOBSPLIT: PROCNAME DATASTEP */
/* JOBSPLIT: STEP SOURCE FOLLOWS */

data OUT;
set sashelp.class
 sashelp.cars;
 Z=999;
run;


/* JOBSPLIT: DATASET INPUT SEQ WORK.OUT.DATA */
/* JOBSPLIT: DATASET OUTPUT SEQ WORK.SUMMARY.DATA */
/* JOBSPLIT: FILE OUTPUT /home/morioka0380/record.txt */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Name TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Sex TYPE:CHARACTER LENGTH:1 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Age TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Height TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Weight TYPE:NUMERIC LENGTH:8 LABEL:Weight (LBS) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Make TYPE:CHARACTER LENGTH:13 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Model TYPE:CHARACTER LENGTH:40 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Type TYPE:CHARACTER LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Origin TYPE:CHARACTER LENGTH:6 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:DriveTrain TYPE:CHARACTER LENGTH:5 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:MSRP TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Invoice TYPE:NUMERIC LENGTH:8 LABEL: FORMAT:DOLLAR8. INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:EngineSize TYPE:NUMERIC LENGTH:8 LABEL:Engine Size (L) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Cylinders TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Horsepower TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:MPG_City TYPE:NUMERIC LENGTH:8 LABEL:MPG (City) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:MPG_Highway TYPE:NUMERIC LENGTH:8 LABEL:MPG (Highway) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Wheelbase TYPE:NUMERIC LENGTH:8 LABEL:Wheelbase (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Length TYPE:NUMERIC LENGTH:8 LABEL:Length (IN) FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.OUT.DATA INPUT VARIABLE:Z TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.SUMMARY.DATA OUTPUT VARIABLE:_TYPE_ TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.SUMMARY.DATA OUTPUT VARIABLE:_FREQ_ TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: ATTR WORK.SUMMARY.DATA OUTPUT VARIABLE:mean TYPE:NUMERIC LENGTH:8 LABEL: FORMAT: INFORMAT: */
/* JOBSPLIT: SYMBOL GET SYSSUMSTACKODS */
/* JOBSPLIT: SYMBOL GET SYSSUMTRACE */
/* JOBSPLIT: ELAPSED 8  */
/* JOBSPLIT: PROCNAME SUMMARY */
/* JOBSPLIT: STEP SOURCE FOLLOWS */

proc summary data=OUT;
var age;
output out=summary mean=mean;
run;

inputされたデータセットの変数情報とoutputの変数情報をだすためにattrオプションをつけてます。
これを使って差分を考えれば、一応、そのステップで新規に作成されたであろう変数を、ある程度特定できますね

X軸の値ラベルに改行を含む場合、axistableで実装するのは邪道っすか?って話

sgの機能は凄い充実してるし、未だ凄い勢いで増え続けてるけど、たまに、えっ!ていうのがなかったりしますよね。
或いは、あるのかもしれないけど、もはや指定できるオプションが多すぎて、目的のものをうまく探せないっていう。

例えば、聞きたいんですけど、以下のような軸ラベルのグラフをSGで描く場合って皆さん一体どうしてるんです?




























軸の値ラベルに改行ってどうしてます?って話です。
そもそもそんなデザインのグラフにしないっていうのはなしで。

離散値であれば、軸のtype=discreteしてsplitcharで指定して区切ればいいですけど、
未だ最新verでも連続量に対応してないですよね?
unicodeで改行コード打ち込んでもなんかうまくいかないです。多分fontを対応しているものに変えればできるんだろうけど、それはしたくない。

やっぱannotateデータセット作ってsgannoで描いてるんでしょうか?
こんなちょっとしたことのために、annotate作るのは正直しゃくですよね。十分慣れてるならいいですけど

ふと思ったんですけど、annotate作るぐらいなら、xaxistableで軸附属表を軸の値ラベルに見せかけてみたらどうです?
本来の軸値ラベルと、軸ラベルは消してしまって、表で表現しちゃうっていう。

実は先の図は実際にその方法(以下のコード)で書いたやつです。

data Q1;
call streaminit(1234);
do i=1 to 10;
 X=round(rand('uniform')*10,0.1);
 Y=round(rand('uniform')*100,1);
 output;
end;
drop i;
run;

data xa;
do X=1 to 10;
xvalue1=cats(X);
xvalue2="時間";
xvalue3="経過後";
if X=5 then xvalue4="経過時間";
else xvalue4 ="";
output;
end;
run;

data A;
set xa Q1;
run;

proc sgplot data=A;
scatter x=X y=Y;
xaxis display=(nolabel novalues) values=(1 to 10 );
xaxistable xvalue1 xvalue2 xvalue3/x=X nolabel;
xaxistable xvalue4/x=X nolabel valueattrs=(size=11) pad=(top=10);
run;


axistableの本来の使用目的から外れている気がしますが、annotateの構造より正直
万人に受け入れやすいと思うんですけど、どうなんでしょう




小技というか裏技? subpad関数は現実に存在しない場所から文字をもってこれる

すっかり久しぶりの更新になってしまいました

この1ヶ月ほど色々ありました

藤井聡太が勝ったりとか、負けたりとか、また勝ったりとか。
あとは特に何もないですね。

リハビリにしょうもない記事をひとつ

subpad関数のリファレンスを読むと

・SUBPAD(string, position <, length>)

第2引数のpositionに関しては正の整数です。
と書いてある。

そんな風に書かれると、0とか負の整数指定したらどうなるのかって
試してみたくなるのが人の性ってもんですね。

というわけでやってみます。

data A;
X="ABCDE";
do i = 5 to -5 by -1;
Y=subpad(X,i);
output;
end;
run;

どうせエラーになるんだろうなぁと思っていたら

おおぅ、ログにはなにもでず、正常に実行完了。

そして中身をみると






















0番目の文字位置とか-1番目の文字位置とか存在しない場所から半角スペースとってきてる。亜空間でも見に行ったんですかね。

任意の数だけデータを半角スペース下げするのに使えるかなぁ。
でも正規なリファレンスに反した書き方だから、将来的にエラーにされても文句いえんなぁ


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ステートメントが強力なので、インプットやアウトプットの形にちょっと頭を使えば、同じ機能のステップを反復する必要がない状況が多いです。

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