ラベル intervalds の投稿を表示しています。 すべての投稿を表示
ラベル intervalds の投稿を表示しています。 すべての投稿を表示

intnx関数とintervaldsオプションである日付の前後に発生するイベント日を取得する

例えば、何かの祝日で、普段であれば平日の曜日に休みがあったりすると、この休みが終わったら次の祝日はなんだっけ?来月は祝日あったっけ?とか考えたりすることってあると思います。

data Q1;
X=input('2014/03/05',yymmdd10.);output;
X=input('2014/04/29',yymmdd10.);output;
X=input('2014/07/14',yymmdd10.);output;
format X: NLDATEW30.;
run;







のように、ある日付が与えられた時に、その日から見て、直前にあった祝日と、直後にある祝日の日付を取得せよという問題があったとします。

まず、米国の祝日であればholiday関数とかでeaster = holiday('easter', 2014);とかすれば、その年の祝日の日付を取得できますが、日本の祝日には対応してません。

fcmpプロシジャやマクロで日本用のholiday関数を誰かが作ってくれれば有り難いのですが、今回は手抜きで

data EVENT;
begin=input('2014年01月13日 月曜日',NLDATEW30.);output;
begin=input('2014年02月11日 火曜日',NLDATEW30.);output;
begin=input('2014年03月21日 金曜日',NLDATEW30.);output;
begin=input('2014年04月29日 火曜日',NLDATEW30.);output;
begin=input('2014年05月05日 月曜日',NLDATEW30.);output;
begin=input('2014年07月21日 月曜日',NLDATEW30.);output;
begin=input('2014年09月15日 月曜日',NLDATEW30.);output;
begin=input('2014年09月23日 火曜日',NLDATEW30.);output;
begin=input('2014年10月13日 月曜日',NLDATEW30.);output;
begin=input('2014年11月03日 月曜日',NLDATEW30.);output;
begin=input('2014年11月24日 月曜日',NLDATEW30.);output;
begin=input('2014年12月23日 火曜日',NLDATEW30.);output;
format begin NLDATEW30.;
run;
















のように2014年において、月曜日から金曜日が祝日または振替で休みとなる日付がデータとして与えられたとしましょう。


そうすると以下の様なプログラムが成立します。(後でコメントみてください!)

options intervalds=(event=EVENT);
data A1;
 set Q1;
 NEXT_HOLI=intnx('event',X,1);
 PRE_HOLI=intnx('event',X-1,-1,'end')+1;
 format NEXT_HOLI PRE_HOLI NLDATEW30.;
run;






おそらくintervaldsオプションについて書いた以前の記事と

intnx関数の記事をみていただければ


理屈がわかるかと思います。

ただ、直前の祝日といったように逆向きに負の値でまたぎをとるのはちょっと頭を悩まします。
上記の書き方で本当に正解なのか?あるいは最善の書き方なのかはちょっと自信ないです。
どなたかご教示ください。

なんで上記のような書き方にしているかについては、'end'を外したらり、-1をとったり、+1をとったりして試して、実感してください。

追記>>コメントに正解の書き方をいただいたので、見てください!!



(以下、いつもの、誰も喜ばないおまけ)

さて、電王戦タッグマッチはSAS忘備録のa matsuさんと見に行くことになりましたが、他にも会場にいらっしゃるSASユーザーの方がいればぜひ教えてください。

あぁ、それにしても昨日の王位戦は、、。木村一基八段を応援していたので、辛かったです。
羽生さんはもう、ほんと強いですね、人間とは思えない。

木村一基を知らないという方は「木村一基 ぼやき」とかで、動画を見ていただければ、その人間元的魅力の一端がわかるんじゃないかと思います。

木村先生は名言が多いのですが
「負けと知りつつ、目を覆うような手を指して頑張ることは結構辛く、抵抗がある。でも、その気持ちをなくしてしまったら、きっと坂道を転げ落ちるかのように、転落していくんだろう。」(将棋世界2007年5月号)
なんかは、自分的に不本意な、美しくないと自覚しているプログラムを書いている時に、気持ちを奮いたたせるためによく思い浮かんだりします。


与えられた区間中に、イベントが何回発生しているか。イベントのデータセットをintervaldsオプションで読み込んでユーザー定義間隔を作成してintck関数で使用する方法

例えば以下のようなデータがあるとします。

data EVENT;
begin=input('2014/01/05',yymmdd10.);output;
begin=input('2014/01/31',yymmdd10.);output;
begin=input('2014/02/01',yymmdd10.);output;
begin=input('2014/02/08',yymmdd10.);output;
begin=input('2014/02/25',yymmdd10.);output;
begin=input('2014/03/01',yymmdd10.);output;
begin=input('2014/03/05',yymmdd10.);output;
begin=input('2014/03/10',yymmdd10.);output;
begin=input('2014/03/14',yymmdd10.);output;
begin=input('2014/03/18',yymmdd10.);output;
format begin yymmdds10.;
run;














これはそうですね、例えば、ある会社と取引のあった日の履歴のデータだとします。


そして次に以下のデータセットを見てください。

data Q1;
ST=input('2014/01/01',yymmdd10.);EN=input('2014/01/15',yymmdd10.);output;
ST=input('2014/01/16',yymmdd10.);EN=input('2014/01/31',yymmdd10.);output;
ST=input('2014/02/01',yymmdd10.);EN=input('2014/02/15',yymmdd10.);output;
ST=input('2014/02/16',yymmdd10.);EN=input('2014/02/28',yymmdd10.);output;
ST=input('2014/03/01',yymmdd10.);EN=input('2014/03/15',yymmdd10.);output;
ST=input('2014/03/16',yymmdd10.);EN=input('2014/04/01',yymmdd10.);output;
format ST EN yymmdds10.;
run;










これは集計する対象区間を表しています。
この企業では、毎月、1日から15日まで、15日から月末までで区切って取引件数を集計している
としましょう。

さてQ1の各期間(STからEN)までの間に、データセットEVENTの日付がそれぞれ何回あるかをカウントするという問題です。

先に、求めるべきデータセットは










のような感じです。


さてさて、皆さんなら、どのようにして切り込みますか?

merge? transpose? sql? hash? array? formatとmeans?

多分、方法はたくさんあると思いますが、今回はintervaldsオプションとintck関数の
コラボで頑張ってみます。

で、早速コードなのですが、驚かないでください。


options intervalds=(event=work.EVENT);
data A1;
 set Q1;
 COUNT=intck('event',ST-1,EN);
 format ST EN yymmdds10.;
run;

はい、なんとこれだけです。

これでさっきの求めるべきデータセットになります。

結構凄くないですか!

でも実はちょっとやりやすいように問題を作ってますので、そこも含めて説明します。


まずはintck関数を知らない場合、まずググってください。ちょっと説明が難しい関数です。

2つの日付(日時)データから、間隔を出すのですが、それが単純に日数差からの計算ではなく、例えば月の初日の開始時点を超えた回数、1月1日の開始時点を超えた回数で、間隔が何ヶ月だとか何年だとかって出します。

つまり、2007年12月31日と2008年1月1日をintck関数でyearを指定してだすと、1って出てきます。
2008年1月1日と2008年12月31日なら0です。
日数差で考えるとかたや1日、かたや364日ですが、重要なのはチェックポイントをまたいだかどうかなのです。

でoptions intervalds=(event=work.EVENT);はそのintck関数に使用するチェックポイントを自分で作成することができる方法なのです。

eventがユーザーがつける任意の名前で、あとでintck関数で指定します。
=でそのチェックポイント日時の入ったデータセットを指定します。

重要なのはここで起点日の変数名を必ず「begin」にしておくことです。なってないならrenameしましょう。
これが定義に必要な決まり文句、予約語になっています。
(ちなみにendという変数もつけれて、チェックポイントに幅をもたせれるのですが、またこんど)

もう一つ超重要なのが、beginの日付は必ず昇順になってないといけません!重複や、endを使う場合、区間の重なりも絶対ダメです。
エラーはでないけど、カウントがめちゃくちゃになるので、マジで危険です。
今回はソートしてませんが、必ずソートしておくことをお勧めします。

でCOUNT=intck('event',ST-1,EN); の部分です。
'event'の部分で、ユーザーが作成した任意の定義名を指定します。

ST-1としているのは、こうしとかないと、同日の場合にまたいだと見なされず、カウントされないからです。

どうでしょうか?このブログにしては割りと実用的な話だっと思います。

ただ、グループごとに処理する場合は使いにくいです。

例えば、臨床試験データなどで、被験者ごとにある注目している区間内に発生した、注目事象のカウントといったケースに適応しようとすると、被験者ごとにユーザー定義チェックポイントを作って、
被験者ごとに関数で指定しなきゃいけないわけです。
少数ならいいですが、データ数が多い場合は厳しいでしょう。処理時間も結構かかります。

またもう一つ、重要なのが、今回は強烈な前提条件として同日に複数レコード発生がないというものを置いています。通常の集計と違って、この方法は、チェックポイントのまたぎをカウントするので、同日に複数たっているとお手上げです。1件としてカウントします。それでいいなら逆に使えますが。