ラベル array の投稿を表示しています。 すべての投稿を表示
ラベル array の投稿を表示しています。 すべての投稿を表示

call sortc(sortn sorth)ルーチンで降順ソートする小技

call sortc(sortn sorth)ルーチンについては何度か取り上げています。

・横方向(列方向)にcall sortn,call sortcルーチンを使ってソート処理をかける
http://sas-tumesas.blogspot.jp/2013/10/call-sortncall-sortc.html

・CHOOSEN(C)関数とCALL SORTN(C)ルーチンで、横に可変的に増える変数をソートして指定した順番にくる変数を取得する
http://sas-tumesas.blogspot.jp/2014/02/choosenccall-sort.html

call sort系ルーチンには降順指定がないため、基本的には昇順しかできません。
それをどうしたら降順にできるかという話です。
※もしかしたら、既にどこかの記事で話したか、或いはamatsuさんからコメント等で教えて貰った内容かもしれません。最近、自分でも何を投稿したか忘れてて、もし重複してたらすみません。

今、

data Q1;
X1='S';
X2='Y';
X3='O';
X4='G';
X5='I';
run;



があって

data A1;
set Q1;
call sortc(of X1-X5);
run;

とすると






このように昇順になります。

これを降順にするには

data A2;
set Q1;
call sortc(of X5-X1);
run;

というように指定を逆向きにすれば






と、結果的に降順になります。

amatsuさんが
記事:変数指定は「V100-V1」のように逆にもできる。
http://sas-boubi.blogspot.jp/2014/06/v100-v1.html

で紹介されていたことと理屈は同じです。

引数に配列を使う場合は、配列に要素を指定する場合の、指定順番を逆にすれば
OKです。

retainステートメントに配列指定できる話

もしかしたら、初等技術なのかもですが、最近初めて知った話です。

例えば












があって、
Xで重複を消して、Yを使って、Zを横展開しろと言われたら
迷うことなく

proc transpose data=Q1 out=A1(drop=_NAME_) prefix=Z_;
 var Z;
 by X;
 id Y;
run;









ですね。

一番好きなプロシージャはと訊かれたらtransposeと答える僕なので
(訊かれたこと一度もありませんが)、これ以外のやり方がぱっと思いつかないんですよね。

ただ、最近知ったのが

proc sql noprint;
 select max(Y) into:MY
 from Q1;
quit;

data A2;
 set Q1;
 by X;
 array Z_{&MY};
 retain Z_;
 if first.X then call missing(of Z_{*});
  Z_{Y}=Z;
  if last.X then output;
 drop Y Z;
run;

で、この場合結果は同じです。
Yの値が例えば1と4だけなどで飛び値になっている場合、transposeだとZ_1とZ_4だだけが作られますが、こちらの方法だとZ_2とZ_3もnullで作成されるので、ケースによって使い分けれそうです。

何が面白いって、retain 配列名が通るんですね!確かに、通りそうですけど、意外と
その発想がなかったです。


あとついでに関係ないけど、これまた最近知った

data A3;
array AX{2,3,4}(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1);
 D1=dim(AX);
 D2=dim2(AX);
 D3=dim3(AX);
keep D1 D2 D3;
run;





へ~dim関数って dim2 dim3とかにして、次元ごとの数とれるんですね!
いつも1次元でdim使ってたけど、dim1ってことだったのか~

変数ごとの転置結果を再マージするような処理について、変数がいくつあろうが必ず2ステップでケリをつける方法。do until(last.変数)ループを使って

例えば、以下のデータセット

data Q1;
X='い';Y=1;Z='A';W=1;output;
X='い';Y=2;Z='B';W=1;output;
X='い';Y=3;Z='C';W=2;output;
X='は';Y=4;Z='D';W=2;output;
X='は';Y=5;Z='E';W=.;output;
X='ろ';Y=6;Z='E';W=3;output;
run;
proc sort data=Q1;
 by X;
run;














を使って









のようにXを起点にしてその他の変数を全て転置したデータセットを作成せよと言われたらどうしますか?

多分、すぐに思いつくのが

proc transpose data=Q1 out=_Y(keep=X Y_:) prefix=Y_;
 var Y;
 by X;
run;
proc transpose data=Q1 out=_Z(keep=X Z_:) prefix=Z_;
 var Z;
 by X;
run;
proc transpose data=Q1 out=_W(keep=X W_:) prefix=W_;
 var W;
 by X;
run;

data A0;
 merge _Y _Z _W;
 by X;
run;

だと思います。

ただし、この方法だと、転置する変数分だけtransposeを書くため、どうしても処理に無駄があるように僕はずっと感じてきました。

特に以前、仕事で、臨床検査値と臨床検査基準値を、検査日と基準値適応開始日を比較しながらマージすることが多かったため、その過程で上記のコードをよく書いていたため、もっと改良できないかと常に思っていました。

それに対する一つの答えが、今年ユーザー総会で発表したハッシュオブジェクトに関する論文で、ハッシュオブジェクトを利用することで、そもそも上記のようなデータセット加工を行わずに、ダイレクトにマッチングができるという内容でした。

それで少しは気が晴れたのですが、それはあくまで、連続transposeと再マージ処理を避けるテクニックなので、まだひっかかりがありました。

そこで今回、2ステップにはなりましたがdo untilの終了条件にlast.変数を使う特殊なループ法でやってみました。

ちなみにこのループ法、海外ではDow loopと呼ばれていて、「SAS dow loop」で検索すると、その応用法がたくさんでてきます。

Dowの「w」の文字はIan Whitlockという方の名前からつけられました。2000年にこの方が、初めてSAS-Lというコミュニティで、この方法を発表し、それがあまりに画期的であったため、リスペクトを込めて、ループの終了条件にfirstやlast等を使用するような変則ループ全般をDOWループと呼ぶようになったそうです。いい話だな~、うらやましいなぁ。

僕はa matsuさんにこのブログのコメントで初めDowの書き方を教えていただきました。

さて、前置きが長くなりましたが、コードです。

proc sql noprint;
 select max(A)into:TNMAX from
 (select count(*) as A from Q1 group by X);
quit;

data A1;
informat X Y_: Z_: W_:;
 array Y_(&TNMAX) 8. ;
 array Z_(&TNMAX) $2.;
 array W_(&TNMAX) 8. ;
 do until(last.X);
  set Q1;
  by X;
  if first.X then i=0;
  i+1;
  Y_{i}=Y;
  Z_{i}=Z;
  W_{i}=W;
 end;
drop Y Z W i;
run;

で、結果は先述の通りになります。

まあ、満足。

ただ、まだ、もうちょっと面白い手順があるんじゃないかと思うのでもう少し考えてみます。

SASのデータステップについて深く勉強すると、思いついた面白い構想や、突飛なアイデアを強引に実現できる力がついてきて、結局それがモチベーションになってきてる気がします。



ちょっとした話 配列要素にコロンとループ変数の初期値

もしかしたら既に別の投稿で触れていたかもしれませんが、最近はっとした、小話です。

data Q1;
 array X{5}(1:5);
run;

とすると




こうなります。

おぉ~、in演算子のコロン範囲指定と同じ理屈か~。

コロン範囲指定については
SAS忘備録の「条件式(IF/WHERE)におけるINオペレータの小技」
http://sas-boubi.blogspot.jp/2014/01/in.html


ちなみに全部に1を入れたければ
data _Q1;
 array X{5}(5*1);
run;

と書けば、1を5要素に入れるという指定になります。


もう一つは、もしかしたら常識なのかも

data Q2;
 do until(X=2);
  output;
  X+1;
 end;
run;

の結果は






です。

あ~、いっつもループ変数に初期値割り当ててから回してたから、意識したことなかったけど
なにも指定しないと0から始まるんだ、そうだっけ?知ってたような、初めて知ったような。


of で関数の引数に配列

SAS忘備録「関数の中でOFを使うと変数の指定が楽になる例と応用例」
http://sas-boubi.blogspot.jp/2014/07/of.html

を読んで、思い出したのですが、

data A1;
 array A{3} (1,2,3);
 array B{3} (4,5,6);
 S=sum(of A(*) B(*));
run;

のように複数の配列を引数にとれるんですね。
考えてみると自然な感じですが、わりと最近まで知りませんでした。

上記のように結果的に全数値変数が対象であるなら

sum(of _numeric_);

sum(of _all_);

でも結果は同じです。




非明示添字配列と do over LOOPの利用

ARRAYステートメントを初めて覚える時、
 
array 配列名 {配列要素数} 以下、$や長さや配列要素内容や初期値といった感じで、

とりあえず array 配列名 {配列要素数} までは鉄板と覚えてしまいがちですが
それは正確には明示添字配列の定義法であり、
実は{配列要素数}を省略して定義することができます。

例えば、以下のようなデータセットがあっととします

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





XからZまでを配列として、配列要素全てに+1をする場合、
書き方はいくらでもありますが、例えば

data A0;
 set Q1;
 array AR{*} X--Z;
 do i=1 to dim(AR);
  AR{i}=AR{i}+1;
 end;
 drop i;
run;





と書いたとします。
ここでは{*}と要素数を定数にしていませんが、do to loopの終端条件で
dim関数でARの要素数を取得しているので問題ありません。

ただ、よくよく考えると、配列を定義して、その配列全部におんなじ処理を一括で
かける今回のような場合、要素番号ってあんまり役にたってません。

そこで実は以下のようなコードが成立します。

data A1;
 set Q1;
 array AR X--Z;
 do over AR;
  AR=AR+1;output;
 end;
run;

結果は同じです。
 array AR X--Z;と要素番号定義をすっとばしています。
 do i=1 to dim(AR);が do over AR;となっています。do overは全要素に対して順にループするという
 do i=1 to dim(AR);と同義になります。

ちなみに明示添字配列で定義した配列にdo overを使用すると以下のエラーメッセージです。




「ERROR: 明示添字配列に DO OVER ステートメントは使用できません。」

ただし、たとえば以下のコードのように非明示添字配列で定義しても
要素番号処理は裏でちゃんと勝手に行われているので

data A2;
 set Q1;
 array AR X--Z;
  A=AR{1};
  B=dim(AR);
run;




エラーになったりはしません。


ARRAY配列の記述法

ARRAYの話題をもう一つ連投します。
といっても、こっちは他愛のない話です。

私は最近までしらなかったのですが、以下のコードの結果はどうなるでしょうか?

data A1;
array ANUM {5} (5*3);
run;





となるんですね、へー。5回、3を入れているってことですか。


同じ理屈で

data A2;
array ANUM {5} (2*3 3*4);
run;







です。

基礎知識なんでしょうか??
知らないと、コード見たとき、??ってなります。


ARRAY配列の要素番号は1から始めなくてもよく、任意の値を設定して意味を持たせられる。loopについてはdim関数の代わりにlbound,hbound関数を使える

ARRAYステートメントによる1次元配列の定義を行った場合、一つ目に配列に含まれる変数が要素番号1をもち、次は2、次は3と連番になっていきます。そこで
do i=1 to dim(配列名);処理;end;といった書き方をして、要素数分処理をループさせたりして利用することが多いと思います。

さて、話は変わって、今以下のようなデータセットがあったとします。

data Q1;
_95=1;_96=5;_97=8;_98=12;output;
run;




適当なデータですが、これは1995年から98年までのある商品の売り上げだとします。
_95は1995年の売上といったように、数字が意味を持っています。

そういった場合で、配列を定義する際、処理の内容によっては要素番号を1からの連番にして
意味を失わせるよりも、任意の数字を定義して、リンクさせた方が処理がわかりやすい場合もあります。

先にコードから

data A1;
set Q1;
 array SALES{95:98} _:;

 X=SALES(97);

run;





ここでSALESという配列を定義していますが{95:98}とすることで、この配列は
要素番号95から98までを持つ配列となります。

たとえば X=SALES(97);とするとxには_97の値、すなわち1997年の売上を取得できます。
X=SALES(1)などと書くと、当然エラーになります。


なるほど、要素番号に任意の数字を設定できるのはわかったとして、じゃあi=1 to dim(配列名)みたいな処理が書けなくなってるんじゃないかと思いますが、

そこはlbound関数やhbound関数を使用できます。こいつらは配列名を引数にとり、その配列の要素番号の最小値または最大値を返します。

つまり

data A2;
 set Q1;
 array SALES{95:98} _:;
 do YEAR=lbound(SALES) to hbound(SALES);
   X=SALES(YEAR);
  output;
 end;
run;

とすると95から98でループがかかります。







面白いです。