DATEKEYSプロシジャを使って日付にイベントを関連付ける(なんか凄い応用効く予感)

SASにはholidayname関数、holidaycount関数、その他holidayから始まる
たくさんの関数があります。それらは日付や期間を与えることで祝日の名前や祝日の数を
返したりする機能を持っているのですが…

試しに11月11日を与えて、そこに定義されている祝日の名前を書きだしてみます

data _null_;
dt='11NOV2016'd;
do count= 1 to holidaycount(dt);
name=holidayname(dt,count);
put dt yymmdds10. +2  count= name= ;
end;
run;






と、なんか3つでてきましたね
ちなみに
VETERANS : 退役軍人の日 11月11日
VETERANSUSG : 退役軍人の日(米国政府の祝日)
VETERANSUSPS : 退役軍人の日(米国郵政公社の祝日)
らしいです。

要するに、対応しているのが米国の祝日なんですね。
日本じゃ使い道ね~、工数計算とかスケジュールをSASで作るときに使えたら楽なのに~ってずっと思ってました。
ところが9.4で試用版として導入されたproc datekeysを使うことで話は変わってきます。

proc datekeysは結構深いプロシジャのようなのですが、今回は一番単純に使ってみます

2016年の祝日を直打ちで設定(データセットからも流し込める)して
それを使って5月の祝日判定をしてみましょう

まずdatekeysプロシジャを使って、イベント日の定義情報をデータセット化します

proc datekeys;
   /*元日*/ datekeydef New_Years_Day= '1JAN2016'd / pulse=day;
   /*成人の日*/ datekeydef Coming_of_Age_Day= '11JAN2016'd / pulse=day;
   /*建国記念の日*/ datekeydef National_Foundation_Day= '11FEB2016'd / pulse=day;
   /*春分の日*/ datekeydef Vernal_Equinox_Day= '20MAR2016'd / pulse=day;
   /*振替休日*/ datekeydef Substitute_Holiday= '21MAR2016'd / pulse=day;
   /*昭和の日*/ datekeydef Showa_Day= '29APR2016'd / pulse=day;
   /*憲法記念日*/ datekeydef Constitution_Memorial_Day= '3MAY2016'd / pulse=day;
   /*みどりの日*/ datekeydef Greenery_Day= '4MAY2016'd / pulse=day;
   /*子供の日*/ datekeydef Childrens_Day= '5MAY2016'd / pulse=day;
   /*海の日*/ datekeydef Marine_Day= '18JUL2016'd / pulse=day;
   /*山の日*/ datekeydef Mountain_Day= '11AUG2016'd / pulse=day;
   /*敬老の日*/ datekeydef Respect_for_the_Aged_Day= '19SEP2016'd / pulse=day;
   /*秋分の日*/ datekeydef Autumnal_Equinox_Day= '22SEP2016'd / pulse=day;
   /*体育の日*/ datekeydef Health_Sports_Day= '10OCT2016'd / pulse=day;
   /*文化の日*/ datekeydef Culture_Day= '3NOV2016'd / pulse=day;
   /*勤労感謝の日*/ datekeydef Labour_Thanksgiving_Day= '23NOV2016'd / pulse=day;
   /*天皇誕生日*/datekeydef Emperor_Akihitos_Birthday= '23DEC2016'd / pulse=day;

   datekeydata out=MyHolidays ;
run;

次にeventds=オプションで定義データセットを指定します。ここでnodefaultsも追加しておくと
デフォルトの定義(=アメリカの祝日)を外すことができます。日米両方の祝日をだしたければnodefaultsはいりません。

options eventds=(nodefaults MyHolidays);
data MAYHOLI;
length dt 8. name $50.;
do dt='1MAY2016'd  to '31MAY2016'd ; 
call missing(name);
if holidaycount(dt) = 0 then output;
else do count= 1 to holidaycount(dt);
name=holidayname(dt,count);
output;
end;
end;
format dt yymmdds10.;
keep dt name;
run;






















というわけです(画像は途中まで)。
ちなみにイベントデータは、週単位や月単位、あるいは幅を持って設定もできますし、
リファレンス見てるともっと色々できそうです。

これ、祝日とか判定する以外にもなんか使えそうじゃないですか?

特に、開始と終了日を与えてその間の祝日回数を数えるHOLIDAYCK関数とか応用できそうじゃないですか?

頭のいい人は何か使い道を編み出して発表してください


ハッシュオブジェクト感動物語(おまけ付き)

タイトル適当です。

たまたまSAS初心者の方が書かれている以下のブログ記事を読んで、感動しました。
ついに、ついに、mergeステートメントを覚えるより先にハッシュオブジェクトを覚えて、データ結合をハッシュオブジェクトでやる方が現れだしましたよ。

「SAS初心者のメモ」
hashの世界へようこそ①
http://sasbiginner.seesaa.net/article/442279733.html

ぜひこれからも頑張ってください(組織でハブられないようにほどほど気をつけて…)。

やっぱり新しい人がピュアな判断で良いと感じれる技術は、
ある程度確かな「正しさ」を持っていて、必ず定着化すると思うんですよね。

長くやってると自分のプログラミングスタイルで価値観が曇りがちじゃないですか。

ハッシュオブジェクトが一部のマニアなおっさん(自分を筆頭)にしか使われない裏技程度の位置づけになったら嫌だなぁ、スタンダードになるべき技術なのに…
と感じていたので、本当に感動しました。

別に「ハッシュは俺が育てた!」と言うつもりはないですが、もう何年もハッシュ使え~ハッシュ使え~と、わめいていた身としては感無量です。

ハッシュオブジェクトのメリットと仕事への具体的な導入法がわからないという方は一例として
以下の記事を読んでみてください。

「大半のソートは百害あって一利無しという話」
http://sas-tumesas.blogspot.jp/2016/04/blog-post.html

さて、ここからはおまけ
最近Luaに首ったけでしたが
たまにはハッシュオブジェクトの新しい話でもしましょうか。

9.4から追加されたdo_overメソッドは超イケてますよ。
なにせ、findメソッドとfind_nextメソッドを組み合わせて書いていたmultidataの処理が
一撃ですからね。find_nextメソッドの存在価値なくなった気がしますね

例えば以下の2つのデータセットがあって、

data A;
do x=1 to 5;
output;
end;
run;












data B;
x=1;y='A';val=1;output;
x=2;y='B';val=2;output;
x=2;y='C';val=3;output;
x=2;y='D';val=4;output;
x=2;y='E';val=5;output;
x=4;y='F';val=6;output;
x=4;y='G';val=7;output;
x=4;y='';val=.;output;
x=6;y='H';val=8;output;
x=6;y='I';val=9;output;
run;


















Bのデータセットをxの値でグループ化して、文字変数は「,」で連結、数値変数は
合計と平均をだしてAにくっつけるという処理を1ステップでかけと言われても

data C;
length x 8. z $100.;
if 0 then set B;
set A;
if _N_=1 then do;
declare hash h1(dataset:"B",multidata:"Y");
h1.definekey("x");
h1.definedata("y","val");
h1.definedone();
end;
call missing(count,total);
do while(h1.do_over() eq 0);
 z=catx(',',z,y);
 count+val ne .;
 total +val;
end;
mean = divide(total,count);
keep x z total mean;
run;



で終わりです。

いままでなら、

data D;
length x 8. z $100.;
if 0 then set B;
set A;
if _N_=1 then do;
declare hash h1(dataset:"B",multidata:"Y");
h1.definekey("x");
h1.definedata("y","val");
h1.definedone();
end;
call missing(count,total);
rc=h1.find()
do while( rc eq 0);
 z=catx(',',z,y);
 count+val ne .;
 total +val;
 rc=h1.find_next();
end;
mean = divide(total,count);
keep x z total mean;
run;


と書かなきゃならんような処理です。

それにしてもハッシュオブジェクトのコードはやっぱり良いですね。本当はDS2のハッシュの方が美しいですが、データステップのハッシュも、それでもやっぱり美しい。

この気持ちをできるだけ多く共有したいですね。
やっぱりコード書いて、読んで、美しい楽しいと感じれるのは
人生の充実に繋がると思うので、これからも普及させてたいですね(怪しい宗教みたいですね)



ちょっとした情報共有:明らかにFALSE(TRUE)なWHERE句です(9.4から)

かなりの小ネタというか、発見なのですが

data A;
set sashelp.class;
where 2 < 1;
run;

data B;
set sashelp.class;
where 2 > 1;
run;

のようにwhere句が、論理式が定数で作れていて、どうあがいてもTrueかFalseしかとりえない
場合、9.4から




って、感じでNOTEの中に/* */のコメントの形でアナウンスがでるようになったみたいですよ。

NOTEとかWARNINGとかのSASが生成するメッセージの中にコメントステートメント入ることって今までありましたっけ??

変な出方なんで、もし、会社でログチェック系のプログラムとかツールを管理してる人は一応正しく処理できるか気をつけてくださいね。

マクロ変数の結果などで意図的にそうなるように組む場合もなきにしもあらずなので(別データセットのobs)、チェック対象にするかどうかは各自の判断ですけど、一応注意喚起のため拾った方がいいメッセージかもですね。



解析対象集団とかのフロー図を一緒に書いてみよう!

たまには役に立つ話をしましょうか!!

各解析対象集団のフロー図は、いろんな業界で頻繁に書きますよね
でもだいたいの業界では、SASでは数字だけ出して図は別のソフトとかで書いちゃいます

ただ、医薬系は色々あって、手で数字の切り貼りはあんまりやらないし、
いろんなソフトを組み合わせるのも、ちょっとやりにくかったりします。

完全にSASでやれと言われると結構面倒ですよね。
出力がEXCELならまあ、エクセルの罫線でテンプレート書いといて、セルにDDEやらLIBNAMEで出せばいいかな。

RTFなら?
まあ、EXCELと同じようにWORDで表作って、そこにマクロ変数埋めてproc streamするとか、
proc reportでも頑張れば書けるらしいので、もし先人の作った偉大なマクロがあればそれを使いますね。
putでタグを頑張って表を作るのもありでしょう。

画像で作って欲しいと言われたら?
GPLOTとかSGPLOTで書くのか~。annotateいるのかな~、なんか面倒そうだな~って感じします。

みんなどうしてるんでしょうね

最近の感想としては、9.4からsgplotに一筆書きの感覚でどんな図形でも書き放題のpolygonステートメントができて、四角の枠が書きやすくなったことと(まあ、以前でも直線4つで箱書けばいいんですけどね。)、textステートメントが追加(以前はscatterのマーカーにテキストあててじゃないとできなかったはず)されたことで、エクセル芸を駆使するような特殊なものでなければsgplotで書くのが一番楽なんじゃないかなと思うようになりました

一気に書くと何やってるかわかりにくいので、一個ずつ手順を踏んで一緒に素敵なフロー図をつくましょう

まずは箱を一個作ってみましょう

/*1個目の箱*/
data hako1;
boxid=1;box_x=10;box_y=100;output;
boxid=1;box_x=30;box_y=100;output;
boxid=1;box_x=30;box_y=90;output;
boxid=1;box_x=10;box_y=90;output;
run;

title "Step1 箱を書いてみよう";
proc sgplot data=hako1 noborder noautolegend;
  polygon id=boxid x=box_x y=box_y;
  xaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
  yaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
run;



はい、適当な解説をいれます。
polygonステートメントは1個のをまとまりを作るためにid=でIDを指定する必要があります。
後はx軸の値をy軸の値で四角形になるように座標取りすればいいのですが注意点は
オブザベーションの並びです。四角形の左上→右上→右下→左下

次は箱にテキスト入れてみましょう

/*1個目のテキスト*/
data text1;
text="将棋経験者#N=100";text_x=20;text_y=95;output;
run;

data hakotext1;
set hako1 text1;
run;

title "Step2 文字を入れてみよう";
proc sgplot data=hakotext1 noborder noautolegend;
  polygon id=boxid x=box_x y=box_y;
  text x=text_x y=text_y text=text / textattrs=(size=12 )  splitchar='#' splitpolicy=splitalways;
  xaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
  yaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
run;




楽ですね~。座標と内容を書くだけですね。 split系のオプションを使うことで改行を制御できます

次は矢印ですね!これはseriesにarrow系オプションを併用することでできます

/*1個目の矢印*/
data yaji1;
yajiid=1;yaji_x=20;yaji_y=90;output;
yajiid=1;yaji_x=20;yaji_y=80;output;
run;

data hakotextyaji1;
set hako1 text1 yaji1;
run;

title "Step3 矢印を入れてみよう";
proc sgplot data=hakotextyaji1 noborder noautolegend;
  polygon id=boxid x=box_x y=box_y;
  text x=text_x y=text_y text=text / textattrs=(size=12 )  splitchar='#' splitpolicy=splitalways;
  series x=yaji_x y=yaji_y/ group=yajiid  lineattrs=(color=black) arrowheadpos=end  arrowheadscale=0.3;

  xaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
  yaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
run;



でけたでけた。

じゃあその調子で2段目に2個ブロックを作って、そこに矢印引いてみましょうか

/*2段目の箱*/
data hako2;
boxid=2;box_x=10;box_y=80;output;
boxid=2;box_x=30;box_y=80;output;
boxid=2;box_x=30;box_y=70;output;
boxid=2;box_x=10;box_y=70;output;

boxid=3;box_x=40;box_y=80;output;
boxid=3;box_x=60;box_y=80;output;
boxid=3;box_x=60;box_y=70;output;
boxid=3;box_x=40;box_y=70;output;
run;
/*2段目のテキスト*/
data text2;
text="振飛車党#N=50";text_x=20;text_y=75;output;
text="居飛車党#N=50";text_x=50;text_y=75;output;
run;

/*2段目の矢印★途中で折り曲げてるので3つで1データ*/
data yaji2;
yajiid=2;yaji_x=20;yaji_y=85;output;
yajiid=2;yaji_x=50;yaji_y=85;output;
yajiid=2;yaji_x=50;yaji_y=80;output;
run;

data hakotextyaji2;
set hako1 text1 yaji1 hako2 text2 yaji2;
run;

title "Step4 もう要領つかんだよね";
proc sgplot data=hakotextyaji2 noborder noautolegend;
  polygon id=boxid x=box_x y=box_y;
  text x=text_x y=text_y text=text / textattrs=(size=12 )  splitchar='#' splitpolicy=splitalways;
  series x=yaji_x y=yaji_y/ group=yajiid  lineattrs=(color=black) arrowheadpos=end  arrowheadscale=0.3;

  xaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
  yaxis display=none min=0 max=100 offsetmin=0 offsetmax=0;
run;



矢印をカギ形に曲げたりするのも普通に3点指定するだけなので、そんなに難しくないはず。

ここまで、できた人は、チャレンジで振飛車を中飛車、四間飛車、三間飛車、向かい飛車に分類するフロー図を書いてみましょう!!

僕は面倒なのでここまでで!

クロージャってなんじゃろ?

今日もまたSASマクロの悪口、Proc Lua導入のメリットを紹介する記事です。

今回はクロージャです。

ちなみにクロージャやコルーチン(いずれ紹介)などスコープ絡みの部分が、Luaのメリットのひとつであるということは、かなり初期の頃からfukiyaさんやmatsuさんが示唆されてました。僕はその時点では全く意味がわかってませんでした。本当尊敬です。

wikipediaでクロージャと調べると
「引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる」
う~ん、やっぱり、なんじゃろ??

closure → 直訳すると閉鎖、閉店ガラガラみたいな感じ クローズ。

この閉じてるというのがクロージャのポイントなんですね。

SASマクロで何か処理をして、その結果を保持して、次にそのマクロを実行するときに参照して処理したいとなったらどうします?

例えば、データセットをパラメータとして与えると、オブザベーション数をカウントして返す。呼び出されるたびにそれまでにカウントした合計に足していくマクロを作れといわれたらどうしますか?

以下のようにかけると思います。

%let tobs=0;

/*マクロ定義部分*/
%macro m(ds);
data _null_;
 call symputx('tobs',nobs+&tobs);
 if 0 then set &ds nobs=nobs;
 stop;
run;
%put 合計=  &tobs;
%mend;

/*実行部分*/
%m(sashelp.class)
%m(sashelp.iris)
%m(sashelp.cars)

結果は







何がいいたいかと言いますと、値の保持、受け渡しの手段としてグローバルマクロ変数
(&tobs)を作成して使ってますね。

SASの場合、データステップ内であればretainを使って値を保持できますが、ステップをまたぐ場合、データセットを作ってその中に入れるか、あるいはグローバルマクロに入れるかしか方法がありません。
(まあ、フォーマット作るとか外部ファイルにだすとかもあるかもしれませんが…)

SAS使いにとっては疑いのない当たり前の手段ですが、これは本当はちょっと怖いことやってるんですね。

値を受け渡す前に、意図せずマクロ変数の中身が変えられていたら?参照するデータセットが意図せず更新されたら?
マクロを呼び出すときに、参照するものが、本当に前回までの結果なのか保証できますか?

例えばプログラムをどんどん増築したり、複数のプログラムをincludeしまくる等した時、プログラム作成者が複数人になればグローバルマクロ変数の名前やデータセット名がかぶる事は珍しいことではないです。

値がオープンで、処理と結びついていないところで参照・更新できうるということが問題なわけです。

なんとなく話が見えてきたかと思いますが、それに対してのクロージャはというと

/*関数定義部分*/
proc lua;
 submit;
  function L(ds)
   local nobs= 0
   return function(ds)
    local dsid = sas.open(ds)
    nobs = nobs+sas.nobs(dsid)
    sas.close(dsid)
    return nobs
   end
  end
endsubmit;
quit;

/*実行部分*/
proc lua;
 submit;
  L1=L()
  print ("合計=",L1("sashelp.class"))
  print ("合計=",L1("sashelp.iris"))
  print ("合計=",L1("sashelp.cars"))
 endsubmit;
quit;

結果は同じ






ですが

proc lua;
 submit;
  print(nobs)
 endsubmit;
quit;

としても、




中身は空、つまり定義されていない。
nobsという変数は、Lの中でのみ存在・保持されていて、外からは触れられないんですね。

これにより、他の処理でnobsという変数名を使っても衝突することがなくなります。
完全に閉じてるわけです。

Lを代入して変数であり関数のL1(インスタンスみたいなもん)を作って実行しているわけですが、例えばL2 L3を作ってそれぞれ
独立してカウントすることもできます

proc lua;
submit;
 L2=L()
 L3=L()
 print ("合計=",L2("sashelp.class"))
 print ("合計=",L2("sashelp.iris"))
 print ("合計=",L3("sashelp.class"))
 print ("合計=",L2("sashelp.class"))
 print ("合計=",L3("sashelp.class"))
endsubmit;
quit;









さて、肝心の解説(たぶん正確ではないから詳しい人に聞いてね)

  function L(ds)
   local nobs= 0
   return function(ds)
    local dsid = sas.open(ds)
    nobs = nobs+sas.nobs(dsid)
    sas.close(dsid)
    return nobs
   end
  end
L1=L()

関数Lはそのローカルの範囲に変数nobsと戻り値として名前をもたない関数(return function(ds)の部分ね。無名関数という)をもってますよね。、
このとき戻り値(関数)には自分の上層にいるL(エンクロージャという)のローカル変数nobsも環境としてくっついているんですね。
しかし、L1に代入されたとき、戻り値の関数から見た変数nobsは、ローカル変数ではなく・グローバル変数でもないという謎の状態になります
ローカル変数ではないので、関数終了後に消えるという運命は適応されません。、かといってグローバルではないので他からアクセスもできません
関数実行時にのみ内側でしかアクセスできずに残る。このようにグローバルやローカルではなく、処理ブロック自体がスコープを閉じ込めているのをレキシカルスコープ(静的スコープ)といい、今回のnobsは「レキシカル変数」といわれるのです

小ネタ Proc fslistでテキストファイルの中身をすぐ確認できるよ

CSVつくったり、プログラムの自動生成とか、なんしかテキストファイルの類を出力した後、ちゃんと出力できてるかの確認ってどうやってます?

いちいちファイルを開いてもいいですが

proc fslist file="\XXXX.txt";
run;

fslistプロシジャを使うと、専用のfslistウインドウが開いてすぐに確認できますよ。
それだけですけど、意外と便利ですよ。

たぶん古いプロシジャ過ぎるのと、最近、固定長とか扱う人が減ったのであんまり知られてないのかも。

最近proc streamでプログラムの自動生成をトライ&エラーでやってるときとか楽に感じますね。

fslistウインドウが開いた状態でコマンドウインドウにnums または colsとかを打つことで、列・行位置に番号振ってくれるので、固定長ファイルを読み込む前のちょっとした確認とかにもよろしいですよ。

温故知新な感じでいいですね。sas on demandで使えないので画像なしですが、まあ試してください



別のオプション空間を作ってSASを実行する話。個人的には微妙な仕様だけども

sasのオプションはoptionsステートメントで定義できます。
これはいわゆるグローバルな定義で、一旦設定をいじると、それ以降ずっとそのオプションが
反映されます。

options missing = - ;
とすれば数字欠損値の表示がハイフンになり、
options obs=1;とすると、1オブザベーションしか読み込まれなくなります。

よくある失敗として、そのオプションを適用したい部分が終わっても、元に戻すoptionsステートメント
を書き忘れて、それ以降の処理に全部適用されちゃったってパターンがあります。

処理が終わったらかならず、

options missing = . obs=max;

などとして、戻してやらなければいけません

まあ、この程度のオプションならいいですが、非常に込み入った設定をごく一部の処理にだけ
適用したい場合、戻すのも面倒なんですね。

それをパパっとやりたい、そんなあなたに朗報!

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

data a;
x=.;output;
x=1;output;
x=2;output;
run;

proc lua restart;
 submit;
 sas.submit[[
 title "Luaのsas.submitの中①";
 options missing=- obs=1;
 proc print data=a;
 run;
 ]]
 endsubmit;
run;

 title "普通のsas";
 proc print data=a;
 run;

 proc lua;
 submit;
 sas.submit[[
 title "Luaのsas.submitの中②";
 proc print data=a;
 run;
 ]]
 endsubmit;
run;

 proc lua restart;
 submit;
 sas.submit[[
 title "restartしてリセットされたLuaのsas.submitの中";
 proc print data=a;
 run;
 ]]
 endsubmit;
run;











































と、luaのsas.submitの中で定義したオプションが
その中でのみ働いていることがわかります。

便利ですね。proc luaの使い道の一つかも知れません。


でも個人的には
う~ん、この仕様、正直いいんだか悪いんだか、ちょっと僕には微妙です。
ちょっとしたときにはいいけど、オプション空間が2つあるのは混乱を招くような気も…
ちなみにLuaのSAS.submit内のoptionは、最初に実行した際のグローバルの設定がベースになりますからね。

SAS.submit内でoptionを設定した場合と、Luaのsas.submit初回実行後にsasの方でoptionいじったときに初めて、環境がズレていくわけです。