¿Cómo generar números basados ​​en una distribución discreta arbitraria?

28

¿Cómo genero números basados ​​en una distribución discreta arbitraria?

Por ejemplo, tengo un conjunto de números que quiero generar. Digamos que están etiquetados de 1-3 de la siguiente manera.

1: 4%, 2: 50%, 3: 46%

Básicamente, los porcentajes son probabilidades de que aparezcan en la salida del generador de números aleatorios. Tengo un generador de números al azar que generará una distribución uniforme en el intervalo [0, 1]. ¿Hay alguna forma de hacer esto?

No hay límites en la cantidad de elementos que puedo tener, pero el% agregará hasta el 100%.

FurtiveFelon
fuente
2
Podría sugerir que especifique "... distribuciones discretas arbitrarias" en el título, si esa es su pregunta. El caso continuo es diferente.
David M Kaplan
3
Una forma genérica es realizar una búsqueda binaria dentro de una lista de las probabilidades acumuladas, que en este ejemplo sería . En promedio, esto toma sondas por evento de generación. Si ninguna probabilidad es extremadamente pequeña, puede obtener rendimiento de creando un vector de valores igualmente espaciados en y (en una etapa de precomputación) asignando un resultado a cada valor. Por ejemplo, en este ejemplo, puede crear el vector (con 2 y 3). Genere un uniforme, multiplique por 100 e indexe en este vector: hecho. log ( n ) / 2 O ( 1 ) [ 0 , 1 ] ( 1 , 1 , 1 , 1 , 2 , , 2 , 3 , , 3 ) 50 46(0,0.04,0.54,1.0)log(n)/2O(1)[0,1](1,1,1,1,2,,2,3,,3)5046
whuber
También vea aquí
Glen_b -Reinstate Monica
Ese enlace "aquí" en realidad enlaza con esta misma pregunta, @Glen_b ... ¿error de copiar y pegar?
buruzaemon
@buruzaemon gracias, sí, fue un error; Lo he corregido.
Glen_b: reinstala a Monica el

Respuestas:

26

Uno de los mejores algoritmos para el muestreo de una distribución discreta es el método de alias .

El método de alias (eficientemente) calcula previamente una estructura de datos bidimensional para dividir un rectángulo en áreas proporcionales a las probabilidades.

Figura

En este esquema del sitio referenciado, un rectángulo de altura unitaria se ha dividido en cuatro tipos de regiones, diferenciadas por color, en las proporciones , , y , en para muestrear repetidamente desde una distribución discreta con estas probabilidades. Las tiras verticales tienen un ancho constante (unidad). Cada uno se divide en solo una o dos piezas. Las identidades de las piezas y las ubicaciones de las divisiones verticales se almacenan en tablas accesibles a través del índice de columna.1 / 3 1 / 12 1 / 121/21/31/121/12

La tabla se puede muestrear en dos pasos simples (uno para cada coordenada) que requieren generar solo dos valores uniformes independientes y cálculo . Esto mejora el cálculo de necesario para invertir el CDF discreto como se describe en otras respuestas aquí.O(1)O(log(n))

Lucas
fuente
2
Este algoritmo solo es mejor si las probabilidades son baratas de calcular. Por ejemplo, si es enorme, es mejor no construir todo el árbol. norte
probabilityislogic
3
+1 Hasta ahora, esta es la única respuesta para sugerir y describir un algoritmo eficiente.
whuber
19

Puede hacerlo fácilmente en R, solo especifique el tamaño que necesita:

sample(x=c(1,2,3), size=1000, replace=TRUE, prob=c(.04,.50,.46))
Dominic Comtois
fuente
3
Personalmente, preferiría un algoritmo (o algún lugar para aprender los conocimientos necesarios), ya que estoy tratando de incorporar esto en una aplicación que estoy construyendo :) Muchas gracias por su respuesta :)
FurtiveFelon
Hmmm ok ... Saber un poco más sobre lo que quieres hacer nos ayudaría a guiarte. ¿Puedes contarnos más al respecto? (Propósito, contexto, etc.)
Dominic Comtois
Es para votar. Por ejemplo, tengo un montón de fotos, y solo puedo mostrar 6 a un usuario a la vez, me gustaría incorporar el "mejor" a un usuario a la vez, y el usuario puede votar hacia arriba o hacia abajo en cada foto . La solución más simple que podría funcionar en este momento es el esquema que describí (cada número representa una foto, cada voto
negativo
1
@furtivefelon, siempre puedes portar el código desde R, o descifrar el algoritmo del código y reimplementarlo.
mpiktas
Estoy pensando que podría obtener algunos buenos (mejores) consejos sobre Stack Overflow, ya que probablemente existan algunas soluciones bien conocidas para este propósito específico. Sugiero también incluir la información de su último comentario directamente en su pregunta.
Dominic Comtois
19

En su ejemplo, supongamos que dibuja su valor de uniforme pseudoaleatorio [0,1] y lo llama U. Luego, genera:

1 si U <0.04

2 si U> = 0.04 y U <0.54

3 si U> = 0.54

Si el% especificado es a, b, ..., simplemente envíe

valor 1 si U

valor 2 si U> = a y U <(a + b)

etc.

Esencialmente, estamos mapeando el% en subconjuntos de [0,1], y sabemos que la probabilidad de que un valor aleatorio uniforme caiga en cualquier rango es simplemente la longitud de ese rango. Poner los rangos en orden parece la forma más simple, si no única, de hacerlo. Esto supone que solo está preguntando sobre distribuciones discretas; para continuo, puede hacer algo como "muestreo de rechazo" ( entrada de Wikipedia ).

David M Kaplan
fuente
8
El algoritmo es más rápido si ordena las categorías en orden decreciente de probabilidad. De esa manera, haces menos pruebas (en promedio) por número aleatorio generado.
jbowman
1
Solo para agregar una nota rápida sobre la clasificación, esto será efectivo solo si lo hace una vez al comienzo de un esquema de muestreo, por lo que no funcionará bien en los casos en que las probabilidades se muestren como parte de un esquema general más amplio ( por ejemplo, y luego P r ( Y = j ) = p j ). Al ordenar en este caso, está agregando la operación de clasificación en cada iteración de muestreo, que agregará O ( n log ( n ) )pagsjDistPAGSr(Y=j)=pagsjO(norteIniciar sesión(norte))tiempo para cada iteración. Sin embargo, puede ser útil ordenar por una aproximación aproximada del tamaño de las probabilidades al comienzo en este caso.
probabilidadislogica
4

Supongamos que hay posibles resultados discretos. Divide el intervalo [ 0 , 1 ] en subintervalos basados ​​en la función de masa de probabilidad acumulativa, F , para dar el intervalo dividido ( 0 , 1 )metro[0 0,1]F(0 0,1)

yo1yo2yometro

donde y F ( 0 ) 0 . En tu ejemplo m = 3 yyoj=(F(j-1),F(j))F(0 0)0 0metro=3

I1=(0,.04),     I2=(.04,.54),     I3=(.54,1)

ya que y F ( 2 ) = .54 y F ( 3 ) = 1 .F(1)=.04F(2)=.54F(3)=1

Entonces puede generar con distribución F usando el siguiente algoritmo:XF

(1) generar UUniform(0,1)

(2) Si , entonces X = j .UIjX=j

  • Este paso se puede lograr observando si es menor que cada una de las probabilidades acumuladas y viendo dónde ocurre el punto de cambio (de a ), que debería ser una cuestión de usar un operador booleano en cualquier lenguaje de programación que esté usando y encontrar dónde ocurre lo primero en el vector.UTRUEFALSEFALSE

Tenga en cuenta que estará exactamente en uno de los intervalos I j ya que son disjuntos y partición [ 0 , 1 ] .UIj[0,1]

Macro
fuente
¿No deberían todos esos intervalos estar medio cerrados? De lo contrario, no se incluyen los límites entre intervalos. {[0,0.04), [0.04,0.54), [0.54,1]}
nada101
1
para cualquier punto u (es decir, la medida de Lebesgue del intervalo medio abierto es la misma que la del intervalo abierto), así que no creo que importe. P(U=u)=0u
Macro
1
Sin embargo, en una máquina digital de precisión finita, tal vez algún día antes del fin del universo importará ...
jbowman
1
Muy bien, @whuber, mira mi edición.
Macro
1
OK, eso es un algoritmo. Por cierto, ¿por qué no devuelves algo así min(which(u < cp))? Sería bueno evitar volver a calcular la suma acumulativa en cada llamada también. Con eso precalculado, todo el algoritmo se reduce a min(which(runif(1) < cp)). O mejor, porque el OP pide generar números ( plural ), vectorizarlo como n<-10; apply(matrix(runif(n),1), 2, function(u) min(which(u < cp))).
whuber
2

Un algoritmo simple es comenzar con su número aleatorio uniforme y en un bucle primero restar la primera probabilidad, si el resultado es negativo, devuelve el primer valor, si aún es positivo, pasa a la siguiente iteración y resta la siguiente probabilidad , verifique si es negativo, etc.

Esto es bueno porque el número de valores / probabilidades puede ser infinito, pero solo necesita calcular las probabilidades cuando se acerca a esos números (para algo como generar a partir de una distribución binomial negativa o de Poisson).

Si tiene un conjunto finito de probabilidades, pero generará muchos números a partir de ellos, entonces podría ser más eficiente clasificar las probabilidades para que reste el primero más grande, luego el segundo más grande, y así sucesivamente.

Greg Snow
fuente
2

En primer lugar, permítame llamar su atención sobre una biblioteca de Python con clases listas para usar para la generación de números aleatorios de números enteros o de punto flotante que siguen una distribución arbitraria.

En términos generales, hay varios enfoques para este problema. Algunos son lineales en el tiempo, pero requieren un gran almacenamiento de memoria, algunos se ejecutan en tiempo O (n log (n)). Algunos están optimizados para números enteros y otros están definidos para histogramas circulares (por ejemplo: generar puntos de tiempo aleatorios durante un día). En la biblioteca mencionada anteriormente, utilicé este documento para casos de números enteros y esta receta para números de coma flotante. (Todavía) carece de soporte de histograma circular y generalmente es desordenado, pero funciona bien.

Boris Gorelik
fuente
2

Yo tuve el mismo problema. Dado un conjunto en el que cada elemento tiene una probabilidad y cuyas probabilidades de los elementos suman uno, quería dibujar una muestra de manera eficiente, es decir, sin clasificar nada y sin repetir repetidamente sobre el conjunto .

La siguiente función dibuja el más bajo de números aleatorios distribuidos uniformemente dentro del intervalo [ a , 1 ) . Sea r un número aleatorio de [ 0 , 1 ) .norte[a,1)r[0 0,1)

siguiente(norte,una)=1-(1-una)rnorte

Puede utilizar esta función para dibujar una serie ascendente de N números aleatorios distribuidos uniformemente en [0,1]. Aquí hay un ejemplo con N = 10 :(unayo)nortenorte=10

una0 0=siguiente(10,0 0)
una1=siguiente(9,una0 0)
una2=siguiente(8,una1)
...
una9=siguiente(1,una8)

(unayo)PAGS0 0k<El |PAGSEl |pagskPAGSunayokp0pk>aipkai+1


{(1,0.04),(2,0.5),(3,0.46)}N=10

i a_i k Sum Draw
0 0.031 0 0.04 1
1 0.200 1 0.54 2
2 0.236 1 0.54 2
3 0,402 1 0,54 2
4 0.488 1 0.54 2
5 0.589 2 1.0 3
6 0.625 2 1.0 3
7 0.638 2 1.0 3
8 0,738 2 1,0 3
9 0.942 2 1.0 3

(1,2,2,2,2,3,3,3,3,3)


nextN[a,x)x1

casi
fuente
Parece que el problema que está abordando cambió abruptamente en el segundo párrafo de uno que muestrea de una distribución discreta arbitraria al muestreo de una distribución uniforme . Su solución parece no ser relevante para la pregunta que se hizo aquí.
whuber
Aclaré la última parte.
casi
{1,2,3}
Agregué un ejemplo. Mi respuesta tiene algo en común con la respuesta de David M Kaplan ( stats.stackexchange.com/a/26860/93386 ), pero requiere solo una en lugar de N (= tamaño de muestra) iteraciones sobre el conjunto, a expensas de dibujar N N- las raíces Perfilé ambos procedimientos, y el mío fue mucho más rápido.
casi
unaj=yo=1jIniciar sesión(tuyo)yo=1norte+1Iniciar sesión(tuyo)
tu1,...,tunorte+1