proc streamのoutfile=って出力先の外部ファイルを指定するものですが、
もしかしてクリップボード指定できるんじゃない?って思いつきました。
できました。狙い通りに動いてニヤりなんですが、使い道はしらないです。
9.4使われている方は、以下のコード実行した後に[Ctr]+[V]してみてください。
%let person1=おじいさん;
%let person2=おばあさん;
%let place1=山;
filename clip clipbrd;
proc stream outfile=clip resetdelim="goto";
begin
むかしむかし &person1.と&person2.が&place1 に住んでおったそうだ goto NEWLINE;
&person1.は山に芝刈りにいき goto NEWLINE;
&person2.は川に洗濯にいった。
;;;;
できた~!!わ~い!!
どうして、いつもいつも何の役にも立たないことばかり閃くのか…
Compareプロシジャの結果が一致か不一致か、何が不一致かをマクロ変数で取得する話
コンペアプロシジャで、解析用データセットとかをいっぱいコンペアするときに、ひとつなぎで結果がでてくると見にくいし、スクロールだるいし、そもそもSASアウトプットもhtmlも見づらいので
僕はデータセットごとにコンペア結果をodsでPDFファイルにしたりします。
コンペアすると、いつもだいたい僕が間違ってますが、ダブルやってるもう一人の人が間違ってる場合に印刷してあげやすいしね。
そのときに、一致している場合はファイル名の頭にOK、してない場合はNGをつけるって処理を書いてたんですね。
あらじめOK_XX.pdfで出力しといて、NGの場合に
rc=rename(...\OK_XX.pdf,...\NG_XX.pdf,'file')みたいな感じです。
それはいいんですけど、コンペアが一致したかどうかの判定をods outputで
CompareSummaryとか諸々をだして、その中の文言で判定するという方法でやるしか知りませんでした。
バージョンによってなんか文言が微妙に変わるし、効率的じゃないなと思ってましたが、
最近、コンペアプロシジャの結果を自動マクロ変数で取得できるっていうことを教えていただきました。いや~、しらんかった。
&sysinfoを使います。
こいつはコンペアプロシジャの実行後に展開した場合、完全に一致してれば0が入り、値や属性など何かしら不一致であれば0以外の数がでます。
では見てみましょう。
データセット3つ用意します
data a;
x=1;
run;
data b;
x=1;
run;
data c(label="aaa");
x=2;y=1;output;
x=2;y=1;output;
format x yymmdds10.;
run;
一致している場合、
proc compare base=a comp=b;
run;
%put sysinfoの中身は → &sysinfo;
結果
不一致の場合、
proc compare base=a comp=c;
run;
%put sysinfoの中身は → &sysinfo;
となります。
で、気になるのが不一致だった場合、どんな不一致がでているのかを全部だせるのかという話ですが、band関数という2つの引数のビットごとの論理積を返す関数を使ってメッセージを分離できます。
では実際に、それを組み込んだコンペアマクロを見てみましょう。
%macro compare(main,sub);
%if %sysfunc(exist(&main)) ne 1 %then %do;
%put WARNING:メイン側&main.がないぞ;
%end;
%if %sysfunc(exist(&sub)) ne 1 %then %do;
%put WARNING:サブ側&sub.がないぞ;
%end;
%if %sysfunc(exist(&main)) and %sysfunc(exist(&sub)) %then %do;
proc compare base=&main comp=⊂
run;
%put NOTE:&main.と&sub.コンペアしました;
%if %sysfunc(band(&sysinfo,1)) eq 1
%then %put WARNING:データセットラベルが違うぞ
[Data set labels differ];
%if %sysfunc(band(&sysinfo,2)) eq 2
%then %put WARNING:データセットのタイプが違うぞ ビューとかコンペアしてんのか?
[Data set types differ];
%if %sysfunc(band(&sysinfo,4)) eq 4
%then %put WARNING:インフォーマット違うぞ
[Variable has different informat];
%if %sysfunc(band(&sysinfo,8)) eq 8
%then %put WARNING:フォーマット違うぞ
[Variable has different format ];
%if %sysfunc(band(&sysinfo,16)) eq 16
%then %put WARNING:レングス違うぞ
[Variable has different length];
%if %sysfunc(band(&sysinfo,32)) eq 32
%then %put WARNING:ラベル違うぞ
[Variable has different label];
%if %sysfunc(band(&sysinfo,64)) eq 64
%then %put WARNING:メイン側なんかobs数多いぞ
[Base data set has observation not in comparison];
%if %sysfunc(band(&sysinfo,128)) eq 128
%then %put WARNING:メイン側なんかobs数少ないぞ
[Comparison data set has observation not in base];
%if %sysfunc(band(&sysinfo,256)) eq 256
%then %put WARNING:BY変数でメインにだけあるやつあんぞ
[Base data set has BY group not in comparison];
%if %sysfunc(band(&sysinfo,512)) eq 512
%then %put WARNING:BY変数でメインにないやつあんぞ
[Comparison data set has BY group not in base];
%if %sysfunc(band(&sysinfo,1024)) eq 1024
%then %put WARNING:メイン側変数多いぞ
[Base data set has variable not in comparison];
%if %sysfunc(band(&sysinfo,2048)) eq 2048
%then %put WARNING:メイン側変数少ないぞ
[Comparison data set has variable not in base];
%if %sysfunc(band(&sysinfo,4096)) eq 4096
%then %put WARNING:値が違うね
[A value comparison was unequal];
%if %sysfunc(band(&sysinfo,8192)) eq 8192
%then %put WARNING:型の違う変数があるね
[Conflicting variable types];
%if %sysfunc(band(&sysinfo,16384)) eq 16384
%then %put WARNING:BY変数一致してないね
[BY variables do not match];
%if %sysfunc(band(&sysinfo,32768)) eq 32768
%then %put WARNING:コンペア自体コケてるやん。論外
[Fatal error: comparison not done] ;
%if &sysinfo eq 0 %then %put OKおめでとう!!;
%end;
%mend compare;
%compare(a,c)
というわけです。
[]の中の英語がSASの公式のメッセージで、日本語はメインに非があるという体で僕が適当に書きました。これをうまく使うと、差異を把握しやすいのでコンペア・修正・すり合わせの作業がちょっと楽になるかもしれませんね。
コンペア状況一覧とか作って、細かく進捗管理してるぜ~って感じだすと、
あんまり進んでなくても、ウケがよかったりします
あ、Luaでもかけますからね
僕はデータセットごとにコンペア結果をodsでPDFファイルにしたりします。
コンペアすると、いつもだいたい僕が間違ってますが、ダブルやってるもう一人の人が間違ってる場合に印刷してあげやすいしね。
そのときに、一致している場合はファイル名の頭にOK、してない場合はNGをつけるって処理を書いてたんですね。
あらじめOK_XX.pdfで出力しといて、NGの場合に
rc=rename(...\OK_XX.pdf,...\NG_XX.pdf,'file')みたいな感じです。
それはいいんですけど、コンペアが一致したかどうかの判定をods outputで
CompareSummaryとか諸々をだして、その中の文言で判定するという方法でやるしか知りませんでした。
バージョンによってなんか文言が微妙に変わるし、効率的じゃないなと思ってましたが、
最近、コンペアプロシジャの結果を自動マクロ変数で取得できるっていうことを教えていただきました。いや~、しらんかった。
&sysinfoを使います。
こいつはコンペアプロシジャの実行後に展開した場合、完全に一致してれば0が入り、値や属性など何かしら不一致であれば0以外の数がでます。
では見てみましょう。
データセット3つ用意します
data a;
x=1;
run;
data b;
x=1;
run;
data c(label="aaa");
x=2;y=1;output;
x=2;y=1;output;
format x yymmdds10.;
run;
一致している場合、
proc compare base=a comp=b;
run;
%put sysinfoの中身は → &sysinfo;
結果
不一致の場合、
proc compare base=a comp=c;
run;
%put sysinfoの中身は → &sysinfo;
となります。
で、気になるのが不一致だった場合、どんな不一致がでているのかを全部だせるのかという話ですが、band関数という2つの引数のビットごとの論理積を返す関数を使ってメッセージを分離できます。
では実際に、それを組み込んだコンペアマクロを見てみましょう。
%macro compare(main,sub);
%if %sysfunc(exist(&main)) ne 1 %then %do;
%put WARNING:メイン側&main.がないぞ;
%end;
%if %sysfunc(exist(&sub)) ne 1 %then %do;
%put WARNING:サブ側&sub.がないぞ;
%end;
%if %sysfunc(exist(&main)) and %sysfunc(exist(&sub)) %then %do;
proc compare base=&main comp=⊂
run;
%put NOTE:&main.と&sub.コンペアしました;
%if %sysfunc(band(&sysinfo,1)) eq 1
%then %put WARNING:データセットラベルが違うぞ
[Data set labels differ];
%if %sysfunc(band(&sysinfo,2)) eq 2
%then %put WARNING:データセットのタイプが違うぞ ビューとかコンペアしてんのか?
[Data set types differ];
%if %sysfunc(band(&sysinfo,4)) eq 4
%then %put WARNING:インフォーマット違うぞ
[Variable has different informat];
%if %sysfunc(band(&sysinfo,8)) eq 8
%then %put WARNING:フォーマット違うぞ
[Variable has different format ];
%if %sysfunc(band(&sysinfo,16)) eq 16
%then %put WARNING:レングス違うぞ
[Variable has different length];
%if %sysfunc(band(&sysinfo,32)) eq 32
%then %put WARNING:ラベル違うぞ
[Variable has different label];
%if %sysfunc(band(&sysinfo,64)) eq 64
%then %put WARNING:メイン側なんかobs数多いぞ
[Base data set has observation not in comparison];
%if %sysfunc(band(&sysinfo,128)) eq 128
%then %put WARNING:メイン側なんかobs数少ないぞ
[Comparison data set has observation not in base];
%if %sysfunc(band(&sysinfo,256)) eq 256
%then %put WARNING:BY変数でメインにだけあるやつあんぞ
[Base data set has BY group not in comparison];
%if %sysfunc(band(&sysinfo,512)) eq 512
%then %put WARNING:BY変数でメインにないやつあんぞ
[Comparison data set has BY group not in base];
%if %sysfunc(band(&sysinfo,1024)) eq 1024
%then %put WARNING:メイン側変数多いぞ
[Base data set has variable not in comparison];
%if %sysfunc(band(&sysinfo,2048)) eq 2048
%then %put WARNING:メイン側変数少ないぞ
[Comparison data set has variable not in base];
%if %sysfunc(band(&sysinfo,4096)) eq 4096
%then %put WARNING:値が違うね
[A value comparison was unequal];
%if %sysfunc(band(&sysinfo,8192)) eq 8192
%then %put WARNING:型の違う変数があるね
[Conflicting variable types];
%if %sysfunc(band(&sysinfo,16384)) eq 16384
%then %put WARNING:BY変数一致してないね
[BY variables do not match];
%if %sysfunc(band(&sysinfo,32768)) eq 32768
%then %put WARNING:コンペア自体コケてるやん。論外
[Fatal error: comparison not done] ;
%if &sysinfo eq 0 %then %put OKおめでとう!!;
%end;
%mend compare;
%compare(a,c)
というわけです。
[]の中の英語がSASの公式のメッセージで、日本語はメインに非があるという体で僕が適当に書きました。これをうまく使うと、差異を把握しやすいのでコンペア・修正・すり合わせの作業がちょっと楽になるかもしれませんね。
コンペア状況一覧とか作って、細かく進捗管理してるぜ~って感じだすと、
あんまり進んでなくても、ウケがよかったりします
あ、Luaでもかけますからね
割と画期的なアイデアでは??SASプログラムの自動生成について
ちょっと、今日ぱっと思いついたんですけど、
STREAMプロシジャは性質として、内包されるマクロ変数を展開、そして内包されるマクロを実行コードレベルまで展開するけど実行はしないというものを持ってるじゃないですか?
そしてLuaプロシジャは内包するマクロ変数を展開しないという性質がありますよね。
ということはLuaプロシジャでマクロを定義して、それを入れ込んだSTREAMをsubmitする流れをうまく使えば、例えば仕様書的な情報から効率的にプログラム生成できそうじゃないですか?
業界を問わず、仕様書からのプログラム自動生成って割とみんなの夢だと思うんですが、
既存のSASだと、put文で一生懸命作るやり方で、もちろん部分部分をマクロ化して構造的にしていたけども、全体として煩雑なものになりがちだったと思います。
あと地味にSASマクロのクォーティングを気にしてテキスト生成する必要があったりして結構うざい思いをしたことがあると思います。
例えば、以下のデータセットがあって、ここからプログラムを生成するとしたら
data define_table;
titleno='Table-1-1';title='体重の要約統計量';
table='SASHELP.CLASS';target='WEIGHT';method='UNIVARIATE';output;
titleno='Table-2-1';title='性別の頻度集計';
table='SASHELP.CLASS';target='SEX';method='FREQ';output;
run;
上記を実行すると2つのSASファイルができて、中身は以下のとおりです。
【Table-1-1.sas】
【Table-2-1.sas】
おのおのの部分をつくるマクロを生成しているLuaの部分を関数化などしてやれば
拡張性に富んだ自動生成コードが作れると思うんですよ。
条件分岐とかループも今までのようにマクロで制御するより、Luaで制御し放題なほうが楽だし。
そもそも21世紀にもなって、ずっとputで平テキスト打ってることずっと違和感あったんですよね
医薬業界はもはや言うまでもなくですが、データセットや解析仕様の標準化が進んでいる環境において活かせそうな発想だと思いますけど、どうでしょう?
STREAMプロシジャは性質として、内包されるマクロ変数を展開、そして内包されるマクロを実行コードレベルまで展開するけど実行はしないというものを持ってるじゃないですか?
そしてLuaプロシジャは内包するマクロ変数を展開しないという性質がありますよね。
ということはLuaプロシジャでマクロを定義して、それを入れ込んだSTREAMをsubmitする流れをうまく使えば、例えば仕様書的な情報から効率的にプログラム生成できそうじゃないですか?
業界を問わず、仕様書からのプログラム自動生成って割とみんなの夢だと思うんですが、
既存のSASだと、put文で一生懸命作るやり方で、もちろん部分部分をマクロ化して構造的にしていたけども、全体として煩雑なものになりがちだったと思います。
あと地味にSASマクロのクォーティングを気にしてテキスト生成する必要があったりして結構うざい思いをしたことがあると思います。
例えば、以下のデータセットがあって、ここからプログラムを生成するとしたら
data define_table;
titleno='Table-1-1';title='体重の要約統計量';
table='SASHELP.CLASS';target='WEIGHT';method='UNIVARIATE';output;
titleno='Table-2-1';title='性別の頻度集計';
table='SASHELP.CLASS';target='SEX';method='FREQ';output;
run;
以下のように書ける訳です
proc lua;
submit;
--仕様書から情報を抜きつつループ
local dsid = sas.open("define_table")
while sas.next(dsid) do
local titleno=sas.get_value(dsid,"titleno")
local title =sas.get_value(dsid,"title")
local table =sas.get_value(dsid,"table")
local target =sas.get_value(dsid,"target")
local method =sas.get_value(dsid,"method")
--プログラムヘッダー部分
sas.submit
([[
%macro title;
%nrstr(*---------------------------------------) goto newline;
帳票番号 : @titleno@ goto newline;
表名 : @title@ goto newline;
自動生成日: &SYSDATE9 goto newline;
%nrstr(---------------------------------------*)
%mend title;
]])
--プログラム本体部分
local var="VAR"
if method=='FREQ' then
local var="TABLE"
end
sas.submit
([[
%macro main_pg;
PROC @method@ DATA = @table@; goto newline;
@var@ @target@;goto newline;
RUN;
%mend main_pg;
]])
--実際にファイルを生成する部分
sas.submit
([[
filename outf "D:\temp\@titleno@.sas";
proc stream outfile=outf resetdelim="goto";
begin
/%title/
goto newline;
goto newline;
%main_pg
;;;;
]])
end
sas.close(dsid)
endsubmit;
run;
上記を実行すると2つのSASファイルができて、中身は以下のとおりです。
【Table-1-1.sas】
おのおのの部分をつくるマクロを生成しているLuaの部分を関数化などしてやれば
拡張性に富んだ自動生成コードが作れると思うんですよ。
条件分岐とかループも今までのようにマクロで制御するより、Luaで制御し放題なほうが楽だし。
そもそも21世紀にもなって、ずっとputで平テキスト打ってることずっと違和感あったんですよね
医薬業界はもはや言うまでもなくですが、データセットや解析仕様の標準化が進んでいる環境において活かせそうな発想だと思いますけど、どうでしょう?
proc Luaの世界⑥proc Luaとマクロ変数のカンケイ
珍しく記事連続投稿です。
一つ前の記事「パラメータマクロでやっていた処理をLuaで組むのはどういう感じななのか?の話」
http://sas-tumesas.blogspot.jp/2016/10/lua.html
も是非読んでもらえると嬉しいです。
僕は、処理をまるごとマクロにしてパラメータ与えて実行するマクロは、基本的にLuaで置き換えることができるし、そちらの方が良いと思っています。
%macroをSASコードから1行残らず駆逐してやる!とまでは思わないですが、マクロ一党支配の現状はちょっと変えていきたいですね。何年かかるかわかりませんが。
そんな%macroあんまり好きでない僕ですが、マクロ変数の仕組みは割と好きです。シンプルで強力なテキスト展開なので、、。
今日はProc Lua内でマクロ変数の値を取得する&Proc Lua内でマクロ変数を作成する話です。
さて、今、ふたつのマクロ変数を作ります。
%let m1=I;
%let m2=SAS;
通常、SASコード内に「&マクロ変数名」があると実行時に展開されますが、
以下のコードを実行してみると
proc lua restart;
submit;
print("&m1".. " love ".. "&m2")
endsubmit;
run;
はい、結果はこんな感じです
ダブルコーテーション使ってるのに&のままです。
proc luaの中は、鉄壁の不可侵領域で、一切マクロ変数の展開はおきません。
この仕様、僕は大好きですね。変にクォート処理とかに頭使わなくていいので気が楽です。
じゃあ、proc lua内にマクロ変数持ち込めないんですか?って話になりますけど
もちろんそんなわけないです。
ひとつは、普段のデータステップでもおなじみのsymget関数を使う。
proc lua restart;
submit;
print(sas.symget("m1").. " love ".. sas.symget("m2"))
endsubmit;
run;
結果は
或いは、以下のように
proc lua restart ;
submit "m1='&m1'; m2='&m2'";
print(m1 .. " love " .. m2)
endsubmit;
run;
submitの後のコーテーションの中で
割り当てることにより、Luaのローカル変数にマクロ変数の値を渡せます。
テキストとして渡したいので'&m1'と、コーテーションでくくっていることに注意です。
もしコーテーションでくくらずに、文字を渡した場合、それ自体がLuaの変数を指定していると
みなされるので、例えば以下のようなこともできます
proc lua restart;
submit;
I='YOU'
SAS=' LOVE Lua'
endsubmit;
run;
proc lua;
submit "m1=&m1;m2=&m2";
print(m1 .. m2)
endsubmit;
run;
ちなみにちょっと注意ですが、Proc Lua内ではマクロ変数の展開はおきませんが
proc lua restart;
submit;
sas.submit([[data _null_;
put "&m1 love &m2";
run;
]])
endsubmit;
run;
のようにsas.submitで実行されるSASコードは、実行前に通常と同じくマクロ変数の展開がおきますからね
さて、次は、Lua内でマクロ変数に値をいれる話ですが、簡単なのでさらりと
proc lua restart;
submit;
local x = 1+2+3
local y = 'aaa'
sas.symput('_x',x)
sas.symput('_y',y)
endsubmit;
run;
%put &_x;
%put &_y;
要はsas.symputでいけるという話です。
なので、データセットのオブザベーション数や変数数をマクロ変数に取得するのも超簡単
proc lua;
submit;
local dsid = sas.open("sashelp.class")
local nobs = sas.nobs(dsid)
sas.symput('obs',nobs)
local nvars = sas.nvars(dsid)
sas.symput('vars',nvars)
sas.close(dsid)
endsubmit;
run;
%put オブザベーション数は &obs;
%put 変数の数は &vars;
です
やっぱProc Lua楽しいですね
一つ前の記事「パラメータマクロでやっていた処理をLuaで組むのはどういう感じななのか?の話」
http://sas-tumesas.blogspot.jp/2016/10/lua.html
も是非読んでもらえると嬉しいです。
僕は、処理をまるごとマクロにしてパラメータ与えて実行するマクロは、基本的にLuaで置き換えることができるし、そちらの方が良いと思っています。
%macroをSASコードから1行残らず駆逐してやる!とまでは思わないですが、マクロ一党支配の現状はちょっと変えていきたいですね。何年かかるかわかりませんが。
そんな%macroあんまり好きでない僕ですが、マクロ変数の仕組みは割と好きです。シンプルで強力なテキスト展開なので、、。
今日はProc Lua内でマクロ変数の値を取得する&Proc Lua内でマクロ変数を作成する話です。
さて、今、ふたつのマクロ変数を作ります。
%let m1=I;
%let m2=SAS;
通常、SASコード内に「&マクロ変数名」があると実行時に展開されますが、
以下のコードを実行してみると
proc lua restart;
submit;
print("&m1".. " love ".. "&m2")
endsubmit;
run;
はい、結果はこんな感じです
ダブルコーテーション使ってるのに&のままです。
proc luaの中は、鉄壁の不可侵領域で、一切マクロ変数の展開はおきません。
この仕様、僕は大好きですね。変にクォート処理とかに頭使わなくていいので気が楽です。
じゃあ、proc lua内にマクロ変数持ち込めないんですか?って話になりますけど
もちろんそんなわけないです。
ひとつは、普段のデータステップでもおなじみのsymget関数を使う。
proc lua restart;
submit;
print(sas.symget("m1").. " love ".. sas.symget("m2"))
endsubmit;
run;
結果は
或いは、以下のように
proc lua restart ;
submit "m1='&m1'; m2='&m2'";
print(m1 .. " love " .. m2)
endsubmit;
run;
submitの後のコーテーションの中で
割り当てることにより、Luaのローカル変数にマクロ変数の値を渡せます。
テキストとして渡したいので'&m1'と、コーテーションでくくっていることに注意です。
もしコーテーションでくくらずに、文字を渡した場合、それ自体がLuaの変数を指定していると
みなされるので、例えば以下のようなこともできます
proc lua restart;
submit;
I='YOU'
SAS=' LOVE Lua'
endsubmit;
run;
proc lua;
submit "m1=&m1;m2=&m2";
print(m1 .. m2)
endsubmit;
run;
ちなみにちょっと注意ですが、Proc Lua内ではマクロ変数の展開はおきませんが
proc lua restart;
submit;
sas.submit([[data _null_;
put "&m1 love &m2";
run;
]])
endsubmit;
run;
のようにsas.submitで実行されるSASコードは、実行前に通常と同じくマクロ変数の展開がおきますからね
さて、次は、Lua内でマクロ変数に値をいれる話ですが、簡単なのでさらりと
proc lua restart;
submit;
local x = 1+2+3
local y = 'aaa'
sas.symput('_x',x)
sas.symput('_y',y)
endsubmit;
run;
%put &_x;
%put &_y;
要はsas.symputでいけるという話です。
なので、データセットのオブザベーション数や変数数をマクロ変数に取得するのも超簡単
proc lua;
submit;
local dsid = sas.open("sashelp.class")
local nobs = sas.nobs(dsid)
sas.symput('obs',nobs)
local nvars = sas.nvars(dsid)
sas.symput('vars',nvars)
sas.close(dsid)
endsubmit;
run;
%put オブザベーション数は &obs;
%put 変数の数は &vars;
です
やっぱProc Lua楽しいですね
パラメータマクロでやっていた処理をLuaで組むのはどういう感じななのか?の話
最近いただいた意見で
「Luaをマクロの代わりにできるというイメージがわきません。
例えば、患者背景の一覧表作成に際して,数値データに対して要約統計量をだし、文字データに対しては頻度集計を行うといったマクロを最初にそれぞれ作って、適宜実行し、結果を成型して結合していますが、同じことがLuaでもできますか?」
というのがありました。
どっちでやるのがいいのかという議論はさておき、当然可能です。
細部まで作りこむのが面倒だったので、ざっくりこんな感じかなっていうのを作ってみました。
最初にマクロを定義して、適宜実行ということなので、プログラムの途中途中で呼び出すのだと思いますから、パラメータマクロと同じ機能を、Luaの関数でグローバルにして定義しています。
プログラムごとに管理するなら最初のproc luaにrestartをつけて、そこで一度リセットするようにしておけばよいでしょう。
/*Luaでパラメターマクロっぽく関数を定義*/
proc lua restart;
submit;
--カテゴリデータに対して頻度集計する関数
function freq(ds,var)
local code = [[proc freq noprint
data=@ds@;
tables @var@/out=temp
(rename=(@var@=out2 count=out3));
quit;
data out_@var@;
set temp;
out1="@var@";
run;
]]
sas.submit(code)
end
--数値データに対して要約統計量をだす関数
function summary(ds,var)
local code = [[proc summary noprint
data=@ds@;
var @var@;
output out=temp(drop=_TYPE_ _FREQ_)
mean=Mean std=Std min=Min median=Median max=Max;
quit;
proc transpose data=temp out=out_@var@;
run;
data out_@var@;
set out_@var@;
out1="@var@";
out2=_NAME_;
out3=col1;
run;
]]
sas.submit(code)
end
endsubmit;
quit;
で、あとは実際使用する場所で、いかのようにパラメータを指定して実行します
マクロを実行するようにluaの関数を実行する
proc lua;
submit;
freq("sashelp.class","sex")
summary("sashelp.class","age")
summary("sashelp.class","height")
summary("sashelp.class","weight")
endsubmit;
quit;
/*ただのデータステップで結果を結合すれば*/
data output;
length out1 out2 $50.;
set out_:;
keep out:;
run;
round入れるべきでしたが、まあこんなもんでしょう。
データセットを指定したら、全変数に対して自動で数値型ならsummary 文字ならfreqをかけて
結果の結合までやる関数が欲しければ、以下のようにすればいいでしょう
proc lua;
submit;
function autoreport(ds)
local dsid = sas.open(ds)
for var in sas.vars(dsid) do
if var.type == "C" then
freq(ds,var.name)
end
if var.type == "N" then
summary(ds,var.name)
end
end
sas.close(dsid)
sas.submit([[
data auto_output;
length out1 out2 $50.;
set out_:;
keep out:;
run;
]])
end
endsubmit;
run;
proc lua;
submit;
autoreport("class")
endsubmit;
run;
結果は上とだいたい同じですが、NAMEもカテゴリ集計されちゃうので、そういう部分の処理の工夫は必要ですね。
どうでしょう?作ってる感じとしてはマクロ作るのと、感覚的に大差ないので、スムーズに移行できると思いますが。
「Luaをマクロの代わりにできるというイメージがわきません。
例えば、患者背景の一覧表作成に際して,数値データに対して要約統計量をだし、文字データに対しては頻度集計を行うといったマクロを最初にそれぞれ作って、適宜実行し、結果を成型して結合していますが、同じことがLuaでもできますか?」
というのがありました。
どっちでやるのがいいのかという議論はさておき、当然可能です。
細部まで作りこむのが面倒だったので、ざっくりこんな感じかなっていうのを作ってみました。
最初にマクロを定義して、適宜実行ということなので、プログラムの途中途中で呼び出すのだと思いますから、パラメータマクロと同じ機能を、Luaの関数でグローバルにして定義しています。
プログラムごとに管理するなら最初のproc luaにrestartをつけて、そこで一度リセットするようにしておけばよいでしょう。
/*Luaでパラメターマクロっぽく関数を定義*/
proc lua restart;
submit;
--カテゴリデータに対して頻度集計する関数
function freq(ds,var)
local code = [[proc freq noprint
data=@ds@;
tables @var@/out=temp
(rename=(@var@=out2 count=out3));
quit;
data out_@var@;
set temp;
out1="@var@";
run;
]]
sas.submit(code)
end
--数値データに対して要約統計量をだす関数
function summary(ds,var)
local code = [[proc summary noprint
data=@ds@;
var @var@;
output out=temp(drop=_TYPE_ _FREQ_)
mean=Mean std=Std min=Min median=Median max=Max;
quit;
proc transpose data=temp out=out_@var@;
run;
data out_@var@;
set out_@var@;
out1="@var@";
out2=_NAME_;
out3=col1;
run;
]]
sas.submit(code)
end
endsubmit;
quit;
で、あとは実際使用する場所で、いかのようにパラメータを指定して実行します
マクロを実行するようにluaの関数を実行する
proc lua;
submit;
freq("sashelp.class","sex")
summary("sashelp.class","age")
summary("sashelp.class","height")
summary("sashelp.class","weight")
endsubmit;
quit;
/*ただのデータステップで結果を結合すれば*/
data output;
length out1 out2 $50.;
set out_:;
keep out:;
run;
round入れるべきでしたが、まあこんなもんでしょう。
データセットを指定したら、全変数に対して自動で数値型ならsummary 文字ならfreqをかけて
結果の結合までやる関数が欲しければ、以下のようにすればいいでしょう
proc lua;
submit;
function autoreport(ds)
local dsid = sas.open(ds)
for var in sas.vars(dsid) do
if var.type == "C" then
freq(ds,var.name)
end
if var.type == "N" then
summary(ds,var.name)
end
end
sas.close(dsid)
sas.submit([[
data auto_output;
length out1 out2 $50.;
set out_:;
keep out:;
run;
]])
end
endsubmit;
run;
proc lua;
submit;
autoreport("class")
endsubmit;
run;
結果は上とだいたい同じですが、NAMEもカテゴリ集計されちゃうので、そういう部分の処理の工夫は必要ですね。
どうでしょう?作ってる感じとしてはマクロ作るのと、感覚的に大差ないので、スムーズに移行できると思いますが。
proc Luaの世界⑤SASデータセットをLuaのテーブルに取り込んでみる話
しばらく間があいてしまいましたが、僕のProcLua魂はまだ燃えてます
自分の何に変えても必ずやProc Luaを普及しなければならないという謎の使命感があります。
SASのやりすぎで、ちょっと頭のどこかがおかしくなったのかもしれませんね。
さて、僕は今のところproc luaを実行されるsasコードの処理分岐に使うことが多く、
SASデータセットの存在を確認したり、オブザベーション数や、含まれる変数の数、変数名など、いわゆるメタデータを取得して、実行SASコードを合成するといった、SASマクロの代替法としての使い方を主としてます。SASマクロの在り方に美しさを感じれない性があるので…
しかし、処理コードをどうこうするのではなく
Proc Lua自体でSASデータセットそのものを操作したり
データセットをLuaのテーブルに取り込んで処理をして、そこからLuaのテーブルを
SASデータセットにして戻すといったことも当然できます。
今日はSASデータセットをLuaのテーブルに取り込む話をします。
ただ、個人的な感触としてはがっつりとしたデータハンドリングは、敢えてLuaテーブルに取り込んでやるよりSASコードで捌いた方がいいかなぁとは思います。
なんでかっていうと、Luaのテーブルはメモリ上にあるので、あんまり大きいデータセットをテーブルにするのはパフォーマンス的に危険じゃないかなって点と、単純にSASデータセットの操作するのはSASコードが一番書きやすいですしね…
あとLuaのテーブルはちょっと柔軟すぎて、僕も含めてSASデータセットに慣れた頭には結構ついていけないとこもあるのです。
とはいえLuaのテーブルならではの処理の仕方も今後紹介する予定なので、
データの大きさに留意するという点を踏まえつつ、見てきましょう。
まずはテストデータ
data a;
x=1;y='A';output;
x=2;y='B';output;
label x='ラベル';
format x z2.;
run;
ではLuaからこのデータセットaにアクセスして、
xとyの値をログに書きだして見ましょう
proc lua;
submit;
--データセット内の変数を指定してプリントしてみる
local dsid = sas.open("a")
while sas.next(dsid) do
print(sas.get_value(dsid,"x"), sas.get_value(dsid,"y"))
end
sas.close(dsid)
endsubmit;
quit;
結果は以下のとおり
普段通常のデータステップにはSCL関数系を使い慣れている人にとっては、
書き方が似ているのでピンとくるかと思います。
まずsas.open("a")でデータセットを開いて、そのデータセットIDを取得します。
sas.next(対象データセットID) は順番にobsを移動していって、最終obsまでループします。
sas.get_value(対象データセットID,変数名)で指定した変数に入っている値を取得できます。
取得した値をそのままluaのprint関数ではいているわけです。
あと、これは鉄則ですがopenで開いたsasデータセットは必ず sas.close(dsid)で閉じなければ
いけません。
さて、毎度、getvalueに変数名を直打ちして指定するには正直面倒です。
存在する変数全ての名前を自動的に取得して、値を取りたくなります。
変数情報はどうやって取得するのかというと
proc lua;
submit;
--イテレータ関数sas.varsを使って、全変数の変数情報をプリントしてみる
local dsid = sas.open("a")
local table_1 = {}
for var in sas.vars(dsid) do
print(var.name)
print(var.label)
print(var.length)
print(var.format)
end
sas.close(dsid)
endsubmit;
run;
結果は以下のとおり
変数yにはラベルもフォーマットもついていないので変数名だけがプリントされています。
for var in sas.vars(dsid) do の書き方についてですが以前紹介した pairsと同じでイテレータ関数というもので、こう書くことによって、varにデータセット内の変数が順番に入っていきます。
さてさて、そういえば今回やりたかったのはSASデータセットをLuaのテーブルにすることでしたね。
今まででてきたことに加えて、Lua備え付けのテーブルライブラリ関数のtable.insertを使ってそれを実現してみましょう
proc lua;
submit;
--応用してSASデータセットをLuaのテーブルに入れてみる
--変数名ごとにobsをキーにした子テーブルを作っている構造
local dsid = sas.open("a")
local table_1 = {}
for var in sas.vars(dsid) do
table_1[var.name] = {}
end
while sas.next(dsid) do
for i, v in pairs(table_1) do
table.insert(table_1[i], sas.get_value(dsid, i))
end
end
sas.close(dsid)
print(table.tostring(table_1))
endsubmit;
run;
結果は
あ、ちなみにtostringで吐いた場合、出力順は固定じゃないので、xの前にyがきていても不思議じゃないですからね
構造を説明すると、今回table_1というLuaのテーブルにデータセットaの内容を入れたわけですが
まずLuaのtable_aは{ x={},y={} }というようにxとyのテーブルを中にもっていて、
xとyの中身はx[1]=1 x[2]=2 y[1]='A' y[2]='B'となっているわけです。
つまりデータセットを表現するのに、変数ごとに子テーブルを作り、オブゼベーション番号を要素キーにして表現したわけです。
別のやり方を見てみましょう。実はもっとお手軽にsas.load_ds(データセット名)で一発でSASデータセットをLuaのテーブルにできます。
しかし、その取り込まれ方にはちょっと癖があります
proc lua;
submit;
--load_dsでデータセットを読み込んでみる
table_2=sas.load_ds("a")
print(table.tostring(table_2))
endsubmit;
quit;
結果、

さっきとは少し違い、まずオブザベーション番号ごとに子テーブルをつくって、そこの変数名をキーとしてデータを持たせています。
さらにvarsテーブルというのが自動で作られ、それは変数名ごとの子テーブルを持ち、そこには変数タイプやラベルフォーマットなどのメタ情報が入っています。
さらにnameテーブルにはデータセット名、nvarsテーブルには変数の数が入ります。
要するにsas.load_dsでデータセットを読み込むと本体のデータ部分以外にコンテンツ情報も子テーブルにして入れてくれるというわけです。
はい、今日はここまで、次はもうちょっと簡単で具体的な話をいれたいなぁと思います
自分の何に変えても必ずやProc Luaを普及しなければならないという謎の使命感があります。
SASのやりすぎで、ちょっと頭のどこかがおかしくなったのかもしれませんね。
さて、僕は今のところproc luaを実行されるsasコードの処理分岐に使うことが多く、
SASデータセットの存在を確認したり、オブザベーション数や、含まれる変数の数、変数名など、いわゆるメタデータを取得して、実行SASコードを合成するといった、SASマクロの代替法としての使い方を主としてます。SASマクロの在り方に美しさを感じれない性があるので…
しかし、処理コードをどうこうするのではなく
Proc Lua自体でSASデータセットそのものを操作したり
データセットをLuaのテーブルに取り込んで処理をして、そこからLuaのテーブルを
SASデータセットにして戻すといったことも当然できます。
今日はSASデータセットをLuaのテーブルに取り込む話をします。
ただ、個人的な感触としてはがっつりとしたデータハンドリングは、敢えてLuaテーブルに取り込んでやるよりSASコードで捌いた方がいいかなぁとは思います。
なんでかっていうと、Luaのテーブルはメモリ上にあるので、あんまり大きいデータセットをテーブルにするのはパフォーマンス的に危険じゃないかなって点と、単純にSASデータセットの操作するのはSASコードが一番書きやすいですしね…
あとLuaのテーブルはちょっと柔軟すぎて、僕も含めてSASデータセットに慣れた頭には結構ついていけないとこもあるのです。
とはいえLuaのテーブルならではの処理の仕方も今後紹介する予定なので、
データの大きさに留意するという点を踏まえつつ、見てきましょう。
まずはテストデータ
data a;
x=1;y='A';output;
x=2;y='B';output;
label x='ラベル';
format x z2.;
run;
ではLuaからこのデータセットaにアクセスして、
xとyの値をログに書きだして見ましょう
proc lua;
submit;
--データセット内の変数を指定してプリントしてみる
local dsid = sas.open("a")
while sas.next(dsid) do
print(sas.get_value(dsid,"x"), sas.get_value(dsid,"y"))
end
sas.close(dsid)
endsubmit;
quit;
結果は以下のとおり
普段通常のデータステップにはSCL関数系を使い慣れている人にとっては、
書き方が似ているのでピンとくるかと思います。
まずsas.open("a")でデータセットを開いて、そのデータセットIDを取得します。
sas.next(対象データセットID) は順番にobsを移動していって、最終obsまでループします。
sas.get_value(対象データセットID,変数名)で指定した変数に入っている値を取得できます。
取得した値をそのままluaのprint関数ではいているわけです。
あと、これは鉄則ですがopenで開いたsasデータセットは必ず sas.close(dsid)で閉じなければ
いけません。
さて、毎度、getvalueに変数名を直打ちして指定するには正直面倒です。
存在する変数全ての名前を自動的に取得して、値を取りたくなります。
変数情報はどうやって取得するのかというと
proc lua;
submit;
--イテレータ関数sas.varsを使って、全変数の変数情報をプリントしてみる
local dsid = sas.open("a")
local table_1 = {}
for var in sas.vars(dsid) do
print(var.name)
print(var.label)
print(var.length)
print(var.format)
end
sas.close(dsid)
endsubmit;
run;
結果は以下のとおり
変数yにはラベルもフォーマットもついていないので変数名だけがプリントされています。
for var in sas.vars(dsid) do の書き方についてですが以前紹介した pairsと同じでイテレータ関数というもので、こう書くことによって、varにデータセット内の変数が順番に入っていきます。
さてさて、そういえば今回やりたかったのはSASデータセットをLuaのテーブルにすることでしたね。
今まででてきたことに加えて、Lua備え付けのテーブルライブラリ関数のtable.insertを使ってそれを実現してみましょう
proc lua;
submit;
--応用してSASデータセットをLuaのテーブルに入れてみる
--変数名ごとにobsをキーにした子テーブルを作っている構造
local dsid = sas.open("a")
local table_1 = {}
for var in sas.vars(dsid) do
table_1[var.name] = {}
end
while sas.next(dsid) do
for i, v in pairs(table_1) do
table.insert(table_1[i], sas.get_value(dsid, i))
end
end
sas.close(dsid)
print(table.tostring(table_1))
endsubmit;
run;
結果は
あ、ちなみにtostringで吐いた場合、出力順は固定じゃないので、xの前にyがきていても不思議じゃないですからね
構造を説明すると、今回table_1というLuaのテーブルにデータセットaの内容を入れたわけですが
まずLuaのtable_aは{ x={},y={} }というようにxとyのテーブルを中にもっていて、
xとyの中身はx[1]=1 x[2]=2 y[1]='A' y[2]='B'となっているわけです。
つまりデータセットを表現するのに、変数ごとに子テーブルを作り、オブゼベーション番号を要素キーにして表現したわけです。
別のやり方を見てみましょう。実はもっとお手軽にsas.load_ds(データセット名)で一発でSASデータセットをLuaのテーブルにできます。
しかし、その取り込まれ方にはちょっと癖があります
proc lua;
submit;
--load_dsでデータセットを読み込んでみる
table_2=sas.load_ds("a")
print(table.tostring(table_2))
endsubmit;
quit;
結果、

さっきとは少し違い、まずオブザベーション番号ごとに子テーブルをつくって、そこの変数名をキーとしてデータを持たせています。
さらにvarsテーブルというのが自動で作られ、それは変数名ごとの子テーブルを持ち、そこには変数タイプやラベルフォーマットなどのメタ情報が入っています。
さらにnameテーブルにはデータセット名、nvarsテーブルには変数の数が入ります。
要するにsas.load_dsでデータセットを読み込むと本体のデータ部分以外にコンテンツ情報も子テーブルにして入れてくれるというわけです。
はい、今日はここまで、次はもうちょっと簡単で具体的な話をいれたいなぁと思います
proc Luaの世界④Luaの関数は柔軟で奥が深いな~って話
Fukiyaさんの新作記事「Proc Luaを勉強してみての感想とProc Luaをオブジェクト指向プログラミグの試み」が激アツいです。
今日は関数のお勉強になるんですが、これがまた深いところなんで、いつも言ってますが
とりあえず簡単なとこだけやります。
僕もまだまだよくわかってなくて、本当はFukiyaさんかmatsuさんに解説してもらってから記事書きたいぐらいです。間違ったこと言ってたら殴ってくださいね
さて
と、そのまえに前回記事の補足です
sas.submit([[SASコード]],{置換パラメータ指定})の
置換パラメータ指定の部分ですが、別にsubmitの中で指定しなくても
実行時点で置換パラメータがLuaの中で変数として定義されていればそれが適応されます
ので以下のコードも成立します。
proc lua;
submit;
local ds="b"
local var="y"
local code=[[data @ds@;
@var@=1;
run;]]
sas.submit(code)
endsubmit;
quit;
あと、さらに唐突な補足です。Luaの論理式はFalseとnilが偽でそれ以外が真という話をしました。それに絡んで面白い性質があって、以下のような代入式(SASでいう割り当てステートメント)において左辺をorで結ぶと、左から順に評価して、偽である値はスキップされ、初めて真である値が代入されます
コードと結果を見てください
proc lua;
submit;
local a = 99;
local b = nil;
local v1 = b or a
local v2 = (1>2) or a
local v3 = d or e or f or a or 88
local v4 = g or h
print(v1)
print(v2)
print(v3)
print(v4)
endsubmit;
quit;
結果
それがなんじゃい?なにに使うんじゃい?と思われた方はちょっと甘い!
X=Y or 99 は、YがあればYの値を使うし、Yがなければ99を使う。初期化されていない変数にはnilが入っている。それらの性質を使うと、パラメータのデフォルト値を設定できるということなんですよ。
SASマクロにも、キーワードパラメータが指定されなかったときにはこの値を使うみたいな書き方できるでしょう、あれと同じことができるんですね
はい、いよいよ本題です。
Luaの関数ですが、以下のように作って、使います
proc lua;
submit;
--定義
function add(a,b)
return a+b
end
--型もみてみよう
print(type(add))
--早速使ってみる
print(add(1,2))
print(add(1,5))
endsubmit;
quit;
結果
文法は
function 関数名(引数)
戻り値があるならreturn 戻り値
end
です。簡単ですね。
ただ、細かい話なんですが、正確に言うと、Luaには「関数名」っていう考え方は間違ってるんです。
関数名というものは存在しません。
はぁ?って話なんですが、以下のコードを見てください
proc lua;
submit;
--[[厳密にいうとLuaの関数に名前はない(無名関数)、
関数を変数に入れて使っており、変数に名前がついている
]]--
add=function(a,b)
return a+b
end
endsubmit;
quit;
実は最初に紹介したのは、こういう書き方もできるって話で糖衣構文みたいなもんです。
本質的には
変数名 = function(引数)
戻り値があるならreturn 戻り値
end
なんです。これは無名関数といって、本当は関数自体には名前がなくて、その名前のない処理を
変数に入れる。変数には名前がある。ので関数の入ってる変数名がいわゆる関数名みたいになるということです。
だからそれがどうしたって話なんですけど、一応知っておいてください。
いずれ複雑なことをするときに必要になるので。
さて、わけのわからん話はやめて、続きです。
冒頭に紹介したorを使えば、関数の引数を省略した場合の挙動を制御できますよという話
proc lua;
submit;
function add(a,b)
local a= a or 3
local b= b or 5
return a+b
end
print(add(aaa))
print(add(1))
print(add(nill,1))
endsubmit;
quit;
結果
そして、引数の部分について以下のようにハッシュ型の変数定義も使うことができます
こうすると本当キーワードパラメータマクロのようですね
proc lua;
submit;
function add(para)
para.a= para.a or 3
para.b= para.b or 5
return para.a+para.b
end
print(add({a=2,b=2}))
print(add({a=2}))
print(add({b=2}))
endsubmit;
quit;
結果
戻り値のない関数も全然ありなので
たとえば、以下のようなSASコードの実行を関数にすることもできますよっと
proc lua;
submit;
function ds_make(para)
local ds= para.ds or "aaa"
local var= para.var or "bbb"
sas.submit([[data @ds@;@var@=1;run;]])
end
ds_make({ds="ds1",var="var1"})
ds_make({})
endsubmit;
quit;
次に、引数の数がが可変の場合の定義です。
以下のように...とかきます。その後、関数の中でテーブルに入れて
捌いてやればOK。
引数がいくつでも動く関数ができます
proc lua;
submit;
function add3(...)
list = {...}
result=0
for val in pairs(list) do
result =result+val
end
return (result)
end
print(add3(1,2))
print(add3(1,2,3,4))
endsubmit;
quit;
結果
・・・で引数を定義するのが面白いですね、受け取ったらまずテーブルに入れて捌いてます。
ループで足してます。sum関数みたいなのができましたね。
逆に、戻り値が複数あるパターン。
海外論文にも載ってた(論文のコードには誤植あるけど)、本日日付を年月日に分解して
3つの戻り値をえるコードです
proc lua;
submit;
function split_date()
local date = sas.date()
return sas.day(date), sas.month(date), sas.year(date)
end
local day, month, year = split_date()
print("d=", day, "m=", month, "y=",year)
endsubmit;
quit;
結果(実行したのは2016/8/30です)
さらに、戻り値をテーブルにしたり、テーブルを引数にするパターンを見てみましょう。
まず、与えた文字列を1文字ずつ分解してテーブルを返すsplit_char関数をつくってみて
その後、テーブルを受け取ってまた1つの文字にするcats_table関数を作ってみました。
ただテーブルから文字をつくるのは普通にtable.concat関数というものがあって、これだと連結文字や対象範囲も指定できて、絶対こっちの方がいいです。車輪の再発明も勉強にはなりますけどね
proc lua restart;
submit;
--文字列を1文字ずつ取り出してテーブルにする関数(戻り値はテーブル)
function split_char(char)
local result_table={}
local len=string.len(char)
for i =1,len do
result_table[i]=string.sub(char, i,i)
end
return(result_table)
end
local tb1=split_char("abc")
print("★文字列が分解されてテーブルになりました→",table.tostring(tb1))
--テーブルを引数にして、テーブルの中身を連結して1つの文字値にする関数
function cats_table(table)
local result=''
for i, item in pairs(table) do
result = result..item
end
return result
end
local st1=cats_table(tb1)
print("★テーブルが連結されて文字列にになりました→",st1)
local st2=table.concat(tb1,"/")
print("★concat関数はcatxみたいで便利だな→",st2)
endsubmit;
quit;
結果
なんかFCMPよりだいぶ書きやすいなぁって思うのは僕だけしょうか。
--おまけ
ちょっと難しい話になりますが、
関数は、変数に過ぎないので、関数の中に関数を入れることができ
それを使うと関数を作る関数とかも定義できます
proc lua;
submit;
function concat3_func(x,y,z)
print(x..' '..y..' '..z)
end
function make_func(x,y)
return function (target)
concat3_func(x, y, target)
end
end
i_love_func =make_func("I","love")
you_love_func =make_func("You","love")
i_study_func =make_func("I","study")
print(i_love_func("SAS"))
print(you_love_func("SAS"))
print(i_study_func("Lua"))
endsubmit;
quit;
こういうのの詳しい話はまたいずれ。とりあえず今日はここまで!!
今日は関数のお勉強になるんですが、これがまた深いところなんで、いつも言ってますが
とりあえず簡単なとこだけやります。
僕もまだまだよくわかってなくて、本当はFukiyaさんかmatsuさんに解説してもらってから記事書きたいぐらいです。間違ったこと言ってたら殴ってくださいね
さて
と、そのまえに前回記事の補足です
sas.submit([[SASコード]],{置換パラメータ指定})の
置換パラメータ指定の部分ですが、別にsubmitの中で指定しなくても
実行時点で置換パラメータがLuaの中で変数として定義されていればそれが適応されます
ので以下のコードも成立します。
proc lua;
submit;
local ds="b"
local var="y"
local code=[[data @ds@;
@var@=1;
run;]]
sas.submit(code)
endsubmit;
quit;
あと、さらに唐突な補足です。Luaの論理式はFalseとnilが偽でそれ以外が真という話をしました。それに絡んで面白い性質があって、以下のような代入式(SASでいう割り当てステートメント)において左辺をorで結ぶと、左から順に評価して、偽である値はスキップされ、初めて真である値が代入されます
コードと結果を見てください
proc lua;
submit;
local a = 99;
local b = nil;
local v1 = b or a
local v2 = (1>2) or a
local v3 = d or e or f or a or 88
local v4 = g or h
print(v1)
print(v2)
print(v3)
print(v4)
endsubmit;
quit;
結果
それがなんじゃい?なにに使うんじゃい?と思われた方はちょっと甘い!
X=Y or 99 は、YがあればYの値を使うし、Yがなければ99を使う。初期化されていない変数にはnilが入っている。それらの性質を使うと、パラメータのデフォルト値を設定できるということなんですよ。
SASマクロにも、キーワードパラメータが指定されなかったときにはこの値を使うみたいな書き方できるでしょう、あれと同じことができるんですね
はい、いよいよ本題です。
Luaの関数ですが、以下のように作って、使います
proc lua;
submit;
--定義
function add(a,b)
return a+b
end
--型もみてみよう
print(type(add))
--早速使ってみる
print(add(1,2))
print(add(1,5))
endsubmit;
quit;
結果
文法は
function 関数名(引数)
戻り値があるならreturn 戻り値
end
です。簡単ですね。
ただ、細かい話なんですが、正確に言うと、Luaには「関数名」っていう考え方は間違ってるんです。
関数名というものは存在しません。
はぁ?って話なんですが、以下のコードを見てください
proc lua;
submit;
--[[厳密にいうとLuaの関数に名前はない(無名関数)、
関数を変数に入れて使っており、変数に名前がついている
]]--
add=function(a,b)
return a+b
end
endsubmit;
quit;
実は最初に紹介したのは、こういう書き方もできるって話で糖衣構文みたいなもんです。
本質的には
変数名 = function(引数)
戻り値があるならreturn 戻り値
end
なんです。これは無名関数といって、本当は関数自体には名前がなくて、その名前のない処理を
変数に入れる。変数には名前がある。ので関数の入ってる変数名がいわゆる関数名みたいになるということです。
だからそれがどうしたって話なんですけど、一応知っておいてください。
いずれ複雑なことをするときに必要になるので。
さて、わけのわからん話はやめて、続きです。
冒頭に紹介したorを使えば、関数の引数を省略した場合の挙動を制御できますよという話
proc lua;
submit;
function add(a,b)
local a= a or 3
local b= b or 5
return a+b
end
print(add(aaa))
print(add(1))
print(add(nill,1))
endsubmit;
quit;
結果
そして、引数の部分について以下のようにハッシュ型の変数定義も使うことができます
こうすると本当キーワードパラメータマクロのようですね
proc lua;
submit;
function add(para)
para.a= para.a or 3
para.b= para.b or 5
return para.a+para.b
end
print(add({a=2,b=2}))
print(add({a=2}))
print(add({b=2}))
endsubmit;
quit;
結果
戻り値のない関数も全然ありなので
たとえば、以下のようなSASコードの実行を関数にすることもできますよっと
proc lua;
submit;
function ds_make(para)
local ds= para.ds or "aaa"
local var= para.var or "bbb"
sas.submit([[data @ds@;@var@=1;run;]])
end
ds_make({ds="ds1",var="var1"})
ds_make({})
endsubmit;
quit;
次に、引数の数がが可変の場合の定義です。
以下のように...とかきます。その後、関数の中でテーブルに入れて
捌いてやればOK。
引数がいくつでも動く関数ができます
proc lua;
submit;
function add3(...)
list = {...}
result=0
for val in pairs(list) do
result =result+val
end
return (result)
end
print(add3(1,2))
print(add3(1,2,3,4))
endsubmit;
quit;
結果
・・・で引数を定義するのが面白いですね、受け取ったらまずテーブルに入れて捌いてます。
ループで足してます。sum関数みたいなのができましたね。
逆に、戻り値が複数あるパターン。
海外論文にも載ってた(論文のコードには誤植あるけど)、本日日付を年月日に分解して
3つの戻り値をえるコードです
proc lua;
submit;
function split_date()
local date = sas.date()
return sas.day(date), sas.month(date), sas.year(date)
end
local day, month, year = split_date()
print("d=", day, "m=", month, "y=",year)
endsubmit;
quit;
結果(実行したのは2016/8/30です)
さらに、戻り値をテーブルにしたり、テーブルを引数にするパターンを見てみましょう。
まず、与えた文字列を1文字ずつ分解してテーブルを返すsplit_char関数をつくってみて
その後、テーブルを受け取ってまた1つの文字にするcats_table関数を作ってみました。
ただテーブルから文字をつくるのは普通にtable.concat関数というものがあって、これだと連結文字や対象範囲も指定できて、絶対こっちの方がいいです。車輪の再発明も勉強にはなりますけどね
proc lua restart;
submit;
--文字列を1文字ずつ取り出してテーブルにする関数(戻り値はテーブル)
function split_char(char)
local result_table={}
local len=string.len(char)
for i =1,len do
result_table[i]=string.sub(char, i,i)
end
return(result_table)
end
local tb1=split_char("abc")
print("★文字列が分解されてテーブルになりました→",table.tostring(tb1))
--テーブルを引数にして、テーブルの中身を連結して1つの文字値にする関数
function cats_table(table)
local result=''
for i, item in pairs(table) do
result = result..item
end
return result
end
local st1=cats_table(tb1)
print("★テーブルが連結されて文字列にになりました→",st1)
local st2=table.concat(tb1,"/")
print("★concat関数はcatxみたいで便利だな→",st2)
endsubmit;
quit;
結果
なんかFCMPよりだいぶ書きやすいなぁって思うのは僕だけしょうか。
--おまけ
ちょっと難しい話になりますが、
関数は、変数に過ぎないので、関数の中に関数を入れることができ
それを使うと関数を作る関数とかも定義できます
proc lua;
submit;
function concat3_func(x,y,z)
print(x..' '..y..' '..z)
end
function make_func(x,y)
return function (target)
concat3_func(x, y, target)
end
end
i_love_func =make_func("I","love")
you_love_func =make_func("You","love")
i_study_func =make_func("I","study")
print(i_love_func("SAS"))
print(you_love_func("SAS"))
print(i_study_func("Lua"))
endsubmit;
quit;
こういうのの詳しい話はまたいずれ。とりあえず今日はここまで!!
登録:
投稿 (Atom)




















