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でデータセットを読み込むと本体のデータ部分以外にコンテンツ情報も子テーブルにして入れてくれるというわけです。

はい、今日はここまで、次はもうちょっと簡単で具体的な話をいれたいなぁと思います