Эффективная генерация случайных чисел из усеченного нормального распределения

Я хотел бы попробовать 50 000 значений из нормального распределения со средним = 0 и sd -1. Но я хочу ограничить значения [-3,3]. Я написал код для этого, но не уверен, что он наиболее эффективен? Надеялся получить какие-то предложения.

lower <- -3 
upper <- 3
x_norm<-rnorm(75000,0,1)
x_norm<-x_norm[which(x_norm >=lower & x_norm<=upper)]
repeat{
    x_norm<-c(x_norm, rnorm(10000,0,1))
    x_norm<-x_norm[which(x_norm >=lower & x_norm<=upper)]
    if(length(x_norm) >= 50000){break}
}
x_norm<-x_norm[1:50000]

3 ответов


если вы действительно забота об эффективности этот короткий кусок кода Rcpp будет трудно превзойти. Хранить в файле, скажем /tmp/rnormClamp.cpp:

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
NumericVector rnormClamp(int N, int mi, int ma) {
    NumericVector X = rnorm(N, 0, 1);
    return clamp(mi, X, ma);
}

/*** R
  system.time(X <- rnormClamp(50000, -3, 3))
  summary(X)
*/

использовать sourceCpp() (от Rcpp, а) построить и запустить его. Фактическая ничья и зажим занимает около 4 миллисекунд на моем компьютере:

R> sourceCpp("/tmp/rnormClamp.cpp")

R>   system.time(X <- rnormClamp(50000, -3, 3))
   user  system elapsed 
  0.004   0.000   0.004 

R>   summary(X)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-3.00000 -0.67300 -0.00528  0.00122  0.68500  3.00000 
R> 

на clamp() функция Сахара была показана в этот предыдущий ответ так Ромен, который также отмечает, что вы хотите, версии 0.10.2 на Rcpp.

Edit:

// [[Rcpp::export]]
List rnormSelect(int N, int mi, int ma) {
  RNGScope scope;
  int N2 = N * 1.25;
  NumericVector X = rnorm(N2, 0, 1);
  LogicalVector ind = (X < mi) | (X > ma);
  return List::create(X, ind);
}

который можно добавить к более раннему файлу. Затем:

R>   system.time({ Z <- rnormSelect(50000, -3, 3); 
+                  X <- Z[[1]][ ! Z[[2]] ]; X <- X[1:50000]})
   user  system elapsed 
  0.008   0.000   0.009 

R>   summary(X)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-3.00000 -0.68200 -0.00066 -0.00276  0.66800  3.00000 
R> 

Я вернусь к логическому индексированию и подмножеству строк, которые мне придется искать. Может, завтра. Но 9 миллисекунд еще не так уж и плохо:)

Edit 2: похоже, у нас действительно нет логической индексации. Мы должны добавить этот. Эта версия делает это "вручную", но не намного быстрее, чем индексирование из R:

// [[Rcpp::export]]
NumericVector rnormSelect2(int N, int mi, int ma) {
  RNGScope scope;
  int N2 = N * 1.25;
  NumericVector X = rnorm(N2, 0, 1);
  LogicalVector ind = (X >= mi) & (X <= ma);
  NumericVector Y(N);
  int k=0;
  for (int i=0; i<N2 & k<N; i++) {
    if (ind[i]) Y(k++) = X(i);
  }
  return Y;
}

и вывод:

R>   system.time(X <- rnormSelect2(50000, -3, 3)) 
   user  system elapsed 
  0.004   0.000   0.007 

R>   summary(X)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-2.99000 -0.66900 -0.00258  0.00223  0.66700  2.99000 

R>   length(X)
[1] 50000
R> 


что-то вроде вашего кода, безусловно, будет работать, но вы сильно переоцениваете, сколько значений вам нужно. Учитывая, что это известное распределение и довольно большое количество образцов, вы знаете, сколько будет отображаться более или менее 3.

(1-pnorm(3))*2 * 50000
[1] 134.9898

таким образом, учитывая, что вы, вероятно, получите только около 135 из диапазона в розыгрыше 50,000, довольно легко нарисовать несколько больше, но все же не чрезмерно большее число и обрезать его. Просто возьмите первые 50,000 из 50,500, которые меньше или больше 3.

x <- rnorm(50500)
x <- x[x < 3 & x > -3]
x <- x[1:50000]

Я пробежал первые 2 строки 40 000 раз, и каждый раз он возвращал длину больше 50000. Может небольшой логический проверьте гарантию всегда.

x <- 1
while (length(x) < 50000){
    x <- rnorm(50500)
    x <- x[x < 3 & x > -3]}
x <- x[1:50000]

для меня это выполняет почти 100% времени в 6 мс. Это простой способ сделать это в R, что выполняется очень быстро, легко читается и не требует дополнений.


Джон и Дирк дали хорошие примеры выборки отклонения, которые должны быть прекрасны для данного вопроса. Но чтобы дать другой подход, когда у вас есть кумулятивная функция распределения и ее обратные (или разумные их приближения) , вы можете просто генерировать данные из равномерного распределения и преобразовывать:

x <- qnorm( runif(50000, pnorm(-3), pnorm(3)) )
range(x)
hist(x)

для данного вопроса я не ожидаю, что это будет намного лучше (если лучше), чем методы выборки отклонения, но если вы хотите генерировать данные между 2 и 3 от усеченного нормального 0,1 тогда этот метод, вероятно, был бы намного эффективнее. Это зависит от кумулятивного и его обратного (pnorm и qnorm в этом случае), и поэтому не было бы так просто, как выборка отбраковки для распределения без тех, которые легко доступны.