proc Luaの世界③SASコードを実行する話 四六時中遊べる異常な面白さ間違いなし!!

いよいよproc Luaの中でSASコードを実行する話です。多分これも後にさらに掘り下げて
記事を書くと思いますが、とりあえず触りです。

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

proc lua;
 submit;
  --sas.set_quiet(true) 実行されるコードをログにださない場合:マクロのnomprintみたいなもん
  sas.submit([[data a;x=1;run;]])
endsubmit;
quit;








こんな感じです、ログに実行されたSASコードがでて、実行されます。
マクロと違ってデフォルトでは、実行された実際のコードがログに出力される設定sas.set_quiet(false )になってます。
nomprintのように実行コードをログにだしたくない場合はsas.set_quiet(true )としましょう。

sas.submit([[ SASコード ]])が基本の書き方になります。sas.submtのタイミングで記述したSASコードがsubmitされます。

次に以下のコードをみてください。

proc lua;
 submit;
 local code=[[data a;x=1;run;]]
 sas.submit(code)
endsubmit;
quit;

結果は同じです。つまり一旦変数の中にSASコードを格納し、それをsas.submitで指定することでも同じことになります。

Luaのテーブルは柔軟なので、例えば再利用性の高い処理であれば
カテゴリでまとめたりしてテーブルにいれて、

proc lua ;
 submit;
 makeds={} --グローバルでテーブルmakedsを作って、その中にコードを入れている
 makeds.mds1=[[data ds1;x=1;run;]]
 makeds.mds2=[[data ds2;x=1;run;]]
 makeds.mds3=[[data ds3;x=1;run;]]
endsubmit;
quit;

以下のように使いたい時に取り出して実行することができます。

proc lua ;
 submit;
 sas.submit(makeds.mds2) --makdedsテーブル内のmds2のコードが実行される
endsubmit;
quit;

以前の記事で、グローバルで定義したものはnilを代入しないと消えないといった話をしたのですが
あれはどうやら、ちょっと間違いです。

proc luaの後にrestartと記述すると一旦、Luaの環境をリセットし、グローバル定義も全て初期化されます。
つまり、以下のコードを実行すると

proc lua restart;
--[[restartは記述したproc lua開始時点で一旦Luaの環境をリセットしてメモリ上に滞留してるグローバル変数などを全部殺すよ]]--
--terminateは記述したproc luaが終わった時点でリセットするよ
 submit;
 sas.submit(makeds.mds2) --makdedsテーブル内のmds2のコードが実行される
endsubmit;
quit;

makedsはnilに初期化されているので参照エラーになります

ちなみにproc lua terminateとすると、そのprocが終わるまでは環境が保持されてますが、終わる時点でリセットがかかります。restartとのタイミングの違いに注意しましょう。

つづいて、マクロパラメータのように、可変的部分を実行時に与えて実行する方法について紹介します。

以下のコードを実行すると
proc lua;
 submit;
 --置換(可変)部分を@で括って定義できる
 local code=[[data @ds@;
              @var@=1;
             run;]]
 --実行時に置換パラメータを指定して実行する
 sas.submit(code,{ds="b",var="y"})
endsubmit;
quit;











となります。
実行時に置換される部分を@置換変数@と書いて定義しておき、実行時に
sas.submit([[SASコード]] ,{置換変数1= , 置換変数2= ,,,, })として実行します。

さて、次に話です。call executeのように、実行するSASコードを刻んでいきたい場合、つまり
sas.submit_([[data c;x=1;run;]])を、

sas.submit_([[data c;)]]
sas.submit_([[x=1;)]]
sas.submit_([[run;)]]
としたいな~って思った場合、そのままだとエラーになります。
なぜかというと、submitすると、そこを部分実行したのと同じで
x=1;というのだけ実行しても、それはエラーですよね。
ちなみに、data c;の部分は暗黙的runが補完されたような形になって一応動きますけど。

さて、ではどうすればいいのかという話ですが、
subimit_と、アンダースコアをつけるのです。
subimit_で実行されたコードは、submitとアンダースコア無しのsubmitが実行されるまでは
ストックされた状態で、待機したような形になります。

proc lua;
submit;
 sas.submit_([[data c;]]) --submit_とアンダースコアがついてることに注意!
 sas.submit_([[x=1;]])    --アンスコなしのsubmitが実行されるまでアイドリング状態になる
 sas.submit([[run;]])     --ここで一気に実行される
endsubmit;
quit;

と書けるということです。

これが、何を意味するのかというと、Luaで実行するSASコードを可変的に
生成する場合、全文コードを生成してからsubmitしなくても純粋に可変部分のみ
で済むということ、すなわち!

proc lua;
submit;
 sas.submit_([[data d;]])
 for i = 1,4 do
  sas.submit_([[x=@val@;output;]],{val=i})
 end
sas.submit([[run;]])
endsubmit;
quit;

というコードが成立し、
ログに出ている実際に実行されたコードをみると以下のようになっています









まだまだ紹介したいことはあるんですが、とりあえずここまで!

面白い。僕は心から面白いと感じるんですけど、この感動、情動をどうやったらうまく表現できるのか。どうすれば流行らせられるか、次世代のスタンダードな知識にできるのか、いいアイデアがあれば是非教えて下さい



Proc Luaの世界②-自由奔放なLuaのテーブルの話

さて、Luaの文法的な話ばかりだと面白くないので、今回の記事で紹介する機能を使ってできる小技を先に紹介します。

他の方のブログでも何度か言及されていますがSASマクロの%doループはちょっと使いにくいところがあります。

[データ解析備忘録]
アルファベットや飛び飛びの値でもループしたい

たとえばSASHELPライブラリにあるCLASS FISH CARSの3つのデータセットをループ処理で
順番にproc printにかけたいなと思えば

%macro mloop;
 %let loopval=CLASS FISH CARS;
 %do i=1 %to %sysfunc(countw(&loopval));
  %let target=%scan(&loopval,&i);
      proc print data=sashelp.⌖run;
 %end;
%mend;
%mloop;

とかけます。
はい、わかりにくいですね(別にSASマクロをディスってるわけじゃないですよ!)

proc luaの場合、色々と書けるんですが、とっつきやすい1パターン紹介です。

proc lua;
submit;
local target = {'CLASS','FISH','CARS'}
for i, item in ipairs(target) do
sas.submit("proc print data=sashelp."..item..";run")
end
endsubmit;
run;

と、これでOKです。うん、発想的にも見かけ的にもcall executeと同じなので、何やってるかはわかりますね。

上のように実行コードが単純な1行程度なら文字列連結でもいいですけど、複雑になってくると
手に負えないので、キーワードパラメータもちのマクロのように、パラメータを分離して以下のように書くほうが綺麗かな

proc lua;
submit;
local target = {'CLASS','FISH','CARS'}
local code =
[[
proc print data=sashelp.@dataset@;
run;
]]
for i, item in ipairs(target) do
sas.submit(code, { dataset = item } ) --ここでパラメータ指定して実行
end
endsubmit;
quit;

うん、発想はマクロと同じですが、こっちの方が美しいと僕は思いますけどね。
まあ、Luaの中でSASコードを生成して実行する話はまた今度詳しくやります。

さて、今回勉強したいのは target = {'CLASS','FISH','CARS'}の部分で「テーブル」といいます。

Luaにおいて、とても特徴的なのが、データ構造が唯一その「テーブル」というもので管理されるという点です。
「テーブル」と聞いて一般的に想像する形式とは違い、Luaのテーブルは、入れ子にできたり関数を保持できたり、かなり何でもアリな概念だと思ってください。

多分、何回か掘り下げることになりうと思いますが、まずポイントとして、Luaのテーブルは
配列(ARRAY)と連想配列(HASH)の両方の性質を持つという点です。
いや正確には内部的に「Array」管理パートと「Hash」管理パートがあるといった感じですかね。
まあ、まだそんなに難しく考える必要はないです。なんでも入れれると思ってください


proc lua;
 submit;
 --配列的なテーブル定義の例 とりあえずなんか突っ込んでみる
 local sampletable = {'りんご', 999, 'ABCD', 123}

 --型を見てみる
 print("sampletableの型は",type(sampletable),"だ")
 print("===================================")


 --ループで1から4番目の要素をプリントする
 for i =1 , 4 do
   print(i,"=",sampletable[i])
 end

 --1番目の値を変えて、5番目に値を追加する
 sampletable[1]='みかん'
 sampletable[5]=456

 print("===================================")

 --配列テーブルのループ参照には、ipairs関数というものを使って以下の書き方をする。
 --とりあえず丸覚えしちゃえばいいと思う
 for i, val in ipairs(sampletable) do
  print(i, "=",val)
 end

endsubmit;
quit;

結果は

















文字と数値混ぜれたり、途中で要素増やしたり、なんかSASのarrayをゆるゆるにした感じですね。
代入の仕方は簡単ですね。{}の中に値を区切っていれるだけです。
参照の仕方も、テーブル名[要素番号]でシンプル。

ちなみに
table ={}と空でつくっておいてから
table[1]=1と値をいれるのも当然OK


Luaの通常のインデックスループは
for インデックス変数 = 開始値 , 終了値 ,加算値 do
 処理
end
といったごく普通。

ただ、テーブルの中を参照する場合、決まり文句のようなものがあって

for i, 変数 in ipairs(テーブル名) do
変数の中にテーブルの値が入っていく
end

といった書き方をします。上記の例だとvalの中にsampletableの要素が順番にはいる形で
ループが発生します。

ipairs関数はイテレータ関数と呼ばれるもので、配列としてのテーブル(keyが1から始まる数字で連続しているならハッシュでもいいけど)には
これを使います。


つぎに同じテーブルでもハッシュ的にkeyと値で管理してみます


proc lua;
 submit;
  --ハッシュ的テーブル定義の例 とりあえずなんか突っ込んでみる
  local sampletable2 = {fruits='りんご', no1=999, valc='ABCD',[1]=1}

  --キーがkey1のデータと、キーが99のデータを追加してみる
  sampletable2["key1"] = "data1"
  sampletable2[99] = 100

  print("===================================")
  --キーがvalcのデータを参照
  print("キーがvalcのデータは",sampletable2.valc)
  --キーが99のデータを参照
  print("キーが99のデータは",sampletable2[99])
  print("===================================")

  --ハッシュテーブルのループ参照には、pairs関数というものを使って以下の書き方をする。
  --とりあえず丸覚えしちゃえばいいと思う
 for key, val in pairs(sampletable2) do
  print(key, "=",val)
 end

endsubmit;
quit;

結果は














local sampletable2 = {fruits='りんご', no1=999, valc='ABCD',[1]=1}
について
sampletable2テーブルのキーは
fruits
no1
valc
1
で、それぞれにデータが入ってます。

参照はsampletable2.valc でもsampletable2['valc']でもOK

ハッシュのように管理されているテーブルの場合
ipairsではなくpairs関数を使います。「key」っていう変数名はなんでもいいんですが、
慣習的にipairsではi、pairsではkeyって名前にするみたいです。

多分、もうおなかいっぱいだと思うんですけどもう少しだけ
今まで説明したのはいわゆる1行データ、1次元配列のようなパターンだけでした。
ただ、それだけだと、今後SASデータセットをLuaのテーブルにしたり
Luaのテーブルからデータセットを作る際にちょっとわかりにくくなると思います。
SASデータセットは変数とOBS、つまり2次元になるので。

多次元構造のつくり方はいたって簡単で、テーブルの要素をテーブルにしちゃえばOKですつまり

proc lua;
 submit;
 local samp3 = {};
 samp3[1] = {1,2,3};
 samp3[2] = {4,5,6};
 samp3[3] = {7,8,9};
 --local samp3 = {{1,2,3},{4,5,6},{7,8,9}};でも当然OKよ

 --中身を確認するためLuaのテーブル関数tostringを使ってみる
 print(table.tostring(samp3))

 endsubmit;
quit;






















としてみます。以下のようにきちんと2重ループにしてプリントしてもいいけど
for i, val in ipairs(samp3) do
for j, val2 in ipairs(val) do
print(i..'-'..j, "=",val2)
end
end

面倒なので、Luaのテーブル関数(またいずれ説明)tostringを使って、テーブルの中身をみます。
階層構造で表現されます。結果は以下のとおり

ハッシュタイプで階層的に表現したいのなら以下の感じかな
proc lua;
 submit;
 local samp4 = {};
 samp4.row1={col1=1,col2=2,col3=3};
 samp4.row2={col1=4,col2=5,col3=6};
 samp4.row3={col1=7,col2=8,col3=9};
 print(table.tostring(samp4))
 endsubmit;
run;























ごめんなさい、もう少しだけ。
sasのdim関数のようなものはないのかということなんですが#テーブル名かtable.size関数があります。
通常の配列的に使用されるテーブルなら結果は同じですが、キー値が文字、あるいは数字であっても1から始まって連番でなければ#の結果は狂います。狂うというか、#はテーブルのArrayパートの1以上の数値の最大値しか参照していない仕様のようです

どういうことかというと

proc lua;
 submit;
 --ハッシュ的テーブル定義の例 とりあえずなんか突っ込んでみる
  local samp5 = {1,2,3,4}
  print ('#samp5=',#samp5);
  print ('table.size=',table.size(samp5));

 --飛び番で定義してみる
  samp5[6] = 6
  print ('#samp5=',#samp5);
  print ('table.size(samp5)=',table.size(samp5));

  --0とか文字のキーとかたしてみる
  samp5[0] = 0
  samp5.key1 ="data"
  print ('#samp5=',#samp5);
  print ('table.size(samp5)=',table.size(samp5));

endsubmit;
quit;










ということです
これは気をつけないと駄目ですね


ところで最後にYoshihiro Fukiyaさんが嬉しい記事を書いてくれました!!

[みんなでLuaを勉強してみよう!!]

凄く嬉しいです。
参照の論文もおすすめです(ただ、ちょっと気になるところもあって、get関数じゃなくてget_valueじゃない?とか、書かれた時期のメンテナンスバージョンのせいなのかそのまま通すとエラーになる部分がある気がします。そんなことない?)


Proc Luaの世界①-こんにちはからif文あたりまで

Proc Luaは間違いなく、相当に便利な機能です。今までSASマクロが担ってきた機能の多くはProc Luaでより簡易に記述することができます。
前の記事を読んでいない方は先にご一読ください

「Proc Luaの衝撃」
http://sas-tumesas.blogspot.jp/2016/08/proc-lua.html


今後は、具体的かつ実践的な例をあげる記事と、基礎から一緒に勉強する記事を織り交ぜていきたいと思います。

僕も超初心者なので、多分間違ったことも書いてしまうと思いますが、一緒に切磋琢磨していきたいので適宜指摘や情報交換いただけますと幸いです。
Luaそのものについては、既に良質な情報が公式、非公式を問わずに出揃っているのですが、Proc LuaはあくまでSASの中で動くものなので、ちょっとSASプログラマーにしかわかりにくところがあります。
なので僕が一から解説することも無意味ではないはず、、。ということでいきましょう!

まず最初は以下のコードです

proc lua;
 submit;
print('こんにちは世界')
 endsubmit;
quit;

実行するとログにこのようにでます。




まず、Luaプロシジャはproc luaで始まりquitで終わります。
そしてsubmit;からendsubmit;の間がLuaの世界で、ここにはLuaの文法で記述されたコードが
実行されます。

print関数はログ画面に引数を出力する関数になります。
Luaでは文字列はコーテーションで括りますが、シングルでもダブルでもOKです。
そして、SASと違ってセミコロン「;」は要りません。まあ、つけてもLuaでは空文の意味なので
影響ないですが。

つづいてコメントのやり方について、以下をみてください
proc lua;
/*submitまでのこのスペースはまだSASの世界なのでSASのコメントができる*/
submit;
  --ここからはLuaのやり方でコメントする
  --1行コメントはこのようにバー2連続で書く
print('こんにちは世界')
  --[[
    複数行コメントは
このように書きます
  --]]
 endsubmit;
quit;

とまあ、こんな感じですが、SASの拡張エディタだと色変わらないんでちょっと嫌ですね。

次は変数についてです。

proc lua;
 submit;

  local a =1
  local b='あああ'

print("a=" ..a)
print("b=" ..b)

 endsubmit;
quit;

結果は





local a=1でローカル変数aを定義しそのまま値を代入しています。
local aとして値を入れない宣言もありです。
後で説明しますが、基本localでの定義を推奨します。
これも後で説明しますが、Luaには変数の型というものはなく、
入っている値によって型が動的に決まります。
入っている値が数値なら数値型、文字なら文字型ということです。
print("a=" ..a)..ですが、これは文字列の連結を表します。SASでいうところの||と同じですね


変数についてもう少し見てみます

proc lua;
 submit;

    --こうも書ける
  local c ,d =1 , 2

print("c=" ..c)
print("d=" ..d)

--こうやって中身を入れ替えることもできる
local c ,d = d,c

print("c=" ..c)
print("d=" ..d)

--型はtype関数で調べれる
print("cの型は"..type(c).."です")

--Luaは動的型つき言語→つまり変数は型を持たず、値のみが型を持つ。
c="A"
print("c=" ..c)
print("cの型は"..type(c).."です")

--未初期化の変数には「nil」が入っている
print(zzz)

 endsubmit;
quit;

結果は












です。
 local 変数,変数,変数... =値,値,値...といった書き方がかけるということ
local c ,d = d,cで値が入れ替えられるということ。
またtype関数で型が調べられること。そして型が値によって変わるということを押さえてください。

またnilという概念があって、要するにnullみたいなもんなんですが、未初期化の変数にはnilが入ってます。
いずれまた詳しく掘り下げますが、Luaでは、論理演算において「false」と「nil」だけが偽それ以外は全て真というルールで動きます。つまり0だろうが、長さ0の""のような文字列だろうがそれらを真偽値では真になるので注意です。また関数などでも、指定しなかった引数にはnilがはいるので注意です。

さて、localでローカル変数が定義できるということはグローバルも当然あります。これは何もつけずにそのまま値を代入するとグローバル変数になります。グローバル変数になるとproc lua; quit;が終了してもその変数が残ります。つまり

proc lua;
 submit;
  --local宣言せずに変数をつくるとGlobalになっちゃう。多分それは避けた方がいい
  e = 100
 endsubmit;
quit;

一旦プロシジャを閉じても

proc lua;
 submit;
  --global変数はずっと生きちゃうから
  print ("e="..e)
 endsubmit;
quit;




と変数が生きちゃってます。nilを入れない限り、ずっと参照できます。
それをうまく使うことも当然できますが、しばらくはグローバルで定義せずにひとつのproc luaからquitの間で完結するコードを書いていこうと思います。

つづいて、算術演算子に関しては、まあ普通な感じなので、さっと見て覚えてください

/*算術演算子*/
proc lua;
submit;
 local a ,b, c, d;
 print(3+2)
 print(3-2)
 print(3/2)
 print(3%2) --剰余
 print(3^2)
endsubmit;
quit;










関係演算子については、ちょっと間違いやすいです

/*関係演算子*/
proc lua;
submit;
 local a =1
 local b =2
 local c ='1'
 print(a==1) --★SASと違うから要注意!=1だとエラーになる。
 print(a>b)
 print(a>=b)
 print(a<b)
 print(a<=b)
 print(a~=b) --★^=じゃなくて~=なので注意
 print(a==c) --★型が違うと不等とみなされるから注意!
 endsubmit;
quit;












SASは割り当ての際も=をつなぐし、等しいことも=で表現しますがLuaの等しいは==で表現することに要注意です。僕は既にこれで10回はエラーだしました


最後にIF文について紹介します。
基本的に
IF 条件式 THEN 処理 END と書きます。
SASだとendがつくのは処理が複数で then doの場合のみですがLuaは必ずENDがいるので注意です。
例をみます。

/*if文*/
proc lua;
submit;
if sas.weekday(sas.today()) == 2 then
print('今日は月曜だ')
end  --★if文の終わりに絶対endが必要なことに要注意
endsubmit;
quit;

結果は日によって変わるので省略。

また後日説明しますが、sas.SAS関数名でSASの関数を使うことができます。

proc lua;
submit;
if sas.weekday(sas.today()) == 2 then
print('今日は月曜だ')
print('もう一度言おう今日は月曜だ')
end
endsubmit;
quit;

then からendの間にはいくらでもかけます。

proc lua;
submit;
if sas.weekday(sas.today()) == 1 then
 print('今日は日曜だ')
elseif sas.weekday(sas.today()) == 7 then --★else ifじゃなくてelseifなことに注意
          print('今日は土曜だ')
else
 print('今日は平日だ')
end
endsubmit;
quit;

elseifで条件分岐をいくらでも深くできます。elseifとスペースをいれずに1キーワードで記述することに注意です。

今回はここまで。

Proc Luaの衝撃

先日、下記のようなコードを見かけて、久々に震えるほどの感激を受けました。

proc Lua;
 submit;
  if sas.exists("WORK.CLASS") then
   print("NOTE:データセットCLASSは既にあるので何もしません")
  else
   print("NOTE:データセットCLASSがなかったから作成しました")
   sas.submit([[data CLASS; set sashelp.CLASS ; run;]])
  end
 endsubmit;
run;

実行されるコードの分岐がマクロを使わずに記述されてる......。

今までなら例えば

%macro test;
 %if %sysfunc(exist(WORK.CLASS)) eq 1 %then %do;
  %put NOTE:データセットCLASSは既にあるので何もしません;
 %end;
 %else %do;
  %put NOTE:データセットCLASSがなかったから作成しました;
   data CLASS; set sashelp.CLASS; run;
  %end;
%mend test;

%test

って書いてた処理ですね。

どうでしょう?SAS初心者、或いは全く知らない人の気持ちになってコードを見た時に
どっちが見やすいですか?わかりやすいと感じますか?

僕は前者だと思います。

proc luaというものについて何も知らない状態で最初のコードを見ましたが
何の処理をしているのか、実行した時にどうなるかが一瞬で理解できました。

で、肝心のProc Luaって何ですか?って話ですが、Luaっていうプログラミング言語があるんですね。
スクリプトタイプの言語で簡易な記述と、高速な処理、他の言語への組み込みのしやすさが特徴みたいです。

これは、正直惚れてしまいました。ちょっと真剣にLuaをゼロから勉強してみようと思います。
SASプログラミングは、なまじSASマクロがなんでもできるだけに、SASマクロに頼り過ぎていて、簡易な記述、合理的な構造から遠ざかっている側面があるのではないか?
それがSASを苦手な人、SASアレルギーの一因になっているのでは?って思ったりもするので
proc Luaを知ったのは僥倖でした。

まだ勉強し始めですが、やはり結構簡単な上に、処理をわかりやすくできるので
実際の業務にも取り入れやすいんじゃないかと思います。マクロでどうしょうもなくなったプロジェクトの銀の弾丸になりえるんじゃなかなぁって淡い希望を抱いてます。

ただ惜しむらくは、9.4のメンテナンスリリース3からの正式導入なことですね(とりあえず9.4なら試用版で使えますが)。

でも、コレはこれからかなりくる技術だと思います。だって普通に便利だから。いや、正直DS2より効果がわかりやすい気が…(オイッ)

というわけで暫くproc Luaの話が増えると思います。9.4の最新なんか入ってね~しって人が多いとは思いますが、未来のため、後ろに続くこれからのSASプログラマー達が仕事しやすくなるようにみんなで勉強してみませんか?


RTF出力の新視点-STREAMプロシジャの応用力は侮れないという話

RTF出力、実は未だあんまり経験ないんですが、色々大変なのはわかります。

多分おおまかに2つ方法があって、一つはODS RTFを利用する方法、もう一つはputでタグ情報と共にデータを出力して作ってしまう方法だと思います。
どちらも過去のユーザー総会等で素晴らしい発表が多くでているので、そちらを参照してください。

まあ、それで終わりの話なんですが、僕はひねくれているので、何か他に面白い方法はないかなと考えてました。

上記の2つの方法もファイルの新規作成なので、書式等の調整も全てコードでやる必要があります。
要するに、あらかじめ用意したRTFファイルに値を差し込む方法ではないということです。
(一応DDEを使った既存WORDファイルへの値入れする方法もあるみたいですが、少なくとも日本ではあんまり主流ではない気がします)

そこで、書式設定等を済ませたテンプレートRTFにSASから出力する新しい方法について紹介します。

ズバリproc streamを使います! 9.4からの新プロシジャです。一応9.3でも正式版ではないですが実行できます。

proc stream?って方は過去記事
「proc stremの世界」
http://sas-tumesas.blogspot.jp/2015/07/proc-strem.html

を見てください。

要はマクロ変数を展開したテキスト生成ができるプロシジャで、普通の平テキストはもとより、HTMLやXML、RTFなどタグ言語の生成に強みがあるんですね。

さあ、前置きが長くなりましたが、やっと本題に行きます。


まず、ノートパッドでもMS WORDでもいいので、以下の様な出力テンプレートを拡張子RTFで作成、保存してください。(例は「テンプレート.rtf」という名前で保存)



ちなみに値を差し込みたい部分をマクロ変数で記述します。

で、例えば以下の様なデータセットがあった場合







data _NULL_;
set SALES end=eof;
call symputx(ITEM,SALE);
TOTAL+SALE;
if eof then call symputx('TOTAL',TOTAL);
run;

とでも書いて、とりあえずテンプレートのマクロ変数名に合わせて、マクロ変数を作ってやります。

そうしたら、いよいよ次のように書きます

filename in "/home/sasyamasasyama0/テンプレート.rtf" lrecl = 32755;;
filename out "/home/sasyamasasyama0/アウトプット.rtf" lrecl = 32755;;
proc stream outfile=out resetdelim="rdlm" quoting=single;
begin
rdlm; %include in;
;;;;

これだけです。

実行すると、「アウトプット.rtf」ができて、それを開いてみると















と、値が展開されていることがわかります。

どうでしょう?
結構面白いですよね。

resetdlm=で、リセットデリミタを指定しています。リセットデリミタの後に%includeを記述しないと、ただのテキストとして「%include」ってでちゃいます。
%include ファイル名で、指定したファイルをproc streamの中に展開します。そこでその中に含まれるマクロ変数が展開されるわけです。

こういうレイアウトが固定されたrtfに値を出すのには結構向いていると思います。
まあ、多分ご想像されているとおり、表がデータに応じて可変だったり、出力する値の数が多いものには正直オススメできないですね。
(できなくはないけど、それに応じて表を構成するためのタグを作って一緒に出す必要があるので、putで出す際のコードをマクロ化してstreamの中で展開するイメージになるので、お手軽さがなくなる)

定型のちょっとしたレポート向きだと思いますが、知っていて損はない使い方だと思います。

以上です。

monotonic関数の性質。_N_とはちょっと違うという話

monotonic関数は色々注意点もありますが、中々面白い関数です。

例えば以下のデータセットがあります

data Q1;
X=1;output;
X=1;output;
X=2;output;
X=3;output;
X=2;output;
X=2;output;
X=4;output;
X=3;output;
X=3;output;
run;

















それで、偶数のオブザベーション番号だけproc printしろっていわれたら、

proc print data=Q1;
 where mod(_N_,2)=0;
run;

なんて書きたくなりますが、もちろん_N_はwhereでは使えません。

ところが

proc print data=Q1;
 where mod(monotonic(),2)=0;
run;

ならいけます









これは何回も紹介している話なんですが、上記のことから
monotonic関数はwhereでも使える_N_みたいなものと認識してしまいがちなのですが
それはちょっと違います

例えば

data A1;
set Q1;
Y=monotonic();
run;



















となり

Y=_N_;とした場合と全く同じですが

以下のコードはどうなるでしょう?

data A2;
set Q1;
if x=1 then Y=monotonic();
if x=2 then Y=monotonic();
if x=3 then Y=monotonic();
if x=4 then Y=monotonic();
run;

実は

















と、Xの値ごとに連番が振られるんですね。

つまりmonotonic関数は、行番号を返しているのではなく、
monotonic関数を通って処理される回数をカウントして返しているようなイメージということなんですね。(理解が間違っていたらご指摘ください)

これは非常に面白い性質ですね。







パッケージ内で別パッケージのメソッドを使用するための方法-コンポジション

今回の記事については説明や用語・概念の説明についてちょっと自信がないです。
SASメインで、他のオブジェクト指向の言語をあんまりやったことがないので、もしもっと上手く説明できる方がいらっしゃれば是非どこかにアップして欲しいです。

さて、苦手ながらも大事な部分なので頑張って書いてみます。

一般的なオブジェクト指向言語でのクラスという概念が、DS2ではパッケージになると思います。
DS2のパッケージには、いわゆる「インヘリタンス(継承)」という機能がありません。

継承は、既存クラスの全機能を引き継いで新たなクラスを定義することで、オブジェクト指向の3大要素とかって言われるぐらい重要なものです。親のメソッドを全部引き継いだ子を作るイメージです

それがないってどうなの?ってことですが、代わりにすべて、同じくオブジェクト指向の「コンポジション(集約)」という概念でまかないます。

インヘリタンスとコンポジションの違いは、ちょっと検索して貰えればたくさん説明がでてくるので、そちらを参考にしてください。
僕がわかりやすかったのは以下のとか?

要するに、単純に他のパッケージの必要なメソッドだけもってきて新規パッケージの中で使えるようにしちゃうってイメージで僕は理解しました。

例えば以下のような、数値に1を加えるメソッド(tasu1メソッド)と2倍するメソッド(kake2)をもったパッケージ(p1パッケージ)があるとします

proc ds2 libs=work;
package p1 /overwrite=yes;
declare double v;
method tasu1(double v) returns double;
return v+1;
end;
 method kake2(double v) returns double;
  return v*2;
 end;
endpackage;
run;
quit;

次に新しく作成するパッケージ(p2パッケージ)の中で、p1のtasu1メソッドの機能を使いたい場合は以下のように書けます

proc ds2 libs=work;
package p2 /overwrite=yes;
declare package p1 pp1();
declare double v;
method p2();
pp1=_new_ p1();
end;
method tasu1(double v) returns double;
return pp1.tasu1(v);
end;
method tasu2(double v) returns double;
return v+2;
end;
endpackage;
run;
quit;

最初のdeclareでパッケージp1をpp1という名前で呼び出している(パッケージp1からインスタンスpp1を生成)のは、普通にパッケージを利用する場合と同様なので自然にかけると思います。
ただ、それだけだと不十分で、

method p2();
pp1=_new_ p1();
end;

の部分、パッケージ名と同じなのでコンストラクタになるわけですが、
ここで謎の式を書いてますが、これも実はパッケージの呼び出し方(インスタンスの生成)の書き方なんですね。p1パッケージからpp1インスタンスを生成するという意味になります。

declareステートメントと同じことしてんじゃんって感じで、なんでこんなのが必要なのかなんですが
呼び出しのコードをみるとわかりやすいかもです

proc ds2 libs=work;
data A1(overwrite=yes);
declare double x y;
declare package p2 p2();
method run();
x=p2.tasu1(1);
y=p2.tasu2(1);
end;
enddata;
run;
quit;

ちなみに結果は





なんですが、ようするにp2パッケージしか呼び出してないのに、内部でp1パッケージの機能も呼び出されている、つまりp1のインスタンスも生成されているのがミソなんです。
パッケージが呼び出された際に、最初に実行されるコンストラクタの部分でp1のインスタンスを作らないと実際使えないよってことなんですね。パッケージを呼び出した際に内部のdecalreステートメントが実行されるわけではないので。


はぁ、自分の説明力のなさに嫌気がさします、、。
ほんとすみません、どなたか、もっとちゃんと説明しなおしてください