DS2のパッケージにおけるコンストラクタの概念の話

まずDS2のpackageって何って方は忘備録の記事を読んでください。

・DS2プロシジャ入門8:ユーザー定義パッケージ
http://sas-boubi.blogspot.jp/2015/08/ds28.html

DS2のリファレンス(現時点では英語版しかないので辛い)で、packageのところを読んでいると
やたらConstructor(コンストラクタ)っていう単語がでてくる。純粋にSASしか経験してないと、前に紹介したオーバーロードとかもそうだけどオブジェクト指向の用語をいきなりマニュアルで出して、DS2でコードではこうなるからって説明されても、みんな困ると思う。

で、コンストラクタって何かって言うと
「オブジェクト指向の言語において、クラスからオブジェクトを作成した際に、
自動実行されるメソッドで、メンバ変数の初期化などについて行います。」みたいな説明になる。

それをDS2で考えるなら、pakkageからデータステップ内でinstance(インスタンス)を生成するタイミング、つまりは、declareでパッケージに参照名をつけて使えるようにした時点で先に自動実行されるメソッドだと考えればいいはず。

忘備録のサンプルにコンストラクタを加えてみると以下のような感じになる


*** パッケージの作成 ;
proc ds2 libs=work;
   package MYPACK / overwrite=yes;
   dcl double VAR1 ;
       method MYPACK() ;
          put 'NOTE:コンストラクタが実行されたんだよ';
 VAR1=1;
       end;


       method TEST () returns double;
          return VAR1+10;
       end;
       method TEST (double VAR1) returns double;
          return VAR1+10;
       end;

   endpackage;
   run;
quit;


*** パッケージを使う ;
proc ds2 libs=work;
   data _NULL_;
       dcl package MYPACK mp();
       method init();
           dcl double OUTVAR;
           OUTVAR = mp.TEST();
           put '******' OUTVAR '******';     
           OUTVAR = mp.TEST(100);
           put '******' OUTVAR '******';     
       end;
   enddata;
   run;
quit;


結果は以下。










パッケージの作成の部分で
       method MYPACK() ;
          put 'NOTE:コンストラクタが実行されたんだよ';
 VAR1=1;
       end;
     
というコードを追加している。
大事なのは、このメソッドの親になってるパッケージの名前もMYPACKだということ。
つまりパッケージと同じ名前のメソッドを中に入れれば、それがコンストラクタになるということ(のはず)
で、そのコンストラクタの中で、メッセージをputするのと、V1に1という初期値を代入している。

以下、前回紹介したオーバーロードの話で、引数がある場合とない場合、両方についてメソッドを定義する
【参考】
★SASマクロに比べてDS2のメソッドが優れている点★の一つ、オーバーロード(多重定義)が可能なこと
http://sas-tumesas.blogspot.jp/2016/03/sasds2.html

       method TEST () returns double;
          return VAR1+10;
       end;
       method TEST (double VAR1) returns double;
          return VAR1+10;
       end;
     
メソッドにパラメータがあれば+10するし、なければ、その時点のVAR1に+10した値を返すということです。


で、パッケージを使う部分で最初にdcl package MYPACK mp();(これがインスタンスを作るってこと)でコンストラクタが動いて、V1に1がセットされます。
だから、最初のOUTVAR = mp.TEST(); で返ってくる値が11なんですね。

あんまり、うまい説明ではないですが、何か間違ってるところがあれば教えてください。

使い方を見てると、パッケージの構成として、メインの処理を実行するメソッドと、それに必要なパラメータをセットするメソッドを分けて設定しているものが多く、コンストラクタではそのパラメータの初期値をいれているという使い方が一般的のようです。
(次の記事もパッケージ絡みの予定なので、またそこで例示します)

まだ何とも言えませんが、多分、集団開発においても、全員がDS2をきちんと理解さえしていればパッケージを共有して、うまく管理できればマクロより作業効率あがる気がします。
SASマクロは複雑になってくると、展開形を頭の中で描くのがかなり困難になりますが、DS2の場合はトレースしやすい感じがします。ただ、全員がDS2を理解するっていうのがとても大変そう。


あと、少し文句を言わせていただくと、DS2プロシジャのエラーメッセージがわかりにくすぎです。なんでそのコードが駄目なのか
さっぱりわからない。もうちょっと優しく指南して欲しい




DS2のifステートメントの話

DS2のifステートメントが、通常のデータステップでの用法に加えてさらに柔軟な書き方ができるようなので自分用メモ。

proc ds2 libs=work;
data _null_;
declare double a b c;
declare char(10) d;

method init();

/*例1*/
a=1;
b=2;
c = b + (if a < 2 then + a else - a );
put ' ①-1 ' c=;

a=3;
b=2;
c = b + (if a < 2 then + a else - a );
put ' ①-2 ' c=;

/*例2*/
a=4;
b=2;
c= if b ne 0 then a / b else null;
put ' ②-1 ' c=;

a=4;
b=0;
c= if b ne 0 then a / b else null;
put ' ②-2 ' c=;


/*例3*/
a=3;
d = if . < a <= 2 then 'C'
else if 2< a <=3 then 'B'
else if 3 < a then 'A'
else NULL
;

put ' ③-1 ' d=;

a=4;
d = if . < a <= 2 then 'C'
else if 2< a <=3 then 'B'
else if 3 < a then 'A'
else NULL
;
put ' ③-2 ' d=;

/*例4*/
a=1;
b=2;
if a=1 then
if b=2 then put 'メッセージA';
else put 'メッセージB';
else put 'メッセージC';

end;
enddata;
run;
quit;

















例1 c = b + (if a < 2 then + a else - a );

は今までのSASだと
if a < 2 then c= b + a;
else c = b -a;

例2 意味は例1と同じだけど0除算防止はよく使う

例3 これも使えそう、

例4 then doなくていいんだ。すごく違和感だけど通る

注意点として1~3共通だけど、変数=if文って表現やるときは
かならずelseステートメントつけて、文法上でパターンを網羅した形に
しとかないとエラーになるみたい(実際にelseを通ることがありえなかったとしても)


ちなみにselect whenでも同じようにかける
例1なら
c = b + select when(a<2) + a otherwise - a end;
みたいに書ける。

ちなみにちなみにelse ifはelifと書ける。

今回の話とはあんまり関係ないですが
なんかDS2って最早、他のオブジェクト指向の言語で使う表現がすっと通って、旧来のデータステップの表現だとはじかれるイメージ(他言語知らないくせに言うのもアレだけど)があります

他の言語習得してからSASやるのにDS2を介すといいかも。あるいはSASから他言語学ぶのも
DS2をちらっとやってからだとスムーズなのかも。

もしかしたらDS2が普及すると、他言語からの学習コストが一気に下がり、ユーザーも増えて
SASプログラマーの特殊な立ち位置というか、ある種の希少価値や既得権益(笑)がなくなるのかもしれませんね。
若い人とかに「あのオッサン、未だに全部古いデータステップで書いてて、勘弁して欲しいよ」とか言われたらどうしよ。

あんまり宣伝しない方がいいか(笑)

★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でやるようにコーディングルール作った方がいいのでは?本末転倒だ。