変数の値によって指定された変数の値をとる方法 VVALUEXによる裏技

なんのこっちゃいってタイトルですが、

今回は、簡単そうで意外とわからない処理の話です。

data Q1;
A='赤';B='い';C='歩';X='A';output;
A='青';B='ろ';C='金';X='C';output;
A='黄';B='は';C='飛';X='B';output;
run;


というデータセットがあるとします。

ここから











というデータセットが欲しいとします。

つまり、Xの中に入っている値は、Yにどの変数の値を持ってくるかを指示しているわけです。

さて、1つ1つ

if X='A' then Y=A; 
else if ・・・・と書いていいってもいいですが、仮に変数が100個とかあったら面倒ですね。

この処理を1行で書くことができます。
それは

data A1;
 set Q1;
  Y=vvaluex(X);
run; 

です。

これだけで、上記の結果がでます。


vvaluex?、vvalueじゃないんですね。この関数って本来どういうのでしたでしょうか?

本来は

data Q2;
X=1;
format X Z2.;
run;





のように、フォーマットが設定された変数に対して、その変数に設定されたフォーマット
を使って、putして文字変数を作ってくれる便利関数です(デフォのlengthはいつもの$200.)

data A2;
set Q2;
XV=vvalue(X);
XVX=vvaluex('X');
format X best.;
run;






わかりにくいですが、XVとXVXは文字型変数です。

でvvalue関数がそのまま変数を引数にとるのに対して、vvaluexは変数名の文字値を引数に
とるんですね!括弧の中のXによくみると、シングルコーテーションがついてます。

そして変数に特にフォーマットが設定されていなければそのままデフォルトのフォーマットをつかいますので、つまり値そのまんまということです。

その性質をうまく使ってるんですね!!

vvaluexというと、フォーマットでputするだけの関数と思わせておいてのこの離れ業!
面白いですね!


means(summary)プロシジャのautonameオプションの話

今、以下のデータセットがあって

data Q1;
X=1;Y=2;Z=3;output;
X=2;Y=4;Z=3;output;
X=3;Y=2;Z=6;output;
run;









で、

proc means data=Q1 noprint;
 var X Y Z;
 output out=OUT(drop=_TYPE_ _FREQ_) mean=;
run;

とすると、データセットOUTの中身は





となります。

要約統計量=の後、何も指定しなければ、元の集計対象の変数名を上書きして
結果が格納されます。

出したい要約統計量が1つならそれでいいのですが

proc means data=Q1 noprint;
 var X Y Z;
 output out=OUT(drop=_TYPE_ _FREQ_) mean= sum=;
run;

のように2つ指定しても、格納する変数がないので、




上記のようなWarningがでて(University Editionのメッセージは全部英語なんです、、。)
sum=の方は無視されます。


なので

proc means data=Q1 noprint;
 var X Y Z;
 output out=OUT_1(drop=_TYPE_ _FREQ_) 
            mean=MEAN_X MEAN_Y MEAN_Z
            sum=SUM_X SUM_Y SUM_Z;
run;

のように集計結果を格納したい新規変数名を、順番に記述してやれば



となります。


が、はっきり言って、1つ1つ指定するのは、変数が多いと大変面倒なので

proc means data=Q1 noprint;
 var X Y Z;
 output out=OUT_2(drop=_TYPE_ _FREQ_)
        mean= sum=/autoname;
run;

こうしてやります。

すると







「変数名_要約統計量名」のルールに従って、自動的に変数が作成されるのです。
まあ便利!


ちなみに、例えば、XとYの平均、Zの合計だけを出したいとかって感じで、選択したい場合は

proc means data=Q1 noprint;
 var X Y Z;
 output out=OUT_3(drop=_TYPE_ _FREQ_)
            mean(X Y)=
            sum(Z)= /autoname;
run;






OKです。

森下卓九段が今期のNHK杯で敗退してしまったので、最近テンション低めです

小技紹介:①sumabsで絶対値合計 ②リンク付きタイトル

全く関連性ないですが、最近知った小技を2つ。

data Q1;
A=1;B=.;C=-3;D=4;
X=sumabs(A,B,C,D);
run;

sumabsという関数があって、そのまんまで、絶対値の合計を出してくれます。

結果は





9.2から登場の関数ですが、そんなに需要あったんかなぁ。何の処理に使うんでしょうか。


で次は

ods html;
title1 '普通のタイトル';
title2 link="http://tumesas.progoo.com/bbs/" "リンクがついてるタイトル";
proc print data=Q1;
run;
ods html close;















のtitle2ように link=を指定すると、タイトルをクリックするとリンク先に飛ぶ仕様にすることができます。


シングルコーテーション内のマクロ変数を展開する

下書きのまま、すっかりあげ忘れていた記事があったのでアップします。


今回の話は、マクロ変数の展開についてです。

さて今

%let M=いろは;

data Q;
 X='&M';
run;

といったコードを実行するとどうなるでしょうか?
やりたいこととしては、マクロ変数Mに「いろは」という値を入れて、それを後続のデータステップで
展開したいわけですが、この処理は期待どおりの結果になりません。







このように、マクロ変数が展開されません。
どうしてかというと、シングルコーテーションを使っているからです。
シングルコーテーション内のマクロ変数は展開されないというルールなんですね。
この場合は、ダブルコーテーションを使うのが正解でした。


さて、ここまではSASマクロを少しでも知っている人であれば、常識と言っていいことでしょう。

ただ、SASを長くやっていると、dmステートメント内の記述や、DDE等外部アプリケーションとの連携のため、シングルコーテーションを使う縛りがある上で、尚且つその中でマクロ変数を展開したいといったケースが生じることがあります。

無理。仕様だからしようがないといって諦めるのもそれはそれで一局の将棋ですが、新手一生の升田幸三はこう言いました。
「もう一歩突っこんで、不可能を可能にする努力、―将棋を創作し、また、勝負を勝ちきるには、この“えぐる”という修練が必要である」

というわけで、諦めずに、えぐってみましょう。

まず、思いつくのは

data E;
 X=%bquote('&M');
run;

ですね。シングルコーテーションに、マクロ変数展開を拒絶する能力があるというのならば、それを無効化して、意味のない記号文字に変えてしまえという発想です。
これをクォート処理っていいます。クォート処理を行う関数は山ほどあって、対象とする記号の種類や、処理のタイミングによって使い分けるのですが、詳しく書くと量がやばいことになるので、今回はしません。
%bquoteはシングルコーテーションもクォート対象なので今回使っています。
おまけですが%strは、デフォではシングルはクォート対象外ですが、
data E;
 X=%str(%'&M%');
run;
のように%を対象の前につけることで、同じ効果を得られます。

で、先ほどのコードを実行すると


となって、もれなくエラーになりました。
マクロ変数の展開はできているみたいですが、なぜでしょう?

わかりにくいんですが、ヒントはエラーメッセージです。

例えばですが、

data E;
X= $ABC$;
run;

というコードを実行すると、











あれ、同じメッセージだ!
どういうこと?

これはABCという文字列をくくっている$ドルマークに何の意味もないから、SASが、「お前が何をしたいのかさっぱりわからん、意味のある記号を使って、成立しているコードを書いてくれ」
っていう意味なんですね。


さて、これで先ほどの問題がわかりました。
data E;
 X=%bquote('&M');
run;

は確かに、シングルコーテーションのマクロ展開拒絶能力を無効化したことで、&Mの内容は展開できました。
しかし同時に、シングルコーテーションの、括った値を文字列として扱うという能力も無効化していたのです。
シングルコーテーションが全く意味のない役立たずの無意味文字になりさがったわけです。

なるほど、やっと解決法が見えてみました。

つまりマクロを展開する際は、シングルコーテーションを無効化(クォート)し、展開が終わったら再度有効化(クォート解除)すればいいわけです。

そこで、強制的にクォートを解除する関数%unquoteで

data A;
 X=%unquote(%bquote('&M'));
run;

とすれば






です。

ちなみに、残念ながらこれは別に僕が考えた新手ではなく、割と有名な定跡なんですけどね。


ods escapechar=で、Unicodeの特殊文字を使ったり、文字を修飾したりする話

深夜にカイジのアニメ(再放送)がやっていて、それを見ていると、無性にSASで星マークをprintしたくなりました。

さて、まずSASで特殊文字をアウトプットに出力したい場合、unicodeで対応する文字を検索します
(例は:http://www.decodeunicode.org/で探しました)

































そこで、出力したい文字を見つけたら、上の星でいうと「2605」という文字列に注目します。

そしたら、今回の大事な呪文

ods escapechar='^';

を実行します。これによって^はエスケープ文字となります。
ここで指定したエスケープ文字に様々な表現を足すことで、拡張的な文字表現ができます。

で、以下のようにして

data Q1;
X="^{style [color=yellow] ^{unicode '2605'x}}";
KAIJI=repeat(X,2);
run;

proc print data=Q1;
 var KAIJI;
run;

実行すると









みたいな感じです。
※追記:html出力がデフォでない場合はods rtf;やods html;、ods pdf;と、対応するods html close;やods rtf close; ods pdf close;でプロシジャ部分を囲んで出力ファイル形式を指定ください。通常のlistingアウトプットでは展開されません


例えば、

data Q2;
X="^{unicode '2721'x}";output;
X="^{unicode '2603'x}";output;
X="^{unicode '263A'x}";output;
run;

は、データセットの中身は当然









って感じで、意味不明なんですが、ひとたび出力するとunicodeが解釈されて

proc print data=Q2;
run;














絵文字みたいな感じでかわいいですね。


最後に真面目な話
ods escapecharを実務で使う場合は、

data Q3;
X=cats('χ','^{super 2}');output;
X=cats('H','^{sub 2}','O');output;
run;

proc print data=Q3;
run;











みたいに上付き文字とか下付き文字とかを入れたい場合ですかね?
主にタイトルとかフットノートとか、ラベルで使うことが多いと思います。
(やり方は同じです)

あ、最後に、ちょっと帰省等、用事があって、しばらく更新止まります。

また今まで書いた記事で、最近ちゃんとラベルの設定してなかったんですけど
その辺をちょっと、ちゃんと整理していきます。



詰めSAS12回目:数字が1文字ずつに分解された、数列から100番目の値を取得する

リンクさせていただいたサイト「うずまき (3rd Gen)」の管理人じゅんさんに、あるプログラマ向けチャレンジサイトの問題を教えて貰いました。

ただ、何のサイトか書いて、そのまま問題文載せて、解答例を示してしまうと、ちょっとまずいかなと思うので、問題を参考にした上で内容を変更しています。結構ありがちで一般的な問題だと思いますが。

そもそもSASで解く人なんて皆無だから問題ないと思いますけど、、。

問題:
4,6,8,1,0,1,2,1,4,.......と続く数列のN番目の値について、Nをマクロ変数で与えて実行すれば、値が表示されるようなSASコードをかけ。そして、取り敢えずN=100のときの値を示せ

あ~!こういうの見るとダメですよね。将棋好きに詰め将棋みせるのと同じで、どれだけ忙しくても考えることを避けれない。
ようするに4から+2ずつしていくけど、値が2桁以上になったら、1桁ずつ分解されるっていうルールの数列ですね。

僕が書いたのは以下の感じです。

%let N=100;

data _NULL_;
file print;
retain  K 0;
do i=1 to &N;
 X=put(2+(2*i),best. -L);
 do j=1 to length(X);
  Y=char(X,j);
  K+1;
  if K=&N then do;
    put Y;
    return;
  end;
 end;
end;
run;

答えは






[1]です。




なにをしているかをわかりやすくするため、

%let N=10;

data A1;
file print;
retain  K 0;
do i=1 to &N;
 X=put(2+(2*i),best. -L);
 do j=1 to length(X);
  Y=char(X,j);
  K+1;
  output;
  if K=&N then do;
    return;
  end;
 end;
end;
run;

とすると


















Kが実際何番目かのカウンタです。Xが加算ででてくる値で、それを1桁ずつ分解したのはYです。
Kが設定したNになった時点でループを打ち切ります。

さて、じゅんさんが提示されたコード(問題を変えたことにより、影響のあった箇所等のみかえています。)は

%let x=100;
data A2;
 length line2 $ 32767;
 DO num=6 TO 4+&x*2 BY 2;
  numvar=num;
  line=PUT(numvar,best12.);
   IF num=6 THEN DO;
    line2='4'||line;
    line2=COMPRESS(line2,' ');
   END;
   IF num NE 6 THEN DO;
    line2=TRIM(line2)||TRIM(line);
    line2=COMPRESS(line2,' ');
   END;
  END;
  ans=SUBSTR(line2,"&x",1);
 run;

 PROC PRINT data=A2;
  var ans;
 run;

で、結果は同じです。

なるほど、オブザベーション起こすわけではなく、1変数内で横に連結していくわけですね。問題のイメージと処理のイメージが合致しています。1変数内の文字値制限を超えない限りは、こちらの方が自然かもしれません。


さて、多分、同じ思いの方が多いと思いますが、この問題、めっちゃIML向きですよね。
というかSAS向きでないのか?

一応書いてみたのが

%let N=100;
proc iml;
X=compress(rowcat(char(shape(t((2:&N+1)*2),1))));
Y=substr(X,&N,1);
print X Y;
quit;



処理過程がわかりやすいようにX Y二つに分けていますが、答えはYです。

ただ、多分X=の部分もっと短くかけると思うのですが、まだIMLに慣れていなくてわかりませんでした。どなたか教えてください。


proc corrのアウトプットで、相関係数の値やp値の値に基づいて色を変える方法

最近交流させていただいております方から、「膨大な数の変数の相関をみる場合があるのですが、相関係数の値が一定以上、P値の値が一定以下の際に、そこがわかるようにアウトプットに色づけしたい」といった趣旨の質問がありました。

プロシジャのアウトプットをダイレクトにいじるのは、あんまやったことないから無理です~!って逃げるところでしたが、ちょっと頑張ってみました。

確かにproc corrかけると、対象変数の相関行列がでてくるんですが、変数多いと、目が滑って見落としちゃうことあります。

まず

data Q1;
 A=2;B=1;C=2;D=3;E=9;F=9;output;
 A=4;B=2;C=3;D=8;E=2;F=22;output;
 A=6;B=1;C=3;D=2;E=13;F=9;output;
 A=8;B=4;C=6;D=3;E=23;F=1;output;
 A=10;B=5;C=7;D=5;E=8;F=8;output;
run;

適当にデータを作ります。

で、

ods trace on;
proc corr data=Q1;
run;
ods trace off;

とすると










































と、いっぱい出てきます。

今回、いじくりたいのは3つめの相関行列の表です。

ods traceつけて実行したので、ログには各アウトプットの情報がでています。
そこから相関行列に該当するものを探します。
(ods tarce onについて詳しくは




みっけ! そこで、テンプレートの箇所をみます。
このテンプレートこそが、解析結果をアウトプットするためのデザイン情報、スタイルシートみたいなもんなんですね。

そこで、テンプレートを特定したら、次にそのテンプレートの中身を展開して確認します

proc template;
 source Base.Corr.StackedMatrix;
run;

templateプロシジャでsourceの後に、確認したい先ほどのテンプレート名を指定して実行
ログに以下のようなものがでてきます。






















ふむふむ、成程。さっぱりわからん。読めるか、こんなもん。
まあ、GraphTemplateLangage(GTL)やってる方なら知っているかもしれませんが、これはグラフじゃない方ののテンプレートランゲージなんですね。

これをカスタマイズするのか~。まあ頑張って読んでみます。
大切なのは、どこの記述が、アウトプットのどこに紐づいているかを見極めることです。

じっくり見てると、なんとな~く
column (RowName RowLabel) (Matrix) * (Matrix2) * (Matrix3) * (Matrix4);
の箇所が、全体の構造に関係してる気がしませんか?勘で。

さっきのアウトプットと見比べてみると、column(RowName RowLabel)はどうも、縦にA B C D E  F、横にA B C D E Fとなっている、アウトプットの一番外側の部分ではないか?と当たりがつきます(強引)。そうすると、次に(Matrix)とあるものは、もしかして相関係数の行列に対応しているのでは?と推測されます(強引)、じゃあ次の(Matrix2)はP値か!
じゃあ(Matrix3)と(Matrix4)はなんなんだ? あ、そうか、corrプロシジャにはたくさんオプションが付けられる!その指定によっては出現する階層じゃないのか!(もはや、こじつけ)

ということがわかります。まあ、詳しくはods関係をじっくり勉強してください。

そして、もう一つさっきのTemplateプロシジャの出力で重要ななのが、NOTEの部分で、このcorrプロシジャの相関行列をだすテンプレートはSASHELPのTMPLISTって中に格納されているよって部分です。このことを心に留めていてください。


さて、実際にこっからテンプレート改造手術を開始する前に下準備

proc format;
 /*相関係数の色*/
   value rf low--0.7='RED'
            -0.7<--0.4='BLUE'
            -0.4<--0.2 ='GREEN'
            -0.2-<0.2='WHITE'
            0.2-<0.4='GREEN'
            0.4-<0.7='BLUE'
            0.7-<1='RED'
            1='WHITE';
  /*P値の色*/
   value pf low-0.01='RED'
            0.01<-0.05='BLUE'
            0.05<-high='BLACK';
run;

まあそのまんまですね、相関係数の値によって変わるフォーマット、P値によって変わるフォーマットを先んじて作っておきます。

そしたら

proc template;
   edit Base.Corr.StackedMatrix;
      column (RowName RowLabel) (Matrix) * (Matrix2);
      edit matrix;
         cellstyle _val_ as {backgroundcolor=rf.};
       end;
        edit matrix2;
          style={foreground=pf.};
       end;
      end;
run;

これで手術完了です。
 edit Base.Corr.StackedMatrix;は、カスタマイズする元のテンプレート名を指定
column (RowName RowLabel) (Matrix) * (Matrix2);
は、今回はここまでの定義以上は必要ないので、こうしてます
edit matrix;からend;までが、相関係数の部分をいじりますよって意味です。
cellstyle _val_ as {backgroundcolor=rf.}; これはセルの値に基づいて、セルのスタイルを変更してください、背景色はrfフォーマットで定義しますって意味です。
edit matrix2;からも同様で、今度はセル全体ではなく、値の書式のみを変更する書き方です。

そして実行すると、このテンプレートはWORK.templateというところに保存されます。え?SASHELPじゃないの?って思うかもしれませんが、SASHELPのテンプレートをうっかり変な風に変えちゃったら、今後のcorrプロシジャのアウトプット全部変になって大変でしょう?
だからWORKに複製が作られて、そこが改造されるようになってるんです。


ods path work.template(update) sashelp.tmplmst;
ods html file='/folders/myfolders/sample.html';
proc corr data=Q1;
 ods select PearsonCorr;
run;
ods html close;

です。あ、ちなみにfile=の記述は、なぜかUniversityではこれでhtmlつくらないと、僕の環境でうまくいかなかったのでつけてるだけで、本来いらないです。
リスト出力以外ならods pdf;でもods rtf;でもods htmlでもなんでもいいです。

ods path work.template(update) sashelp.tmplmst;
これは、WORKにある、カスタマイズしたテンプレートがあるかを先に見ろよ、あったらそっち使えよ!って命令です。

結果は






















です。相関係数の絶対値が0.7以上ならセルが赤、0.4以上なら青、0.2以上は緑になります。
P値は5%以下なら青字、さらに1%以下なら赤字になります。

カスタマイズしたテンプレートを消す場合
proc template; 
   delete base.corr.stackedmatrix;
run;

でOKです。