例えば、以下のデータセット
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のデータステップについて深く勉強すると、思いついた面白い構想や、突飛なアイデアを強引に実現できる力がついてきて、結局それがモチベーションになってきてる気がします。
水準がデータで揃わずスカスカの集計表に立ち向かう_meansやsummaryのclassdata=とtransposeのid 複数変数を利用して
以前、meansまたはsummaryプロシジャのclassdata=オプションは集計表を作成するうえで役立つと言い詳細はまた紹介すると書きました。
(ちなみにmeansとsummaryの違いは、デフォルトでアウトプット出力するかどうかです。つまりmeansにnoprintをつければsummaryと同じで、逆にsummaryにprintをつければmeansと同じです。)
またtransposeプロシジャのIDステートメント複数づけのおかげで、集計表が作りやすくなったとも言い、実例をいつか紹介すると書きました。
遅くなりましたが、ざっくりとした流れを紹介したいと思います。
今、以下のようなデータセットがあるとします
data Q_1;
GROUP='A';SUBGROUP='X';LEVEL1=2;LEVEL2=1;LEVEL3=5;output;
GROUP='A';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=4;output;
GROUP='B';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=3;output;
run;
で、GROUPはA群とB群がいて
それぞれのグループにSUBGROUP X、Y、 Zがあるとします。
そして、3つの変数があり
LEVEL1は1-2の値をとります。
LEVEL2は0-1の値をとります。
LEVEL3は3-5の値をとります。
ところが、今まだデータが集まっていない、またはデータの収集が打ち切られた等の
理由で、たった3オブザベーションしかありません。
このわずかのデータで
のようなEXCELの集計表テンプレートに出力する必要があるとします。
つまり、大半のセルは0になります。
どんな言語でも、あるデータをだすのは簡単ですが、ないデータをだすプログラムは難しいものです。とりあえず先にコード全部のせます。
data CLDS;
do GROUP='A','B';
do SUBGROUP='X','Y','Z';
do DUMMY=0 to 20;
output;
end;
end;
end;
run;
/*================================================
マクロ名:syogi
引数-①dsname 対象データセット
②varname 対象変数
③minval 対象変数のとりうる最小のカテゴリ数値
④maxval 対象変数のとりうる最大のカテゴリ数値
=================================================*/
%macro syougi(dsname,varname,minval,maxval);
proc means data=&dsname.
classdata=CLDS(rename=(DUMMY=&varname.) where=(&minval.<=&varname.<=&maxval.))
noprint nway exclusive;
class GROUP SUBGROUP &varname.;
var &varname.;
output out=&varname._(drop=_TYPE_ _FREQ_) N=COUNT;
run;
proc sort;
by &varname.;
run;
proc transpose data=&varname._ out=_&varname.(drop=_NAME_ rename=(&varname.=VAL)) delimiter=_;
var COUNT;
id GROUP SUBGROUP;
by &varname.;
run;
%mend;
/*実行*/
%syougi(Q_1,LEVEL1,1,3)
%syougi(Q_1,LEVEL2,0,1)
%syougi(Q_1,LEVEL3,3,5)
data A_1;
set _:;
run;
/*EXCEL出力*/
libname OUTEX "D:\集計.xlsx" header=no scan_text=no;
data OUTEX."Sheet1$D11:I18"n ;
set A_1;
modify OUTEX."Sheet1$D11:I18"n ;
F1=compress(put(A_X,best.));
F2=compress(put(A_Y,best.));
F3=compress(put(A_Z,best.));
F4=compress(put(B_X,best.));
F5=compress(put(B_Y,best.));
F6=compress(put(B_Z,best.));
run;
libname OUTEX clear;
まず、CLDSというのを作っています。
これはクラスデータセットといって、meansのクラスで指定する変数の、フルパターンをクラスデータセットに作成しておくことで、カウントであれば実際のデータの水準が足りなくても0で補完して計算してくれます。
ただ、1つの変数に対しての集計ならいいのですが、複数で、かつ採りうる値が変数ごとに違う場合、変数分それに対応するクラスデータセットをつくってたら煩雑すぎます。
なので、通常、群やサブグループなど固定のクラス変数は固定して、それ以外のカテゴリ値の部分は一端ダミーの変数名にして、大きめにデータセットを作っておきます。
以下がCLDSの中身(一部です)です。
(一部です)
それで、マクロの中で、実際に集計する対象の変数を指定して、ダミー変数をその変数名にrenameして、かつそのカテゴリ値の採りうる値でwhereでクラスデータセットを絞っています。
以下が、マクロにLEVEL1を指定した時の、meansからoutされたデータセットです。
欠損水準を0で補って、フルセットで集計されています。
しかしこのままだと縦持ちなのでこれをグループ、サブグループで転置します。
以下は上記のデータセットがtransposeされた後のデータセットです
delimiter=でアンダーバーを追加しています。
たとえば、A_XはグループAのサブグループXの集計結果というわけです。
それでLEVEL1-3までを集計してその結果をコロンモディファイア指定でSetしてつなぎます。
最後にLIBNAME EXCELでだしていますが、DDEでもなんでもいいです。
ざっくりとした例なので、実際に使用される場合は、適宜書き換えてください(とりあえずマクロ名変えましょう)。そのままべたっと貼っても動きません多分。
で注目点はクラスデータと、実際のデータのフォーマットやラベルなどのメタデータが同期していることが必要なのでLENGTHを合わせたり、
などで、余計なメタデータを消しておいた方がいいです。
classdata=使うといつもエラーになって、あきらめますという話を聞くのですが
大半はここが原因です。
あと最後に、若干ネタ切れ感がでてきたので、取り上げてほしい部分や、詰めSASの問題など
アイデアがある方はご連絡ください
(ちなみにmeansとsummaryの違いは、デフォルトでアウトプット出力するかどうかです。つまりmeansにnoprintをつければsummaryと同じで、逆にsummaryにprintをつければmeansと同じです。)
またtransposeプロシジャのIDステートメント複数づけのおかげで、集計表が作りやすくなったとも言い、実例をいつか紹介すると書きました。
遅くなりましたが、ざっくりとした流れを紹介したいと思います。
今、以下のようなデータセットがあるとします
data Q_1;
GROUP='A';SUBGROUP='X';LEVEL1=2;LEVEL2=1;LEVEL3=5;output;
GROUP='A';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=4;output;
GROUP='B';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=3;output;
run;
で、GROUPはA群とB群がいて
それぞれのグループにSUBGROUP X、Y、 Zがあるとします。
そして、3つの変数があり
LEVEL1は1-2の値をとります。
LEVEL2は0-1の値をとります。
LEVEL3は3-5の値をとります。
ところが、今まだデータが集まっていない、またはデータの収集が打ち切られた等の
理由で、たった3オブザベーションしかありません。
このわずかのデータで
のようなEXCELの集計表テンプレートに出力する必要があるとします。
つまり、大半のセルは0になります。
どんな言語でも、あるデータをだすのは簡単ですが、ないデータをだすプログラムは難しいものです。とりあえず先にコード全部のせます。
data CLDS;
do GROUP='A','B';
do SUBGROUP='X','Y','Z';
do DUMMY=0 to 20;
output;
end;
end;
end;
run;
/*================================================
マクロ名:syogi
引数-①dsname 対象データセット
②varname 対象変数
③minval 対象変数のとりうる最小のカテゴリ数値
④maxval 対象変数のとりうる最大のカテゴリ数値
=================================================*/
%macro syougi(dsname,varname,minval,maxval);
proc means data=&dsname.
classdata=CLDS(rename=(DUMMY=&varname.) where=(&minval.<=&varname.<=&maxval.))
noprint nway exclusive;
class GROUP SUBGROUP &varname.;
var &varname.;
output out=&varname._(drop=_TYPE_ _FREQ_) N=COUNT;
run;
proc sort;
by &varname.;
run;
proc transpose data=&varname._ out=_&varname.(drop=_NAME_ rename=(&varname.=VAL)) delimiter=_;
var COUNT;
id GROUP SUBGROUP;
by &varname.;
run;
%mend;
/*実行*/
%syougi(Q_1,LEVEL1,1,3)
%syougi(Q_1,LEVEL2,0,1)
%syougi(Q_1,LEVEL3,3,5)
data A_1;
set _:;
run;
/*EXCEL出力*/
libname OUTEX "D:\集計.xlsx" header=no scan_text=no;
data OUTEX."Sheet1$D11:I18"n ;
set A_1;
modify OUTEX."Sheet1$D11:I18"n ;
F1=compress(put(A_X,best.));
F2=compress(put(A_Y,best.));
F3=compress(put(A_Z,best.));
F4=compress(put(B_X,best.));
F5=compress(put(B_Y,best.));
F6=compress(put(B_Z,best.));
run;
libname OUTEX clear;
まず、CLDSというのを作っています。
これはクラスデータセットといって、meansのクラスで指定する変数の、フルパターンをクラスデータセットに作成しておくことで、カウントであれば実際のデータの水準が足りなくても0で補完して計算してくれます。
ただ、1つの変数に対しての集計ならいいのですが、複数で、かつ採りうる値が変数ごとに違う場合、変数分それに対応するクラスデータセットをつくってたら煩雑すぎます。
なので、通常、群やサブグループなど固定のクラス変数は固定して、それ以外のカテゴリ値の部分は一端ダミーの変数名にして、大きめにデータセットを作っておきます。
以下がCLDSの中身(一部です)です。
(一部です)
それで、マクロの中で、実際に集計する対象の変数を指定して、ダミー変数をその変数名にrenameして、かつそのカテゴリ値の採りうる値でwhereでクラスデータセットを絞っています。
以下が、マクロにLEVEL1を指定した時の、meansからoutされたデータセットです。
欠損水準を0で補って、フルセットで集計されています。
しかしこのままだと縦持ちなのでこれをグループ、サブグループで転置します。
以下は上記のデータセットがtransposeされた後のデータセットです
delimiter=でアンダーバーを追加しています。
たとえば、A_XはグループAのサブグループXの集計結果というわけです。
それでLEVEL1-3までを集計してその結果をコロンモディファイア指定でSetしてつなぎます。
最後にLIBNAME EXCELでだしていますが、DDEでもなんでもいいです。
ざっくりとした例なので、実際に使用される場合は、適宜書き換えてください(とりあえずマクロ名変えましょう)。そのままべたっと貼っても動きません多分。
で注目点はクラスデータと、実際のデータのフォーマットやラベルなどのメタデータが同期していることが必要なのでLENGTHを合わせたり、
proc datasets nolist;
modify Q_1;
attrib _all_ informat= format= label='';
quit;
などで、余計なメタデータを消しておいた方がいいです。
classdata=使うといつもエラーになって、あきらめますという話を聞くのですが
大半はここが原因です。
あと最後に、若干ネタ切れ感がでてきたので、取り上げてほしい部分や、詰めSASの問題など
アイデアがある方はご連絡ください
正直あまりいいオプションとは思えませんが、むしろ反面教師としてTRANSPOSEプロシジャのletの説明
transpsoeプロシジャのletオプションは、ちょっと危ないオプションというか、
絶対にWarningのでるオプションで、はっきり言って僕は絶対使わない方がいいと思うオプションです。
よくわかってない人が全部のtransposeプロシジャの使用箇所にletオプションをつけたせいで、えらいことになって、代わりに書きなおすはめになったことがあるので、個人的に恨みがあります。
どういうことかといいますと、
通常transposeでidステートメントを使用する場合、指定された変数の値に重複があると、転置ができないためエラーになります。これは当然の処置で、本来idに指定する変数は一意に値を定める手がかりにするものなので重複は論外、エラーになるのが正しいのです。
ところがletオプションは、これを強引にWARNINGに格下げして、データセットを最後まで作成します。重複をどのようにして解決するかというと、重複する変数の最後の値のみ採用するという方法で回避します。
例えば以下のようなデータセットがあったとします。
data Q1;
X='A';Y=1;Z=1;output;
X='A';Y=2;Z=2;output;
X='A';Y=3;Z=4;output;
X='A';Y=3;Z=3;output;
X='A';Y=3;Z=3;output;
X='B';Y=1;Z=1;output;
X='B';Y=2;Z=3;output;
X='B';Y=2;Z=2;output;
X='B';Y=3;Z=4;output;
run;
proc transpose data=Q1 out=A1 prefix=Y_;
var Z;
id Y;
by X;
run;
を実行すると
となってデータセットは作成されません。
Yの値が重複してるからです。
例えばX='A'のグループにおいてY_3変数が3つ作られてしまい、SASは同じ変数名をデータセット内
に保持できないのでエラーなのです。
ところが
proc transpose data=Q1 out=A1 prefix=Y_ let;
var Z;
id Y;
by X;
run;
となってERRORからWARNINGにメッセージレベルが変わり、データセットは作成されます。
中身は
です。
いや、まあ便利といってしまえばそうなのかもしれませんが、どうせ重複した値を最後を除いて
消すのなら
transposeに流す前に、nodupkeyとかfirst.last.とかで加工して、一意になる形にしてから
通常の転置をした方がWARNINGもでないし、正道だと思うのです。
どうしても一意にできないデータセットなら、そもそも転置をかけること自体が間違っているか
データ構造に不備があるのではないかと思います。
「このオプションつけるとなんでも転置できて凄い便利ですよ~、でもなんかログが緑になるんですよね~。WARNINGでないようにするオプション知りませんか?」とか言ってたら、ちょっと、その人はヤバイというか、身内なら、全力で叩きなおした方がいいかもしれません。
絶対にWarningのでるオプションで、はっきり言って僕は絶対使わない方がいいと思うオプションです。
よくわかってない人が全部のtransposeプロシジャの使用箇所にletオプションをつけたせいで、えらいことになって、代わりに書きなおすはめになったことがあるので、個人的に恨みがあります。
どういうことかといいますと、
通常transposeでidステートメントを使用する場合、指定された変数の値に重複があると、転置ができないためエラーになります。これは当然の処置で、本来idに指定する変数は一意に値を定める手がかりにするものなので重複は論外、エラーになるのが正しいのです。
ところがletオプションは、これを強引にWARNINGに格下げして、データセットを最後まで作成します。重複をどのようにして解決するかというと、重複する変数の最後の値のみ採用するという方法で回避します。
例えば以下のようなデータセットがあったとします。
data Q1;
X='A';Y=1;Z=1;output;
X='A';Y=2;Z=2;output;
X='A';Y=3;Z=4;output;
X='A';Y=3;Z=3;output;
X='A';Y=3;Z=3;output;
X='B';Y=1;Z=1;output;
X='B';Y=2;Z=3;output;
X='B';Y=2;Z=2;output;
X='B';Y=3;Z=4;output;
run;
proc transpose data=Q1 out=A1 prefix=Y_;
var Z;
id Y;
by X;
run;
を実行すると
となってデータセットは作成されません。
Yの値が重複してるからです。
例えばX='A'のグループにおいてY_3変数が3つ作られてしまい、SASは同じ変数名をデータセット内
に保持できないのでエラーなのです。
ところが
proc transpose data=Q1 out=A1 prefix=Y_ let;
var Z;
id Y;
by X;
run;
となってERRORからWARNINGにメッセージレベルが変わり、データセットは作成されます。
中身は
です。
いや、まあ便利といってしまえばそうなのかもしれませんが、どうせ重複した値を最後を除いて
消すのなら
transposeに流す前に、nodupkeyとかfirst.last.とかで加工して、一意になる形にしてから
通常の転置をした方がWARNINGもでないし、正道だと思うのです。
どうしても一意にできないデータセットなら、そもそも転置をかけること自体が間違っているか
データ構造に不備があるのではないかと思います。
「このオプションつけるとなんでも転置できて凄い便利ですよ~、でもなんかログが緑になるんですよね~。WARNINGでないようにするオプション知りませんか?」とか言ってたら、ちょっと、その人はヤバイというか、身内なら、全力で叩きなおした方がいいかもしれません。
TRANSPOSEプロシジャのdelimiter=を使ってみる
SAS9.2からtransposeプロシジャのidステートメントに複数変数を指定できることは何回か紹介したのですが、それに付随してdelimiter=が追加されていることを紹介し忘れていました。
今、以下のデータセットがあったとします。
data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;
idステートメントにXとYの2変数を指定して以下のコードで転置すると
proc transpose data=Q1 out=A1;
var Z;
id X Y;
run;
となります。
変数名をみるとXとYの値がそのままくっついているのがわかります。
ただ、これって例えば変数によって入っている値の長さがばらばらで、内容が似ていたりすると
どこからどこまでが何個目の変数から生成された部分か判別できないことがあります。
delimiter=はその間に接続文字を入れることができます(変数の命名規則には注意)
つまり
proc transpose data=Q1 out=A2 delimiter=_;
var Z;
id X Y;
run;
とすると
となります
proc transpose data=Q1 out=A3 delimiter=and;
var Z;
id X Y;
run;
なら
です。
今、以下のデータセットがあったとします。
data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;
idステートメントにXとYの2変数を指定して以下のコードで転置すると
proc transpose data=Q1 out=A1;
var Z;
id X Y;
run;
となります。
変数名をみるとXとYの値がそのままくっついているのがわかります。
ただ、これって例えば変数によって入っている値の長さがばらばらで、内容が似ていたりすると
どこからどこまでが何個目の変数から生成された部分か判別できないことがあります。
delimiter=はその間に接続文字を入れることができます(変数の命名規則には注意)
つまり
proc transpose data=Q1 out=A2 delimiter=_;
var Z;
id X Y;
run;
とすると
となります
proc transpose data=Q1 out=A3 delimiter=and;
var Z;
id X Y;
run;
なら
です。
TRANSPOSEプロシジャのname=を使ってみる
transposeプロシジャのname=、そういえば使ったことありませんでした。
例えば以下のデータセットがあって
data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;
proc transpose data=Q1 out=A1;
var Y Z;
id X;
run;
となるところを、name=をつけると
proc transpose data=Q1 out=A2 name=YZ;
var Y Z;
id X;
run;
要は_NAME_を任意の変数名にかえるオプションですか。なるほど。
例えば以下のデータセットがあって
data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;
proc transpose data=Q1 out=A1;
var Y Z;
id X;
run;
となるところを、name=をつけると
proc transpose data=Q1 out=A2 name=YZ;
var Y Z;
id X;
run;
要は_NAME_を任意の変数名にかえるオプションですか。なるほど。
縦→横 横→縦 共に転置でtransposeプロシジャの領分だという話
縦持ちのデータを横持ちにかえるのがtransposeプロシジャなら、
逆は何プロシジャですかと直接ご質問をいただきました。
有難うございます。了解をいただいたので、せっかくなのでネタにさせていただきます。
縦から横への構造変化を例として、転置という言葉が用いられることが多いため
つい誤解してしまうところなのですが、転置は両方の意味を持ちます。
つまり、横から縦、つまり変数からオブザベーションへの変化もtransposeプロシジャのお仕事です。
今、下記のデータがあるとします。
2人の検査データを1週おきに3週間とって、横に値を持たせた構造です。
data Q1;
ID='TEST-001';WEEK_1=5;WEEK_2=6;WEEK_3=7;output;
ID='TEST-002';WEEK_1=3;WEEK_2=9;WEEK_3=8;output;
run;
こいつを縦にしたければ
proc sort data=Q1;
by ID;
run;
proc transpose data=Q1 out=A1;
var WEEK_:;
by ID;
run;
で
となり、まあ体裁を整えるなら
data A2;
set A1;
WEEK=input(scan(_NAME_,2,'_'),best.);
VAL=COL1;
drop _NAME_ COL1;
run;
とでもして、以下の形です。
ちなみに、これを一番最初の横持ちに戻したければ
proc sort data=A2;
by ID;
run;
proc transpose data=A2 out=Q1(drop=_NAME_) prefix=WEEK_;
var VAL;
id WEEK;
by ID;
run;
です。
逆は何プロシジャですかと直接ご質問をいただきました。
有難うございます。了解をいただいたので、せっかくなのでネタにさせていただきます。
縦から横への構造変化を例として、転置という言葉が用いられることが多いため
つい誤解してしまうところなのですが、転置は両方の意味を持ちます。
つまり、横から縦、つまり変数からオブザベーションへの変化もtransposeプロシジャのお仕事です。
今、下記のデータがあるとします。
2人の検査データを1週おきに3週間とって、横に値を持たせた構造です。
data Q1;
ID='TEST-001';WEEK_1=5;WEEK_2=6;WEEK_3=7;output;
ID='TEST-002';WEEK_1=3;WEEK_2=9;WEEK_3=8;output;
run;
こいつを縦にしたければ
proc sort data=Q1;
by ID;
run;
proc transpose data=Q1 out=A1;
var WEEK_:;
by ID;
run;
で
となり、まあ体裁を整えるなら
data A2;
set A1;
WEEK=input(scan(_NAME_,2,'_'),best.);
VAL=COL1;
drop _NAME_ COL1;
run;
とでもして、以下の形です。
ちなみに、これを一番最初の横持ちに戻したければ
proc sort data=A2;
by ID;
run;
proc transpose data=A2 out=Q1(drop=_NAME_) prefix=WEEK_;
var VAL;
id WEEK;
by ID;
run;
です。
TRANSPOSE→CATX関数のコンボで縦持ちのデータを1行1変数に指定文字で連結してまとめてみる
CATX関数の使い勝手がよいという話題です。
たとえば以下のようなデータがあったとします。
data Q1;
length ID $10.;
ID='AAA';NO=2;ITEM='ろ';output;
ID='AAA';NO=1;ITEM='い';output;
ID='AAA';NO=3;ITEM=''; output;
ID='AAA';NO=4;ITEM='は';output;
ID='BBB';NO=1;ITEM='A'; output;
ID='BBB';NO=5;ITEM='B'; output;
run;
これをIDごとに、出現するITEMの内容を、NO順に「、」で連結して
ALLITEMという変数にいれるという問題があったとします。
求めたい結果は以下です。
なんか面倒そうだと感じますが、楽勝です。
まず、IDとNOでソートした後、IDをbyで固定してtransposeします。
このときIDステートメント(すみません、変数とまぎらわしいですね)でNOを指定します。
proc sort data=Q1;
by ID;
run;
proc transpose data=Q1 out=Q2(drop=_NAME_) prefix=ITEM_;
var ITEM;
by ID;
id NO;
run;
すると
のデータセットが得られます。
後はこれを順番に「、」で繋ぎたいのですが、nullの場合は飛ばしたいわけです。
空白が間にはいるのも嫌なので、う~んどうしよう、IFいっぱい書かなきゃいけないかと思いきや
data A;
set Q2;
ALLITEM=catx('、',of ITEM_:);
keep ID ALLITEM;
run;
これで、終わりです。
catx関数は指定した区切り文字で、null以外の値を、前後の空白を除去した上で連結してくれるという、凄く気のきいた関数です。
もちろん
ALLITEM=catx('、',ITEM_1,ITEM_2,ITEM_3,ITEM_4,ITEM_5);
とかいていいのですが、そうすると元データが増えてNO「6」ができたら
コードを修正する必要がでてしまいます。
TRANSPOSEプロシジャのprefix=は接頭語を指定できるので(suffix=は接尾語)
ここをうまく使えば、NOがどんな数字だろうと、転置後の変数は「ITEM_」から始まります。
そこで of とコロンモディファイアで、配列を使うまでもなくターゲットの変数を全て捕まえることが
できます。
CAT やCATSなど、微妙に違うことをしてくれる関数もあるので調べてみるといいと思います
たとえば以下のようなデータがあったとします。
data Q1;
length ID $10.;
ID='AAA';NO=2;ITEM='ろ';output;
ID='AAA';NO=1;ITEM='い';output;
ID='AAA';NO=3;ITEM=''; output;
ID='AAA';NO=4;ITEM='は';output;
ID='BBB';NO=1;ITEM='A'; output;
ID='BBB';NO=5;ITEM='B'; output;
run;
これをIDごとに、出現するITEMの内容を、NO順に「、」で連結して
ALLITEMという変数にいれるという問題があったとします。
求めたい結果は以下です。
なんか面倒そうだと感じますが、楽勝です。
まず、IDとNOでソートした後、IDをbyで固定してtransposeします。
このときIDステートメント(すみません、変数とまぎらわしいですね)でNOを指定します。
proc sort data=Q1;
by ID;
run;
proc transpose data=Q1 out=Q2(drop=_NAME_) prefix=ITEM_;
var ITEM;
by ID;
id NO;
run;
すると
のデータセットが得られます。
後はこれを順番に「、」で繋ぎたいのですが、nullの場合は飛ばしたいわけです。
空白が間にはいるのも嫌なので、う~んどうしよう、IFいっぱい書かなきゃいけないかと思いきや
data A;
set Q2;
ALLITEM=catx('、',of ITEM_:);
keep ID ALLITEM;
run;
これで、終わりです。
catx関数は指定した区切り文字で、null以外の値を、前後の空白を除去した上で連結してくれるという、凄く気のきいた関数です。
もちろん
ALLITEM=catx('、',ITEM_1,ITEM_2,ITEM_3,ITEM_4,ITEM_5);
とかいていいのですが、そうすると元データが増えてNO「6」ができたら
コードを修正する必要がでてしまいます。
TRANSPOSEプロシジャのprefix=は接頭語を指定できるので(suffix=は接尾語)
ここをうまく使えば、NOがどんな数字だろうと、転置後の変数は「ITEM_」から始まります。
そこで of とコロンモディファイアで、配列を使うまでもなくターゲットの変数を全て捕まえることが
できます。
CAT やCATSなど、微妙に違うことをしてくれる関数もあるので調べてみるといいと思います
TRANSPOSEプロシジャのCOPYステートメントを使って、総当たりの対戦表を作ってみる
たとえば
data Q;
do TEAM='A','B','C','D','E';
output;
end;
run;
do TEAM='A','B','C','D','E';
output;
end;
run;
のようなデータがあったとします。
そこに、
proc transpose data=Q out=Q1(drop=_NAME_) prefix=TEAM_;
var TEAM;
copy TEAM;
id TEAM;
run;
var TEAM;
copy TEAM;
id TEAM;
run;
とすると何がおきるでしょうか?
transposeプロシジャのcopyステートメントは
あまり活躍の機会の少ないステートメントです。
こいつに指定された変数は、元のデータセットのデータを保持します。
しかし、BYとは違って、ただそのままであるだけです。
つまり結果は
となります。
面白いですが、このような形の結果を求めることが少ないです。
今回はチームAからチームEまでが総当たりで対戦をする場合の
星取り表を作るプログラムとして使ってみました。
上の結果に少し手を加えて
data ANS;
set Q1(in=ina where=(TEAM_A^=''))
Q1(in=inb);
if ina then TEAM='';
if inb then call missing(of TEAM_:);
label TEAM='対戦表';
run;
set Q1(in=ina where=(TEAM_A^=''))
Q1(in=inb);
if ina then TEAM='';
if inb then call missing(of TEAM_:);
label TEAM='対戦表';
run;
以下が完成です。
社内イベント等で対戦表をつくることがあれば
あえて、SASでやってやりましょう。
きっとEXCELよりもめんどくさいうえに
誰も褒めてはくれないでしょう。
詰めSAS7回目_n番目に大きい値を取得する
最小、最大をとる処理に比べて、n番目に大きい(小さい)値を取得する処理を書くことは少ないと思います。
そういった処理を書けといわれると、とりあえず、対象となる降順(昇順)sortして、次のデータステップにbyをつけて、連番を付与したのち、その連番がnであるオブザベーションをとるという手順が最初に思い浮かぶ方も多いと思います。
それでまったく間違いではないのですが、ここではそれ以外の方法を紹介します。
【問題】
data Q_1;
input X;
cards;
1
5
6
3
2
4
;
run;
ranks RANK;
run;
var X;
run;
data A_3;
set Q_1_;
RANK=2;
X=LARGEST(RANK,of VAL_:);
keep RANK X;
run;
そういった処理を書けといわれると、とりあえず、対象となる降順(昇順)sortして、次のデータステップにbyをつけて、連番を付与したのち、その連番がnであるオブザベーションをとるという手順が最初に思い浮かぶ方も多いと思います。
それでまったく間違いではないのですが、ここではそれ以外の方法を紹介します。
【問題】
data Q_1;
input X;
cards;
1
5
6
3
2
4
;
run;
上記のデータセットから2番目に大きいXの値(5)を取得して

上記のようなデータセットを作ります。
【解法1】
proc rank data=Q_1 out=A_1(where=(RANK=2)) descending;
var X;ranks RANK;
run;
rankプロシジャは指定した変数の値を比較して順位をつけてくれるプロシジャです。
ranksステートメントを指定しないとvarで指定した変数が順位データに上書きされるので
元の値と順位両方をだしたい場合は指定します。
descendingをつけないと昇順に順位付けします。
【解法2】
ods output extremeobs=A_2(keep=HIGH HIGHOBS rename=(HIGH=X HIGHOBS=RANK) where=(RANK=2));
proc univariate data=Q_1;var X;
run;
univariateプロシジャでも同じようなことができます。
univariateを実行するとアウトプットに
極値という項目がでて、最小最大ともにデフォルトでTOP5のデータがでるので
これをODSでとってしまえばよいのです。
n番目に値以外にunivariateで出すものがある場合はRANKプロシジャなんかを使うより
UNIVARIATEついでにこっちのやり方でやればいいですね。
【解法3】
proc transpose data=Q_1 out=Q_1_ prefix=VAL_;
run;data A_3;
set Q_1_;
RANK=2;
X=LARGEST(RANK,of VAL_:);
keep RANK X;
run;
わかりやすいように転置でtransposeプロシジャを使用して1ステップ使っており、2ステップになっています。(transposeプロシジャを使わない転置は別頁参照)
ようはSASのLARGEST関数を紹介したかったのです。これはlargest(n,x,y,z....)でn番目に大きい値をx,y,z・・・からとる変数です。小さい値はSMALLEST関数です。
SQLでもとれますが少しめんどうなので割愛します
詰めSAS2回目_複数の変数の値で転置する
SAS9.2からtransposeプロシジャのidステートメントに2個以上の変数を指定可能になりました。
あまり取り上げられるところを見たことがないのですが、これはかなり革命的なことだと思っています。いずれ取り上げたいですが、meansプロシジャのclassdataステートメントと合わせれば、医薬系で臨床試験の有害事象を群別Grade集計するのなどが、かなり楽でスマートにできるようになりました。
ほとんど答えを先に言ってしまいましたが、詰めSAS_2問目です。
data Q1;
input X Y Z;
cards;
1 4 3
2 9 8
7 6 5
;
run;
このデータセットについてX、Yの値によってZの値を転置して以下のデータセットを作ります。
目的局面図は以下です。
【解法1】
proc transpose data=Q2 out=A1(drop=_NAME_);
var Z;
id X Y;
run;
これだけで詰んでます。idステートメントの複数変数指定はもっと注目されるべきSASのバージョンアップポイントだと思います。
【解法2】
data A2;
set Q1(where=(X=1 and Y=4) rename=(Z=_14));
set Q1(where=(X=2 and Y=9) rename=(Z=_29));
set Q1(where=(X=7 and Y=6) rename=(Z=_76));
drop X Y;
run;
あまり取り上げられるところを見たことがないのですが、これはかなり革命的なことだと思っています。いずれ取り上げたいですが、meansプロシジャのclassdataステートメントと合わせれば、医薬系で臨床試験の有害事象を群別Grade集計するのなどが、かなり楽でスマートにできるようになりました。
ほとんど答えを先に言ってしまいましたが、詰めSAS_2問目です。
data Q1;
input X Y Z;
cards;
1 4 3
2 9 8
7 6 5
;
run;
このデータセットについてX、Yの値によってZの値を転置して以下のデータセットを作ります。
目的局面図は以下です。
【解法1】
proc transpose data=Q2 out=A1(drop=_NAME_);
var Z;
id X Y;
run;
これだけで詰んでます。idステートメントの複数変数指定はもっと注目されるべきSASのバージョンアップポイントだと思います。
【解法2】
data A2;
set Q1(where=(X=1 and Y=4) rename=(Z=_14));
set Q1(where=(X=2 and Y=9) rename=(Z=_29));
set Q1(where=(X=7 and Y=6) rename=(Z=_76));
drop X Y;
run;
Setの連続掛けは個人的に、とてもとても好きな方法です。指定したデータセットの中で最小のobs数に統一されることと、同名の変数が上書きされる性質は扱いにくい一方で、mergeステートメントとは違った魅力があると思います。
登録:
コメント (Atom)

























