Conversión de una distribución uniforme en una distribución normal

106

¿Cómo puedo convertir una distribución uniforme (como la mayoría de los generadores de números aleatorios producen, por ejemplo, entre 0,0 y 1,0) en una distribución normal? ¿Qué pasa si quiero una desviación estándar y media de mi elección?

Terhorst
fuente
3
¿Tiene una especificación de idioma o es solo una pregunta general de algoritmo?
Bill the Lizard
3
Pregunta general de algoritmo. No me importa en qué idioma. Pero preferiría que la respuesta no se base en una funcionalidad específica que solo proporciona ese lenguaje.
Terhorst

Respuestas:

47

El algoritmo Ziggurat es bastante eficiente para esto, aunque la transformación Box-Muller es más fácil de implementar desde cero (y no muy lento).

Tyler
fuente
7
Las advertencias habituales sobre los generadores congruentes lineales se aplican a ambos métodos, así que utilice un generador subordinado decente. Salud.
dmckee --- ex-moderador gatito
3
Como Mersenee Twister, o tienes otras sugerencias?
Gregg Lind
47

Hay muchos métodos:

  • No , no utilizar la caja de Muller. Especialmente si dibuja muchos números gaussianos. Box Muller produce un resultado que se fija entre -6 y 6 (asumiendo doble precisión. Las cosas empeoran con flotadores). Y es realmente menos eficiente que otros métodos disponibles.
  • Ziggurat está bien, pero necesita una búsqueda en la tabla (y algunos ajustes específicos de la plataforma debido a problemas de tamaño de la caché)
  • La relación de uniformes es mi favorito, solo unas pocas sumas / multiplicaciones y un log 1/50 del tiempo (por ejemplo, mire allí ).
  • Invertir el CDF es eficiente (y se pasa por alto, ¿por qué?), Tiene implementaciones rápidas disponibles si busca en Google. Es obligatorio para números cuasi aleatorios.
Alexandre C.
fuente
2
¿Está seguro de la sujeción [-6,6]? Este es un punto bastante significativo si es cierto (y digno de una nota en la página de wikipedia).
redcalx
1
@locster: esto es lo que me dijo un maestro mío (él estudió tales generadores, y confío en su palabra). Quizás pueda encontrarle una referencia.
Alexandre C.
7
@locster: esta propiedad indeseable también es compartida por el método CDF inverso. Ver cimat.mx/~src/prope08/randomgauss.pdf . Esto puede aliviarse utilizando un RNG uniforme que tenga una probabilidad distinta de cero para producir un número de punto flotante muy cercano a cero. La mayoría de los RNG no lo hacen, ya que generan un entero (normalmente de 64 bits) que luego se asigna a [0,1]. Esto hace que esos métodos no sean adecuados para muestrear colas de variables gaussianas (piense en la fijación de precios de opciones de huelga baja / alta en finanzas computacionales).
Alexandre C.
6
@AlexandreC. Para ser claros en dos puntos, usando números de 64 bits, las colas salen a 8.57 o 9.41 (el valor más bajo corresponde a convertir a [0,1) antes de tomar el log). Incluso si se fija a [-6, 6], las posibilidades de estar fuera de este rango son de aproximadamente 1.98e-9, lo suficientemente bueno para la mayoría de las personas, incluso en ciencias. Para las cifras de 8.57 y 9.41, esto se convierte en 1.04e-17 y 4.97e-21. Estos números son tan pequeños que la diferencia entre un muestreo de Box Muller y un muestreo gaussiano verdadero en términos de dicho límite es casi puramente académica. Si necesita algo mejor, sume cuatro de ellos y divídalos por 2.
CrazyCasta
6
Creo que la sugerencia de no usar la transformación Box Muller es engañosa para un gran porcentaje de usuarios. Es genial conocer la limitación, pero como señala CrazyCasta, para la mayoría de las aplicaciones que no dependen en gran medida de valores atípicos, probablemente no tenga que preocuparse por esto. Como ejemplo, si alguna vez ha dependido del muestreo de un normal usando numpy, ha dependido de la transformada Box Muller (forma de coordenadas polares) github.com/numpy/numpy/blob/… .
Andreas Grivas
30

Cambiar la distribución de cualquier función a otra implica usar la inversa de la función que desea.

En otras palabras, si su objetivo es una función de probabilidad específica p (x), obtiene la distribución integrando sobre ella -> d (x) = integral (p (x)) y usa su inversa: Inv (d (x)) . Ahora use la función de probabilidad aleatoria (que tiene una distribución uniforme) y emita el valor del resultado a través de la función Inv (d (x)). Debería obtener valores aleatorios emitidos con distribución de acuerdo con la función que elija.

Este es el enfoque matemático genérico: al usarlo, ahora puede elegir cualquier función de probabilidad o distribución que tenga siempre que tenga una aproximación inversa o una buena aproximación inversa.

Espero que esto haya ayudado y gracias por el pequeño comentario sobre el uso de la distribución y no la probabilidad en sí.

Adi
fuente
4
+1 Este es un método pasado por alto para generar variables gaussianas que funciona muy bien. En este caso, la CDF inversa se puede calcular de manera eficiente con el método de Newton (la derivada es e ^ {- t ^ 2}), una aproximación inicial es fácil de obtener como fracción racional, por lo que necesita 3-4 evaluaciones de erf y exp. Es obligatorio si usa números cuasialeatorios, un caso en el que debe usar exactamente un número uniforme para obtener uno gaussiano.
Alexandre C.
9
Tenga en cuenta que debe invertir la función de distribución acumulativa, no la función de distribución de probabilidad. Alexandre implica esto, pero pensé que mencionarlo de manera más explícita podría no doler, ya que la respuesta parece sugerir el PDF
ltjax
Puede utilizar el PDF si está preparado para seleccionar aleatoriamente una dirección relativa a la media; ¿Lo entiendo bien?
Mark McKenna
2
Esto se llama muestreo de transformada inversa
dashesy
1
Aquí hay una pregunta relacionada en SE con una respuesta más generalizada con una buena explicación.
guiones el
23

Aquí hay una implementación de JavaScript que utiliza la forma polar de la transformación Box-Muller.

/*
 * Returns member of set with a given mean and standard deviation
 * mean: mean
 * standard deviation: std_dev 
 */
function createMemberInNormalDistribution(mean,std_dev){
    return mean + (gaussRandom()*std_dev);
}

/*
 * Returns random number in normal distribution centering on 0.
 * ~95% of numbers returned should fall between -2 and 2
 * ie within two standard deviations
 */
function gaussRandom() {
    var u = 2*Math.random()-1;
    var v = 2*Math.random()-1;
    var r = u*u + v*v;
    /*if outside interval [0,1] start over*/
    if(r == 0 || r >= 1) return gaussRandom();

    var c = Math.sqrt(-2*Math.log(r)/r);
    return u*c;

    /* todo: optimize this algorithm by caching (v*c) 
     * and returning next time gaussRandom() is called.
     * left out for simplicity */
}
user5084
fuente
5

Utilice la entrada mathworld del teorema del límite central de wikipedia para su ventaja.

Genere n de los números distribuidos uniformemente, súmelos, reste n * 0.5 y obtendrá el resultado de una distribución aproximadamente normal con media igual a 0 y varianza igual a (1/12) * (1/sqrt(N))(consulte wikipedia sobre distribuciones uniformes para la última)

n = 10 te da algo medio decente rápido. Si desea algo más de la mitad decente, opte por la solución de tylers (como se indica en la entrada de wikipedia sobre distribuciones normales )

jilles de wit
fuente
1
Esto no dará una normalidad particularmente cercana (las "colas" o puntos finales no estarán cerca de la distribución normal real). Box-Muller es mejor, como han sugerido otros.
Peter K.
1
Box Muller también tiene colas incorrectas (devuelve un número entre -6 y 6 con doble precisión)
Alexandre C.
n = 12 (sumar 12 números aleatorios en el rango de 0 a 1 y restar 6) da como resultado stddev = 1 y mean = 0. Esto luego se puede usar para generar cualquier distribución normal. Simplemente multiplique el resultado por el stddev deseado y agregue la media.
JerryM
3

Usaría Box-Muller. Dos cosas sobre esto:

  1. Termina con dos valores por iteración.
    Normalmente, almacena en caché un valor y devuelve el otro. En la siguiente llamada para obtener una muestra, devuelve el valor almacenado en caché.
  2. Box-Muller da un puntaje Z
    Luego, debe escalar el puntaje Z por la desviación estándar y sumar la media para obtener el valor total en la distribución normal.
hughdbrown
fuente
¿Cómo escala la puntuación Z?
Terhorst
3
scaled = mean + stdDev * zScore // le da normal (mean, stdDev ^ 2)
yoyoyoyosef
2

Donde R1, R2 son números uniformes aleatorios:

DISTRIBUCIÓN NORMAL, con SD de 1: sqrt (-2 * log (R1)) * cos (2 * pi * R2)

Esto es exacto ... ¡no es necesario hacer todos esos bucles lentos!

Erik Aronesty
fuente
Antes de que alguien me corrigiera ... aquí está la aproximación que se me ocurrió: (1.5- (R1 + R2 + R3)) * 1.88. A mí también me gusta.
Erik Aronesty
2

Parece increíble que pudiera agregar algo a esto después de ocho años, pero para el caso de Java, me gustaría señalar a los lectores el método Random.nextGaussian () , que genera una distribución gaussiana con una media de 0.0 y una desviación estándar de 1.0 para ustedes.

Una simple suma y / o multiplicación cambiará la media y la desviación estándar según sus necesidades.

Pepijn Schmitz
fuente
1

El módulo de biblioteca estándar de Python al azar tiene lo que desea:

normalvariate (mu, sigma)
Distribución normal. mu es la media y sigma es la desviación estándar.

Para el algoritmo en sí, eche un vistazo a la función en random.py en la biblioteca de Python.

La entrada manual está aquí

Brent.Longborough
fuente
2
Desafortunadamente, la biblioteca de Python usa Kinderman, AJ y Monahan, JF, "Generación por computadora de variables aleatorias usando la proporción de desviaciones uniformes", ACM Trans Math Software, 3, (1977), pp257-260. Esto usa dos variables aleatorias uniformes para generar el valor normal, en lugar de una sola, por lo que no es obvio cómo usarlo como el mapeo que el OP quería.
Ian
1

Esta es mi implementación de JavaScript del algoritmo P ( método polar para desviaciones normales ) de la Sección 3.4.1 del libro de Donald Knuth El arte de la programación informática :

function normal_random(mean,stddev)
{
    var V1
    var V2
    var S
    do{
        var U1 = Math.random() // return uniform distributed in [0,1[
        var U2 = Math.random()
        V1 = 2*U1-1
        V2 = 2*U2-1
        S = V1*V1+V2*V2
    }while(S >= 1)
    if(S===0) return 0
    return mean+stddev*(V1*Math.sqrt(-2*Math.log(S)/S))
}
Alessandro Jacopson
fuente
0

Yo cosa que debe tratar esto en EXCEL: =norminv(rand();0;1). Esto producirá los números aleatorios que deberían distribuirse normalmente con la media cero y unir la varianza. Se puede suministrar "0" con cualquier valor, de modo que los números tengan la media deseada, y al cambiar "1", obtendrá la varianza igual al cuadrado de su entrada.

Por ejemplo: =norminv(rand();50;3)cederá a los números distribuidos normalmente con MEAN = 50 VARIANCE = 9.

Hipopótamo
fuente
0

P ¿Cómo puedo convertir una distribución uniforme (como la mayoría de los generadores de números aleatorios producen, por ejemplo, entre 0.0 y 1.0) en una distribución normal?

  1. Para la implementación de software, conozco un par de nombres de generadores aleatorios que le dan una secuencia aleatoria pseudo uniforme en [0,1] (Mersenne Twister, Linear Congruate Generator). Llamémoslo U (x)

  2. Existe un área matemática que se llama teoría de la probabilidad. Primero: si desea modelar rv con distribución integral F, puede intentar evaluar F ^ -1 (U (x)). En teoría pr. Se demostró que tal rv tendrá distribución integral F.

  3. El paso 2 puede aplicarse para generar rv ~ F sin el uso de ningún método de conteo cuando F ^ -1 se puede derivar analíticamente sin problemas. (por ejemplo, distribución exp.)

  4. Para modelar la distribución normal, puede calcular y1 * cos (y2), donde y1 ~ es uniforme en [0,2pi]. e y2 es la distribución relevante.

P: ¿Qué pasa si quiero una desviación estándar y media de mi elección?

Puede calcular sigma * N (0,1) + m.

Se puede demostrar que tales cambios y escalas conducen a N (m, sigma)

bruziuz
fuente
0

Esta es una implementación de Matlab que utiliza la forma polar de la transformación Box-Muller :

Función randn_box_muller.m:

function [values] = randn_box_muller(n, mean, std_dev)
    if nargin == 1
       mean = 0;
       std_dev = 1;
    end

    r = gaussRandomN(n);
    values = r.*std_dev - mean;
end

function [values] = gaussRandomN(n)
    [u, v, r] = gaussRandomNValid(n);

    c = sqrt(-2*log(r)./r);
    values = u.*c;
end

function [u, v, r] = gaussRandomNValid(n)
    r = zeros(n, 1);
    u = zeros(n, 1);
    v = zeros(n, 1);

    filter = r==0 | r>=1;

    % if outside interval [0,1] start over
    while n ~= 0
        u(filter) = 2*rand(n, 1)-1;
        v(filter) = 2*rand(n, 1)-1;
        r(filter) = u(filter).*u(filter) + v(filter).*v(filter);

        filter = r==0 | r>=1;
        n = size(r(filter),1);
    end
end

E invocar histfit(randn_box_muller(10000000),100);este es el resultado: Caja-Muller Matlab Histfit

Obviamente, es realmente ineficiente en comparación con el randn incorporado de Matlab .

madx
fuente
0

Tengo el siguiente código que tal vez podría ayudar:

set.seed(123)
n <- 1000
u <- runif(n) #creates U
x <- -log(u)
y <- runif(n, max=u*sqrt((2*exp(1))/pi)) #create Y
z <- ifelse (y < dnorm(x)/2, -x, NA)
z <- ifelse ((y > dnorm(x)/2) & (y < dnorm(x)), x, z)
z <- z[!is.na(z)]
grandes mentes piensan igual
fuente
0

También es más fácil usar la función implementada rnorm () ya que es más rápido que escribir un generador de números aleatorios para la distribución normal. Vea el siguiente código como prueba

n <- length(z)
t0 <- Sys.time()
z <- rnorm(n)
t1 <- Sys.time()
t1-t0
peterweethetbeter
fuente
-2
function distRandom(){
  do{
    x=random(DISTRIBUTION_DOMAIN);
  }while(random(DISTRIBUTION_RANGE)>=distributionFunction(x));
  return x;
}

fuente
Sin embargo, no se garantiza que regrese, ¿verdad? ;-)
Peter K.
5
Los números aleatorios son demasiado importantes para dejarlos al azar.
Drew Noakes
No responde a la pregunta: la distribución normal tiene un dominio infinito.
Matt