Como gerar números com base em uma distribuição discreta arbitrária?

28

Como faço para gerar números com base em uma distribuição discreta arbitrária?

Por exemplo, eu tenho um conjunto de números que quero gerar. Digamos que eles sejam rotulados de 1 a 3 da seguinte maneira.

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

Basicamente, as porcentagens são probabilidades de que elas aparecerão na saída do gerador de números aleatórios. Eu tenho um gerador de números aleatórios que gerará uma distribuição uniforme no intervalo [0, 1]. Existe alguma maneira de fazer isso?

Não há limites para quantos elementos posso ter, mas o% adicionará até 100%.

FurtiveFelon
fonte
2
Eu poderia sugerir a especificação de "... distribuições discretas arbitrárias" no título, se essa for sua pergunta. O caso contínuo é diferente.
David M Kaplan
3
Uma maneira genérica é realizar uma pesquisa binária em uma lista de probabilidades cumulativas, que neste exemplo seria . Em média, são necessários testes por evento de geração. Se nenhuma probabilidade for extremamente pequena, você poderá obter desempenho criando um vetor de valores igualmente espaçados em e (em um estágio de pré-computação) atribuindo um resultado a cada valor. Por exemplo, neste exemplo, você pode criar o vetor (com 2 e 3). Gere um uniforme, multiplique por 100 e indexe para esse vetor: done. log ( n ) / 2 O ( 1 ) [ 0 , 1 ] ( 1 , 1 , 1 , 1 , 2 , , 2 , 3 , , 3 ) 50 46(0 0,0,04,0,54,1.0)registro(n)/2O(1)[0 0,1](1,1,1,1,2,...,2,3,...,3)50.46.
whuber
Também veja aqui
Glen_b -Reinstate Monica
Esse link "aqui" está realmente vinculado a essa pergunta: @Glen_b ... erro de copiar e colar?
buruzaemon
@buruzaemon obrigado sim, isso foi um erro; Eu o corrigi.
Glen_b -Reinstala Monica

Respostas:

26

Um dos melhores algoritmos para amostragem de uma distribuição discreta é o método de alias .

O método de alias (eficientemente) pré-computa uma estrutura de dados bidimensional para particionar um retângulo em áreas proporcionais às probabilidades.

Figura

Neste esquema do local referenciado, um rectângulo de altura unidade foi dividida em quatro tipos de regiões - como diferenciadas pela cor - nas proporções , 1 / 3 , 1 / 12 , e 1 / 12 , em para amostrar repetidamente a partir de uma distribuição discreta com essas probabilidades. As faixas verticais têm uma largura (unidade) constante. Cada um é dividido em apenas uma ou duas peças. As identidades das peças e os locais das divisões verticais são armazenados em tabelas acessíveis através do índice da coluna.1/21/31/121/12

A tabela pode ser amostrada em duas etapas simples (uma para cada coordenada), exigindo a geração de apenas dois valores uniformes independentes e cálculo de O ( 1 ) . Isso melhora a computação O ( log ( n ) ) necessária para inverter o CDF discreto, conforme descrito em outras respostas aqui.O(1)O(registro(n))

Lucas
fonte
2
Esse algoritmo é melhor apenas se as probabilidades forem baratas de calcular. Por exemplo, se for enorme, é melhor não construir a árvore inteira. n
probabilityislogic
3
+1 Até agora, esta é a única resposta para sugerir e descrever um algoritmo eficiente.
whuber
19

Você pode fazer isso facilmente no R, basta especificar o tamanho necessário:

sample(x=c(1,2,3), size=1000, replace=TRUE, prob=c(.04,.50,.46))
Dominic Comtois
fonte
3
Pessoalmente, eu preferiria um algoritmo (ou em algum lugar para aprender o conhecimento necessário), desde que eu estou tentando incorporar isso em um aplicativo que estou construindo :) Muito obrigado pela sua resposta embora :)
FurtiveFelon
Hmmm ok ... Saber um pouco mais sobre o que você quer fazer nos ajudaria a guiá-lo. você pode nos falar mais sobre isso? (Finalidade, contexto, etc.)
Dominic Comtois
É para votar. Por exemplo, tenho várias fotos e só posso mostrar 6 para um usuário por vez, gostaria de incorporar o "melhor" a um usuário por vez, e o usuário pode votar em cima ou para baixo em cada foto . A solução mais simples que poderia funcionar agora é o esquema delineado (cada número representa uma foto, cada voto para baixo diminuiria a probabilidade de que a foto, e aumentar em tudo o resto)
FurtiveFelon
1
@furtivefelon, você sempre pode portar o código de R, para descobrir o algoritmo a partir do código e reimplementá-lo.
Mvctas
Eu estou pensando que você pode obter alguns bons (melhores) conselhos sobre o estouro de pilha, pois provavelmente existem algumas soluções conhecidas para esse fim específico. Sugiro também incluir as informações do seu último comentário diretamente na sua pergunta.
Dominic Comtois
19

No seu exemplo, digamos que você desenhe seu valor pseudoaleatório Uniforme [0,1] e chame-o de U. Em seguida, imprima:

1 se U <0,04

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

3 se U> = 0,54

Se o% especificado for a, b, ..., basta imprimir

valor 1 se U

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

etc.

Essencialmente, estamos mapeando a% em subconjuntos de [0,1], e sabemos que a probabilidade de um valor aleatório uniforme cair em qualquer intervalo é simplesmente o comprimento desse intervalo. Colocar os intervalos em ordem parece a maneira mais simples, se não única, de fazê-lo. Isso pressupõe que você esteja perguntando apenas sobre distribuições discretas; para contínuo, pode fazer algo como "amostragem por rejeição" ( entrada da Wikipedia ).

David M Kaplan
fonte
8
O algoritmo é mais rápido se você classificar as categorias em ordem decrescente de probabilidade. Dessa forma, você realiza menos testes (em média) por número aleatório gerado.
jbowman
1
Apenas para adicionar uma observação rápida sobre a classificação - isso só será eficaz se você fizer isso uma vez no início de um esquema de amostragem - para que não funcione bem nos casos em que as probabilidades sejam elas mesmas amostradas como parte de um esquema geral maior ( por exemplo, e, em seguida, P r ( Y = j ) = p j ). Ao classificar neste caso, você está adicionando a operação de classificação a cada iteração de amostragem - que adicionará O ( n log ( n ) )pjDistPr(Y=j)=pjO(nregistro(n))tempo para cada iteração. No entanto, pode ser útil classificar por uma estimativa aproximada do tamanho das probabilidades no início neste caso.
probabilityislogic
4

Suponha que existem possíveis resultados discretos. Você divide o intervalo [ 0 , 1 ] em subintervalos com base na função de massa de probabilidade cumulativa, F , para fornecer o intervalo particionado ( 0 , 1 )m[0 0,1]F(0 0,1)

Eu1Eu2Eum

onde e F ( 0 ) 0 . No seu exemplo m = 3 eEuj=(F(j-1),F(j))F(0 0)0 0m=3

Eu1=(0 0,.04),     Eu2=(.04,.54),     Eu3=(.54,1)

Como e F ( 2 ) = 0,54 e F ( 3 ) = 1 .F(1)=.04F(2)=.54F(3)=1

Em seguida, você pode gerar com a distribuição F usando o seguinte algoritmo:XF

(1) gerar vocêvocênEuform(0 0,1)

(2) Se , então X = j .vocêEujX=j

  • Esta etapa pode ser realizada examinando se é menor que cada uma das probabilidades cumulativas e vendo onde ocorre o ponto de mudança (de para ), o que deve ser uma questão de usar um operador booleano em qualquer linguagem de programação usada e descobrindo onde o primeiro ocorre no vetor.vocêTRUEFALSEFALSE

Observe que estará exatamente em um dos intervalos I j, pois eles são disjuntos e particionam [ 0 , 1 ] .vocêEuj[0 0,1]

Macro
fonte
Todos esses intervalos não deveriam estar meio fechados? Caso contrário, os limites entre os intervalos não serão incluídos. {[0 0,0,04), [0,04,0,54), [0,54,1]}
naught101
1
para qualquer ponto u (ou seja, a medida de Lebesgue do intervalo de meia abertura é a mesma que a do intervalo de abertura), então eu não acho que isso importe. P(você=você)=0 0você
Macro
1
Em uma máquina digital de finito de precisão, embora, talvez um dia antes do fim do universo que importa ...
jbowman
1
É justo, @whuber, veja minha edição.
Macro
1
OK, isso é um algoritmo. BTW, por que você simplesmente não retorna algo assim min(which(u < cp))? Seria bom também evitar calcular novamente a soma acumulada em cada chamada. Com isso pré-computado, todo o algoritmo é reduzido para min(which(runif(1) < cp)). Ou melhor, porque o OP pede para gerar números ( plural ), vetorize-o como n<-10; apply(matrix(runif(n),1), 2, function(u) min(which(u < cp))).
whuber
2

Um algoritmo simples é começar com seu número aleatório uniforme e, em um loop, subtrair primeiro a primeira probabilidade; se o resultado for negativo, você retornará o primeiro valor; se ainda positivo, passará para a próxima iteração e subtrairá a próxima probabilidade. , verifique se negativo, etc.

Isso é bom porque o número de valores / probabilidades pode ser infinito, mas você só precisa calcular as probabilidades quando se aproximar desses números (para algo como gerar a partir de uma distribuição binomial de Poisson ou negativa).

Se você tiver um conjunto finito de probabilidades, mas gerar muitos números a partir deles, poderá ser mais eficiente classificar as probabilidades para subtrair a maior primeiro, depois a segunda maior a seguir e assim por diante.

Greg Snow
fonte
2

Antes de tudo, deixe-me chamar sua atenção para uma biblioteca python com classes prontas para uso, para geração de número aleatório inteiro ou de ponto flutuante que segue a distribuição arbitrária.

De um modo geral, existem várias abordagens para esse problema. Alguns são lineares no tempo, mas requerem grande armazenamento de memória, outros são executados no tempo O (n log (n)). Alguns são otimizados para números inteiros e outros são definidos para histogramas circulares (por exemplo: gerar pontos de tempo aleatórios durante um dia). Na biblioteca acima mencionada, usei este artigo para casos com números inteiros e esta receita para números de ponto flutuante. (Ainda) não possui suporte circular ao histograma e geralmente é confuso, mas funciona bem.

Boris Gorelik
fonte
2

Eu tive o mesmo problema. Dado um conjunto em que cada item tem uma probabilidade e cujas probabilidades somam um, eu queria desenhar uma amostra com eficiência, ou seja, sem classificar nada e sem repetir a iteração sobre o conjunto .

A função a seguir desenha o menor de números aleatórios distribuídos uniformemente dentro do intervalo [ a , 1 ) . Seja r um número aleatório entre [ 0 , 1 ) .N[uma,1)r[0 0,1)

Próximo(N,uma)=1-(1-uma)rN

(umaEu)NN=10

a 1 = próximo ( 9 , a 0uma0 0=Próximo(10,0 0)
uma1=Próximo(9,uma0 0)
uma2=Próximo(8,uma1)
...
uma9=Próximo(1,uma8)

(umaEu)P0 0k<|P|pkPumaEukp0 0...pk>umaEupkumaEu+1


{(1,0,04),(2,0,5),(3,0,46)}N=10

i a_i k Sorteio
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

Amostra: (1,2,2,2,2,3,3,3,3,3)


PróximoN[uma,x)x1

casi
fonte
Parece que o problema que você está abordando mudou abruptamente no segundo parágrafo de um que faz uma amostra de uma distribuição discreta arbitrária para uma amostra de uma distribuição uniforme . Sua solução parece não ser relevante para a pergunta que foi feita aqui.
whuber
Esclarei a última parte.
casi
{1,2,3}
Eu adicionei um exemplo. Minha resposta tem algo em comum com a resposta de David M Kaplan ( stats.stackexchange.com/a/26860/93386 ), mas requer apenas uma iteração em vez de N (= tamanho da amostra) no conjunto, às custas do desenho N N- raízes. Criei um perfil de ambos os procedimentos e o meu foi muito mais rápido.
21816
umaj=Eu=1jregistro(vocêEu)Eu=1N+1registro(vocêEu)
você1,...,vocêN+1