BookmarkSubscribeRSS Feed
chie_sas
Obsidian | Level 7

1~3の数値を重なり(被り)の無いように無作為で抽出したいと思っています。

どのように記述するとよいでしょうか?

 

[例]

data dt1;

do i=1 to 3;

x = (1~3の乱数);

output;

end;

run;

-------------

3

1

2

 

9 REPLIES 9
sasone
Quartz | Level 8

こんにちは。

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;
chie_sas
Obsidian | Level 7

sasone様

 

そちらでできそうですね。

ありがとうございます。

 

尚、データステップ内で完結する方法があると嬉しいのですが

そのような方法はご存知でないでしょうか?

重複が無くユニークな数値の割り当てがデータステップ内でできればと思っています。

japelin
Rhodochrosite | Level 12

力技ですが、どうしても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をそのマクロ変数に変更する必要があります)

 

chie_sas
Obsidian | Level 7

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)

 

 

japelin
Rhodochrosite | Level 12

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;

 

japelin
Rhodochrosite | Level 12

何度もすみません。

 

以下のコードだと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);
chie_sas
Obsidian | Level 7

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、

確かにこれの方がシンプルでよいですね

こういうのを思いつくかどうかはセンスですね。。

大変勉強になります。

japelin
Rhodochrosite | Level 12


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等は可能ですが、関数等は使用できません。

 

chie_sas
Obsidian | Level 7

kawakami様

 

ご返信ありがとうございます。

わたしの理解に誤りがあることがわかりました。

 

data dt;
x = 2;
if x = 1 then do;
 %let a = OK;
end;
run;

%put &=a;

if文がTrueだと%letが実行されると思いきや、DATAステップ外のオープンコードなので、

if文がTrueだろうがFalseだろうが実行されてしまうということですね。

 

よく理解できました。

ありがとうございました。