Come generare numeri basati su una distribuzione discreta arbitraria?


28

Come faccio a generare numeri basati su una distribuzione discreta arbitraria?

Ad esempio, ho un insieme di numeri che voglio generare. Di 'che sono etichettati da 1-3 come segue.

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

Fondamentalmente, le percentuali sono probabilità che appaiano nell'output del generatore di numeri casuali. Ho un generatore di numeri pesudorandom che genererà una distribuzione uniforme nell'intervallo [0, 1]. C'è un modo per farlo?

Non ci sono limiti al numero di elementi che posso avere, ma% aggiungerà fino al 100%.


2
Potrei suggerire di specificare "... distribuzioni arbitrarie discrete" nel titolo, se questa è la tua domanda. Il caso continuo è diverso.
David M Kaplan,

3
Un modo generico è quello di eseguire una ricerca binaria all'interno di un elenco di probabilità cumulative, che in questo esempio sarebbe (0,0.04,0.54,1.0) . In media ciò richiede sonde per evento di generazione. Se nessuna probabilità è estremamente ridotta, è possibile ottenere prestazioni O ( 1 ) creando un vettore di valori equidistanziati in [ 0 , 1 ] e (in una fase di precomputazione) assegnando un risultato a ciascun valore. Ad esempio, in questo esempio potresti creare il vettore ( 1 , 1 , 1 ,log(n)/2O(1)[0,1](1,1,1,1,2,,2,3,,3) (con50 2 e46 3). Genera un'uniforme, moltiplica per 100 e indicizza in questo vettore: fatto.
whuber


Quel link "qui" in realtà si collega proprio a questa domanda, errore @Glen_b ... copy-n-paste?
Buruzaemon,

@buruzaemon grazie sì, è stato un errore; L'ho corretto.
Glen_b

Risposte:


26

Uno dei migliori algoritmi per il campionamento da una distribuzione discreta è il metodo alias .

Il metodo alias (in modo efficiente) precompone una struttura di dati bidimensionale per partizionare un rettangolo in aree proporzionali alle probabilità.

figura

In questo schema dal sito di riferimento, un rettangolo di altezza dell'unità è stato partizionato in quattro tipi di regioni - come differenziate per colore - in proporzione , 1 / 3 , 1 / 12 , e 1 / 12 , in per campionare ripetutamente da una distribuzione discreta con queste probabilità. Le strisce verticali hanno una larghezza (unitaria) costante. Ciascuno è diviso in solo uno o due pezzi. Le identità dei pezzi e le posizioni delle divisioni verticali sono memorizzate in tabelle accessibili tramite l'indice di colonna.1/21/31/121/12

La tabella può essere campionata in due semplici passaggi (uno per ciascuna coordinata) che richiedono la generazione di soli due valori uniformi indipendenti e il calcolo . Ciò migliora il calcolo O ( log ( n ) ) necessario per invertire il CDF discreto come descritto in altre risposte qui.O(1)O(log(n))


2
Questo algoritmo è ottimale solo se le probabilità sono economiche da calcolare. Ad esempio, se è enorme, è meglio non costruire l'intero albero. n
probabilityislogic

3
+1 Finora questa è l' unica risposta per suggerire e descrivere un algoritmo efficiente.
whuber

19

Puoi farlo facilmente in R, basta specificare la dimensione che ti serve:

sample(x=c(1,2,3), size=1000, replace=TRUE, prob=c(.04,.50,.46))

3
Personalmente, preferirei un algoritmo (o da qualche parte per apprendere le conoscenze necessarie), dal momento che sto cercando di incorporarlo in un'app che sto costruendo :) Grazie mille per la tua risposta :)
FurtiveFelon

Hmmm ok ... Sapere qualcosa in più su ciò che vuoi fare ci aiuterebbe a guidarti. Puoi dirci di più al riguardo? (Scopo, contesto, ecc.)
Dominic Comtois,

È per il voto. Ad esempio, ho un sacco di foto e posso mostrarne solo 6 a un utente alla volta, vorrei incorporare il "migliore" per un utente alla volta e l'utente può votare su o giù su ogni foto . La soluzione più semplice che potrebbe funzionare in questo momento è lo schema che ho delineato (ogni numero rappresenta una foto, ogni voto
negativo

1
@furtivefelon, puoi sempre portare il codice da R, o capire l'algoritmo dal codice e reimplementarlo.
mpiktas,

Sto pensando che potresti ricevere dei buoni (migliori) consigli su Stack Overflow, poiché probabilmente esistono alcune soluzioni ben note per questo scopo specifico. Suggerisco anche di includere le informazioni del tuo ultimo commento direttamente nella tua domanda.
Dominic Comtois,

19

Nel tuo esempio, supponi di disegnare il tuo valore Pseudorandom Uniform [0,1] e chiamarlo U. Quindi emetti:

1 se U <0,04

2 se U> = 0,04 e U <0,54

3 se U> = 0,54

Se le% specificate sono a, b, ..., semplicemente l'output

valore 1 se U

valore 2 se U> = ae U <(a + b)

eccetera.

In sostanza, stiamo mappando la% in sottoinsiemi di [0,1] e sappiamo che la probabilità che un valore casuale uniforme rientri in qualsiasi intervallo è semplicemente la lunghezza di quell'intervallo. Mettere in ordine le gamme sembra il modo più semplice, se non unico, di farlo. Questo presuppone che tu stia chiedendo solo distribuzioni discrete; per continuo, può fare qualcosa come "campionamento di rifiuto" ( voce di Wikipedia ).


8
L'algoritmo è più veloce se si ordinano le categorie in ordine decrescente di probabilità. In questo modo, esegui meno test (in media) per numero casuale generato.
jbowman,

1
Solo per aggiungere una breve nota sull'ordinamento - questo sarà efficace solo se lo fai una volta all'inizio di uno schema di campionamento - quindi non andrà bene per i casi in cui le probabilità sono esse stesse campionate come parte di uno schema generale più ampio ( es. e poi P r ( Y = j ) = p j ). Ordinando in questo caso si aggiunge l'operazione di ordinamento in ogni iterazione del campionamento, che aggiungerà O ( n log ( n ) )pjDistPr(Y=j)=pjO(nlog(n))tempo per ogni iterazione. Tuttavia, in questo caso può essere utile ordinare in base a un'ipotesi approssimativa della dimensione delle probabilità all'inizio.
Probislogic,

4

Supponiamo che ci siano possibili esiti discreti. Dividi l'intervallo [ 0 , 1 ] in sottointervalli in base alla funzione di massa di probabilità cumulativa, F , per dare l' intervallo partizionato ( 0 , 1 )m[0,1]F(0,1)

I1I2Im

dove e F ( 0 ) 0 . Nel tuo esempio m = 3 eIj=(F(j1),F(j))F(0)0m=3

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

poiché e F ( 2 ) = .54 e F ( 3 ) = 1 .F(1)=.04F(2)=.54F(3)=1

Quindi puoi generare con la distribuzione F usando il seguente algoritmo:XF

(1) genera UUniform(0,1)

(2) Se , allora X = j .UIjX=j

  • Questo passaggio può essere realizzato esaminando se è inferiore a ciascuna delle probabilità cumulative e vedendo dove si verifica il punto di cambiamento (da a ), che dovrebbe essere una questione di utilizzo di un operatore booleano in qualsiasi linguaggio di programmazione che si sta utilizzando e trovare dove si verifica il primo nel vettore.UTRUEFALSEFALSE

Si noti che sarà esattamente in uno degli intervalli I j poiché sono disgiunti e partizionati [ 0 , 1 ] .UIj[0,1]


{[0,0.04), [0.04,0.54), [0.54,1]}

1
P(U=u)=0 for any point u (i.e. the Lebesgue measure of the half open interval is the same as that of the open interval) so I don't think it matters.
Macro

1
On a finite-precision digital machine, though, maybe someday before the end of the universe it will matter...
jbowman

1
Fair enough, @whuber, see my edit.
Macro

1
OK, that is an algorithm. BTW, why don't you just return something like min(which(u < cp))? It would be good to avoid recomputing the cumulative sum on each call, too. With that precomputed, the entire algorithm is reduced to min(which(runif(1) < cp)). Or better, because the OP asks to generate numbers (plural), vectorize it as n<-10; apply(matrix(runif(n),1), 2, function(u) min(which(u < cp))).
whuber

2

One simple algorithm is to start with your uniform random number and in a loop first subtract off the first probability, if the result is negative then you return the first value, if still positive then you go to the next iteration and subtract off the next probability, check if negative, etc.

This is nice in that the number of values/probabilities can be infinite but you only need to calculate the probabilities when you get close to those numbers (for something like generating from a Poisson or negative binomial distribution).

If you have a finite set of probabilities, but will be generating many numbers from them then it could be more efficient to sort the probabilities so that you subtract the largest first, then the 2nd largest next and so forth.


2

First of all, let me draw your attention to a python library with ready-to-use classes for either integer or floating point random number generation that follow arbitrary distribution.

Generally speaking there are several approaches to this problem. Some are linear in time, but require large memory storage, some run in O(n log(n)) time. Some are optimized for integer numbers and some are defined for circular histograms (for example: generating random time spots during a day). In the above mentioned library I used this paper for integer number cases and this recipe for floating point numbers. It (still) lacks circular histogram support and is generally messy, but it works well.


2

I had the same problem. Given a set where each item has a probability and whose items' probabilities sum up to one, I wanted to draw a sample efficiently, i.e. without sorting anything and without repeatedly iterating over the set.

The following function draws the lowest of N uniformly distributed random numbers within the interval [a,1). Let r be a random number from [0,1).

next(N,a)=1(1a)rN

You can use this function to draw an ascending series (ai) of N uniformly distributed random numbers in [0,1). Here is an example with N=10:

a0=next(10,0)
a1=next(9,a0)
a2=next(8,a1)

a9=next(1,a8)

While drawing that ascending series (ai) of uniformly distributed numbers, iterate over the set of probabilities P which represents your arbitraty (yet finite) distribution. Let 0k<|P| be the iterator and pkP. After drawing ai, increment k zero or more times until p0pk>ai. Then add pk to your sample and move on with drawing ai+1.


Example with the op's set {(1,0.04),(2,0.5),(3,0.46)} and sample size 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

Sample: (1,2,2,2,2,3,3,3,3,3)


If you wonder about the next function: It is the inverse of the probability that one of N uniformly distributed random numbers lies within the interval [a,x) with x1.


It appears the problem you are addressing abruptly changed in the second paragraph from one that samples from an arbitrary discrete distribution to sampling from a uniform distribution. Its solution appears not to be relevant to the question that was asked here.
whuber

I clarified the last part.
casi

Your answer still seems unrelated to the question. Could you perhaps provide a small but nontrivial worked example of your algorithm? Show us how it would generate a single draw from the set {1,2,3} according to the probabilities given in the question.
whuber

I added an example. My answer has something in common with David M Kaplan's answer (stats.stackexchange.com/a/26860/93386), but requires just one instead of N (= sample size) iterations over the set, at the expense of drawing N N-th roots. I profiled both procedures, and mine was much faster.
casi

Thank you for the clarification (+1). It may be of interest to many readers that this isn't a simple random sample, because the outcomes appear in a predetermined, fixed order: a random permutation would have to be applied to the results in order to create a simple random sample. You might also be interested in a parallelizable version of this algorithm in which
aj=i=1jlog(ui)i=1N+1log(ui)
where u1,,uN+1 is a simple random sample of Uniform(0,1] variates.
whuber
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.