前のページ|次のページ

乱数関数と乱数CALLルーチンの使用

乱数関数の種類

SASには2種類の乱数関数が用意されています。最新の乱数関数はRAND関数です。この関数は、松本と西村(1998)によって開発されたMersenne-Twister擬似乱数ジェネレータ(RNG)を応用しています。このRNGは219937 – 1の非常に長い周期を持ち、高品質な統計的特性を有しています (周期は疑似乱数列が繰り返されるまでの生成数)。
RAND関数は単一のシードで開始します。つまり、ジェネレータを停止した後、停止点からの再開はできません。ストリームの最初から始まる数値列を生成するには、STREAMINIT関数を使用します。 詳細については、RAND関数の詳細セクションを参照してください。
従来からの乱数ジェネレータには、UNIFORM、NORMAL、RANUNI、RANNORの他、RANで始まる関数があります。これらの関数では周期が231 – 2以下しかありません。擬似乱数ストリームは単一のシードで開始し、プロセスの状態を新しいシードに取得できます。つまり、ジェネレータを停止した後、適切なシードを対応するCALLルーチンに与えることでジェネレータを停止点から再開できます。ストリームの途中から始まる数値列を生成するには、乱数関数を使用します。

シード値

乱数関数とCALLルーチンは、シードと呼ばれる初期開始点から疑似乱数のストリームを生成します。シードはユーザーが指定するか、コンピュータのクロックから取得します。シードは231–1(2,147,483,647)より小さい負でない整数値である必要があります。正のシードを使用すると、同じDATAステップを使用することで乱数のストリームを常に再現できます。ゼロをシードに使用すると、コンピュータクロックによってストリームが初期化され、乱数のストリームは再現できなくなります。

関数の乱数ストリーム生成方法の理解

DATAステップで乱数のシングルストリームを生成する

このセクションのDATAステップで、乱数関数のプロパティをいくつか説明します。関数を呼び出すDATAステップは、各ステップとも初回呼び出しの初期シードが7となっているため、それぞれシード値7に基づく単一の疑似乱数ストリームを生成します。一部のDATAステップでは、各種の方法でシード値を変更します。関数呼び出しが1つしかないステップもあれば、複数の関数呼び出しが行われるステップもあります。これらのDATAステップは、どれもシードを変更しません。関数呼び出しに関連するシードは、最初の乱数関数の初回実行に使われるシードのみです。関数で独立したストリームを作成する方法はなく(作成するにはCALLルーチンを使用します)、新しいDATAステップを開始するしか、関数による乱数ストリームを再開する方法はありません。
次の例では複数のDATAステップを実行します。
  /* This DATA step produces a single stream of random numbers */
   /* based on a seed value of 7.                               */ 
data a;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
   a = ranuni (7); output;
run;

   /* This DATA step uses a DO statement to produce a single */
   /* stream of random numbers based on a seed value of 7.   */  
data b (drop = i);
   do i = 7 to 18;
      b = ranuni (i);
      output;
   end;
run;

   /* This DATA step uses a DO statement to produce a single */
   /* stream of random numbers based on a seed value of 7.   */ 
data c (drop = i);
   do i = 1 to 12;
      c = ranuni (7);
      output;
   end;
run;

   /* This DATA step calls the RANUNI and the RANNOR functions */
   /* and produces a single stream of random numbers based on  */
   /* a seed value of 7.                                       */   
data d;
   d = ranuni (7); f = ' '; output;
   d = ranuni (8); f = ' '; output;
   d = rannor (9); f = 'n'; output;
   d = .;          f = ' '; output;
   d = ranuni (0); f = ' '; output;
   d = ranuni (1); f = ' '; output;
   d = rannor (2); f = 'n'; output;
   d = .;          f = ' '; output;
   d = ranuni (3); f = ' '; output;
   d = ranuni (4); f = ' '; output;
   d = rannor (5); f = 'n'; output;
   d = .;          f = ' '; output;
run;

   /* This DATA step calls the RANNOR function and produces a     */
   /* single stream of random numbers based on a seed value of 7. */
data e (drop = i);
   do i = 1 to 6;
      e = rannor (7); output;
      e = .;          output;
   end;
run;

   /* This DATA step merges the output data sets that were */
   /* created from the previous five DATA steps.           */ 
data five;
   merge a b c d e;
run;

   /* This procedure writes the output from the merged data sets. */
proc print label data=five;
   options missing = ' ';
   label f = '00'x;
   title 'Single Random Number Streams';
run;
このプログラムの結果出力を次に示します。
単一の乱数ストリームを生成した結果
単一の乱数ストリームを生成した結果
出力データセットA、BおよびCの疑似乱数ストリームは同一です。出力データセットDのストリームには、RANUNI関数とRANNOR関数への呼び出しが併用されています。オブザベーション1、2、5、6、9、10では、RANUNIから返される値が先のストリームの値と完全に一致しています。"n"フラグ付きのオブザベーション3、7および11には、RANNOR関数から返された値が含まれています。関数呼び出しを併用しても、疑似乱数ストリームの生成には影響しません。結果はすべて、一様に分布した値の単一ストリームに基づいています。RANNORなどの他の関数によって変換されてから返されるものもあります。RANNOR関数の結果は、RANUNIへの2回の内部呼び出しによって生成されたものです。出力データセットDを作成するDATAステップは、次のステップを3回実行し、12のオブザベーションを作成します。
  • RANUNIへの呼び出し
  • RANUNIへの呼び出し
  • RANNORへの呼び出し(RANUNIへの内部呼び出しを2回実行)
  • RANUNIへの2回目の内部呼び出しを補正するためのスキップ行
データセットEを作成するDATAステップでは、RANNORが6回呼び出されます。呼び出し時に毎回1行をスキップしますが、これはRANNORへの呼び出しごとに実行される2回のRANUNIへの内部呼び出しを補正するためです。データセットDを作成するDATAステップでRANNORから返される3つの値は、データセットEの対応する値と一致しています。

%SYSFUNCマクロで乱数のシングルストリームを生成する

%SYSFUNCを使用し、マクロ言語を介してRANUNI関数を呼び出すと、疑似乱数ストリームが1つ作成されます。SASを終了し、新しいSASセッションを開始しない限りはシード値を変更できません。%SYSFUNCマクロは初回の起動時のみ、データセットA、BおよびCを生成したDATAステップと同一の疑似乱数ストリームを生成します。後続のマクロ呼び出しでは、単一ストリームの続きを生成します。
%macro ran;
   %do i = 1 %to 12;
      %let x = %sysfunc (ranuni (7)); 
      %put &x;
   %end;
   %mend;

%ran;
次の出力がログに書き込まれます。
%SYSFUNCマクロを使った実行の結果
10 %macro ran; 11 %do i = 1 %to 12; 12 %let x = %sysfunc (ranuni (7)); 13 %put &x; 14 %end; 15 %mend; 16 %ran; 0.29473798875451 0.79062100955779 0.79877014262544 0.81579051763554 0.45121804506109 0.78494144826426 0.80085421204606 0.72184205973606 0.34855818345609 0.46596586120592 0.73522999404707 0.66709365028287

乱数関数と乱数CALLルーチンのシード値の比較

乱数関数とCALLルーチンは、それぞれ特定の統計的分布から疑似乱数を生成します。乱数関数はいずれも、整数定数、または整数定数を含む変数で表されるシード値を必要とします。CALLルーチンはいずれもシード値を含む変数を呼び出します。また、すべてのCALLルーチンに生成された疑似乱数を含む変数が必要です。
シード変数は、関数またはCALLルーチンの初回実行前に初期化する必要があります。関数の毎回の実行後、現在のシードは内部で更新されますが、シード引数の値は変化しません。ただし、CALLルーチンの毎回の反復後、シード変数は次の疑似乱数を生成するストリーム内の現在のシードを含みます。関数ではシード値を制御できません。そのため、初期化後の疑似乱数を制御できません。
各乱数関数と同名のCALLルーチンがSASに用意されています。例外は、NORMAL関数とUNIFORM関数(それぞれRANNOR関数とRANUNI関数と同等)です。CALLルーチンを使用すると、シード値をさらに高度に制御できます。

乱数CALLルーチンで複数のシードから複数のストリームを生成する

乱数CALLルーチンとストリームの概要

乱数CALLルーチンを使用すると、単一のDATAステップで複数の疑似乱数ストリームを生成できます。各シード変数の初期化に異なるシード値を指定すると、生成される疑似乱数ストリームは計算量的には独立しますが、シード値を注意深く選択しなければ統計的に独立しません。
注: 複数のシードを使えば複数のストリームを作成できますが、これを実行することはお勧めしません。ストリームの作成は1つにとどめるのが安全です。複数のストリームを作成すると、ストリームが長くなるにつれて、ストリームが重複する可能性が増します。
次の2つの例では、最悪のシナリオとなるようなシードを故意に選択しています。これらの例では、複数のシードを使って複数のストリームを作成する方法を示しています。これは非推奨の方法ですが、乱数CALLルーチンで複数のシードを使えます。

例1:複数のストリームを作成するために複数のシードを使用する

この例は、乱数CALLルーチンを使用し、複数のシードから疑似乱数的に分布した値の複数ストリームを生成できることを示しています。最初のDATAステップでは、正規分布の変数から3つのデータセットを作成します。2番目のDATAステップでは、一様分布の変数を作成します。SGSCATTERプロシジャ(SAS ODS Graphics: プロシジャガイドを参照)は、2種類の分布のそれぞれで、各変数ペア間にどのような関係があるかを示すために使用しています。
data normal;
   seed1 = 11111;
   seed2 = 22222;
   seed3 = 33333;
   do i = 1 to 10000;
      call rannor(seed1, x1);
      call rannor(seed2, x2);
      call rannor(seed3, x3);
      output;
   end;
run;

data uniform;
   seed1 = 11111;
   seed2 = 22222;
   seed3 = 33333;
   do i = 1 to 10000;
      call ranuni(seed1, x1);
      call ranuni(seed2, x2);
      call ranuni(seed3, x3);
      output;
   end;
run;

proc sgscatter data = normal;
   title 'Nonindependent Random Normal Variables';
   plot x1*x2 x1*x3 x3*x2 / markerattrs = (size = 1);
run;

proc sgscatter data = uniform;
   title 'Nonindependent Random Uniform Variables';
   plot x1*x2 x1*x3 x3*x2 / markerattrs = (size = 1);
run;
複数のシードから作成した複数のストリーム:独立していないランダムな正規変数
複数のシードから作成した複数のストリーム:独立していないランダムな正規変数
複数のシードから作成した複数のストリーム:独立していないランダムな一様変数
複数のシードから作成した複数のストリーム:独立していないランダムな一様変数
1番目のプロット(複数のシードから作成した複数のストリーム:独立していないランダムな正規変数)では、正規変数が直線的な相関関係にはないように見えますが、明らかに独立していません。2番目のプロット(複数のシードから作成した複数のストリーム:独立していないランダムな一様変数 )では、一様変数が明確に相関関係にあることがわかります。この部類の乱数ジェネレータでは、ストリームが独立している保証は一切ありません。

例2:CALL RANUNIルーチンで異なるシードを使用する

次の例では、3つの異なるシードとCALL RANUNIルーチンを使用して複数のストリームを作成しています。
data uniform(drop=i);
   seed1 = 255793849;
   seed2 =1408147117;
   seed3 = 961782675;
   do i=1 to 10000;
      call ranuni(seed1, x1);
      call ranuni(seed2, x2);
      call ranuni(seed3, x3);
      i2 = lag(x2);
      i3 = lag2(x3);
      output;
   end;
label i2='Lag(x2)' i3='Lag2(x3)';
run;

title 'Random Uniform Variables with Overlapping Streams';
proc sgscatter data=uniform;
   plot x1*x2 x1*x3 x3*x2 / markerattrs = (size = 1);
run;

proc sgscatter data=uniform;
   plot i2*x1 i3*x1 / markerattrs = (size = 1);
run;
 
proc print noobs data=uniform(obs=10);
run;
CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット1
CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット1
CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット2
CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット2
重複するストリームを持つランダムな一様分布変数
重複するストリームを持つランダムな一様分布変数
1番目のプロット(CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット1)は、変数が統計的に独立した、期待どおりの結果を示しています。ただし、2番目のプロット(CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット2 )と最初の10のオブザベーションのリストは、2つのストリームでほぼ完全な重複があることを示しています。x1の最後の9999個の値はx2の最初の9999個の値と一致しており、x1の最後の9998個の値はx3の最初の9998個の値と一致しています。言い換えれば、x1とlag(x2)に加え、x1とlag2(x3)の間の非欠損部分に完全な一致が存在します。1番目のプロットのように、一見ストリームが独立しているようでも重複の可能性があります。ストリームの用途によっては、これは望ましくありません。
実際には、個別のシードをランダムに選んで小さいストリームを複数作成すれば、最初の2つの例に示したような問題にはおそらく遭遇しません。 CALL RANUNIで異なるシードを使用する:重複するストリームを持つランダムな一様分布変数:プロット2では、最悪のシナリオとなるようなシードを故意に選択しています。
ストリームの作成は1つにとどめるのが安全です。複数のストリームを作成すると、ストリームが長くなるにつれて、ストリームが重複する可能性が増します。

乱数関数で単一のシードから複数の変数を生成する

関数とストリームの概要

プログラムで関数を使用する場合は、DATAステップ内で複数のシードを指定して複数の疑似乱数ストリームは生成できません。
RANUNI関数を使用し、単一のシードで同一のストリームから複数の変数を作成する最も安全な方法を次の例に示します。

例:重複するストリームを持つランダムな一様分布変数を生成する

次の例では、重複するストリームを持つランダムな一様分布変数を作成するのにRANUNI関数を使用しています。この例は、RANUNI関数を使用して複数の変数を作成する最も安全な方法を示しています。すべての変数が単一のシードで同一のストリームから作成されます。
data uniform(drop=i);
   do i = 1 to 10000;
      x1 = ranuni(11111);
      x2 = ranuni(11111);
      x3 = ranuni(11111);
      i2 = lag(x2);
      i3 = lag2(x3);
      output;
   end;
label i2 = 'Lag(x2)' i3 = 'Lag2(x3)';
run;

title 'Random Uniform Variables with Overlapping Streams';
proc sgscatter data = uniform;
   plot x1*x2 x1*x3 x3*x2 / markerattrs = (size = 1);
run;

proc sgscatter data = uniform;
   plot i2*x1 i3*x1 / markerattrs = (size = 1);
run;
重複するストリームを持つランダムな一様分布変数:プロット1
重複するストリームを持つランダムな一様分布変数:プロット1
重複するストリームを持つランダムな一様分布変数:プロット2
重複するストリームを持つランダムな一様分布変数:プロット2
,例:重複するストリームを持つランダムな一様分布変数を生成するでは、変数が独立しているように見えます。ただし、このプログラミング手法でも全般的に問題なく機能するとは限りません。乱数関数とCALLルーチンの周期は231 - 2(およそ21億)以下でしかありません。この限界に達するとストリームは繰り返されます。複雑なシミュレーションをこなす現代のコンピュータなら、ストリーム全体をわずか数分で簡単に使い果たしてしまいます。

RAND関数を代替として使用する

ランダムな一様分布変数を生成する手法としてより好ましいのは、複数ストリームが許可されないRAND関数を使用する方法です。RAND関数の周期は219937 - 1です。少なくとも21世紀初頭のコンピュータには、この限界は到達できません。219937 - 1という数は、およそ106000 (1の後にゼロが6000個)です。比較として、SASを実行するコンピュータのほとんどが8バイトで表すことのできる最大値はおよそ10307です。
開発された最も新しい乱数関数のRAND関数では、複数のストリームを使用できません。RAND関数は乱数CALLルーチンから異なるアルゴリズムを使用することで、複数のシードを使った複数のストリームを作成します。RANDプロセスの状態は単一のシードでは取得できないため、ジェネレータを停止した後、停止点からの再開はできません。したがって、RAND関数で許可されるのは単一の数列ストリームのみですが、RANUNI関数と同様に、この関数を使用して複数のストリームを作成できます。

乱数CALLルーチンを効果的に使用する

ストリームを開始、停止および再開する

RANUNIのストリームが使い果たされない限り、通常は単一のストリームを開始、停止するのに乱数CALLルーチンを使用します。たとえば、SASで反復を実行し、停止して結果を評価し、ストリームの停止点から再開したい場合を考えます。次の例ではこの原理を説明しています。

例:ストリームを開始、停止および再開する

この例では、5つの数値のストリームを生成、停止および再開してから同じストリームからさらに5つの数値を生成し、結果を組み合わせて比較用の完全なストリームを生成します。最初のDATAステップでは、次のステップで開始シードに使用するために、乱数シードの状態をマクロ変数シードに格納しています。この例の個別ストリームの出力は、完全なストリームに一致します。
data u1(keep=x);
   seed = 104;
   do i = 1 to 5;
      call ranuni(seed, x);
      output;
   end;
   call symputx('seed', seed);
run;

data u2(keep=x);
   seed = &seed;
   do i = 1 to 5;
      call ranuni(seed, x);
      output;
   end;
run;

data all;
   set u1 u2;
   z = ranuni(104);
run;

proc print label;
   title 'Random Uniform Variables with Overlapping Streams';
   label x = 'Separate Streams' z = 'Single Stream';
run;
ストリームを開始、停止および再開する
ストリームを開始、停止および再開する

CALLルーチン内の場合と関数内の場合のシード変更の比較

例:CALLルーチン内と関数内のシード変更

シードの変更にCALLルーチンを使用する場合の結果は、関数を使用してシードを変更する場合と異なります。この違いの例を次に示します。
data seeds;
   retain Seed1 Seed2 Seed3 104;
   do i = 1 to 10;
      call ranuni(Seed1,X1);
      call ranuni(Seed2,X2);
      X3 = ranuni(Seed3);
      if i = 5 then do;
         Seed2 = 17;
         Seed3 = 17;
      end;
      output;
   end;
run;

proc print data = seeds;
   title 'Random Uniform Variables with Overlapping Streams';
   id i;
run;
CALLルーチン内と関数内のシード変更
CALLルーチン内と関数内のシード変更
CALL RANUNIステートメントでi=5のときにSeed2を変更すると、X2のストリームはX1のストリームから強制的に逸脱させられます。ただし、RANUNI関数でSeed3を変更した場合には影響はありません。何も変更がなかったかのようにX3のストリームが続行するため、X1とX3のストリームは同一です。
前のページ|次のページ|ページの先頭へ