詰めSAS12回目:キーが一致しないものを残すマージ(共通部分を除く結合)の話

タイトルで意味がわかりますでしょうか?うまく説明できません














ベン図で書くと、上の黄色の部分です(ペイントで適当に書いたベン図なので、○の大きさが違う、、)

例えば

data Q1;
do X=1,3,4,6;
 output;
end;
run;








data Q2;
do X=1,3,5,6,8;
 output;
end;
run;











のようなデータセットがあった場合に1つのデータセットにしかないXの値をもった結果







を得るには、どんなコードを書けばいいでしょうか?


ただし、MergeステートメントとSQL両方のやり方で詰ませないといけないというのが
今回の問題です。


え?第一感楽勝!と思われるかもしれませんが、案外、つまづきませんか?
実際、このタイプの結合処理って、頻度少ない気がします。

大体は両方一致するものを残すか、片方を全部残して結びつくものを残す、いわゆる内部結合や片側外部結合が大半ですよね。

実は今回も自分の解答に自信がなくて、もし間違いや、抜け穴、最適じゃない書き方であれば
ご指摘いただけると助かります。




以下、(一応)解答


【MERGEの場合】

data A1;
 merge Q1(in=ina)
       Q2(in=inb);
 by X;
 if ^(ina*inb);
run;

こんな感じでしょうか?

ifのところは (ina=1 and inb=0) or  (ina=0 and inb=1)みたいな感じでもいいですし
^(ina and inb)でも、ようはin=で指定した変数が全部1になるケースを除く書き方をしていれば
なんでもいいです。


【SQLの場合】

proc sql noprint;
 create table A2 as
  select coalesce(Q1.X,Q2.X) as X
  from Q1 full outer join Q2
       on Q1.X=Q2.X
 where ^Q1.X | ^Q2.X;
quit;


こんな感じでしょうか?
完全外部結合がみそですね。

わかりやすくするために、create tableとりはずして
where句で Q1.X=0 or Q2.X=0 をしている部分も外しましょう(|はorを意味する記号)

proc sql;
  select Q1.X  label='Q1のX'
         ,Q2.X label='Q2のX'
  from Q1 full outer join Q2
       on Q1.X=Q2.X;
/* where ^Q1.X | ^Q2.X;*/
quit;

こうしたら










こうなるので、これをみれば、上のコードも理解しやすいはずです。
どっちかが欠測の場合を抽出しているんで、最初に見つけた欠測以外の値を取得する
coalesceが効いいるわけですね。


あ、ちなみに、僕が最初に思いついたSQLは

proc sql noprint;
 create table A3 as
  select X from Q1
   union all corr
  select X from Q2
  
  except

  select coalesce(Q1.X,Q2.X) as X
  from Q1 innner join Q2 on Q1.X=Q2.X;
  ;

quit;

です。まあ、確かにベン図を表現していなくはない、気持ちはわからなくはないと思っていただければ浮かばれます。






リンクさせていただいたサイト様

ここ最近で、SAS関連の情報を発信されているサイト様に多くリンクを貼らせていただきました。普段、私が参考にさせていただいているサイト、またSASを最初に学習する際に特にお世話になったサイトになります。
リンクさせていただきましたサイト様にはここで改めて御礼申し上げます。

アクセス解析などはよくわからないのですが、最近、検索されたワード一覧みたいなやつを見てみると、結構、統計解析の知識を求めて、僕のサイトに迷い来てしまった方も多いようです。
普段、読んでいただいている方はおわかりかと思いますが、僕のブログから、統計解析の理論について学べる部分は限りなくゼロに近いので心苦しかったです。

また、SASを最初に覚える際の学習にも、このブログは向いてないと思います。とりあげる話題に脈絡がなく、気の向くままに、ちょっとマニアックなことばかり紹介しているので、、。申し訳ないです。

やはり、その人によって、SASといっても、必要とされている知識の種類に差があるようです。海外であれば、適当に検索してても、情報が多いのですぐにたどり着けそうですが、日本の場合は、なかなか求めるべき情報があるページを探しきれないことが多い気がします。

なので、いい出会いが多くなって、日本のSASがもっと活発になれば嬉しいなぁと思って僭越ながら様々なサイト様にリンクさせて頂きました。

今後もぜひ増やしていけたらと思います。これから新しくブログを立ち上げる方もぜひ相互にリンクさせていただけたら嬉しい限りです。



indsname定跡

indsname=オプションが気になっていて、面白そうだから、何かの処理に活用したいんだけど、いい使い道がわからないので教えてくださいというご連絡を匿名で直接いただきました。

手段のために目的を探すその姿勢、僕は大好きです、とても共感します。

というわけで、僕の中で定跡となっているindsnameを使った手順を紹介します

あ、ちなみに以前のindsname=の記事は
http://sas-tumesas.blogspot.jp/2013/10/indsname.html
です。



さて、今、WORKの中にたくさんのデータセットがあったとします。

data Q1;
ID='0001';output;
ID='0002';output;
ID='0003';output;
ID='0001';output;
run;

data Q2;
ID='0006';output;
ID='0002';output;
ID='0004';output;
ID='0003';output;
run;

data Q3;
ID='0005';output;
ID='0002';output;
ID='0004';output;
ID='0003';output;
run;

data Q4;
ID='0005';output;
ID='0001';output;
ID='0004';output;
ID='0003';output;
run;

data Q5;
ID='0001';output;
ID='0001';output;
ID='0001';output;
ID='0003';output;
run;


















さて、これらのデータセットの中で変数「ID」の値が0001のデータを含むデータセットの名前と
0001のデータのオブザベーション数を抽出したデータセットを作成しなさいと言われたら、
どういった処理を書くでしょうか?

要するに求めたい結果は


こんな感じです。

やり方はたくさんあると思いますが、僕はこういったケースの場合

data ALLDS;
 set Q: indsname=NAME;
  DSNAME=scan(NAME,2,'.');
run;

proc freq data=ALLDS noprint;
 tables DSNAME*ID/out=A(drop=percent ID);
 where ID='0001';
run;

こう書きます。

2ステップですが、わかりやすいです。

freqに書ける前のALLDSの中身は






















こんな感じです。


これって結構応用が利いて、
複数のデータセットを対象として、何か条件をかけて
その結果とデータセットの名前を、結果としたデータセットを作りたい時にわかりやすいです。

GLOBALのユーザー会でも、まだほとんど見かけないので、開拓の余地があるオプション
だと思います。テーマにするなら狙い目じゃないでしょうか。










ODS Graphics Editorの話 (特別なライセンスなし、SAS/GRAPHでできる便利な機能)

templateプロシジャで、Glaph Template Languageで、グラフのテンプレートを作って、proc sgrenderでそのテンプレートを指定して、プロットする。

SAS9.2以降、SG系グラフの登場により、グラフの表現力はケタ違いにアップしましたが、その代わり、最初のハードルも随分高くなってしまった気がします。

いきなりGTLガリガリ書いてグラフを作るのは、ちょっと難しいので、最初のうちは
SASのグラフサンプル集http://support.sas.com/sassamples/graphgallery/PROC_SGRENDER.html

やSASユーザー総会で公開されているものや、或いは書籍、コピペして、少しずつカスタマイズして
自分のものにしていくのがいいと思います。
リンクさせていただいている「僕の頁 <SASと臨床試験と雑談と>」(http://sasboku.blog.fc2.com/
にもGTLを使ったプロットが公開されていて勉強になります。

で、今回紹介するのは、それとは違ったアプローチです。
GUI、ようはプログラムを書かずに手でグラフを書いて、それを自動的にコード化する話です。

まず、適当なデータをつくります。

data TEST;
call streaminit(2014);
do GROUP='A','B';
 do i=1 to 100;
  x=rand('uniform');
  y=rand('uniform');
  output;
 end;
end;
drop i;
run;


そしたら、まず、SASをインストールする時に、ODS Graphics Editerにチェックをつけた人は、すでにアイコンがあるか、すべてのプログラムから立ち上げられるので、そうしてください。





もし、そういったものが見当たらない人は

%sgdesign;

とエディタに書いて、実行してください。
何かが裏で凄い動いた感じがして、10数秒後、

















こんな画面がでてきます。

ここで、いろんな形のグラフを選ぶことで、実は相当高度なグラフもかけます。
















試しにグラフギャラリーから「グループ化散布図」でも選んでみましょう









グラフを選ぶと、次はグラフに使うデータセットを指定します。
デフォルトがなぜかSASHELPなので、参照元にするライブラリを指定します。


※ちなみにここで、%sgdesign;で起動した人は、WORKの中をみてみましょう。
わけわかんないデータセットであふれているはずですが、これは、グラフを書くサンプル用の
データセットを勝手に用意してくれいるんです。迷惑です。
















で、最初につくったデータセットを指定します


そしたら























X軸に対応する変数とY軸に対応する変数、そしてグループ化する変数をプルダウンで
選びます。

そしたら、それだけで、グラフがポンとでてきます。
で、なんと、そのグラフを手で修正できます。

まずはグラフタイトルをクリックして、好きなタイトルにします



















右クリックで、軸や背景などのいろんなプロパティをいじれます。
感覚的にはEXCELのグラフを編集するのと全く同じです。

背景色を灰色にしてみました。



















要素の追加で、さらにプロットを重ねたり
フリーテキストや凡例などを追加できます



















凡例を追加してみました























やりたい放題やって、気がすんだら、ここからが本番













表示から「コード」をクリックすると





なんと、proc templateでのテンプレート定義から
proc sgrenderの実行までを全てコード化してくれます。


プロットに指定した変数はdynamicステートメントで受け渡すことを想定したコード化を
してくれるので、標準化もしやすく、超便利。

あとはこれをエディタにペタ

proc template;
define statgraph sgdesign;
dynamic _X _Y _GROUP;
begingraph / backgroundcolor=CXE8E6E8;
entrytitle _id='title' halign=center '散布図ですわ' /;
entryfootnote _id='footnote' halign=left 'Type in your footnote...' /;
layout lattice _id='lattice' / columndatarange=data columngutter=10 rowdatarange=data rowgutter=10;
         layout overlay _id='overlay' / xaxisopts=(gridDisplay=on label='X軸のラベルも自由') yaxisopts=(linearopts=(viewmin=0.2 viewmax=0.9 ) tickvalueattrs=(size=12) gridDisplay=on type=linear);
            scatterplot _id='scatter' x=_X y=_Y / group=_GROUP name='scatter';
            discretelegend _id='legend' 'scatter'  / border=true displayclipped=true down=1 halign=center location=inside opaque=false order=columnmajor valign=top;
         endlayout;
endlayout;
endgraph;
end;
run;

proc sgrender data=WORK.TEST template=sgdesign;
dynamic _X="X" _Y="Y" _GROUP="GROUP";
run;

で実行するだけで

さっきまで、手でいじって作ってたグラフが、ポンです。

















これは、GTLを勉強するとき、何と何が結びついているかを理解する上でも、大変役立ちますね。
レイアウト分割の複合グラフを、一からコードで起こすのは、まあ、なかなか、つらいですよね、それはそれで勉強になるけども






inとコロン(:)の合わせ技、文字型に対する接頭一致

リンクサイト「SAS忘備録」で
「条件式(IF/WHERE)におけるINオペレータの小技」
http://sas-boubi.blogspot.jp/2014/01/in.html

の中で

data DT1;
  do V=1 to 6 ;
   output;
  end;
run;
proc print data=DT1 noobs;
 where  V in (1, 3 : 5) ;
run;

とすると






となるように、inの中での3:5が3~5の範囲指定として実行されるという技を紹介されていました。

今回は、文字型について、括弧の中にコロンを入れるのではなく外に出したら、どうなるかを見てみます。

data Q1;
length X $20.;
X='ABCD';output;
X='ABCDEF';output;
X='DEFG';output;
X='AB';output;
X='BBC';output;
X='DBC';output;
run;










こういうデータがあって

data A1;
 set Q1;
 where X in:('ABC','DE','DB');
run;

としたら、どうなるでしょうか?

答えは








です。
これはつまり、Xの値がABCから始まるか、DEから始まるか、DBから始まる場合に抽出しているということです。

keepやdropステートメント等で X_:とするとX_から始まる変数名の変数が対象になったり
set X_:とするとX_から始まるデータセット名のデータセットが対象になったりするのと同じ
前方一致を意味するコロンな使い方です。

なんでinじゃなくて

data A2;
 set Q1;
 where X=:'ABCDE';
run;

でも使え、





こうなります。

コロンは色々使える分、ややこいですね



proc printでラベルを折り返したり、縦にだしたり

proc printは、ぱっとデータだすのに便利です。proc report等の柔軟性には当然、及びもつかないのですが、頑張れば結構いけます。

結構、いろんなところで既出のネタですが、

data Q1;
X=1;
label X='ABCDEFGHIJKLMN';
run;

のようになデータについてラベルの内容を出力するのは

proc print data=Q1 label noobs;
run;

labelオプションを追加します。





で、例えばデータの割にラベルが長くて、間延びする場合は

data Q2;
X=1;
label X='A*BCDE*FGHI*JKLMN';
run;

proc print data=Q2 label split='*' noobs;
run;








のようにsplitで区切り文字を指定して、それを入れてラベルを定義すれば
その場所で改行できます。

以前紹介したvlabel関数などと組み合わせれば、一定の長さで区切り文字を挿入して、ラベルを再定義するマクロ等が組めるかと思います。

ちなみに

proc print data=Q1 label heading=v noobs;
run;

heading=v とすれば















のようにすれば、ラベルが縦にでます。

でも、これ2byte文字が入っていると出力されないんですよね、多分なんかオプションを加えないと
いけないのでしょうが、それを知りません。




1980年代のユーザー総会

SASのWebページって、結構コロコロ変わりますよね、しかも知らないうちに。

多分、最近までなかったと思うのですが、、今日みると
1982年から89年にかけての日本SASユーザー会の論文集がPDFで全て無料公開されていました。
いつのまに??

http://www.sas.com/offices/asiapacific/japan/usergroups/index.html

タイトルと著者一覧も別表であります。

30年前と今で、大きく変わった部分と、あんまり変わってないなと思う部分、
とても面白いです。


駒交換における得点計算プログラム_Formatプロシジャのinvalueで入力形式を作成して計算する話にかこつけて

【※注意】後で、SASの話も一応でてきますので、、


将棋において、なんの犠牲も払わずに、タダで相手の駒をとることができる状況はまれです。
序盤は特にそうですが、
例えば、相手の「銀」を取れる代わりに、自分の「桂馬」がとられるといったように、戦う以上、駒の交換は避けられません。
しかし、避けられないならば、できる限り、自分に得になって、相手に損になるように交換するのが、勝つための大原則です。

例えば、「歩」と「飛車」の交換であれば、明らかに「飛車」の方が価値が高いので、喜んで「歩」を差し出していいでしょう。

では、例えば、相手の「飛車」を取れる代わりに、こちらの「香車」と「桂馬」の2つを犠牲にしなければならない攻め方の場合、その交換を行うべきか、避けるべきかどちらでしょう?

当然、状況によって駒の価値は変わるので、答えはでませんが、単純化して考える方法として駒の得点化ということをよく言います。


谷川浩司九段の有名な点数化例では、以下の通りです。


飛=15
竜=17点
角=13点
馬=15点
金=9点
銀=8点
成銀=9点
桂=6
成桂=10点
香=5
成香=10点
歩=1点
と金=12点

これをみると「香車」と「桂馬」の2つの点数の合計は11点、対して「飛車」は15点なので、積極的に交換してもよいという理屈になります。



さて、ここからはおまけみたいなもんですが、とりあえず、やっとSASの話です。

以下のようなデータセットがあったとします。

data TRADE;
SENTE='桂';GOTE='金';output;
SENTE='香';GOTE='銀';output;
SENTE='香';GOTE='';output;
SENTE='歩';GOTE='';output;
run;









変数名SENTEは「先手」、GOTEは「後手」です。
このように、交換対象の駒をオブザベーションごとにいれます。

今回は先手4枚と後手2枚の交換ですが、どっちが得をするかぱっとわかりますか??

SASに計算させたいのですが、これ文字なんで、そのままじゃどうしようもないわけです。
アプローチはたくさんありそうですが、今回はインフォーマットでいきましょう

proc format;
invalue  koma '飛'=15 '竜'=17 '角'=13 '馬'=15 '金'=9 '銀'=8 '成銀'=9
              '桂'=6 '成桂'=10 '香'=5 '成香'=10 '歩'=1 'と金'=12;
run;

として点数換算表をインフォーマットにしちゃいましょう!
valueステートメントはよく使いますが、invalueって個人的にはあまりつかわないので新鮮です。

あとはもう

proc sql;
 select sum(input(SENTE,koma.)) as SPOINT label='先手の駒の得点合計'
       ,sum(input(GOTE,koma.)) as GPOINT label='後手の駒の得点合計'
  from TRADE;
 quit;

なんでもいいんで計算しちゃえば










こんなアウトプットがでて、損とも得ともいえない交換ということがわかります。
でも、僕が先手ならこの交換は絶対やりますけどね。まあ目安です。

この得点化プログラム、将棋の際に是非使ってください。

遊びで作ったコードの方が、実戦的なものよりプログラム能力を上達させる効果があるというのが僕の持論(言い訳)です。






do toループ

今日の小さな発見。

do i=1 to 100; みたいな範囲指定で感じでループ書くか
do i=1,5,6,10;   みたいにリストで書くことが多いので、やったことなかったんですが

例えば

data A1;
 do i=1 to 5
    ,9,10
    ,14 to 20 by 2
    ,100 to 98 by -1;
      output;
 end;
run;

みたいに、混ぜ混ぜで書いても通るんですね。



















VLABEL関数の話

VLABEL関数は、変数についてるラベルの内容を返します。データによってはラベルに必要な情報が含まれていることもある(むしろラベルにしかなかったりするバットな場合も)ので例えば

data Q1;
NAME='Aさん';CHECK1='Y';CHECK2='N';CHECK3='Y';output;
NAME='Bさん';CHECK1='Y';CHECK2='Y';CHECK3='Y';output;
NAME='Cさん';CHECK1='N';CHECK2='N';CHECK3='Y';output;
NAME='Dさん';CHECK1='N';CHECK2='N';CHECK3='N';output;
label CHECK1='高学歴' CHECK2='高収入' CHECK3='高身長';
run;








みたいなデータセットがあって







みたいなデータセットを作れと言われた時に、

まあ、このラベルの内容がこの程度の長さなら、普通に書いてもいいですが、以下のように
VLABEL関数でラベルの内容を利用して


data A1;
 set Q1;
 PROFILE=catx('、',
          IFC(CHECK1='Y',VLABEL(CHECK1),'')
         ,IFC(CHECK2='Y',VLABEL(CHECK2),'')
         ,IFC(CHECK3='Y',VLABEL(CHECK3),'')
         );
 run;

て感じでも書けます。最近たまたま実戦で多く使ったので、紹介してみました。




COMPRESSの第3引数

恐らく、僕だけだと思うのですが、、

例えば、ある文字変数から数字だけを残して他の文字を消したい場合
= COMPRESS(変数,,'KD')
逆に文字だけ残したい場合、
= COMPRESS(変数,,'KF');

と書けます。
これはSASのFAQを見て覚えたんですが
http://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/ba250.html

その時、ちゃんと説明を読んでなかったのでしょう、KD、KFという固定された呪文というか、決まり文句だと思ってたんですが、違いました。
Kは残すという意味(多分keep) Dは数字という意味(多分digit)、それを組み合わせて、KDで「数字を残す」といったようにキーワードを組み合わせて、挙動を設定しているのでした。

数字を残すか、文字を残すと言った状況が多かったのでKDかKFばかり指定していたせいで気付くのが遅くなりました。

詳しくはヘルプや関数リファレンス等、正式なもので勉強していただけたらと思いますが
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212246.htm



簡単な例だけ

data Q1;
X='123 AaaaBb_';
run;





に対して

data A1;
set Q1;
A1=compress(X,'ab');/*aとbを取り除く*/
A2=compress(X,'ab','k'); /*aとbだけ残す*/
A3=compress(X,'ab','ik'); /*aとAとbとBだけ残すiは大文字小文字を無視する意味*/
A4=compress(X,,'d');/*数字を取り除く*/
A5=compress(X,,'kd');/*数字を残す*/
A6=compress(X,,'f');/*アンダースコアと文字を取り除く*/
A7=compress(X,,'kf');/*アンダースコアと文字を残す*/
A8=compress(X,,'l');/*アルファベット小文字を取り除く*/
run;

とすると


みたいな感じです。

多分、僕より詳しい方、山ほどいると思うので、もっと面白い使い方の例があれば教えてください。






推理詰めSAS②coalesce関数の戻り値が元データにない??

推理詰めSAS2回目です。

【1回目 似て非なるもの】
http://sas-tumesas.blogspot.jp/2013/12/sas.html

また今回も僕とYさんの会話から、一体Yさんが、どんな処理をしているのかを推理し、その上で解決法までを答えてください。

Yさん
すみません、前に教えてもらったcoalesce関数を使って、X,Y,Zっていう3つの変数があるデータセットに対して、X,Y,Zを順番にみて、最初の非欠損値を取得する処理を書いたんですが、、、


「エラーになった?」

Yさん
いえ、エラーにはなりませんでした。WARNINGもでませんでした。だけど、結果をみるとXにもYにもZにも存在しない値が返されてくるんです。XにもYにもZも0から3までしたとらない変数なのに戻り値が4とかになるんです。


「どんな風に書いたの」

Yさん
「以前教えてもらった、ハイフンを2つ並べて変数の格納順に一括指定する方法をつかいました


以上。
さて、Yさんは一体どんなコードを書いてしまったのでしょうか?どのように直せば正常に実行できるでしょうか?








【答え】
今回は、簡単でしたね。
次に僕の言うセリフはこうですね。

「ofつけてないでしょ!!」


つまり、

data Q1;
X=1;Y=.;Z=3;output;
X=.;Y=2;Z=3;output;
X=.;Y=.;Z=3;output;
run;







こういうデータに対して

data A0;
set Q1;
 A=coalesce(X--Z);
run;

こう書いちゃったんですね。








X--ZはX+Zと同じ意味になります。ハイフンがマイナスの意味なるんですね。
マイナスのマイナスはプラスだからXとYを足した一つの値に対してcoalesceをかけています。
これは全く意味のない処理ですが、文法的なエラーではありません。

ただしくは

data A1;
set Q1;
 A=coalesce(of X--Z);
run;

と書いて






でした。

あと一歩、惜しかったねYさん。







whichn関数はwhereステートメントでも使える話とその応用をだらだら

SASを勉強中のYさんから、たとえば以下のようなデータがあって

data Q1;
X=1;Y=2;Z=3;output;
X=2;Y=3;Z=4;output;
X=1;Y=5;Z=1;output;
X=2;Y=.;Z=4;output;
run;

XかYかZのいずれかが1であるデータを抽出するとき

data A0;
set Q1;
where X=1 or Y=1 or Z=1;
run; 

といったように横にひたすらorで同じ条件を変数だけ変えて書き連ねているのですが
もっとマシな書き方ありませんかと聞かれました。

多分、かなりいくらでもあると思うのですが、最近whichn(c)関数やchoosen(c)関数が
マイブームなので

data A1;
set Q1;
where whichn(1,X,Y,Z)>0;
run;

としました。

ただし難点としては

/*エラーになる*/
data E1;
set Q1;
where whichn(1,of X--Z)>0;
run;

whereステートメントで上記のような変数指定の仕方は通らないので一括指定で
どうしても抽出したいなら

data A1_;
set Q1;
if whichn(1,of X--Z)>0;
run;

サブセット化ifにする必要があります。

でも基本的にif抽出よりwhereの方が速いので、対象が巨大なデータの場合ifはお勧めできないかもしれません。


捜索対象がnullの場合whichn(c)関数は使えないので

where X=. or Y=. or Z=.;の場合は例えば

data A2;
set Q1;
where nmiss(X,Y,Z)>0;
run;

とでもしてください。


おまけ

SAS忘備録の「関数の小技」
http://sas-boubi.blogspot.jp/2014/02/blog-post_7.html

にインスパイアされて。

もし抽出条件が

X<1 or Y>=5 or Z^=4

のようにバラバラだった場合も

data OMAKE1;
set Q1;
where whichn(1,X<1,Y>=5,Z^=4);
run;

のように引数に式を入れると、真偽結果0、1が戻る性質を利用して、こんな風にかけちゃう。



data OMAKE1;
set Q1;
where whichn(1,X<1,Y>=5,Z^=4)>0;
A=whichn(1,X<1,Y>=5,Z^=4);
run;

割り当てとけば、どの条件に(最初に)合致して、抽出されたデータなのか確認できるので便利かも










CHOOSEN(C)関数とCALL SORTN(C)ルーチンで、横に可変的に増える変数をソートして指定した順番にくる変数を取得する

固定されたデータが手元に完全にある状態で、プログラムを書けることは幸せです。
実際のデータをみて取りうる範囲から固定した処理がかけるからです。

しかし往々にして、手元にこないと実際どんなデータかわからない、あるいは見てはいけない、という状況で、どんなデータが来ても可変的に対応できるプログラムを書くこともあると思います。



data Q1;
X_1='か';X_2='あ';X_3='さ';X_4='た';output;
X_1='B';X_2='C';X_3='A';X_4='Z';output;
run;






こんなデータがあったとします。

そして非常識なことに、なぜか、このデータは横にも増える可能性があるとします。
今プログラムを組んでも、次に実行する時のデータにはX_5やX_6があるかもしれません。

そして、求められている処理は、1行ごとに、横に辞書的に並び替えて、一番後ろから2番目に並ぶ
値を特定するという、これまたよくわからない処理だとします。

縦に増えていくデータは余裕ですが、横に変数が増えていくデータはやっかいです。
ありえないと思っていても、なぜか現実にそういうデータに当たることもあるものです。

転置して縦にしてから、降順ソートして、2番をとるような処理でもいいですが、できれば構造を
変えず単純にできないかと考え、以下のコードを考えました。


data A1;
set Q1;
call sortc(of X_:);
A=choosec(-2,of X_:);
run;






一行目は「さ」がお尻から2番目の文字で、2行目はCです。

横にいくら変数が追加されてもX_という接頭語が崩れない限りはX_100までいこうが動きます。
仮に変数がX_1の一つだけでもAがnullになるだけで、エラーにはなりません。

call sortcルーチンで変数間で値をソート順に交換させて、からchoosec関数で、指定した順番にいる変数を取得します。-2とすることで、右端から2個目(つまりソートされた最大の値の一つ前)をとることができます。

たった4行で書けたので自分的には大満足です。

もし対象が数値型なら

call sortcはcall sortn
choosec はchoosenに変えてください。


/*追記*/
てか、数値型なら、そもそも smallest関数とlargest関数で、何番目の最小値、最大値とれるからソートの必要ないですね、すみません。





outputでデータセットを1オブザベーションずつ作る

ぱっとテストデータやダミーデータ等、割とどうでもいいデータセットを作りたいとき、
input cardsでデータを作るのが面倒で、僕は

data A0;
X='い';output;
X='ろ';output;
X='は';output;
run;

という書き方をよくします。SASの公式なテキストか何かでも、見たことがあるので、必ずしも筋悪ではないのかもしれません。
一行分の変数に値を割り当てるごとにoutputでオブザベーションを起こしています。
1行書いたらコピペして値を変えるだけなので、小さいデータセットだととても楽です。

ただ、よくあるのが

data A1;
X='い';output;
X='ろ';output;
X='いろは';output;
run;

みたいに書いてしまうと、







最初に格納される値で自動的に長さが設定されてしまうので、LENGTHが$2になり
3行目の値 'いろは' が文字切れします。手抜きするなら1行目に一番長い文字をあてればいいですが、まあ、素直にlengthステートメントうてばいいですね。


さて、ちょっとした問題ですが

data A2;
X=1;Y='A';Z='い';output;
X=2;Y='B';output;
X=3;output;
run;

を実行すると結果はどうなるでしょうか??


3行目のY、2・3行目のZが欠損値になると思った方は残念ながら不正解です。







新たに値を入れない限り、未割当の変数は、保持されます。







ODS OUTPUTからデータセットを作る場合に複数変数を指定したプロシジャ出力の場合に(MATCH_ALL)で変数ごとにデータセットを自動連番で分けてもらう

前の投稿に引き続きODS OUTPUTでデータセットを作る話ですが、前回の(PERSIST=PROC)と違って、今回(MATCH_ALL)については、僕はまだ実戦で使ったことはなくて、あんまりいい使いどころも見つけていません。

例えば、

data Q1;
X=1;Y=3;output;
X=4;Y=3;output;
X=6;Y=5;output;
run;







といったデータセットがあった場合

ods listing close;
ods output basicmeasures=A0;
proc univariate data=Q1;
 var X Y;
run;
ods output close;
ods listing;

とするとvarで2変数指定していますが1プロシージャの出力なので、A0の中身は当然






XとYの要約統計量が両方入ります。

しかし、今度は、1変数ずつ、データセットを分けたいけど、複数回proc univariateは書きたくないなという願望があったとします。

その場合、

ods listing close;
ods output basicmeasures(MATCH_ALL)=AA;
proc univariate data=Q1;
 var X Y;
run;
ods output close;
ods listing;

とすると

データセットAAと、AA1という2つのデータセットが作成され


















それぞれにXとYの結果が入ります。
指定した変数の数だけ、勝手に末尾に自動連番して、データセットを作成してくれます。


実戦で使ったことないのに、なんで自分はこの書き方知ってるんだろうと思って調べてみたら
2013年のSAS GLOBAL Forumの投稿で
「OUT= is on the way out. Use ODS OUTPUT instead」というPaperがあって、そこの中でのネタでした。

on the way out.、 「(1) なくなりかけて, すたれかけて.」

なかなか、きついこといいますね。面白いです。




ODS OUTPUTからデータセットを作る場合に(PERSIST=PROC)で同系の出力を1セットにまとめる。あとODS TRACEの話も

たとえば

data Q1;
do X=1 to 20;
 output;
end;
run;

data Q2;
do Y=1 to 10;
 output;
end;
run;


という2つのデータセットがあるとします。

ods listing close;
ods output basicmeasures=A0;
proc univariate data=Q1;
 var X;
run;
proc univariate data=Q2;
 var Y;
 run;
ods output close;
ods listing;

とするとデータセットA0の中身は









となり、つまりQ1に対してunivariateをかけた時のOutputしか捉えれていないです。

2つめのunivariateの結果をデータセットにしたければ、その直前に再度ods outputを打つ必要が
あります。


しかし

ods output basicmeasures(PERSIST=PROC)=A1;
proc univariate data=Q1;
 var X;
run;
proc univariate data=Q2;
 var Y;
 run;
ods output close;
ods listing;


とすると










となって2つのODS出力が、ひとつのデータセットになります。

これは後で結果をデータステップで結合する必要がなくなったりして
しっていると結構便利です。


ちなみにODS出力の名前は

ods trace on;
proc univariate data=Q1;
 var X;
run;
ods trace off;

としてods trace on offで挟んで対象プロシジャを実行すると、ログに




























これの「名前」の部分を ods outputの後につけて、=作りたいデータセット名でOK。

もっと単純に
























結果のところで、データセットにしたい部分をクリックして右クリック
「名前の変更」で
















コピペできます。