Eu precisava escrever uma versão ponderada do random.choice (cada elemento da lista tem uma probabilidade diferente de ser selecionado). Isto é o que eu vim com:
def weightedChoice(choices):
"""Like random.choice, but each element can have a different chance of
being selected.
choices can be any iterable containing iterables with two items each.
Technically, they can have more than two items, the rest will just be
ignored. The first item is the thing being chosen, the second item is
its weight. The weights can be any numeric values, what matters is the
relative differences between them.
"""
space = {}
current = 0
for choice, weight in choices:
if weight > 0:
space[current] = choice
current += weight
rand = random.uniform(0, current)
for key in sorted(space.keys() + [current]):
if rand < key:
return choice
choice = space[key]
return None
Essa função me parece excessivamente complexa e feia. Espero que todos aqui possam oferecer algumas sugestões para aprimorá-lo ou maneiras alternativas de fazer isso. A eficiência não é tão importante para mim quanto a limpeza e a legibilidade do código.
python
optimization
Colin
fonte
fonte
random.choices
para chamadas individuais. Se você precisar de muitos resultados aleatórios, é realmente importante selecioná-los todos de uma vez, ajustandonumber_of_items_to_pick
. Se você fizer isso, é uma ordem de magnitude mais rápida.len(list_of_candidates)
, e faça-olist_of_candidates[draw]
Desde o Python 3.6, existe um método
choices
dorandom
módulo.Observe que
random.choices
a amostra será substituída de acordo com os documentos :Se você precisar fazer uma amostra sem substituição, então, como a brilhante resposta de @ ronan-paixão afirma, você pode usar
numpy.choice
, cujoreplace
argumento controla esse comportamento.fonte
random.choices
não tem, então é claro que é mais lento em uma lista minúscula de 8 itens e, se você escolher 10 mil vezes dessa lista, está certo. Mas, nos casos em que a lista é maior (dependendo de como você está testando, vejo pontos de interrupção entre 100 a 300 elementos),np.random.choice
começa a ter um desempenhorandom.choices
bastante alto. Por exemplo, incluindo a etapa de normalização junto com a chamada numpy, recebo uma aceleração de quase 4xrandom.choices
para uma lista de 10 mil elementos.fonte
upto +=w; if upto > r
if r < 0
r <= 0
. Considere um conjunto de entrada de 1 itens e um rolo de 1,0. A afirmação falhará então. Corrigi esse erro na resposta.# pragma: no branch
0.0 <= x < total
.Se você precisar fazer mais de uma escolha, divida-a em duas funções, uma para criar os pesos cumulativos e outra para dividir em um ponto aleatório.
fonte
O(n)
devido ao cálculo da distribuição cumulativa.random()
não pode retornar 1.0. De acordo com os documentos, ele retorna um resultado no intervalo semiaberto[0.0, 1.0)
, ou seja, pode retornar exatamente 0,0, mas não pode retornar exatamente 1,0. O maior valor que ele pode retornar é 0.99999999999999988897769753748434595763683319091796875 (que Python imprime como 0.9999999999999999 e é o maior flutuador de 64 bits menor que 1).Se você não se importa em usar numpy, pode usar numpy.random.choice .
Por exemplo:
Se você souber quantas seleções precisa fazer com antecedência, poderá fazê-lo sem um loop como este:
fonte
Bruto, mas pode ser suficiente:
Funciona?
Impressões:
Assume que todos os pesos são inteiros. Eles não precisam somar 100, apenas fiz isso para facilitar a interpretação dos resultados dos testes. (Se os pesos forem números de ponto flutuante, multiplique todos por 10 repetidamente até todos os pesos> = 1.)
fonte
[[]]*10
- todos os elementos no ponto lista exterior à mesma lista.int
você ainda está recebendo muitas referências ao mesmo objeto, fazendo algo como[id(x) for x in ([99**99] * 100)]
e observe queid
retorna o mesmo endereço de memória em todas as chamadas.Se você possui um dicionário ponderado em vez de uma lista, pode escrever este
Observe que
[k for k in items for dummy in range(items[k])]
produz esta lista['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']
fonte
No Python
v3.6
,random.choices
poderia ser usado para retornar umlist
dos elementos de tamanho especificado da população especificada com pesos opcionais.população :
list
contendo observações únicas. (Se vazio, aumentaIndexError
)pesos : pesos precisos relativos, mais precisamente necessários para fazer seleções.
cum_weights : pesos cumulativos necessários para fazer seleções.
k : tamanho (
len
) dalist
saída. (Padrãolen()=1
)Poucas advertências:
1) Utiliza amostragem ponderada com substituição, para que os itens sorteados sejam substituídos posteriormente. Os valores na sequência de pesos em si não importam, mas a razão relativa deles.
Diferente do
np.random.choice
que só pode assumir probabilidades como pesos e também que deve garantir a soma de probabilidades individuais até 1 critério, não existem tais regulamentos aqui. Desde que pertençam a tipos numéricos (int/float/fraction
exceto oDecimal
tipo), eles ainda serão executados.2) Se não forem especificados pesos nem cum_weights , as seleções serão feitas com igual probabilidade. Se uma sequência de pesos for fornecida, ela deverá ter o mesmo comprimento que o sequência população .
A especificação de pesos e cum_weights aumenta a
TypeError
.3) cum_weights normalmente são resultados de
itertools.accumulate
funções que são realmente úteis nessas situações.Portanto, fornecer
weights=[12, 12, 4]
oucum_weights=[12, 24, 28]
para o nosso caso artificial produz o mesmo resultado e o último parece ser mais rápido / eficiente.fonte
Aqui está a versão que está sendo incluída na biblioteca padrão do Python 3.6:
Fonte: https://hg.python.org/cpython/file/tip/Lib/random.py#l340
fonte
fonte
Provavelmente estou muito atrasado para contribuir com algo útil, mas aqui está um trecho simples, curto e muito eficiente:
Não há necessidade de classificar suas probabilidades ou criar um vetor com seu cmf, e ele termina assim que encontrar sua escolha. Memória: O (1), tempo: O (N), com tempo médio de execução ~ N / 2.
Se você tiver pesos, basta adicionar uma linha:
fonte
np.random.choice
,. Mas o mais interessante é que existe um modo de falha em que isso gera uma exceção. Fazerprobabilities = weights / sum(weights)
não garante queprobabilities
isso somará 1; por exemplo, seweights
for,[1,1,1,1,1,1,1]
entãoprobabilities
somará apenas 0.9999999999999998, menor que o maior valor de retorno possível derandom.random
(que é 0.9999999999999999). Entãochoice <= cmf
nunca será satisfeito.Se sua lista de opções ponderadas for relativamente estática e você desejar amostragem frequente, poderá executar uma etapa de pré-processamento de O (N) e, em seguida, fazer a seleção em O (1), usando as funções nesta resposta relacionada .
fonte
Eu olhei o outro thread apontado e surgiu com essa variação no meu estilo de codificação, isso retorna o índice de escolha para fins de cálculo, mas é simples retornar a string (alternativa de retorno comentada):
fonte
Depende de quantas vezes você deseja provar a distribuição.
Suponha que você queira provar a distribuição K vezes. Em seguida, a complexidade do tempo que utiliza
np.random.choice()
cada momento éO(K(n + log(n)))
quandon
é o número de itens na distribuição.No meu caso, eu precisava amostrar a mesma distribuição várias vezes da ordem de 10 ^ 3, em que n é da ordem de 10 ^ 6. Usei o código abaixo, que pré-calcula a distribuição cumulativa e a amostra
O(log(n))
. A complexidade geral do tempo éO(n+K*log(n))
.fonte
Se você possui o Python 3 e tem medo de instalar
numpy
ou gravar seus próprios loops, você pode:Porque você pode construir qualquer coisa com uma bolsa de adaptadores de encanamento! Embora ... Devo admitir que a resposta de Ned, embora um pouco mais longa, seja mais fácil de entender.
fonte
Uma solução geral:
fonte
Aqui está outra versão do weighted_choice que usa numpy. Passe o vetor de pesos e ele retornará uma matriz de 0 contendo um 1 indicando qual bin foi escolhida. O código padrão é apenas fazer um único sorteio, mas você pode passar o número de sorteios a serem feitos e as contagens por posição sorteada serão retornadas.
Se o vetor de pesos não somar 1, ele será normalizado.
fonte
Outra maneira de fazer isso, assumindo que temos pesos no mesmo índice que os elementos na matriz de elementos.
Agora, vamos supor que precisamos provar 3 itens em um teste. Você pode supor que há três bolas R, G, B presentes em grande quantidade na proporção de seus pesos dados pela matriz de pesos; o seguinte resultado pode ser possível:
você também pode pensar no número de itens a serem selecionados como número de testes binomiais / multinomiais em um conjunto. Portanto, o exemplo acima ainda pode funcionar como
fonte
Há uma palestra sobre Sebastien Thurn no curso gratuito Udacity AI for Robotics. Basicamente, ele faz uma matriz circular dos pesos indexados usando o operador mod
%
, define uma variável beta como 0, escolhe aleatoriamente um índice, para loops através de N onde N é o número de índices e no loop for incrementa primeiro beta pela fórmula:beta = beta + amostra uniforme de {0 ... 2 * Weight_max}
e depois aninhado no loop for, um loop while abaixo:
Em seguida, passe para o próximo índice para reamostrar com base nas probabilidades (ou probabilidade normalizada no caso apresentado no curso).
O link da palestra: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923
Estou logado no Udacity com a conta da minha escola. Se o link não funcionar, é na Lição 8, vídeo número 21 da Inteligência Artificial para Robótica, onde ele está dando palestras sobre filtros de partículas.
fonte
Uma maneira é aleatorizar o total de todos os pesos e, em seguida, usar os valores como pontos limite para cada var. Aqui está uma implementação bruta como um gerador.
fonte
Usando numpy
fonte
np.random.choice
, como mencionado na resposta aceita, que está aqui desde 2014. Qual é o sentido de criar o seu?Eu precisava fazer algo assim muito rápido, muito simples, desde a busca de idéias que finalmente construí este modelo. A idéia é receber os valores ponderados na forma de um json da API, que aqui é simulada pelo ditado.
Em seguida, traduza-o em uma lista na qual cada valor se repita proporcionalmente ao seu peso e use apenas random.choice para selecionar um valor da lista.
Eu tentei rodando com 10, 100 e 1000 iterações. A distribuição parece bastante sólida.
fonte
Eu não amei a sintaxe de nenhuma delas. Eu realmente queria apenas especificar quais eram os itens e qual era o peso de cada um. Sei que poderia ter usado,
random.choices
mas, em vez disso, escrevi rapidamente a aula abaixo.fonte
Forneça a random.choice () uma lista pré-ponderada:
Solução e teste:
Resultado:
fonte