Ранее я рассказал, как генерировать случайные числа в SAS с помощью функции RAND на шаге DATA или с помощью подпрограммы RANDGEN в программе SAS/IML. Эти функции создают поток случайных чисел. (В статистике случайные числа обычно представляют собой образец из распределения, такого как равномерное или нормальное распределение.) Вы можете управлять потоком случайных чисел, установив начальное значение для случайных чисел. Начальное значение устанавливается с помощью подпрограммы STREAMINIT на шаге DATA или подпрограммы RANDSEED на языке SAS/IML.

Начальное значение позволяет вам генерировать один и тот же набор случайных чисел  при каждом запуске программы. Это похоже на оксюморон: если эти числа одинаковы каждый раз, то как они могут быть случайными? Решение этого парадокса заключается в том, что числа, которые мы называем "случайными", точнее следует называть "псевдослучайными числами". Псевдослучайные числа создаются алгоритмически, но имеют статистические свойства случайности. Хороший алгоритм генерирует псевдослучайные числа, неотличимые от действительно случайных чисел. В SAS используется генератор случайных чисел с алгоритмом вихря Мерсенна (Matsumoto and Nishimura, 1998), который, как известно, обладает отличными статистическими свойствами.

Зачем вам может потребоваться воспроизводимая последовательность случайных чисел? Две важных причины — ведение документации и тестирование. Когда я пишу код SAS и публикую его в блоге, в книге или в документации SAS, важно, чтобы клиенты SAS могли запускать код и получать те же результаты.

Случайные числовые потоки на шаге DATA

Для установки начального числа для функции RAND на шаге DATA используется подпрограмма STREAMINIT. Начальное значение управляет последовательностью случайных чисел. Подпрограмму STREAMINIT нужно вызывать один раз для шага DATA до первого вызова функции RAND. Это гарантирует, что при последующем выполнении шага DATA вы получите те же псевдослучайные числа.

Если вы запустите новый шаг DATA, то сможете указать новое начальное значение. Если вы используете начальное значение 0 или если оно не указано, то для определения начального значения используется системное время. В этом случае поток случайных чисел будет различным при каждом запуске программы.

Чтобы увидеть, как работают случайные числовые потоки, мы создадим пять наборов данных на каждом из следующих шагов DATA. В первом и третьем наборах данных используется одно и то же начальное значение (123), поэтому случайные числа идентичны. Во втором и четвертом наборах для установки начального значения используется системное время (во время первого вызова функции RAND). Следовательно, эти потоки случайных чисел различны. Последний набор данных содержит случайные числа, созданные с помощью другого начального значения (456). Этот поток чисел отличается от других потоков.

data A(drop=i);
  call streaminit(123);
  do i = 1 to 5;
    x123 = rand("Uniform"); output;
  end;
run;
data B(drop=i);
  call streaminit(0);
  do i = 1 to 5;
    x0 = rand("Uniform"); output;
  end;
run;
data C(drop=i);
  call streaminit(123);
  do i = 1 to 5;
    x123_2 = rand("Uniform"); output;
  end;
run;
data D(drop=i);
  /* no call to streaminit */
  do i = 1 to 5;
    x0_2 = rand("Uniform"); output;
  end;
run;
data E(drop=i);
  call streaminit(456);
  do i = 1 to 5;
    x456 = rand("Uniform"); output;
  end;
run;
data AllRand;  merge A B C D E; run; /* concatenate */
proc print data=AllRand; run;

Обратите внимание, что подпрограмма STREAMINIT, если она вызывается, вызывается ровно один раз в начале шага DATA. Вызывать подпрограмму STREAMINIT несколько раз на одном и том же шаге DATA не имеет смысла, так как вызовы STREAMINIT после вызова функции RAND игнорируются. Если в течение шага DATA (D) не вызывается подпрограмма STREAMINIT, то при первом вызове функции RAND неявно вызывается подпрограмма STREAMINIT с нулем в качестве аргумента.

Если одна программа содержит несколько шагов DATA, которые генерируют случайные числа (как в примере выше), используйте различные начальные значения на каждом шаге DATA, иначе потоки не будут независимыми. Это также важно, если вы пишете макрофункцию, которая генерирует случайные числа. Не указывайте фиксированное начальное значение в коде. Вместо этого дайте пользователю возможность указать начальное значение в синтаксисе функции.

Потоки случайных чисел в PROC IML

Чтобы упростить сравнение случайных чисел, сгенерированных в SAS/IML, со случайными числами, сгенерированными на шаге DATA в SAS, я приведу таблицу результатов SAS/IML:

Эти числа генерируются подпрограммами RANDGEN и RANDSEED в PROC IML. Числа генерируются с помощью пяти вызовов процедур, а начальные значения идентичны тем, которые используются в примере для шага DATA. Первая и третья переменные были сгенерированы из начального значения 123, вторая и четвертая — с использованием системного времени, а последняя переменная — с использованием начального значения 456. Следующая программа создает наборы данных, которые затем объединяются вместе.

proc iml;
  call randseed(123);
  x = j(5,1); call randgen(x, "Uniform");
  create A from x[colname="x123"]; append from x;
proc iml;
  call randseed(0);
  x = j(5,1); call randgen(x, "Uniform");
  create B from x[colname="x0"]; append from x;
proc iml;
  call randseed(123);
  x = J(5,1); call randgen(x, "Uniform");
  create C from x[colname="x123_2"]; append from x;
proc iml;
  /* no call to randseed */
  x = J(5,1); call randgen(x, "Uniform");
  create D from x[colname="x0_2"]; append from x;
proc iml;
  call randseed(456);
  x = J(5,1); call randgen(x, "Uniform");
  create E from x[colname="x456"]; append from x;
quit;
data AllRandgen; merge A B C D E; run;
proc print data=AllRandgen; run;

Обратите внимание, что числа в двух таблицах одинаковы для столбцов 1, 3 и 5. На шаге DATA ив  PROC IML используется один и тот же алгоритм для генерации случайных чисел. Поэтому при указании одного и того же начального значения они создают один и тот же поток случайных чисел.

Резюме

  • Для генерации случайных чисел используйте функцию RAND (для шага DATA) и вызов RANDGEN (для PROC IML).
  • Чтобы создать воспроизводимый поток случайных чисел, вызовите подпрограмму STREAMINIT (для шага DATA) или подпрограмму RANDSEED (для PROC IML) до вызова RAND или RANDGEN. Передайте положительное значение (называемое начальным значением) в указанные подпрограммы.
  • Чтобы инициализировать поток случайных чисел, который будет различным при каждом запуске программы, вызовите подпрограмму STREAMINIT или RANDSEED с начальным значением 0.
  • Чтобы получить независимые потоки в рамках одной программы, используйте разные начальные значения на каждом шаге DATA или в каждой процедуре.

Оригинал статьи на английском