1~3の数値を重なり(被り)の無いように無作為で抽出したいと思っています。
どのように記述するとよいでしょうか?
[例]
data dt1;
do i=1 to 3;
x = (1~3の乱数);
output;
end;
run;
-------------
3
1
2
こんにちは。
surveyselectプロシジャを用いてデータセットdt1から無作為非復元抽出するというのは
いかがでしょうか。
以下の例(seed=1)では、3→1→2の順にデータセットXに抽出されます。
data dt1;
do i=1 to 3;
output;
end;
run;
proc surveyselect data=dt1 seed=1 reps=1
method=srs n=3 out=X outorder=random noprint;
run;
sasone様
そちらでできそうですね。
ありがとうございます。
尚、データステップ内で完結する方法があると嬉しいのですが
そのような方法はご存知でないでしょうか?
重複が無くユニークな数値の割り当てがデータステップ内でできればと思っています。
力技ですが、どうしてもDATAステップで、ということであれば以下のような方法はいかがでしょうか。
/* 乱数として扱いたい数値を持つデータセット */
data dt1;
do i=1 to 3;
output;
end;
run;
/* 乱数を取得する場合、ここを繰り返す */
data _null_;
call symputx('ranno',int(rand('UNIFORM')*&sysnobs.)+1);
run;
data dt1;
set dt1;
if _n_=&ranno then do;
put i=;
delete;
end;
run;
/* 繰り返しここまで */
一様分布で「1<=n<=obs数(&sysnobs)」の範囲で整数の乱数を生成し、その乱数をobs番号として扱い数値データ(i)を取得、最後にはdeleteすることで、次の数値取得に備える、という流れです。
(数値の取得後に他のデータセットを扱う場合、&sysnobsが別のデータセットのobs数になってしまうため、上記コードの繰り返しの最後に&sysnobsを別のマクロ変数に格納し、乱数生成のコードでも&sysnobsをそのマクロ変数に変更する必要があります)
kawakami様
ありがとうございます。
教えていただいた方法を参考にこのように作成してみました。
(1~100から3つランダムに取得する)
この方法で重複なくユニークな値が取得できそうです。
%macro roop(cnt,pickup);
data out;
format i 8.;
stop;
run;
data dt1;
do i=1 to &cnt.;
output;
end;
run;
%let finish = 0;
%do %while(&finish.<&pickup.);
data _null_;
call symputx('ranno',int(rand('UNIFORM')*&cnt.)+1);
run;
data dt1 out_temp;
set dt1;
if i=&ranno then do;
%let finish = %eval(&finish.+1);
output out_temp;
delete;
end;
output dt1;
run;
proc append base=out data=out_temp;
run;
%end;
%mend;
%roop(100,3)
chie_sasさん
今のプログラムだと、以下の理由から正しくpickup分のobsが得られない可能性が高いです。
1.乱数の生成が1<n<&cnt固定のため、条件「i=&ranno」を満たすobsが存在しない可能性がある
→1~obs数と同じ範囲で乱数を生成するよう修正(都度obs数を取得するか、減ったobs分を減算する)
2.i=&rannoだと、iは順次削除されていく数値であり、条件「i=&ranno」を満たす数値iが存在しない可能性がある
→iではなく、obs番号である_n_に修正
3.dataステップ内に%letステートメントを記述すると問答無用で実行されるため、%do %whileが想定通り動作しない(pickupが大きくなればなるほど、ミスマッチの可能性が高くなる)
→%do %whileから%doループに変更する、もしくは%letをcall symputに変更する
以下、修正案です。参考にしてみてください。
A.%do %whileを%doループに変更するとマクロ変数「finish」は不要になる
%do _i=1 %to &pickup.;
data _null_;
call symputx('ranno',int(rand('UNIFORM')*%eval(&cnt.-&_i.))+1);
run;
data dt1 out_temp;
set dt1;
if _n_=&ranno then do;
output out_temp;
delete;
end;
output dt1;
run;
%end;
B.%do %whileを活かすなら%letステートメントをcall symputに変更する
%do %while(&finish.<&pickup.);
data _null_;
call symputx('ranno',int(rand('UNIFORM')*%eval(&cnt.-&finish.))+1);
run;
data dt1 out_temp;
set dt1;
if _n_=&ranno then do;
call symputx('finish',%eval(&finish.+1));
output out_temp;
delete;
end;
output dt1;
run;
%end;
何度もすみません。
以下のコードだと1回sortするだけで、ループを使わないので早いです。
元データセットを一様乱数でソートして頭から指定obsだけ取ってきています。
%Macro getrand(cnt,pickup);
data dt1;
do i=1 to &cnt.;
output;
end;
run;
data dt1;
set dt1;
j=rand('UNIFORM');
run;
proc sort;
by j;
run;
data out(drop=j);
set dt1(obs=&pickup.);
run;
%mend;
%getrand(100,15);
kawakami様
お返事ありがとうございます。
>今のプログラムだと、以下の理由から正しくpickup分のobsが得られない可能性が高いです。
>1.乱数の生成が1<n<&cnt固定のため、条件「i=&ranno」を満たすobsが存在しない可能性がある
> →1~obs数と同じ範囲で乱数を生成するよう修正(都度obs数を取得するか、減ったobs分を減算する)
>2.i=&rannoだと、iは順次削除されていく数値であり、条件「i=&ranno」を満たす数値iが存在しない可能性がある
> →iではなく、obs番号である_n_に修正
条件「i=&ranno」を満たすobsが存在しない可能性あるので、
存在すればfinishマクロ変数をカウントアップさせ、条件が満たせば終了、といった仕組みにしていたのですが、
教えていた方法が大変シンプルでスマートですね。
>3.dataステップ内に%letステートメントを記述すると問答無用で実行されるため、%do %whileが想定通り動作しない(pickupが大きくなればなるほど、ミスマッチの可能性が高くなる)
なぜ%letよりcall symputが良いのか、もう少し教えていただけますでしょうか?
普段から特に用途を分けていないので、参考にさせてください。
2つ目に教えていただいたPGM、
確かにこれの方がシンプルでよいですね。
こういうのを思いつくかどうかはセンスですね。。
大変勉強になります。
chie_sasさん
違いを見るために以下を実行してみてください。
options nosource nonotes;/* ログを見やすくするためだけ */
data test;
a=1;
run;
data _null_;
set test;
call symputx('test1',a);/* 暗黙の変換を避けるためにsymputxとしている */
%let test2=a;
run;
%put test1=&test1;
%put test2=&test2;
%put;
data _null_;
call symput('dt1',tranwrd(put(date(),yymmdd10.),'-','_'));
run;
%let dt2=tranwrd(put(date(),yymmdd10.),'-','_');
%put dt1=&dt1;
%put dt2=&dt2;
ざっくりとした私の認識ですが、
call symput/symputxはDATAステップ内で処理を行います。
文字だけでなく、変数の値も格納できます。
割り当てる値を関数などで処理できます。
%letは極端に言うと、DATAステップ外のオープンコードで使います。
値ではなく、文字がそのまま格納されます。DATAステップ内で変数名を指定してもその値は取得できません。
%eval等は可能ですが、関数等は使用できません。
kawakami様
ご返信ありがとうございます。
わたしの理解に誤りがあることがわかりました。
data dt;
x = 2;
if x = 1 then do;
%let a = OK;
end;
run;
%put &=a;
if文がTrueだと%letが実行されると思いきや、DATAステップ外のオープンコードなので、
if文がTrueだろうがFalseだろうが実行されてしまうということですね。
よく理解できました。
ありがとうございました。
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!