例えば、以下のデータセット
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のデータステップについて深く勉強すると、思いついた面白い構想や、突飛なアイデアを強引に実現できる力がついてきて、結局それがモチベーションになってきてる気がします。
論点からはズレますが、Transpose poocedureは双方向に使えたりするので、何とか1回でできないかと挑戦してみました。
返信削除でもMerge作業が厄介で、結局それほど変わりませんでした(残念)。
proc transpose data=Q1 out=A1 prefix=C_;
by X;
var Y Z W;
run;
data A2(drop=_Name_);
merge A1(where=(_Name_="Y") rename=(C_1=Y_1 C_2=Y_2 C_3=Y_3))
A1(where=(_Name_="Z") rename=(C_1=Z_1 C_2=Z_2 C_3=Z_3))
A1(where=(_Name_="W") rename=(C_1=W_1 C_2=W_2 C_3=W_3));
by X;
run;
コメント有難うございます!!
削除いい感じですね!
ただ、確かにtransposeは同時に複数変数の転置が可能なのですが、その場合、転置されて横展開された数に合わせてrename=を書かないといけないのが難点ですね!
例えば問題のデータでX=’に'のデータができると、C_4=Y_4といったコードを全部付け加える必要があります。
transposeを1回ずつ行う場合は、変数分実行が必要な代償に、横展開数がいくつであってもprefixで制御できるというメリットがあります。
だから、本当は
data A2(drop=_Name_);
merge A1(where=(_Name_="Y") rename=(C_:=Y_:))
A1(where=(_Name_="Z") rename=((C_:=Z_:))
A1(where=(_Name_="W") rename=(C_:=W_:));
by X;
run;
という書き方が、通れば話は早くて、それが最善手だと思うんですよね~!
どうしてrenameステートメントには変数一括機能がないんでしょうね。技術的に障害がなんかあるのかな。
或いは
proc transpose data=Q1 out=A1 prefix=Y_ Z_ W_;
by X;
var Y Z W;
run;
のように、転置変数ごとにprefixの値を変える機能があればそれでもいいんですよね~、これは現実的に将来のリリースでいけそうじゃないですか?
idステートメントの複数指定が、確か9.2からでしたし、9.5とか6で採用されないかな?
そういうリクエスト、米SAS社にだしてみましょうか!
そうなんです。確かに欲しい機能ではありますが、ワイルドカードの書式やオプション機能を増やせば増やすほど、高級言語から低級言語に回帰してしまいそうで、それも問題なのかもと思っています。
返信削除自分にはわかりやすく、%transpose(..., var=X Y Z, prefix=X Y Z, ...)というようなマクロを作って対応してはいます。
こんにちは。横からすみません。
返信削除匿名さんの方法で、以下のように改良すればrenameの手間は若干省けるかなと思います。
dkricondはちょっとした禁じ手かもしれませんが。
ただこういう禁じ手でも適切な場面で使用できる人はどんどん使っていいと思うんですよね。
options dkricond=nowarn;
data A2(drop=_Name_);
merge A1(where=(_Name_="Y") rename=(C_1-C_100=Y_1-Y_100))
A1(where=(_Name_="Z") rename=(C_1-C_100=Z_1-Z_100))
A1(where=(_Name_="W") rename=(C_1-C_100=W_1-W_100))
;
by X;
run;
options dkricond=warn;
おおっ鬼手でたっ!dkricondオプションですか。
返信削除nodsnferrの変数版みたいなやつで、読み込まれたデータにrenameやkeepの対象変数が存在しなくても正常実行させるというやつですね!ありでしょ!
相変わらずキレキレですね。
ただ今さらですが、transpose1回だと、対象変数に数値型と文字型が両方あった場合、強制的に全て文字型になってしまいますね。
rename=(C_1-C_100=Y_1-Y_100) こんな書き方ができるんですね。素晴らしい!
返信削除options dkricond=nowarnと書いても書かなくても同じ結果なのじはSAS9.2だからでしょうかね?
あ、わかりました dkricond ・・・ 100ですね。 どちらも大いに使えそうです。ありがとうございました!!!
返信削除