新年の挨拶とか近況とかproc DSTODS2とか

あけましておめでとうございます。本年もよろしくお願いいたします

全然更新できてないですね、まさか去年1年間で5記事しかだせてないとは…。
今年でこのブログも7年目ですかね。今年は少なくとも10記事はだせるように頑張ります。

社会人も10年くらいにして、やっと観念して毎日せっせこ真面目に働いてしまうようになってしまったということ、2歳児って元気で、SASコード書いてるとキーボード触ってきて打てないよっていうこと、面白いSASブログ書かれる人が増えていい時代になったので、そういうの見てて割と満足しちゃうよねっていうのが主な言い訳です(仕事に必要な技術はだいたい忘備録にあるしなぁ)。
しかし、他にも、ちょっとPythonに浮気してみたり、あと今大阪に住んでるのですが、関西も実は結構SASプログラマがいて、そんな人たちと集まってSASで戯れてたりすると、時間が経つのが早くて、それのせいかもしれませんね。

ちなみに話が変わりますがSAS Global Forumっていうイベントご存知でしょうか?

https://www.sas.com/en_us/events/sas-global-forum.html

SAS社の説明を使わせていただくと
======================================
全世界のSASユーザーが集う年次のイベント SAS Global Forum。
本イベントは、600を超えるセッションでワークショップ、プレゼンテーション、e-ポスター、デモおよび交流プログラムがありアナリティクス活用についての事例やテクノロジーが多数紹介されます。昨年は5400人もの登録者があり、世界中のデータサイエンティストと情報交換が可能です。
======================================
ということです。日本でいうユーザー総会みたいなもんですが、さすがSASの総本山、規模がケタ違いです。
2019年4月28日から5月1日まで、米国テキサス州ダラスで開催予定なのです。

で、なんでいきなりそんな話かというと、一応そこにPaperだして、20分間発表させていただけそうです。
アブストラクト審査は通ったようで、あとは論文本体を書いたり、頑張ればいけるはずです。
何人か既に知っている方が日本から参戦することは把握しているのですが、私もでるよって方がいれば、連絡いただけると嬉しいです。
まあ、そんな感じで、毎日頑張ってる感じです。色々レスポンスが遅くて、すみません…。

さてさて、てめぇの近況なんかどうでもいいよ、こっちはSASのこと知りたくて、わざわざこのブログ見に来てんだよ!って思われていると思うので。

小ネタ。

SAS 9.4M5から、DSTODS2プロシジャという、ちょっとネタみたいなやつが実装されてます。絶対、忘備録か晴れSASで紹介するだろうから、それ待とうっと思ってたんですが、なかなか書いてくれないのでさわりだけ。
→/*====訂正======*/
晴れ時々SASで既に紹介されてました!ごめんなさい!
http://dengonmemo.blogspot.com/2017/10/945.html

どういうプロシジャかというと、データステップを書いたプログラムを指定すると、それをDS2のコードに変換して出力するという面白プロシジャです。

まあ、やってみましょうか

適当に意味のないデータステップを1つ書いて

data test;
length  z $200.;
x=1;
y=1;
z="a";
array an{*} x y;
 do i=1 to dim(an);
an{i}=an{i}+1;
 end;
 drop x;
 a=10;
 rename z=z2;
run;

それをPG1.sasという名前で保存します。

そして
proc dstods2
 in="入力パス\PG1.sas"
 out="出力パス¥PG2.sas";
run;

だけです。

するとPG2.sasができており、その中身は

data TEST;
dcl char(200) Z;
 drop X ;
rename Z=Z2 ;
vararray double AN[*] X, Y;
 method run();
X = 1.0;
Y = 1.0;
Z = 'a';
do I = 1.0 to DIM(AN);
AN[I] = AN[I] + 1.0;
end;
A = 10.0;
;
_return: ;
end;
 enddata;

おおぉ!なんかDS2の書き方になってる。
すげーって、最初ぱっと見で、思うんですが、実は結構変換が雑というか、認識可能な文法が少なすぎです…。少なくとも新規変数の宣言とかは自分で足すしかないので(コードからだけだと新規かどうかわかんないので仕方ないですが)、ある程度DS2知っていないと厳しいです。
あと、きほん全部runメソッドにぶち込まれるので、if _N_=1とかって書いたらinitialメソッドになるかなぁとか期待しても裏切られます

サポートとサポート外の文法については
https://documentation.sas.com/?docsetId=proc&docsetTarget=p0zok9sr2a2t46n1r8zfuw5145t5.htm&docsetVersion=9.4&locale=en
をみてください。

あくまで個人の感想ですと前置きした上で、はっきり言わせていただきます。
使えねぇ…。
これで出てくるコードを手直しできるレベルの人なら、1から書いた方が絶対はやいだろ。

ちなみに例ででてきたPG2.sasですが、以下のように補完してやれば一応動きます。

proc ds2 libs=work;
data TEST(overwrite="yes");
dcl char(200) Z;
dcl double a;
 drop X ;
rename Z=Z2 ;
vararray double AN[*] X, Y;
 method run();
dcl double i;
X = 1.0;
Y = 1.0;
Z = 'a';
do I = 1.0 to DIM(AN);
AN[I] = AN[I] + 1.0;
end;
A = 10.0;
;
/* _return: ; */
end;
 enddata;
run;
quit;

今後の進化を期待なのかなぁ




nofmterrが想定してないエラーを隠してしまうこともあるんだよって話

ユーザー定義フォーマット が参照されているSASデータセットを開く場合は、カタログがwork等、デフォルトで検索される場所以外にあるのであればfmtsearchでカタログの存在するライブラリ名を指定する必要があります。

しかし、とりあえず、フォーマット当たってなくて、値でいいから開きたいという場合に
opions nofmterrを指定するケースがあります。そうしておけば、ユーザー定義フォーマットが見つからなくても、値で表示するので、開けるようになります。

で、結構パッと過去のデータみたいけど、フォーマットが見当たらないみたいな時に、使って、効いたまま、次の処理や作業をしちゃうことがあります。

そういう時に怖いのが以下のようなケースです

data  _null_;
x=input("2018/01/01",yymmdds10.);
put x=;
run;

よくあるミスですが、入力形式にyymmdds10というものは存在せず,yymmdd10.と指定するべきなので、当然














エラーになるので、気づくことができます。

ところがoptions nofmterrが効いていると



























赤線は残りますが、ERRORという扱いではなくなり、欠損のまま、問題なく処理が終了してしまいます。
これは見落としてしまいがちなので、要注意です。
必要がなくなったらoptins fmterrに戻しておくことをおすすめします。
あと、SAS ondemandなど一部のパッケージではデフォがnofmterrだったりするっぽいので気をつけてくださいな。



欠損値の大小関係

マニアックな小ネタ。

if ○○<. then FL="Y";else Fl="N";

という条件が真になる○○の値はなんでしょう?

はい、正解は

data a;
x=._;
if x<. then FL="Y";else FL="N";
run;







特殊欠損値の「._」でした。

SASではユーザーが自由に欠損値を区別できるために、.A~.Zと._の特殊欠損値を使えるようになっています。例えば、未観測の欠損と、計算不能の欠損を区別したいとか。
正直、知らない人が多いので欠損のカテゴリを分けたいなら別途カテゴリ変数作って分けた方がいい気もしますが…。
putとかすると[.A]は普通に文字型のAとかになるので、特殊欠損知らないで混乱している人をよく見ます。

ちなみに最初の話に戻りますが、「.」より小さいのは「._」だけです。特殊欠損の大小関係を確認してみましょう

data a;
do x=.,._,.A,.B,.C,.D,.E,.F,.G,.H,.I,.J,.K,.L,.M,.N,
      .O,.P,.Q,.R,.S,.T,.U,.V,.W,.X,.Y,.Z,-9999,0,1;
 output;
end;
run;

大きい順に並べてみると

proc sort data=A out=B;
 by descending x;
run;







































となります。

公式が説明している順番とも一致してますね

欠損値の大小って言われてもねって感じですね


if 0 then setにおいて初期化される変数についてはretain効果が付与されている

これは結構、罠なんですが、例えば以下の2つのデータセットがあって

data A;
X=1;output;
X=2;output;
X=3;output;
run;









data B;
Y="Y";Z=111;output;
Y="N";Z=222;output;
run;








の場合

data C;
set A;
if 0 then set B;
if X=1 then Y="Y";
else if X=2 then Y="N";
run;

のように書いた場合、Yという変数はset時のBには存在しない変数で、データステップ中で作成しているわけですが









となって、X=3の時もYがN”になっちゃってます。
これは前のオブザベーションで格納したYが消えずにretainされているからですね

なので、これが問題として、たまに顕在化するのは

data D;
set A;
if 0 then set B;
if _N_=1 then do;
declare hash h1(dataset:'B');
h1.definekey("Y");
h1.definedata("Z");
h1.definedone();
end;
if X=1 then Y="Y";
else if X=2 then Y="N";

if h1.find() ne 0 then call missing(Z);
run;

のように、合成した変数をキーにして、ハッシュ組む場合とかですね









if X=1 then Y="Y";
else if X=2 then Y="N";
を
if X=1 then Y="Y";
else if X=2 then Y="N";
else Y="";

としてやれば回避できますが、慣れてないとぱっとわかりにくいところですね




ktruncate関数で日本語のデータを壊さずにバイト単位で切りだす

たいした内容ではないですが,更新することに意義があると思い書きます.

マルチバイト文字とシングルバイト文字の混在データを何バイトごとに変数に切り分けろっていう要望があったとします.(200byteとか…)

その場合,substrとかでやってしまうと,ちょうど切り出すバイトの切れ目がマルチバイト文字だった場合,文字が壊れます.とはいえksubstrだと,シングルもマルチも文字単位でとるので,データにシングルバイト文字が多かった場合,余分な空きが生じます(壊れるよりはマシだけど)

その場合,文字列を1文字ずつループで判定して足したりしていくプログラムを書いたりもしますが,ktruncate関数も便利です.

SASの説明をみてみると

KTRUNCATE関数

マルチバイト文字を壊すことなく、文字列をバイト単位で、指定した長さに切ります。

まさしくばっちり.

ためしてみます

data _null_;
file print;
x="あいうえお";
y=substr(x,1,5);
leny=length(y);
put y= leny=;

z=ktruncate(x,1,5);
lenz=length(z);
put z= lenz=;
run;

結果は








substrは無理やり5byte切り出して,文字をぶっ壊してますが
ktruncateはその手前でとどめて置いてくれます.
普通に良い関数なんで,私は好きです





久しぶり過ぎて何を書いていいのやらな話

SASユーザー総会で、毎日見てます!という温かい声援や、更新サボってんじゃねぇよ!っていう叱咤を思いの外たくさんの方にいただきました。本当に感謝してます。

また、全ては確認できてないんですが、発表やスライドの中で触れていただいた方も、たくさんいたようで有り難いかぎりです。ボーっと発表聞いていると、急に「○○さんのブログに書いてましたが…」とかって出てきてドキッとしました。正体バレバレ度合いが凄かったです。

毎日開いていると言ってくれる人に、毎日同じ記事見せるわけにはいかんですね。

SASをやる人に、仕事中のささやかな息抜きを提供するという志を新たに、少しずつ更新します。コメント等も全然返せてなくてすみません。返していきます

しかし、久しぶりすぎて、何を書いていいのかわからないので、SAS社がやってる日本公式の情報交換サイトを紹介します

Japan SAS Discussion
https://communities.sas.com/t5/Japan-SAS-Discussion/bd-p/ja_forum

すでにSAS忘備録で紹介されているので

SASユーザー間の情報交換サイト「Japan SAS Discussion」
http://sas-boubi.blogspot.com/2018/05/sasjapan-sas-discussion.html

知っている方も多いかもしれません。

個人的感想ですが、今が、日本のSASユーザーの踏ん張りどころな気がしています。
SASユーザー総会のオープニングや締めでも、一時期停滞し、高齢化していく一方であった総会が、再度活性化してきた。新しいSASユーザーが育ちつつあるといったことが語られていました。
この潮目をいかすためにも、お互いに情報交換して、高めあっていきたいですね。

話を戻しますが、
「SASプログラミング掲示板(データステップ100万回」
https://tumesas.progoo.com/bbs/

というサイトもあります。管理人のくせして、管理してないと忘備録のmatsuさんに会うたび、いつも怒られてます。本当にすみません、matsuさんやscdentさんやSASAIさん、その他多くの常連の方々に感謝と謝罪いたします。
一応、こっちも残しておくので、好きに使い分けてください。しかし、私がいうのもなんですけどJAPAN SAS Discussionの方を活性化させてほしいなぁと思ってます。(全然使われてねぇなってSAS社が判断して、閉じちゃったら嫌だから)

掲示板の方を使う場合ですが、質問の投げっぱなしはできるだけやめて、回答についてもお礼等のコメントをできるだけつけるようにお願いいたします。いや、私にこういうこと言う資格ないんですけどね…

ナニワデータサイエンスや、日本初開催のPharmaSUG SDE Tokyo 2018(https://www.pharmasug.org/sde/tokyo2018.html)など、関連イベントも活気があって、和製SASプログラマとしては、私達それなりに、いい時代に生きてるんじゃないでしょうかね。
私自身、できる限り、SASプログラマの発展のための活動には積極的に参加していきたいと思っているので、面白いことがありましたら一声かけていただけると嬉しいなぁって思います。

それでは、これから、何回目の再開宣言かわかんないけど、今度こそ!更新がんばります







ifn(ifc)関数の留意点。偽のルートも全部内部で計算されてるからねって話

うっかりしていると2017年も大晦日!
ちょっとブログのサボり癖がついてしまいそうで、このままじゃいかんので更新します。
来年はもうちょっと頑張ります。

たいしたネタじゃないですが、大分前に下書きしてた以下の記事。

=============================

ifn関数便利です。結構使います。
SQLでも使えるし、なかなか良いやつですね。

ただ、ちょっと注意なのがIfステートメントと内部処理の違いを
ちゃんと知ってはいてねということです。

例えば以下のコード。

data a;
X="A";Y=.;output;
X="B";Y=1.;output;
run;

data b;
set a;
A=sum(Y,1);
Z=ifn(X="A",1,Y+1);
run;















結果は全く問題ありません。
ところがログには「欠損値を含んだ計算により‥」とでます。
Zには欠損なんてないのに。

つまりIFN関数は、先に引数を全部計算してから、条件分岐で
どっちを採用するかを選択しているわけです。
結果的に通らないルートも全部処理しちゃうんですよ。
Ifステートメントは、真の場合のみthen以下の処理がおきるので、そこが大きな
違いです。

例えば、処理時間のめっちゃかかる計算の例として、たらいまわし関数を例にだすと
ifステートメントとifn関数で処理時間が何十倍も変わってきたりするわけですね。
以下がコードですが、ifステートメント0.55秒に対してifnだと54.75秒かかってます。

proc fcmp outlib=WORK.FUNCDT.CARCALC ;
  function takf(x, y, z);
  if x <= y then
    return(z);
  else
    return (takf(takf(x - 1, y, z), takf(y - 1, z, x), takf(z - 1, x, y)));
  endsub;
run;
options cmplib=WORK.FUNCDT;


data q1;
do i = 1 to 100;
output;
end;
run;

data a1;
set q1;
if _N_=50 then  A= takf(18, 9, 0);
run;

data a2;
set q1;
A= ifn(_N_=50,takf(18, 9, 0),.);
run;










ちなみに、この性質を逆手にとって、相変わらずぶっ飛んだアイデアを公開している人がいるので紹介しておきましょう。

「LAG関数とIFN / IFC関数のコンボ技」
http://sas-boubi.blogspot.jp/2015/03/lagifn-ifc.html