理解不足 アドバイス欲しい 自分用メモ- ③DS2 sqlstmtパッケージでSQLを実行する

忘備録の記事
「DS2プロシジャのSQLEXEC関数でSQLを実行する。」
http://sas-boubi.blogspot.jp/2015/04/ds2sqlexecsql.html
にいまさらながら触発される

【忘備録のコード】

data DT1;
   A=1;
   output;
   output;
run;
*** DS2でSQLを実行する **********;
proc ds2 libs=work;
   data _NULL_;

      method run();
          dcl double rc A2;
          A2 = 2;
          rc = sqlexec( 'update DT1 set A=' || A2 );
          if rc^=0 then put '更新失敗しました';   
      end;
        
   enddata;
   run;
quit;

このような
ステップ内でのSQLの可変的実行に関しては、SQLSTMTパッケージを使っても可能で
SQLEXECとの違いとして、UPDATEやINSERTに限定されずにSQL文を実行できたり、
パラメータを渡せる(文字列合成でSQL文作ってSQLEXECするのも、ある意味パラメーターみたいなもんだけど)
ということを見たのでSQLSTMTパッケージで、どう書くのかしらと挑戦してみた。

で、できたのが以下です。一応動くし、結果は同じになります。
ただこれが正解の書き方なのかが自信がない。てか多分違う。

proc ds2 libs=work;
 data _null_;
  dcl double rc A2;
  dcl package sqlstmt s('update DT1 set A=?',[A2]);
method run();
A2=2;
rc=s.execute();
if rc^=0 then put '更新失敗しました';
end;
  enddata;
run;
quit;

しかしよくわからないのが、パラメータの渡し方。DT1の部分をパラメーター指定にするのがわからない?
できるのか?
パッケージ自体のパラメータに設定して、executeメソッド内で渡すように書くのかと思ったらそうでもない?
BINDPARAMETERメソッドとか使うのかと思ったらそういうわけでもない?

そもそもパッケージの仕組みの理解がやっぱよくわかってないんだね。

厚かましい話なんですが、SQLSTMTパッケージで何かコード書いてる人がいたら、参考にどっかにあげてくれると
勉強になってありがたいなぁって思いました。
DS2は海外でもまだそこまでコードがあがってないから苦労します。SAS Technical NewsでDS2特集とかやって欲しい

自分用メモ- ②DS2 サブセットifでlike使える

通常のデータステップではlike演算子はwhereでしか使用できないがDS2だとサブセットifで使える

ちょっとした仕様の違いですが、以下のようなコードが通るということです

data Q1;
X='abcde';output;
X='aefcd';output;
X='adcbe';output;
X='aaace';output;
X='aebcd';output;
X='fabcd';output;
run;

proc ds2 libs=work;
data bc(overwrite=yes)
      f(overwrite=yes);
   method run();
  set Q1;
      if X like '%ab%' then output bc;
      if X like 'f%' then output f;
   end;
enddata;
run;
quit;

へ~。

自分用メモ-DS2 ①libs= やmergeとか

最近DS2をいじくっています。これは自分用の覚書で、発見したことを脈絡なくあげていきます。(説明書読まずに使って覚えるタイプなので)
高確率で間違えるので、つっこんでください

ちゃんと勉強したい人はSAS忘備録の連載読んでください
http://sas-boubi.blogspot.jp/search/label/%5B%E2%96%BC%20DS2%E3%83%97%E3%83%AD%E3%82%B7%E3%82%B8%E3%83%A3%E5%85%A5%E9%96%80


①sas on demand特有なのかは知らないが最近のメンテナンスリリース後に
proc ds2;ではエラーになる proc ds2 libs=work;とどのライブラリの中で動かすのかを明示する必要があるらしい

以前は通ってたコードも以下みたいなエラーがでた。
 ERROR: BASE driver, invalid PRIMARYPATH value, /home/sasyamasasyama0/Data
 ERROR: BASE driver, cannot assign library/path
 ERROR: TKTS initialization failed.

② 次のコードのようにDS2でデータセット作成後に、data=を省略したprocを書くと

proc ds2 libs=work;
   data _DT1 (overwrite=yes);

       dcl char(10) VAR1;

       method run();
           dcl char(10) VAR2;
       end;

   enddata;
   run;
quit;

proc print;run;






とするとproc printがこける。つまりDS2で作成したデータセットは_LAST_に保持されないということのようです。sqlプロシジャと同じ感覚でいると痛い目見るらしい。

③9.4メンテナンスレベル3からmergeステートメントが実装されたが、amatsuさん指摘のとおり、多と1のマージ結果がデータステップのmergeステートメントと違う。

SAS忘備録 DS2プロシジャ入門6:データの結合
http://sas-boubi.blogspot.jp/2015/07/ds26.html

data A;
ID=1;x=1;y=2;output;
ID=1;x=1;y=2;output;
ID=2;x=1;y=2;output;
run;
title "データセットA";
proc print;run;

data B;
ID=1;x=2;z=1;output;
ID=2;x=3;z=2;output;
ID=3;x=4;z=3;output;
run;
title "データセットB";
proc print;run;

data M1;
    merge A B;
    by ID;
run;
title "元祖mergeステートメント";
proc print data=M1;run;

proc ds2 libs=work;
    data D1(overwrite=yes);
       method run();
          merge A B;
        by ID;
       end;
     enddata;
    run;
quit;
title "DS2のmergeステートメント";
proc print data=D1;run;










































1の方のみの変数の値の引き延ばしが起きないのか。まるで罠のような仕様だ。
ds2内でのマージはsqlのjoinでやるようにコーディングルール作った方がいいのでは?本末転倒だ。


循環小数の末尾の値が1上がる現象とDECIMALCONVオプションを使った浮動小数点精度向上による解決(ただし9.4)

浮動小数点まわりは常に面倒なことが多いですよね。
基本、ラウンドしとけってことなんでしょうけど、そうはいかない場合もありますよね。

なんで、そうなるのかって理由はよくわかってないんで、浮動小数点に詳しい方に聞いて欲しいですが
とりあえず、以下のコードと出力結果をみてください(ケツ跳ね現象と勝手に命名して、存在は把握してました)

data _null_;
x=28/3;put "28/3=" x;
x=19/3;put "19/3=" x;
x=76/27;put "76/27=" x;
format x best16.;
run;









例えば
 28÷3は9.3333333333333・・と3がずっと続くはずですが、putの結果、ケツが1跳ねて4になっています。
他の2つの結果も全て、小数点の最後が1増加しています。

この謎の尻上がりをラウンドせずに防ぐ方法はあるでしょうか?

実は9.4からDECIMALCONV=オプションというのが追加されました。

デフォルトはoptions decimalconv=compatible;となっていますが、こいつを


options decimalconv=stdieee;とすると、SASのリファレンスの説明によると

============================================================================
DECIMALCONV=システムオプションを STDIEEE に設定した場合、SAS では、IEEE
浮動小数点演算標準 754–2008 を使用して、10 進値の変換とフォーマットが行われま
す。 STDIEEE 引数を使用すると、浮動小数点数の精度と読みやすさが向上します。
場合によっては、より上位の桁を同じフィールド幅で表示することもできます。
w.d、Ew.および Dw.d を含むその他の出力形式も DECIMALCONV=設定の影響
を受ける場合がありますが、変更が最も目立つのは BESTw.出力形式の使用時です。
==============================================================================

まあ、正確に何言ってるかはよくわからんですけど、とにかく精度が増すってことはわかったよってことで

先ほどのコードにオプションをつけて

options decimalconv=stdieee;
data _null_;
x=28/3;put "28/3=" x;
x=19/3;put "19/3=" x;
x=76/27;put "76/27=" x;
format x  best16.;
run;

とすると

結果は








となりました。


すべてのケースにおいて、この方法で問題が回避できるかはわからないんですが、少なくとも悪くはないオプションじゃないかなぁと思いました(処理負荷あがるのかもしれんけど)

より詳しい方がいたら補足して欲しいな~と思いました。

以上



データステップ中に一時的に使って、自動的にドロップしたい変数の話

データステップ中に一時的に作成するけど、完成したデータセットからは落としたい変数の定義法について困っている方がいたので考えてみました。

ひとつは、一律、変数の接頭にXX_とかつけて、最後にdrop XX_: とかで一括ドロップしてもいいと思います。

もうひとつは、以下のようにテンポラリー配列で定義した配列に値を出し入れしたりする方法とかどうでしょう?

以下のコードは、意味のない処理ですが、色々やっても結局データセットにはxとyだけが残ります。

いかがでしょう?

data A1;
array temp{3} _temporary_;

x=1;

temp{1}=10;
temp{2}=15;
temp{3}=x;

x=temp{1}+1;
temp{3}=temp{3}+x;
y=temp{3}*temp{1}+x;

run;

毎月第2火曜日は卵が100円の日だからマスタ更新に流し込むデータセット作るときに 気をつけてねみたいなこと言われたらの話

昔、SASを覚え始めた頃に何年何月の第何週の何曜日を起点としてナントカみたいに何月何週目の何曜日という条件で日付を使って処理する複雑なコードを書いていて気が狂いそうになったことがありました。
病院にデータ入力の督促だか、なんか回収の管理だかそんな系で。

バッチでまわすので、日付を動的に判断する必要がありました。

どんな処理だったかもうあんまり覚えてないですが今ならnwkdom関数を使っていたと思います。

nwkdom関数は、以下のコードと結果を見てもらえたらすぐわかるように何年何月の第何週の何曜日というものを全てパラメータで与えてやると該当の日付を返してくれるという関数です。

data A1;
 /* 
  第一引数:第何週か
  第二引数:曜日指定
 1=日曜
   2=月曜   
 3=火曜
 4=水曜
 5=木曜
 6=金曜
 7=土曜
第三引数 :何月か
第四引数 :何年か
*/

/*第1週~5週の月曜日の日付*/
  do week= 1 to 5;
  x=nwkdom(week,2,2,2016);output;
  end;

/*第1週~5週の火曜日の日付*/
  do week= 1 to 5;
  x=nwkdom(week,3,2,2016);output;
  end;

  format x nldatew30.;
run;



















臨床試験分野でコード書いていると、ふつう投与日等からのDayXXで考えることが多いので便利さがピンとこないですが、他の分野だと、例えば第何週の何曜日はセールだから売り上げ予測がナントカとかいった処理があったりして結構役立つんですね。

注意点としては、ちょっと癖のある仕様で、例えば、先のコードにも入れましたが2016年2月の第5週の火曜日を指定した場合、2016年2月には第5週月曜日までしかないので、代わりに第4週の火曜日の日付が返ります。

つまり第一引数に5を指定した場合、5週目の指定曜日がなかった場合は4週の値、イコールその月の最後の指定曜日の日付が返るというわけです。

よく月の最後の何曜日にナントカの処理があるとかって場合は、5を指定しておけば必ず指定曜日の最終日付がとれるわけです。

4と5に同じ日付が戻ることにを意識の片隅においておかないと、冷凍食品4割引をさらに4割引したりして投売りみたいなことになりかねないので気をつけてくださいね

強さとはなにか。イロレーティングの話

最近教えて貰ったんですが、ドラゴンボールのフリーザって、最近の映画で復活して、体が金色になって戦闘力が1垓(京の上、100000000000000000000)になったそうです。
僕が知っているのは「私の戦闘力は53万です」ですげーっ!!て時代だったんで、そんなジンバブエドルみたいな単位になってるとは知りませんでした

しかし、スカウターみたいな便利なものがあればいいですが(よく爆発するけど)、強さといった複合的要因でなりたっているものをスコア化するのはなかなか難しいです。
また、普段からスカウター慣れしていないと算出された戦闘力の差が、どの程度ならどのくらい勝てるかいうことが実感できないです。
(正確な値だしているのに、ちっ壊れてやがる!グシャっていうシーンあった気もするけど、慣れすぎているが故の弊害でしょうね)


そういう実際の能力を数値化する方法と違うアプローチとして、測定対象の対戦結果データがある程度存在する場合、強さ(勝率と捉える)が正規分布に従うと仮定し、数値化した値の差から勝率が逆算できるように作られたイロレーティングというものがあります。

原理など詳しくはwikiや参考ページなど検索してみてください。

【wikipedia】
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%AD%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0

【ヘキサドライブ(ゲーム会社)公式ブログ】
http://hexadrive.sblo.jp/article/67433861.html


イロレーティングの肝となるのは以下三点。

・ゲームの結果は一方の勝ち、一方の負けのみとし、引き分けは考慮しない(0.5勝0.5敗と扱うものとする)。
・200点のレート差がある対局者間では、レートの高い側が約76パーセントの確率で勝利する。
・平均的な対局者のレートを1500とする。

特に2点目、レーティングの差から、仮に相手を見たこともなくても勝率が推定できるという
のがいいんです。

もともとチェスで運用されていたもので、今はネット将棋、囲碁など主たるサイトはこれを基本にそれぞれカスタマイズして使ってユーザーの実力を測り、対戦マッチング等に利用しています。

僕もネット将棋やるのですが、これが結構よくできてるなぁって実感します。
実際、後で自分の対戦成績と対戦相手のレーティングで集計してみると200差の相手には
3割勝ててないですし、レーティングで100あいてると、やっててもちょっと自分より強いなって感覚を持ちます

算出法はいたって簡単、何局か後でまとめて計算するのと都度計算するので多少ロジック代わりますが、

プレイヤー1のレーティングがrate1、プレーヤー2がrate2とすると
プレイヤー1が勝利する確率e1は以下で算出できます
e1 = 1/( 1 + 10 ** ((rate2-rate1)/400) );

そして、対局後のレーティングは,
勝利の場合S=1 敗北はS=0 引き分けはS=0.5として
対局後のレーティング = 現在のレーティング + 16 * ( S  - e1);

16は定数で、プロなら16、アマなら32にすると安定するといわれています

例えば以下のように現在のレーティングデータと
data Rating;
length name $10.; 
input name rate;
cards;
A 1500
B 1500
C 1500
D 1500
;
run;












時系列に並んだ対局結果のデータセットがあった場合
(1はプレイヤー1が勝者、2は2が勝者、3は引き分け)
data hosi;
length player1 player2 $10.;
input player1 player2 win;
winner=choosec(win,player1,player2,'引き分け');
cards;
A B 1
B A 2
D A 2
B C 1
C B 2
B D 1
D B 1
A C 1
A D 1
C D 3
D C 1
C A 2
D C 1
C D 2
B C 1
;
run;




























コードは

data cal;
if _N_=0 then set rating;
if _N_=1 then do;
declare hash h1(dataset:'rating',ordered:'Y');
h1.definekey('name');
h1.definedata('name','rate');
h1.definedone();
end;
set hosi end=eof;

if h1.check(key:player1) ne 0 then do;
h1.add(key:player1,data:player1,data:1500);
end;
if h1.check(key:player2) ne 0 then do;
h1.add(key:player2,data:player2,data:1500);
end;

h1.find(key:player1);
rate1=rate;
h1.find(key:player2);
rate2=rate;

e1 = 1/( 1 + 10 ** ((rate2-rate1)/400) );
e2 = 1/( 1 + 10 ** ((rate1-rate2)/400) );
rate1 = rate1 + 16*(ifn(win=1,1,ifn(win=3,0.5,0)) - e1);
rate2 = rate2 + 16*(ifn(win=2,1,ifn(win=3,0.5,0)) - e2);

h1.replace(key:player1,data:player1,data:rate1);
h1.replace(key:player2,data:player2,data:rate2);

h1.output(dataset:cats('rating',_N_));

run;

とすれば、1対局ごとのレーティングの状態をデータセット化できます
(わかりやすくするために1局ごとに作ってますが、普通はif eofで最後の
時だけratingのデータセットを吐けばいいです)

1局目、A対BでAが勝利した後のデータセットをみてみると、
このようにAが少しあがり、Bが少し下がります。












最終的に全対局後をみると以下のようになり











全勝のAと全敗のCでは100ほど開いています。
100だと勝率64%ぐらいです

ある程度データがたまらないと安定したレーティングは得られませんが
まあ、AとCが戦ったら6割5分Aが勝ちそうだというのは感覚的に納得できます

ちなみに式をみればわかりやすいですが、レーティング差が大きい相手との
対局ほど、移動するレーティングが大きくなります。
雑魚に負けた方はレーティングが大きく下がり、下克上した方は大きく上がります。
わかりやすいですね。

イロレーティングの改良法については色々研究されているみたいなので興味のある方はぜひ
勉強してみてください