sgplotで日本地図を書いて新入社員の気をひこうって話

春ですね。
新SASプログラマーが増えるこの季節。

ベテランのSAS使いである皆様方は基本的には無口か、話が面白くない人が多いと思うので(偏見)、新人が気を使って適当な話題をふろうとして「SAS社ってどこにあるんですか?」とか当たり障りのないことを訊いたりするわけですよ。

そこで「六本木にあるよ」とか答えてるようじゃ、クソですよ。面白くもなんともない。

そういうときは敢えて何も答えずに、高速タイピングを駆使して
以下のプログラムを打ち込んで(5秒以内が望ましい)、F3かF8をターン!!とおっさんらしく、うるさく弾いて実行!!

data my_map;
set mapsgfk.japan end=eof;
ID=cats(ID,SEGMENT);
output;
/*緯度経度を追加*/
if eof then do;
 XX=139.729075;
 YY=35.660409;
 text="このへんにあるよ";
 output;
end;
run;

proc sgplot data=my_map noborder noautolegend;
/*地図*/
polygon x=LONG y=LAT id=id /
 fill outline lineattrs=(color=lightGray thickness=1)
 fillattrs=(color=Azure);
/*散布図とテキストを重ねてる*/
scatter x=XX y=YY;
text x=xx y=YY text=text/position=BOTTOMRIGHT ;
xaxis display=none;
yaxis display=none;
run;

アウトプットにどかんと





















となるわけです。(散布図は六本木ヒルズの緯度経度を指定してます)
なんて、ユーモアがあってデキる人だ!一生ついていきます!となること請け合いです。

まあ、真に受けて実践したら、キモがられること請け合いですけどね。

でも、たまにはSASのお勉強の間に、こういうの挟むのもいいんじゃないですかね。
(僕のようなふざけたSAS使いになってしまっても責任はとれない)

さて、本題。
sgplotのpolygonステートメントは凄い便利。
あとtextプロットが地味だけどスゲー使える。
gmapとちがってSAS GRAPHのライセンスもいらないし、緯度経度データがあればすぐかける。しかも他のグラフと簡単に重ねれるので、エリアごとの人口とか売上とかを図示するのも比較的かんたんにかける。

解析対象集団の構成フロー図とか、従来なら図としても表としてもプログラミングしにくい系のものも簡単に作れるし、こりゃあ役に立ちますぜ、奥さん。



Proc Luaのsas.write_dsに流す際にvarsテーブルで定義情報を制御してやろうじゃないって話

忘備録の「PROC LUAによる変数作成と行追加のはなし」を読み返してふと思ったこと
(http://sas-boubi.blogspot.jp/2016/09/proc-lua_26.html#comment-form)

既存データセットを上書き更新せずに変数追加はできない。
それはよくわかる。仕様ならしようがない。

代わりにsas.submitでコード流してくれとSAS社からのお達し。
SASデータセットの操作はSASコード実行するのが一番速いので、SAS社の言ってることは正しい。わかる。

けど、それじゃ、Luaで操作してるって感じがないから残念っていうmatsuさんの気持ちも凄いわかる。

じゃあさ、どうせ、setでデータセット読み込んで、データセットを上書き更新しちゃうならsas.load_dsでデータセットをLuaのテーブルにしちゃってさ、
ディスクリプタ部に該当するvars子テーブルの中身をいじって再度write_dsしちゃうのはどう?
処理時間的には非効率だけど、Lua書いてるって感じがして面白いのと、
Lua固有の操作で属性に関与できるから可能性のある考えじゃない?っていう提案。

要するに以下みたいなこと


data Q1;
VAR1=1;VAR2="A";output;
VAR1=2;VAR2="B";output;
run;









proc lua ;
submit;
local TB=sas.load_ds("Q1")
TB.vars.var3={type="C",length=20,label="ラベル"}
sas.write_ds(TB,Q1)
endsubmit;
quit;











write_dsはload_dsで作成される構造と同じテーブルを渡すことで
LuaテーブルからSASデータセットを作ってくれる。

おさらいしておくと、load_dsで作成される構造とは

proc lua;
submit;
local TB=sas.load_ds("Q1")
print(table.tostring(TB))
endsubmit;
quit;

で、自分で確認してほしんですけど(画像でかくなるから)、
テーブル直下に[1]とか[2]とかってobs番号がキーになって子テーブルができて、その中に
各変数名がキーになって値がはいる。
また、それとともにキー[vars]として子テーブルができて,その中に変数名がキーになって子テーブルができて、その中にさらに属性値名がキーになって値が入るわけです。
テーブルの入れ子構造で、本当面白い。

ちなみに、当然の疑問として、varsテーブルを作らずにwrite_dsするとどうなるかという話ですが、以下を実行してみてください。

proc lua ;
submit;
local TB={[1]={X=1,Y="A"}}
sas.write_ds(TB,"Q2")
endsubmit;
quit;

文字値のレングスのデフォが何故か500になったりするから要注意ね。

あと、Luaのテーブルに変換して、またSASデータセットに変換するという迂回をしてるんで速度はsas.submitで捌くのに比べると当然、ゲロ遅くなります。

あと当たり前だけど、ハッシュ型において、絶対的な格納位置みたいな概念ないから
SASデータセットにした際の変数位置って、どうやって制御したもんかなっていうのも課題としてあります。
正直、データハンドリングの本質的には、変数の格納位置なんて本来はどうでもいい。
そこを処理のとっかかりにしたり、やたら縛られるSASが独特なわけで。
本当は、んなもん飾りですよ。偉い人にはそれが、、、。





変数の値の中で、データを分解してソートしたり重複を除いたりしてみる

自分で問題を作って、自分で頑張って解いて
自分で自分を褒めるという悲しい遊びを繰り返す可哀想な子です。

例えば、以下のようなデータがあって、

data Q1;
length x $1000.;
x="3,1,2,5,3";output;
x="1,2,3";output;
x="1,1,1,3,2,2,2,4";output;
x="2";output;
x="";output;
x="999,15,3";output;
run;














xの中身をカンマで分解して、取り出した数字をソートして、重複をとって、再びカンマでつなぎ合わせて戻す。
ただし、入る数字は飛び番もあり、カンマの数も上限がないとする。
上限値を設定することなく、最速最短ステップで解け。

そういう問題に対して、以下のように書いてみました。

data A1;
set Q1;
if _N_=1 then do;
declare hash h1(ordered:"A");
declare hiter hi1("h1");
h1.definekey("val","valc");
h1.definedone();
end;

do i=1 to count(x,",") +1 ;
valc=scan(x,i,",");
val=input(valc,best.);
h1.ref();
end;

   rc = hi1.first();
   _x=valc;
   do while (rc = 0);
  if _x ne valc then _x=catx(",", _x,  valc );
      rc = hi1.next();
   end;

h1.clear();

keep x _x;
run;















パチパチパチ、できたできた。あんたはエライ!

あってるかどうか、最善手かどうかはわからない。

面白ポイントとしては
ハッシュオブジェクトでキー重複除去と並び替えを行って
それをハッシュ反復子オブジェクトで取得してるとこ。
反復子だと、キーがどう設定されてるかを無視するわけで、今回は並び替えが終わった時点で、キーの役目は終わってるわけですからね。
反復子がキーの呪縛から逃れて順次取得できるところにカタルシスがあるんです

そして、1obsごとにハッシュの中身をクリアしてるところも見どころ。
テンポラリーの作業場としてのハッシュオブジェクトという考え方は面白い。

一生懸命定義して、工夫してハッシュに格納したデータを、1obsごとにゴミの様に捨てることの背徳感。ゾクゾクってしません?



Luaの標準ライブラリ関数とかSASライブラリ関数とか

Luaにおいては、関数はfunctionで定義するわけですが、
よっぽど基本的なものは、標準ライブラリとしてproc lua内で使用できます。

たとえばprint()とかipairs()とかtype()とか、今まで普通に説明してきましたが
これらは基本ライブラリの関数です。

標準ライブラリには、基本ライブラリの他にも色々あって
数学関数ライブラリのmathや、文字関数ライブラリのstring、
テーブル操作ライブラリのtableなどです。ライブラリっつうか、テーブルの中に格納された関数を使うイメージで、ライブラリ名.関数名で使います。

例えば以下のイメージ。

proc lua restart;
submit;

--mathのmax関数で最大値
x=math.max(2,5,3,1)
print("max関数:",x)

--stringのsub関数で部分文字列
y=string.sub("ABCDEF",2,3)
print("sub関数:",y)

--tableのsort関数でソートして、unpackでリスト展開
t={5,3,1,2}
table.sort(t)
print("sort関数:",table.unpack(t))

endsubmit;
quit;







どんなライブラリがあって、どういう関数があるかはググッてもらえばすぐに
でてくるのですが、注意があります。

proc luaでは対応していないライブラリや関数があるということです。

例えば外部ファイル操作のioライブラリやOS絡みのosライブラリなどは
対応していないようです。
ようですと言うのは、使ってみるとめっちゃエラー吐いたり
固まったりするので、どうやら無理っぽいという経験でしかないからです。
将来的には動いたりするのかも。
まあ、でもSASの中で動かしてるので、外部とのやりとり系がうまく動かないのは当然なのかもしれません。

でね、これまたproc lua特有なのですが、sasライブラリというのがあってですね。(言葉がややこしい!SAS用語とかぶるから!!)
sas.submit()とかもそうなんですが、

基本的に[sas.SAS関数名]でSAS関数を使うことができます。
つまり、以下のような感じ。

proc lua restart;
 submit;

 t={1,2,3}
 x=sas.catx("," ,table.unpack(t) )
 print(x);

 print(sas.max(3,4,2))

 y=sas.substr("ABCDEF",2,3)
print("substr関数:",y)

 print(sas.constant('pi'))

 endsubmit;
quit;







Luaの標準ライブラリとsasライブラリで、機能が重複しているものは多々あります。
string.subとsas.substrとか、minとかmaxとか。
どっち使ってもいいんですが、基本的に勝手知ったるSAS関数の方がみんなわかりやすいかも。


でも、これまた注意点ですが、全部はサポートされてないんですね。
このへん、ちゃんと独立したproc luaのリファレンス作ってくれないとつらいですね。