dkricondオプションで、データセットオプションで存在しない変数を対象としたdrop keep renameが実行された際のエラー制御の話

さて、以下の2ステップを実行するとどうなるでしょうか??

data Q1;
X=1;
run;

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

答えは2ステップ目でエラーになってA1はちゃんと作成されないでした。

ERROR: DROP, KEEP または RENAME リスト内の変数 Y は参照されませんでした。
NOTE: エラーが発生したため、このステップの処理を中止しました。
WARNING: データセット WORK.A1 は未完成です。このステップは、 0 オブザベーション、0 変数で停止しました。
WARNING: このステップを中止したため、データセット WORK.A1 を置き換えていません。

でログが華やぎます。

で、もちろん、存在しない変数を対象にしたデータセットオプションを指定しなければよい話なのですが、例えばマクロなんか組んでると、いちいち変数の存在確認して分岐処理を入れると煩雑になる場合もあります。

エラー制御系のオプションは良し悪しですが、知ってて損はないのでご紹介。

options dkricond=nowarn;

data A2;
set Q1(drop=Y);
run;

options dkricond=error;

とすると、





、ログには何も出ません。

さてdkricondってなんの略だと思います?

dはdrop kはkeep rはrenameです(多分)。

でiはin dataset optionとかinputとかinの略(多分ね)、でcondはcondition(多分ね)の略です。

つまりdkricondはインデータセットオプションにdrop keep renameが指定されて、その変数が
ない場合にどうするかで、デフォルトはERRORです。

ちなみにdkrocondオプションっていうのもあって、つまりは

data A3(drop=Y);
set Q1;
run;

こういうようにアウトするデータセットにつけるデータセットオプションの場合ですが、こっちは
inの方と違ってデフォルトはwarningなのです。だからwarningが出てもデータセットができてれば一向に構わん!って人はいらないかも。
まあ、試してみてください。


GTLの中でeval関数を使うことによって、SAS関数を使えて、動的なテンプレート定義ができたりする話

ちょっとした話です。

data Q1;
call streaminit(777);
 do Z= 1 to 10;
  X=rand('uniform');Y=rand('uniform');
  output;
 end;
run;


















GTL、テンプレート定義は基本的に静的なもので、
dynamicステートメントやマクロで値を渡す以外は、template内で実行時に
変更を伴う動的な処理はできないのですが
evalを使うとSAS関数が使えるので、

proc template;
 define statgraph G1;
 begingraph;
 layout overlay;
  scatterplot x=X y=Y
    /name='n1'
     markerattrs=(size=11 weight=bold symbol=circlefilled)
     group=eval(ifc(Z<=5,'low','high'));
   discretelegend 'n1'/title='Z'
                        location=inside halign=right valign=bottom;
  endlayout;
  endgraph;
  end;
 run;

 proc sgrender data=Q1 template=G1;
 run;






















のように予めグループ化用の変数を用意してなくても、データから条件を付けて
ダイレクトにグループ化したりできます。

或いは、よく使うのが、グラフに参照線入れる時に、mean関数とかで、平均値とか
要約統計量を使いたい場合とかですかね。


データセット内のすべての文字変数に対して、それぞれに入っているテキストの長さの最大値を取得するハッシュオブジェクト

SAS忘備録の記事「Hashオブジェクトを使おう。」http://sas-boubi.blogspot.jp/2015/02/hash.html

を読んで感動しました。
わかりやすい。僕の記事の数兆倍わかりやすいので、是非読んでください。

そして思ったのが、やっぱり普及させたい技術についてはそのまま実務で使えたり、
使い道が容易に想像できるコードを例にしないと、僕だけが楽しいばかりで、駄目だなということです。

僕だけが楽しい悪い例の一部↓
「ハッシュオブジェクトでマジカルバナナのデータを順番通りに連結する」
http://sas-tumesas.blogspot.jp/2014/09/blog-post_16.html
「ハッシュ反復子オブジェクトとcall compcostルーチンで、マジカルチェンジのデータを順番通りに連結する」
http://sas-tumesas.blogspot.jp/2014/09/call-compcost.html

実は最近 sumメソッド周りを集中的に研究していて、割と実務的かつ面白なサンプルを生産してるのですが、それは一応ユーザー総会用にしようかと思って、ストックしているので今暫くお待ちください。いずれ全てUPします。

で、前置きが長くなりましたが、ここから本題。



さて、文字型の変数の場合、適切なlengthの設定というのは結構、頭の痛い問題です。
文字切れはやばいし、大は小を兼ねるの発想で、せいぜい$100.くらいしか入らないだろうなってとこも、データ入力に制限がない場合、長文入れる人もいて怖いから取りあえず$1000.とかにしがちじゃないでしょうか。
で、一個$1000.にすると、もうめんどいから文字変数は全部1000でいいやみたいな。

オブザベーション数が少ない、或いはそういう文字変数が少ない場合は別にかまわないです。
しかし、経験のある人も多いと思いますが、文字変数のlengthは実行時間に対して、割とダイレクトに影響します。

なので、データが固まった後でlengthをもっかい決めたいなって時、実際、入ってる値のlengthの最大値が欲しくなります。
或いは、SASデータセットを他のシステムやDBに渡す時とかも実際どの程度とればいいのかを調べることあります。

まあ、length関数と文字変数に対するループで、各変数の長さだして、それをmeansとかにかけて最大値とるとか、或いはretainで最大値更新していってeofで出すとかでいいんですけど、その場合、データセット名だけ指定すれば、常に動くマクロ、できれば処理時間も最速でっていうものを作りにくいんですよね(まあ、僕がマクロ苦手なので)。
あと、できれば、ほかのシステムにペタッと貼り付けたり、そこからコード自動生成するのに
縦積みになってた方がうれしいので敢えてハッシュ使ったという個人的事情もあります。
とりあえずハッシュ使いたいだけという趣味的な部分もありますが。

で、今

data A;
length A B C $1000.;
do i= 1 to 5;
 A=repeat('A',int(ranuni(777)*20));
 B=repeat('B',int(ranuni(777)*20));
 C=repeat('C',int(ranuni(777)*20));
output;
end;
drop i;
run;










というデータがあったとして

data _null_;
length var $100. maxlength 8.;
set A end=eof;
array cha _character_;

if _N_ = 1 then do;
call missing(var,maxlength);
declare hash h1();
h1.definekey('var');
h1.definedata('var','maxlength');
h1.definedone();
do over cha;
var = vname(cha);
maxlength = 0;
h1.add();
end;
end;

do over cha;
var = vname(cha);
if h1.find() = 0 & length(cha) > maxlength then do;
maxlength=length(cha);
h1.replace();
end;
end;

if eof then do;
h1.remove(key:'var');
h1.output(dataset:'LEN');
end;
run;


こうすれば、結果は








となります。

今 set A; としているところのデータセット名だけ変えれば常に動くはずです。

実際、自分用に使っているマクロは、その最大文字数の実際のデータと、IDとかもとるようにしていますが、
中身を理解できれば、割と簡単に自分好みにカスタマイズできるはずです。

で、中身の説明ですが
ハッシュオブジェクト定義と、そこへのデータ追加の部分

array cha _character_;

if _N_ = 1 then do;
call missing(var,maxlength);
declare hash h1();
h1.definekey('var');
h1.definedata('var','maxlength');
h1.definedone();
do over cha;
var = vname(cha);
maxlength = 0;
h1.add();
end;
end;

が割と工夫されていて、面白いですね(自画自賛)。
まずからっぽで定義してから、全文字変数の変数名をキーにしてハッシュに格納します。
現状の最大lengthは0にしておきます。

この時点での、このハッシュオブジェクトh1の中身は










となっているはずです。
(慣れるまではデバックのためハッシュオブジェクトの中身をこまめにouputメソッドでだしておくのもいいかもです。)

そのあと
do over cha;
var = vname(cha);
if h1.find() = 0 & length(cha) > maxlength then do;
maxlength=length(cha);
h1.replace();
end;
end;
は、そのまんまですね。findメソッドで、その時点で処理している変数名の現状の最大lengthをハッシュオブジェクトから
引っ張ってきて、比較して、自分の方が大きければreplaceメソッドで更新すると。

なんか今回はハッシュオブジェクトをretainステートメントみたいな感じで使ってるわけです。



最後
if eof then do;
h1.remove(key:'var');
h1.output(dataset:'LEN');
end;

は、varは元のデータセットにない、作った変数なのでremoveメソッドで消します
(※対象のデータセットにvarという名前の変数があったら、コード変えてくださいね)

h1.output(dataset:'LEN')でデータセットを出力します。

if eof then do;にしているのは最後の1回出力すればいいからですね。


以上です。

この変数名をキーにして、ターゲットになる値を更新していく方法は結構応用がききます。



ods csvってcsv以外も作れたんだって話とoptions(doc='Help')の話

ちょっとした話です。

ods csvって、csv専用のods出力形式だと思ってたんですが

data Q1;
A=1;B=2;C='A';
run;

ods csv file='/folders/myfolders/A1.txt' options(delimiter='@');
proc print data = Q1 noobs;
run;
ods csv close;




って感じで、options(delimiter='@')でデリミタを何にでも変えれたんですね。へ~、まあ確かにできそうだけど、気が付かなかった。

と、こんな風に、ods csvやods tagsets.excelxpにどういうオプションがあるかってわからないってときは

ods csv file='/folders/myfolders/dummy.csv' options(doc='Help');
ods csv close;

ods tagsets.excelxp file='/folders/myfolders/dummy.xml' options(doc='Help');
ods tagsets.excelxp close;


とこんな風にdummyの外部ファイルを指定して、options(doc='Help'); とすると

ログに





















っとこんな風にパラメータと、デフォルト値、説明がダーッとでてきます(英語だけど)。


ちょっと遅いですけど、バレンタインデーに絡んだ海外のSASネタを紹介

「Have a traditional SAS/Graph Valentine's Day!」

「Binary heart in SAS」

「What chocolate is best for Valentine’s Day? Not cocoa powder」

海外の人は、真面目にアホみたいなことするからカッコいいですよね。





あったらいい関数やルーチンがないから取り敢えずこう書いてますの話

今回は僕が日頃感じることの多いSAS関数とコールルーチンへの不満についてです。
といっても別にそんな大したことじゃなく
普通に提供されてそうな機能なのに、実際なくて困るからとりあえず僕はこう書いてるけど、
みなさん普段どうしてるんだろうってことを紹介する話です。

なので、え?それは○○関数でできるよ、とか9.3とか4で引数追加されてできるようになったたよとか
あれば是非、てめえは間違っているとご指摘ください。


まずはデータ

data Q1;
length A B C D $10.;
A='い';B='ろ';C='は';D='に';output;
A=''  ;B=''  ;C='は';D='に';output;
A=''  ;B=''  ;C=''  ;D=''  ;output;
A='い';B='ろ';C=''  ;D=''  ;output;
A='ろ';B='ろ';C='ろ';D='ろ';output;
run;












まず一番、よくあるのが、指定した複数の文字値の中で、欠損でないものの数をカウントしたいというケースです。
数値型であればn関数があります。欠損値であれば、数値文字両方対応のcmissがあります。
ただ、文字型の非欠損値のカウントがないんですよね。
で、SAS公式もそのことはわかっているので、以下のように案内してます。
http://support.sas.com/kb/46/235.html

でも、紹介されてる代替コードが(僕としては)微妙すぎ。
文字変数の数から欠損値の数ひけば、非欠損値の数でしょっていう、まあそりゃそうですけど
わざわざ複数ステップで書くの面倒だし、全文字変数が対象じゃなくて、一部の変数を対象にする場合
絞りをいれる必要のあるプログラムなので、ちょっとって感じです。
今後のバージョンでそういう関数を追加します的なことも書いてますが、今現在ないと思うので、いつになるやら。

なので、僕は以下のように書いてます。

data A1;
set Q1;
array AR $ B C D;
CN=0;
do over AR;
 CN + ^missing(AR);
end;
run;












本当はfcmpでそういう関数作りたかったんですが、現状のfcmpだと引数に
temporary配列しかとれないので、結構使い勝手が悪いんですよね。

リンクサイト kickstamp の
【SAS】PROC FCMPプロシージャーでの可変長引数の利用について

http://kickstamp.hatenablog.com/entry/2013/07/18/124954

で、紹介されているようにマクロ噛ませばできるんですが、たかが非欠損値数えるだけで
そこまでするのも何か負けた感じで嫌だな~って。

次に、これは文字型に限った話じゃないんですが、指定した複数の変数の中で条件に合致するものの数が欲しいというケースです。excelのcountif関数みたいなイメージです。
縦にそういうことしたい場合はsqlとかでいいんですが、横にだしたい時も多いので困ります。

ので僕は以下のようにしています

data A2;
set Q1;
array AR $ B C D;
CN=0;
do over AR;
 CN + AR in ('ろ');
end;
run;











最後に同じような内容ですが、以前掲示板にも書いたことがあるのですが
http://tumesas.progoo.com/bbs/tumesas_topic_pr_39.html

複数変数を対象に特定の値を他の値に変換したいという処理です。
とりあえず、今は以下のように書いてます。

data A3;
set Q1;
array AR $ B C D;
do over AR;
 AR = ifc(AR='ろ','は',AR);
end;
run;










つまり今は関数やルーチンがない場合、だいたい配列&ループ処理でまかなっているわけです。
ただ基本的に僕の考え方として、配列もマクロもハッシュオブジェクトも、或いはプロシジャによる処理も極力いれずに、ありものの関数やルーチンの
組み合わせで解決できることは、極力そうしたいと思ってます。(割に変なコードばっか書いてますが…)
まあ、あんまり関数のお化けみたいなコードになるぐらいなら、素直に配列書きますけど、用意された機能でシンプルにできるならそれに越したことはないと思ってます。

おっきい数字を英語で読めない時もSASを使えば大丈夫な話

さて、英語の問題です。

data Q1;
do X=7895,97956,569874,8569974,98569974,123456789;
output;
end;
format X comma15.;
run;












この数字を英語で言えるでしょうか?

僕は昔、バイトしていた時に外人の方に1万五千円の会計ですって言うのを、テンパって
「ワン ミリオン ファイブ サウザンド エン プリーズ」と言って「ワオ」みたいな感じで、爆笑されて
凄い恥ずかしい思いしたことあります。

さて、そんなアホでも、SASを使えば大丈夫!wordsフォーマットを使えば

data A1;
 set Q1;
 Y=put(X,words100.);
run;














これをそのまま読めば、もう恥をかくことはありません!

しかし、落とし穴があります!

data A2;
do X=999999999 ,1000000000,1000000001,10000000000;
Y=put(X,words100.);
output;
end;
format X comma15.;
run;











それは1000000000 10億を超えた数字を全てlarge_numberとしてしまうとこです。
これをそのまま読むと、やっぱりアホな子だと思われてしまいます。

以上、今回も使いどころのないムダ知識でした。