★SASマクロに比べてDS2のメソッドが優れている点★の一つ、オーバーロード(多重定義)が可能なこと

良くも悪くもSASはプログラミング言語としてシンプルかつ、かなりトラディッショナル(上品な言い方にしてます)なとこがあります。
SASに慣れた人からするとSASマクロの仕組みにそれほど疑問は抱かないかもしれないですが、モダンな他言語をやっている人から見ると結構、えぇ~ってとこがあるみたいです。

そのひとつが今回紹介するオーバーロードという機能です。
簡単にいうと、引数の数が違ったり、型は違うんだけど、処理の内容は似ている機能を
同名で多重に定義してまとめることができるということです。
それによって呼び出された際に引数に応じて適切な処理が行われるので、呼び出し側で余計なこと考えなくていいし、似た名前の機能が乱立しない、条件分岐ではなく独立しているので管理しやすい等のメリットがあります

SASマクロは、同じ名前で定義するとフツーに上書きされてしまうので、マクロ処理の中に引数の数や型を判定する分岐を入れるか、別マクロを増やすしかないんですね。極端な例を見てみましょう。

ある部署では、2つの数をかけるという処理が多かったので、以下のようなマクロを作っていました

%macro v1(p1,p2);
 &p1 * &p2;
%mend v1;

次のように使っています。

data a;
x=%v1(3,2)
run;

ところが、ある時に3つの数をかける処理もでてきました。

ここで選択肢は2つあります。

①今まで使っていた%v1の内容を以下のように変える

%macro v1(p1,p2,p3);
%if &p3 ne %str() %then &p1 * &p2 * &p3;
%else &p1 * &p2;
%mend v1;

上記でOKですが、他のメンバーから、色んなところで使っている%v1マクロの中身を変えたら、今まで正常に動いていた箇所もチェックする必要があって怖いという意見がでます。

じゃあ、②新しいマクロを増やす

%macro v2(p1,p2,p3);
 &p1 * &p2 * &p3;
%mend v2;

を作って引数3の時はv2を使おうとなりました。

ところがしばらくすると、引数4の場合もでてきました。
さらに引数が文字のケースも増えてきました。さらに.....
といった度に、分岐を増やすか、マクロを新規にたてるかとやっているうちに、パラメーターの化け物かっていうマクロが生まれたり、共有マクロライブラリの中身が似たような名前のマクロでカオスにっていうのはよくある話ですよね。


これをDS2で考えて見ましょう。マクロカタログはパッケージに、マクロはメソッドになるとイメージしてください。

data Q1;
a=2;
b=3;
c=4;
d='1';
e='9';
run;

というテストデータがあります。

そして以下のコードでpackという名前のパッケージの中に、vという名前のメソッドを3ついれています。これが今までの%macro ~%mendで定義していたような箇所にあたると考えておいてください。
今はパッケージをworkにつくっているので、SASを閉じれば消えます。消したくなければ永久ライブリに作ってください。

proc ds2 libs=work;
package pack/overwrite=yes;

method v(double p1, double p2) returns double;
return p1 * p2 ;
end;

method v(double p1,  double p2,  double p3) returns double;
return p1 * p2 * p3 ;
end;

method v(char p1,  char p2) returns double;
return inputn(p1,'best.') * inputn(p2,'best.');
end;

endpackage;
run;
quit;

で注目は同じ名前でvを3つ作ってますがv( )の括弧内の部分、ここが引数の設定なのですが、そこがそれぞれ違います。
1つ目は引数が二つで、数値の場合
2つ目は引数が三つで、数値の場合
3つ目は引数が二つで、文字の場合
です

さて、では実際、定義したメソッドを呼び出して使ってみましょう

proc ds2 libs=work;
data A1/overwrite=yes;
declare double x1 x2 x3;
declare package pack p();
keep x1 x2 x3;
method run();
set Q1;
x1=p.v(a,b);
x2=p.v(a,b,c);
x3=p.v(d,e);
end;
enddata;
run;
quit;

結果は






となって、引数ばらばらなのに全部vメソッドで処理できました。ということです。

もし、エラーが起きたら、どの引数パターンの時に起きたかをたどれば、どのメソッドに問題があるかわかるのでデバックも楽ですし、同名で追加するだけなので、今までの正常に動いているメソッドに触る必要もありません。

どうです?そろそろDS2、気になってきませんか?

★DS2導入のわかりやすいメリット★-マルチスレッド処理が超簡単、でかいデータの処理やシュミレーションとかに良し!thread

趣味で書いたり、完全に一人で仕事してる人を除いて、組織の中でSASを書くというのは常に何かしらルールに縛られるものですよね。

コードは共有物であったりするわけで、やれハッシュオブジェクトやFCMPやSQLやと様々なやり方を組み合わせてコードを書くと仮にそれが処理上の最善手であったとしても「おい、他のメンバーが読めないもの書くな」ってなるわけです。当然です。

新しい書き方を導入するには、何故それを使うべきなのか、メリットデメリット、学習コスト等を偉い人に説明して、OKもらったら、メンバーにも説明しなきゃならないわけですね。

DS2プロシジャは、個人的には中々イケてる仕組みだと思いますが、メリットをうまく説明して導入にOK貰うのが大変そうだな~って思いました。学習にかかる時間はやっぱりそれなりに大きそうですからね。
で、メリットを説明するのに、HadoopがとかJSONがとかって言っても、うちそんなん使ってないからとか、それなに?で終わる上司もいる気がするし、FedSQLが便利なんですといったって、パススルーでsql飛ばせるだろ、libnameエンジンでいけるだろうと言ってきそうだし、パッケージだメソッドだオブジェクトだと言ったところで、何それ?全部SASマクロで頑張れるだろうって言われちゃうかもなわけです。

まあ、それはそれでいいんですけどね。こっちも、好き勝手DS2で書けたら楽しそうだな~っていう程度の気持ちで薦めてて、細かい理由は後付けだったりするんで。

で、僕が思うのは、そういうごり押ししたい時の簡単な説明として、難しい話はせず、ポイントも1点に絞ってみるのはどうでしょう。以下の感じです。

マルチスレッド処理が簡単に書けて、処理時間がすげー短くなって、効率いいっすよ。
マルチスレッドがわからない?ひとつの処理を後ろでいくつかに分けて同時並行で処理して後で統合するんですわ。シュミレーションとかもいっぱい回せますよ!っていう切り口はどうでしょう?

なんでかというとSPD Serverとか使ってない限り、SASでマルチスレッドの処理書くのってすげー大変なんですね(対応してるプロシジャ除く)。そして、古い人ほどそれをよく知ってるからです。


threadはあくまでDS2でできることの一側面に過ぎないし、それだけでブレイクスルーになるか?
っていう意見はわかりますが、まあいいじゃないですか。入れてしまえばこっちの勝ち?なんだから。

あ、ちなみに全部冗談ですからね。

さて本題、以下のデータセットがあるとします。

data A;
array ar{100};
do i= 1 to 10000000;
do j = 1 to 100;
ar{j}=rand('uniform');
end;
output;
drop i j;
end;
run;

100万obsの100変数です

普通のSASの書き方で、セットします。

data A1;
set A end=eof;
count+1;
if eof then put count "obs読み込みました";
drop count;
run;

ログで処理時間を見てみます。


















んん?結構かかってますね。まあSAS on Demandじゃなくて製品版ならもっと早いはずですけどね。

同じことを普通にDS2で書いて見ます

proc ds2 libs=work;
data A2/overwrite=yes;
   dcl double count;
   drop count;
   method run();
set A;
count +1;
   end;
   method term();
    put count 'obs読み込みました';
   end;
enddata;
run;
quit;

ログで処理時間を見てみます。




















すでにだいぶ早いなぁ、海外のPaperとか見る限り、シングルの処理で書いたら通常ステップと同じくらいのはずなんでけどなぁ。
まあいいや。

次にマルチスレッドで処理するために、一旦処理をthreadというもので定義します。
これはpakageと同じ概念で、一旦workや永久ライブラリに処理内容をデータ化したものをおきます。

proc ds2 libs=work;
/*スレッド処理定義部分*/
thread th/overwrite=yes;
   dcl double count;
   drop count;
   method run();
set A;
count +1;
   end;
   method term();
      put 'スレッド番号' _threadid_ 'が' count 'obs読み込みました';
   end;
endthread;
run;
quit;

上記の実行は0.0何秒で終わります。実際に処理をしているわけじゃなく、処理内容を定義しただけだからです。

そして以下のコードで上で定義したthreadを呼び出して展開します

proc ds2 libs=work;
/*スレッド処理の呼び出し*/
data A3(overwrite=yes);
   dcl thread th t;
   method run();
   set from t threads=3;
   end;
enddata;
run;
quit;

ここで注目はthreads=3のところで、ここで実際に分割する数を指定して見ます。

で結果、






















「スレッド番号〇がXXXXobs読み込みました」

に注目です。全部で100万の処理を、SASが適当に分けてやったことが確認できます。
(この分け方はSASが適当に決めるので毎回変わります)

処理時間も18秒が11秒なので、4割近く減ってます。ただのsetですらね。

ただ、ちょっと注意なのはSAS on demandはブラウザ経由でどっかのサーバーで回してるから
実行する時によって結構時間にムラがあります。
うまく差が出た時のスクショとっておきましょう。逆転することがざらにあるので、、。
あと調子悪いと実行終わらなかったりするので、その時は10万くらいでやってみましょう。
まあ、本来こういうでかい処理はon demandでやるもんじゃないよね。

コードの説明を少しだけ

スレッド処理定義部分の
thread th/overwrite=yes;
は、以下の処理をthというスレッド処理としてworkに保存しますよ、もう一回実行したら
上書きしますよって意味です。
_threadid_ は、自動変数で、実際実行された時、のスレッド番号が0から振られます

スレッド処理の呼び出しの方で  dcl thread th t; でthにtという名前を振って定義しています
そしてスレッドを使ってデータを取得する場合 set from として、次に定義した t で threads=3;です

かなり簡単じゃないですか?
今回はただのsetですが、ds2がわかっていれば基本どんな処理でもかけるので、大規模データ処理やシュミレーションに持って来いです。

まあ、マルチスレッドを活かすにはやっぱりハード性能もそこそこ要りますが、そこは後でこっそり言っておきましょう。

理解不足 アドバイス欲しい 自分用メモ- ③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;

とすると

結果は








となりました。


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

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

以上