俺のWORK、マクロ変数、マクロ、オプション、フォーマットを全てお前に託す。お前が引き継ぐんだ!PRESENVプロシジャ~!!

バトルもので能力の継承って格好いいですね。

さて、それはちょっと置いておいて。


SASプログラムAをバッチ実行

EXCELに戻る アンド 何らかの作業

SASプログラムBをバッチ実行

みたいな際にAを実行したときのオプションやマクロ変数、WORKの中身をBに引き継ぎたいという場合、どうするか?
AとBは別セッションで、連続しないとします。

まあ、色々方法はあると思いますが、9.4からのpresenvプロシジャを使えば

グローバルステートメントの定義内容、つまり
optionやgopition、filenameやtitleなどの定義
および、マクロ変数、マクロ、WORKのデータセットを復元するプログラムを作成することができます。

復元プログラムをincludeしてから、新しいプログラムを実行することで、あたかも連続したセッションで実行しているような環境の引継ぎができるわけですね

もともとSAS Enterprise Guide のグリッド?で使いやすくするために開発されたみたいだけど
普通に便利

例えば、以下のような適当なプログラムを実行
data a;
 x=1;
run;
options msglevel=i;
proc format;
 value fm 1="A";
run;
%macro test;
XXXXX
%mend test;
%let YYYYY=AAA;
%let YYYYY=ZZZZ;
title "タイトル";

そして以下のコードを実行して、

libname lib1 "任意のフォルダ。ここにデータセットやカタログが保存される";
options presenv;
proc presenv
permdir=lib1
sascode="任意のパス\hukugen.sas";
run; 

作成されたhukugen.sasの中身をみると

【以下が生成されたプログラム】

 OPTIONS msglevel=i;
 TITLE "タイトル";
 LIBNAME LIB1 "任意のフォルダ。ここにデータセットやカタログが保存される";
 OPTIONS presenv;
%let saved_options=options %sysfunc(getoption(source))
 %sysfunc(getoption(notes)) %sysfunc(getoption(obs,keyword))
 %sysfunc(getoption(firstobs,keyword));
options obs=max firstobs=1;
options nosource nonotes;
%macro dummy_macro_for_presenv; %mend;
data _null_; rc=filename('presenvf',' ','temp'); run;
proc printto log=presenvf new; run;
proc sql;
create table _data_ as select distinct memname
        from dictionary.catalogs
        where libname='LIB1' and memtype='CATALOG';
     quit;
data _null_; set;
     call execute(
      cats('proc catalog force c=LIB1.',memname,
      '; copy out=work.',memname,'; quit;')
      );
     run;
proc delete; run;
proc copy in=LIB1 out=work mt=data; quit;
proc printto; run;
data _null_; rc=filename('presenvf'); run;
data _null_;
     call symput('YYYYY','ZZZZ' );
     call symput('AFDSID','0' );
     call symput('AFDSNAME',TRIMN(' '));
     call symput('AFLIB',TRIMN(' '));
     call symput('AFSTR1',TRIMN(' '));
     call symput('AFSTR2',TRIMN(' '));
     call symput('FSPBDV',TRIMN(' '));
run;
options _last_=WORK.A;
&saved_options;

一回SASを閉じた後、これを実行すると、閉じる前と全く同じ状況になっていることがわかる。

俺の屍をこえてゆけ みたいな。ゲームやったことないけど




時限爆弾_wakeup関数

SASのtwitterをフォローすると、過去のFAQやテクニカルニュースをランダム?にツイートしてくれる。結構古いTipsが多いので、最近のSAS使いにとっては逆に勉強になる。
ちなみに中の人は、いい人で、たまにユーザー総会の端っこで、本売らされたりしてる。

最近それで知ったのが
https://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/pc072.html


call sleepルーチンの前身として、sleep関数があるというのは知っていたけど
wakeup関数は知らなかったなぁ。

どうも最新のリファレンスには載っていない
でも使える。(on demandでは無理みたい)

日時を指定してその関数にかけると、WAKEUPウインドウというのがチカチカして
アイドリング状態になって。何時何分に起きて動きますよと教えてくれる。

wakeupの時間がくると、そのコード以下が実行される。
戻り値にはアイドリングしていた時間が返るが、通常いらないので_null_を指定する

時間を決めた処理はバッチでやることが多いと思うので、一体どういうときに使うんだろう?
実行開始時間がSASプログラム内で動的に決まるようなケース?
それってなんだろう??

まあ、簡単に書けるので、大して重要でもないSASコードの実行押すためだけに待機っていう悲しいシチュエーションがあるなら、wakeup関数さっと書いて実行して帰っちゃえばいいのかもしんないですね。


data _null_;
   slept_time=wakeup('24JAN2017:23:00:00'dt);

   /*以下は2017年1月24日23時00分以降に実行される*/
run;

data b;
x=1;
run;

絶対無敵の%global/readonly ;

久しぶりに記事連投更新。

9.4のM3から%global %localステートメントにreadonlyが追加されました。
readonlyで定義されたマクロ変数はそのスコープ内におい
て、
変更不能、削除不能、再定義も何もかも不能で、手出し不可能。

%globalでやると、セッションが終了するまで君臨する絶対無敵状態になるので要注意

%global /readonly xxx=1;

%let xxx=2;

%symdel xxx;




















%globalの方はともかくとして、%localのreadonlyは、SASマクロを多用する開発においては、
そこそこ使えるかも。
でも、今まで何十年もそういうオプション無しでやってきたから、今更追加されても浸透するかなぁ感はあり。
そろそろSASマクロに抜本的な変更があってもいいような気がするけど、過去の遺産が半端ないから厳しいのかな。DS2のマクロ版みたいな感じでべつものとして追加とか?それなら、やっぱり時代はProc Luaってことですかね。



カラーリストの出力

SAS忘備録の「カラー名の取得」がとても便利

http://sas-boubi.blogspot.jp/2017/01/blog-post.html

ただ、カラー名だとピンとこないので、ちょっと一手間加えてみる。
以下のコードを実行すれば、その環境で設定されてるSASカラー名一覧が色見本ででるはず。
多分。

filename tempf temp;
proc registry list export= tempf
 usesashelp
 startat='COLORNAMES' ;
 run;

data test;
 infile tempf truncover;
 input text $ 1-100 ;
 color=compress(scan(text,1,"="),'"');
 hex=scan(text,2,"=");
 if _N_>=8;
 dummy="";
 keep color hex dummy;
 run;

proc report data=test nowd;
 column color hex dummy;
 compute dummy;
call define(_col_,'style','style={background='||color||'}');
 endcomp;
run;

以下は、2017/1/22時点のSAS on demandで実行した結果。

colorhexdummy
AliceBluehex: F0,F8,FF 
AntiqueWhitehex: FA,EB,D7 
Aquahex: 00,FF,FF 
Aquamarinehex: 7F,FD,D4 
Azurehex: F0,FF,FF 
Beigehex: F5,F5,DC 
Bisquehex: FF,E4,C4 
Blackhex: 00,00,00 
BlanchedAlmondhex: FF,EB,CD 
Bluehex: 00,00,FF 
BlueViolethex: 8A,2B,E2 
BRhex: A5,2A,2A 
Brownhex: A5,2A,2A 
Burlywoodhex: DE,B8,87 
CadetBluehex: 5F,9E,A0 
Chartreusehex: 7F,FF,00 
Chocolatehex: D2,69,1E 
Coralhex: FF,7F,50 
CornFlowerBluehex: 64,95,ED 
Cornsilkhex: FF,F8,DC 
Crimsonhex: DC,14,3C 
Cyanhex: 00,FF,FF 
DarkBluehex: 00,00,8B 
DarkCyanhex: 00,8B,8B 
DarkGoldenrodhex: B8,86,0B 
DarkGrayhex: A9,A9,A9 
DarkGreenhex: 00,64,00 
DarkGreyhex: A9,A9,A9 
DarkKhakihex: BD,B7,6B 
DarkMagentahex: 8B,00,8B 
DarkOliveGreenhex: 55,6B,2F 
DarkOrangehex: FF,8C,00 
DarkOrchidhex: 99,32,CC 
DarkRedhex: 8B,00,00 
DarkSalmonhex: E9,96,7A 
DarkSeaGreenhex: 8F,BC,8F 
DarkSlateBluehex: 48,3D,8B 
DarkSlateGrayhex: 2F,4F,4F 
DarkSlateGreyhex: 2F,2F,2F 
DarkTurquoisehex: 00,CE,D1 
DarkViolethex: 94,00,D3 
DeepPinkhex: FF,14,93 
DeepSkyBluehex: 00,BF,FF 
DimGrayhex: 69,69,69 
DimGreyhex: 69,69,69 
DodgerBluehex: 1E,90,FF 
FireBrickhex: B2,22,22 
FloralWhitehex: FF,FA,F0 
ForestGreenhex: 22,8B,22 
Fuchsiahex: FF,00,FF 
Ghex: 00,80,00 
Gainsborohex: DC,DC,DC 
GhostWhitehex: F8,F8,FF 
Goldhex: FF,D7,00 
Goldenrodhex: DA,A5,20 
Grayhex: 80,80,80 
Greenhex: 00,80,00 
GreenYellowhex: AD,FF,2F 
Greyhex: 80,80,80 
Honeydewhex: F0,FF,F0 
HotPinkhex: FF,69,B4 
IndianRedhex: CD,5C,5C 
Indigohex: 4B,00,82 
Ivoryhex: FF,FF,F0 
Khakihex: F0,E6,8C 
Lavenderhex: E6,E6,FA 
LavenderBlushhex: FF,F0,F5 
LawnGreenhex: 7C,FC,00 
LemonChiffonhex: FF,FA,CD 
LightBluehex: AD,D8,E6 
LightCoralhex: F0,80,80 
LightCyanhex: E0,FF,FF 
LightGoldenrodYellowhex: FA,FA,D2 
lightGrayhex: D3,D3,D3 
LightGreenhex: 90,EE,90 
LightGreyhex: D3,D3,D3 
LightPinkhex: FF,B6,C1 
LightSalmonhex: FF,A0,7A 
LightSeaGreenhex: 20,B2,AA 
LightSkyBluehex: 87,CE,FA 
LightSlateGrayhex: 77,88,99 
LightSlateGreyhex: 77,88,99 
LightSteelBluehex: B0,C4,DE 
LightYellowhex: FF,FF,E0 
Limehex: 00,FF,00 
LimeGreenhex: 32,CD,32 
Linenhex: FA,F0,E6 
Magentahex: FF,00,FF 
Maroonhex: 80,00,00 
MediumAquamarinehex: 66,CD,AA 
MediumBluehex: 00,00,CD 
MediumOrchidhex: BA,55,D3 
MediumPurplehex: 93,70,DB 
MediumSeaGreenhex: 3C,B3,71 
MediumSlateBluehex: 7B,68,EE 
MediumSpringGreenhex: 00,FA,9A 
MediumTurquoisehex: 48,D1,CC 
MediumVioletRedhex: C7,15,85 
MidnightBluehex: 19,19,70 
MintCreamhex: F5,FF,FA 
MistyRosehex: FF,E4,E1 
Moccasinhex: FF,E4,B5 
NavajoWhitehex: FF,DE,AD 
Navyhex: 00,00,80 
Ohex: FF,A5,00 
Oldlacehex: FD,F5,E6 
Olivehex: 80,80,00 
OliveDrabhex: 6B,8E,23 
Orangehex: FF,A5,00 
OrangeRedhex: FF,45,00 
Orchidhex: DA,70,D6 
Phex: 80,00,80 
PaleGoldenrodhex: EE,E8,AA 
PaleGreenhex: 98,FB,98 
PaleTurquoisehex: AF,EE,EE 
PaleVioletRedhex: DB,70,93 
PapayaWhiphex: FF,EF,D5 
Peachpuffhex: FF,DA,B9 
Peruhex: CD,85,3F 
Pinkhex: FF,C0,CB 
Plumhex: DD,A0,DD 
PowderBluehex: B0,E0,E6 
Purplehex: 80,00,80 
Redhex: FF,00,00 
RosyBrownhex: BC,8F,8F 
RoyalBluehex: 41,69,E1 
SaddleBrownhex: 8B,45,13 
Salmonhex: FA,80,72 
SandyBrownhex: F4,A4,60 
SeaGreenhex: 2E,8B,57 
Seashellhex: FF,F5,EE 
Siennahex: A0,52,2D 
Silverhex: C0,C0,C0 
SkyBluehex: 87,CE,EB 
SlateBluehex: 6A,5A,CD 
SlateGrayhex: 70,80,90 
SlateGreyhex: 70,80,90 
Snowhex: FF,FA,FA 
SpringGreenhex: 00,FF,7F 
SteelBluehex: 46,82,B4 
Tanhex: D2,B4,8C 
Tealhex: 00,80,80 
Thistlehex: D8,BF,D8 
Tomatohex: FF,63,47 
Turquoisehex: 40,E0,D0 
Violethex: EE,82,EE 
Wheathex: F5,DE,B3 
Whitehex: FF,FF,FF 
WhiteSmokehex: F5,F5,F5 
Yellowhex: FF,FF,00 
YellowGreenhex: 9A,CD,32 

リファレンスにはないのに、決定木モデリングのプロットを描くdecisiontreeステートメントが追加されている話

以下を実行してみます。
ちなみにGTLのリファレンスには今時点(9.4 fifth ed)で「decisiontree」に関する記載はないです

https://support.sas.com/documentation/cdl/en/grstatug/69747/HTML/default/viewer.htm

proc template;
define statgraph temp;
   begingraph ;
      layout region;
         decisiontree nodeid=NODEID parentid=PARENTID;
      endlayout;
   endgraph;
end;
run;

data test;
NODEID=0;PARENTID=.;output;
NODEID=1;PARENTID=0;output;
NODEID=2;PARENTID=0;output;
NODEID=3;PARENTID=2;output;
NODEID=4;PARENTID=2;output;
NODEID=5;PARENTID=3;output;
NODEID=6;PARENTID=3;output;
run;

proc sgrender data=test template=temp;
run;































うわぁ、なんかでたし。




なんで、こんなことがわかったかというと
9.4で追加された決定木モデリングを行うhpslitプロシジャがods graphics onで作ってくれるプロットがかなり面白い形をしていたので気になりました。
以下のような感じです。


proc hpsplit data=sashelp.iris;
 class Species;
 model Species=SepalLength SepalWidth PetalLength PetalWidth;
 grow entropy;
 prune costcomplexity;
run;



























そこで気になったのでods trace on;にして
2つのプロットが以下のテンプレートで作成されいることを確認し
 HPStat.HPSplit.Graphics.ZoomedClassificationTreePlot
 HPStat.HPSplit.Graphics.WholeClassificationTreePlot

proc template;
source HPStat.HPSplit.Graphics.ZoomedClassificationTreePlot;
source HPStat.HPSplit.Graphics.WholeClassificationTreePlot;
run;

として、(むちゃくちゃパラメータあって複雑なテンプレートなので割愛)

中身をみて初めてdecisiontreeプロットが追加されていることをしったわけです。


ods output WholeTreePlot=out1;
ods output ZoomedTreePlot=out2;

として、流れ込んでいるデータセットの中身と見比べると、どのパラメータが何に効いているかが推測できます。

ただ、プロシジャ内部でdynamicをつかってどのように値を渡しているかは、見えないんですが、どなたか方法知りません?

sgplotで、生存率曲線の中央値地点からX軸に線落とせとか、そういうグラフの特定点から軸に線引くのはdroplineでいけるという話

グラフで、点から両軸に線を落とすのってどうやるんですか?って聞かれました。
その人はreflineで頑張ってましたが、それだと補助線でデータの位置でとめることができないです。
普通にデータまで縦の直線と、横の直線をグラフとして引いてもいいんですが

sgplotにはいつのまにか(多分9.3)からdroplineステートメントという便利な機能があります。

なんか、sgplotにドカンドカン機能追加されてて、嬉しいんですけど、把握しきれないですね。
あと、グラフの呼称って難しいですね。字面だけみても、どういうグラフ描くものなのかわからないし、検索するときも、なんて入れたもんかってね。

たとえば

data q1;
do x=1 to 10;
 y=x**2;
 output;
end;
run;

proc sgplot data=q1;
  series x=x y=y/markers;
  dropline x=x y=y / dropto=both
      lineattrs=(color=blue  pattern=dot);
  yaxis min=0;
run;

とすると、こんな感じ。





















droplineステートメントにx値とy値を指定して、dropto=でどっちの軸に線を落とすかを指定します。
bothは両方で。

あと、まあ、よくあるのが、凝りだしたら終わりのないグラフの定番kaplan-meierで
50%から線落とせってやつ。
面倒なので、ざくっと描くとこんな感じですか?

ods output SurvivalPlot=OUT1 ;
ods output Quartiles=OUT2;
proc lifetest data=SASHELP.BMT ;
 where GROUP="AML-Low Risk";
  time T * STATUS(0);
run;
ods output close;

/*中央値をマクロ変数に*/
proc sql noprint;
select Estimate into:p50
from out2
where Percent=50;
quit;

proc sgplot data=out1 noautolegend;
/*階段 */
step x=time y=Survival /lineattrs=(thickness=3);
/*髭*/
scatter x=time y=Censored/markerattrs=(symbol=plus size=15) ;

dropline x=&p50.  y=0.5  /label="★ここが50%やで" dropto=both lineattrs=(color=blue  pattern=dot thickness=3);

/*軸*/
yaxis values=(0 to 1 by 0.2 0.5);
xaxis values=(0 to 3000 by 500 &p50.);
run;


date関数やtime関数等はデータステップとSQLだと考え方が違うから注意という話

明けましておめでとうございます。

新年早々、ちょっとしたクイズです。

まず以下のデータセットがあるとします。

data a;
do i=1 to 10000000;
output;
end;
run;

次に2つのコードを示します。仮に全く同じ時間に実行した場合において
このコードの結果、できる2つのデータセットの内容は一致するでしょうか?しないでしょうか?

/*一つ目*/
data b;
set a;
time=time();
format time time.;
run;

/*2つ目*/
proc sql  ;
create table c as
select i,time() as time  format=time.
from a;
quit;

はい、まあ、問題にするってことは一致しないんですよ。

まず一つ目のデータステップでやった方の最初らへんと最後らへんの中身を見てみましょう。





















若干時間が違いますね。
データステップの場合、関数は、1obsごとに実行されるので、まさに
そのオブザベーションを処理した時間を掴みます。
よくあんのが、凄い時間のかかる処理を深夜にバッチで回したりして、翌朝みたら、
obsの途中から日付変わってるやん!ちくしょーみたいな話ですね。

次はSQLの方ですが、以下の通り




















全部おんなじ時間が入ってます。

なんでかっていうと、SQLプロシジャではdate,time,datetime等の関数は
SQLが実行される前に、日時を取得して、それを一律で関数の結果に返すという仕様を持ってます。
(わざわざ検証してないですが、たぶんtoday,weekday,holidayとか日時依存のやつは全部な気がする)

これをデータステップと同じように、そのobsが抽出された時間にしたければproc sqlにnoconstdatetimeオプションを
つけてやればOK。

proc sql noconstdatetime ;
 create table d as
 select i,time() as time  format=time.
 from a;
quit;


あるいは、実行される全てのSQLをずっとその仕様にしたいというなら
options nosqlconstdatetime;
としてやればOKです。元に戻すには
options sqlconstdatetime;
でOK。

データステップで、全obsに同じ時間を入れたい場合は、1obs目に取得してretainするか、
直前にマクロ変数に入れておいて展開するかになると思うので、こういう処理を書きたいなら
SQLでやった方が楽かもしんないですね。



以上。

さて、このブログも2013年にスタートしたので、今年の秋で丸4年です。
よくもまあ、データステップで何年も書けるなぁって自分で思います。
書きたいことを無責任に書き殴るスタイルだから、続いてるんだと思います。

最初の頃みると、月に37回とか記事あげてるんですね、思い返すと、毎日あんまり寝ずに書き続けてたんで、もう狂ってたとしか思えないですね。

今年も今まで通り、マイペースで書いたり休んだりでいくので宜しくお願いします。

また、よろしければ、是非、SASブログ初めてみませんか?
データステップでも、統計でも、グラフでも、ネタはなんでもいいので、書いてみると案外楽しいですよ。