MAXをとるためにORDINALを使う屁理屈

実戦用というよりかは
頭を柔くするパズル感覚、昔やってた「詰めSAS」的な話として見てください。

さて、以下のデータセットがあって、

data A;
X1=1; X2=3 ;X3=2;output;
X1=.;  X2=.  ;X3=2;output;
X1=3; X2=.  ;X3=3;output;
X1=.;  X2=.  ;X3=.;output;
run;









MAXをとれと言われれば一目、こう書きます。

data B;
set A;
 M=max( of X: );
run;









しかし、NOTEがでます。










NOTEを回避するには

data C;
set A;
 if n(of X:) ne 0 then M=max( of X: );
run;

と書くわけですが、結局、引数が全欠損の場合、いずれにせよ
結果は欠損で変わらない。

そうすると、NOTEの有無を除けば「 if n(of X:) ne 0 then 」の部分は完全な無駄手、
結果は同じなのに一手損しているようで気持ち悪いなぁと昔から思っていました。

個人的な感性の話を抜きにしても、
min maxが入れ子になっているとき、かつ各ブロックで全欠損が生じる可能性が許容されている場合、別に出てもいいNOTEを回避するためだけに、いちいち、分解して書かないといけなかったりして面倒なこともあります。

これ、実は以下のように書けば、ifを書かずにNOTEもださずに、同じ結果に
なると思うんですね。

data D;
set A;
 M=ordinal( 3 , of X: );
run;

ordinal関数は、欠損値を含めて、n番目に小さい数をとるという関数です。
nを引数の数に合わせることで、最終番目に小さい数、すなわちそれが最大値という理屈です。

はなから欠損値を含めることを想定した関数なので、引数が全欠損でも仕様範囲内なのでNOTEはでない。
同値があっても、引数の数を指定していれば結果は揺らがない。

最大をとるために、あえて逆に小さい方からみるっていう発想が頭の体操になるなぁと。

対象引数リストが配列なら、ordinalの第一引数にはdim関数を入れちゃえば、より安心なコードになるね。



毎度毎度「SAS 日付 フォーマット 」とかでググるのはもうウンザリなんだ!!って話

日付を「January 1, 1960」(WORDDATE)でだせとか、「昭和35年 1月 1日」(JNENGO)でだせとかってときに毎回、なんのフォーマットあてればいんだっけってなって、だいだい検索してる。

検索すればすぐ出てくるからいいんですけど、何回も何回もデジャブってると、ちょっと面白くない。

以前の記事「カラーリストの出力」
http://sas-tumesas.blogspot.jp/2017/01/blog-post.html
と似た話だけど、SAS使いなら、わかんないことはSASにコードで教えてもらおうぜってことで

以下のコードなんかどうでしょう?


proc sql;
select fmtname
 ,fmtinfo(fmtname,"desc") as 説明
 ,putn(0,cats(fmtname,maxw,".")) as 例
from dictionary.formats
where fmttype eq "F" and first(fmtname) ne "$"
;
quit;

実行すると、その環境で使用可能な全数値フォーマットと
fmtinfo関数で取得したフォーマットの説明、0にそのフォーマットを
あててみた結果がずらっとでます。(画像は一部を適当に切ってます)












































ショートカットキーとか省略形に追加して、すぐ呼び出せるようにしとくと便利

でもね、

アフリカーンス語では金曜日のこと「Vrydag」って言うんだ~。
てかアフリカーンス語ってなんだ?
ググッてみよ~、へ~、とかって遊んでしまって、本末転倒なんですけどね。

中途半端な優しさはいらない。いっそ激しくNOAUTOCORRECT

さて問題。
以下のコードを実行するとどうなるでしょうか?

date a;
sent sashelp.class;
fi sex="女子" the doo;
putit "A";
outputsomething;
endit;
ru;


なんじゃこれ?こんなもん通るかっ!って思ったあなたはまだ甘い!!

WARNINGはでるけど、処理は実行されます。
つまり







































ということ。

スペルミスってレベルを超えてる気がするけど…。

まあ、SASの優しさではあるんだけど、スペルミス混じったコードがそのままになっているのは恥ずかしいことですよね
いっそ一思いにエラーにして俺を叱ってくれ!って思うこともありますよね。


そんなドMには

options noautocorrect;

を送りましょう。


これを使って、さっきのコードを流すと










































気持ちいいくらいに全否定

今更ながら9.4でPROC SQLにMEDIANが集約関数として実装されたよ。

ワシはPROC MEANSつかわんと、要約統計量も全部PROC SQLで書くんじゃい!!
というタイプの人にとって、MEDIANがSQL関数として実装されていないのは大きな障壁でした。

「WARNING: MEDIAN関数が1引数のみを使用して呼び出されました。しかし、SQL集計関数ではありません。SQL集計は行われません。」
に落胆した人も多いことでしょう。

いや、SASなんだから、普通にUNIVARIATEとかMEANS使おうよって気もしますが、一応9.4から対応されましたよ。

proc means data=sashelp.fish median;
var weight;
run;

proc sql ;
select median(weight) as 中央値
from sashelp.fish ;
quit;


















ちなみにUsage Noteでたまたま見つけて、知ったんですが、SQLリファレンスのWhat's Newに記載がないのはなんでなの??


「Usage Note 12133: Prior to SAS® 9.4, the Base SAS® MEDIAN() function has limitations when used in PROC SQL」
http://support.sas.com/kb/12/133.html

outputメソッドって引数に複数指定できたんだねって話

最近、初心に戻ってハッシュオブジェクトのリファレンスを読み返していて初めて知ったこと。

outputメソッドにdataset:を複数指定できる!おぉ、知らなかった。

例えば

data Q1;
X=3;Y="A";output;
X=2;Y="B";output;
X=1;Y="A";output;
X=1;Y="A";output;
X=2;Y="B";output;
X=3;Y="B";output;
run;













といったデータがあって、
data _null_;
set Q1 end=eof;
if _N_=1 then do;
declare hash h1(dataset:"Q1",multidata:"Y",ordered:"Y");
h1.definekey("X");
h1.definedata(all:"Y");
h1.definedone();
end;
h1.ref();
if eof then h1.output(dataset:"OUT1(where=(Y='A'))"
                                       ,dataset:"OUT2(where=(Y='B'))");
run;

とすると

【OUT1】


【OUT2】







となる


お~、そうなんだ~。
複数指定したいと思ったことはあまりないけど、知っておいて損はないですね






データをざっと見る話

プログラムをつくる際、ステップのたびにデータセットの中を確認するのはさすがに大変だけど、ちょいちょい覗いてチェックしながら書いた方が、結果的にミスなく速く終わるっていうのはよくある話です。

で、そういう話からデータをざっと見るのに、どんなことされてますか?って聞かれました。

はい、残念ながら特にこれといった凄技は使ってないです。

例えば僕の場合、補助的なお手軽チェック法としては
上位10カテゴリをmaxlevels=10ですくって全変数見る以下のようなコード

proc freq data=sashelp.cars order=freq;
 tables _all_ / maxlevels=10;
run;



























gsubmit "proc freq data=%8b.'%32b'N  order=freq;tables _all_ / missing maxlevels=10;run;"

と改造して、SASの右クリックカスタムメニューに入れているので、気になるデータセットを右クリックすればすぐにfreqの結果を見れるようにしてます。
(やり方は以下の記事参照)

gsubmitでSASメニューをカスタマイズしたおす話
http://sas-tumesas.blogspot.jp/2015/12/gsubmitsas.html

_all_で指定してるから、いちいち変数指定する必要や漏れもなく手軽。ID番号や登録日もどざっとでちゃうけど、10個なんで、スクロールですぐすっ飛ばせる。

最後はもちろんmaxlevelつけずに見た方がいいけど、途中確認は簡易で高速な方が、小まめにやる気が起きやすいかも。

あとはクロス表でチェックしたい際に
listオプションつけた方が目視チェックしやすいっていう話は、よく見ますよね
9.4からはcrosslistオプションが追加されました。listの場合は合計行の情報が落ちるわけですがcrosslistだと合計に関してもlist化されます。全体集計に意味がある場合はこっちの方がいいかなぁ

何もつけない場合も含めて3種の出力を比べてみる

title"通常";
proc freq data=sashelp.class;
tables sex*age;
run;

title"list";
proc freq data=sashelp.class;
tables sex*age/list;
run;

title"crosslist";
proc freq data=sashelp.class;
tables sex*age/crosslist;
run;






























あと個人的に好きなのが、クロスでチェックしたい際に、リストと合わせて
ods graphics on;
proc freq data=sashelp.cars order=freq;
 tables ORIGIN*TYPE/ plot=mosaic;
run;
ods graphics off;



とfreqのついでにモザイクプロットがplot=mosaicとするだけで出て来るのでつけてます。見た目が素敵で和むっていうのが主な使用理由ですが、リストでは気づきにくいけどプロットすると意外に気づくこともあるものです。

数値変数についてはやっぱり

ods graphics on;
ods select BasicMeasures plots;
proc univariate data=sashelp.class plot;
var _numeric_; 
run;
ods graphics off;






























って感じかなぁ。
要約統計量だけ見て、すぐに状況把握できるといいんでしょうけど、僕はグラフ欲しい派です。
ヒストグラムと箱髭は、考えた人偉いなぁって思います。
やっぱり、ID番号とか、見なくていいものもでちゃうし、グラフ書くと時間かかっちゃうのがネックですけどね

ああ、あと、実際見たことあるのは、変数をビン化するhpbinプロシジャ(9.4)をデータチェックに使っている人がいましたね。ヒストグラム描けばって思うけど、データ量が凄くてできるだけ高速に省エネでっていう状況ならありですかもね。デフォルトだと
データ範囲(最大値-最小値)をビン数で割った数の区切りでビン化されます

proc hpbin data=sashelp.class numbin=10;
 input _numeric_;
run;



subpad関数はもう少し使われてもよさそうだけどもって話

substr関数を使って、特定の文字数で変数を分割したいなってときがあったとします。
3文字ごとに3つの変数にわけようと思って、以下のコードを書いたとします。

data b;
set a;
x1=substr(x,1,3);
x2=substr(x,4,3);
x3=substr(x,7,3);
run;

ところが流し込むデータを受け取ってみると、こんな感じでした。

data a;
x="1234";output;
run;

先のコードをデータセットaに対して実行すると





となり、結果は正しいのですが






ログにノートがでます。
xのlengthが4のため、4文字目から3文字とれと言われても
はみ出てるやんっていうメッセージです。
同様の理由で7文字目はそもそもないのでメッセージがでます。

まあ、理由も明確に説明できるので
いいちゃいいかもしれませんが、理想としてはログにこういうのは極力だしたくないし
_ERROR_=1たつのはやはり健全ではない。

この場合においては、subpad関数が有効で、はみ出た分は自動的に空白で埋めて
ログには何もださない。SASによる説明に少し補足すると
「必要に応じて空白埋め込みを使用し、(第3引数で)指定した長さの部分文字列を返します。」

書き方はsubstrと同じで以下になる。

data c;
set a;
x1=subpad(x,1,3);
x2=subpad(x,4,3);
x3=subpad(x,7,3);
run;

ただし、要注意なのがsubstr変数が作るx1 x2 x3のlengthは
もとの変数xのlengthが引き継がれますが
subpad関数で作成される変数は文字変数戻りのデフォである
$200.になるのでそこは要注意です。

substrなら、性質上、元の変数のlengthを引き継いでおけば生成される変数で内部の値が
それを超えることはありえないので文字切れはないですが、subpadの場合、
 x1=subpad(x,1,300);などとした場合201-300文字は切れることになります。

ただ、その場合は親切設計で



とわかりやすくですので、ログを全く見ないアンポンタン以外は、まず気づけるはず。

割といい関数だけど、存在をよく忘れてしまう