Random.choice'nin ağırlıklı bir versiyonu


245

Random.choice (listedeki her öğenin seçilme olasılığı farklıdır) ağırlıklı bir sürümünü yazmak gerekiyordu. Ben geldi budur:

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

Bu işlev benim için aşırı karmaşık ve çirkin görünüyor. Buradaki herkesin iyileştirilmesi veya bunu yapmanın alternatif yolları hakkında bazı önerilerde bulunabileceğini umuyorum. Verimlilik benim için kod temizliği ve okunabilirliği kadar önemli değil.

Yanıtlar:


297

1.7.0 sürümünden bu yana, NumPy choiceolasılık dağılımlarını destekleyen bir işleve sahiptir.

from numpy.random import choice
draw = choice(list_of_candidates, number_of_items_to_pick,
              p=probability_distribution)

probability_distributionAynı sırada bir sıra olduğunu unutmayın list_of_candidates. Anahtar kelimeyi replace=False, çizilmiş öğelerin değiştirilmemesi için davranışı değiştirmek üzere de kullanabilirsiniz .


11
Testlerime göre, bu, random.choicesbireysel aramalardan daha yavaş bir büyüklük sırasıdır . Çok fazla rastgele sonuca ihtiyacınız varsa, bunları ayarlayarak hepsini bir kerede seçmek gerçekten önemlidir number_of_items_to_pick. Bunu yaparsanız, daha hızlı bir büyüklük sırasıdır.
jpmc26

2
Bu demetler vs ile çalışma değil ( "ValueError: 1 boyutlu bir olmalı") böylece durumda bir almaya numpy sorabilirsiniz indeksi yani listeye içine len(list_of_candidates)ve sonra dolist_of_candidates[draw]
xjcl

218

Python 3.6 beri bir yöntem yoktur choicesgelen randommodül.

Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import random

In [2]: random.choices(
...:     population=[['a','b'], ['b','a'], ['c','b']],
...:     weights=[0.2, 0.2, 0.6],
...:     k=10
...: )

Out[2]:
[['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b']]

Not random.choicesörnek olacak değiştirilmesi ile başı, dokümanlar :

Bir İade kdeğiştirme ile nüfus seçilen elemanların büyüklüğünde listesi.

Eğer aynı, yerine koymadan örnek gerekiyorsa @ ronan-Paixão en parlak cevabı devletler, kullanabileceğiniz numpy.choicekimin, replaceargüman kontrolleri, davranış.


4
Bu numpy.random.choice'den çok daha hızlı. 8.000 ağırlıklı madde listesinden 10.000 kez seçim yapan numpy.random.choice 0.3286 saniyeyi aldı ve random.choices 0.0416 saniyeyi 8 kat daha hızlı aldı.
Anton Kodları

@AntonCodes Bu örnek kirazla toplanır. numpy, sabit bir sabit zaman yüküne sahip olacak random.choices, bu yüzden elbette 8 öğeden oluşan minik bir listede daha yavaş ve eğer böyle bir listeden 10 bin kez seçerseniz, haklısınız. Ancak listenin daha büyük olduğu durumlarda (nasıl test ettiğinize bağlı olarak, 100-300 eleman arasındaki kırılma noktalarını görüyorum), oldukça geniş bir boşlukla np.random.choicedaha iyi performans göstermeye başlar random.choices. Örneğin, numpy çağrısı ile birlikte normalleştirme adımı da dahil olmak üzere, random.choices10k öğelerin bir listesi için neredeyse 4x hızlanma elde ediyorum .
ggorlen

Bu, @AntonCodes'un bildirdiği performans geliştirmesine dayanan yeni yanıt olmalıdır.
Wayne Workman

132
def weighted_choice(choices):
   total = sum(w for c, w in choices)
   r = random.uniform(0, total)
   upto = 0
   for c, w in choices:
      if upto + w >= r:
         return c
      upto += w
   assert False, "Shouldn't get here"

10
For döngüsü içindeki ifadeleri ters çevirerek bir işlemi bırakabilir ve zamandan tasarruf edebilirsiniz:upto +=w; if upto > r
knite

5
bir değişkeni her seferinde silerek ve sadece r ağırlığına göre azaltarak kaydedin. Karşılaştırma o zamanif r < 0
JnBrymn

@JnBrymn Kontrol etmeniz gerekiyor r <= 0. 1 öğeden oluşan bir giriş seti ve 1.0 rulo olarak düşünün. Bu durumda iddia başarısız olur. Cevaptaki hatayı düzelttim.
moooeeeep

1
@Sardathrion for döngüsünü kısmi olarak işaretlemek için bir pragma kullanabilirsiniz:# pragma: no branch
Ned Batchelder

1
@ mLstudent33 Udacity kullanmıyorum.
Anton Codes

70
  1. Ağırlıkları kümülatif bir dağılım olarak düzenleyin.
  2. Rasgele bir kayan nokta seçmek için random.random () kullanın 0.0 <= x < total.
  3. Http://docs.python.org/dev/library/bisect.html#other-examples adresindeki örnekte gösterildiği gibi bisect.bisect kullanarak dağıtımı arayın .
from random import random
from bisect import bisect

def weighted_choice(choices):
    values, weights = zip(*choices)
    total = 0
    cum_weights = []
    for w in weights:
        total += w
        cum_weights.append(total)
    x = random() * total
    i = bisect(cum_weights, x)
    return values[i]

>>> weighted_choice([("WHITE",90), ("RED",8), ("GREEN",2)])
'WHITE'

Birden fazla seçim yapmanız gerekiyorsa, bunu kümülatif ağırlıklar oluşturmak için diğeri rastgele bir noktaya bölmek için iki işleve ayırın.


5
Bu Ned'in cevabından daha etkilidir. Temel olarak, seçenekler arasında doğrusal (O (n)) bir arama yapmak yerine, ikili bir arama yapıyor (O (log n)). 1!
NHDaly

rasgele () 1.0'a dönerse tuple endeksi aralık
Jon Vaughan

10
O(n)Kümülatif dağılım hesaplaması nedeniyle bu durum hala devam etmektedir.
Lev Levitsky

6
Bu seçenek, aynı seçenek kümesi için weightted_choice'a birden fazla çağrı yapılması gerektiğinde daha iyidir. Bu durumda kümülatif toplamı bir kez oluşturabilir ve her çağrıda ikili bir arama yapabilirsiniz.
Amos

1
@JonVaughan random() edemez 1,0 döndürür. Docs Başına, bu yarı açık aralığında bir sonuç döndürür [0.0, 1.0)o yani, olabilir tam olarak 0.0 dönmek fakat edemez tam 1,0 dönün. Dönebileceği en büyük değer 0.99999999999999988897769753748434595763683319091796875'dir (Python 0.9999999999999999 olarak yazdırır ve 1'den küçük en büyük 64 bit kayan değerdir).
Mark Amery

21

Numpy kullanmanın bir sakıncası yoksa numpy.random.choice kullanabilirsiniz .

Örneğin:

import numpy

items  = [["item1", 0.2], ["item2", 0.3], ["item3", 0.45], ["item4", 0.05]
elems = [i[0] for i in items]
probs = [i[1] for i in items]

trials = 1000
results = [0] * len(items)
for i in range(trials):
    res = numpy.random.choice(items, p=probs)  #This is where the item is selected!
    results[items.index(res)] += 1
results = [r / float(trials) for r in results]
print "item\texpected\tactual"
for i in range(len(probs)):
    print "%s\t%0.4f\t%0.4f" % (items[i], probs[i], results[i])

Önceden kaç seçim yapmanız gerektiğini biliyorsanız, bunu böyle bir döngü olmadan yapabilirsiniz:

numpy.random.choice(items, trials, p=probs)

15

Ham, ancak yeterli olabilir:

import random
weighted_choice = lambda s : random.choice(sum(([v]*wt for v,wt in s),[]))

Çalışıyor mu?

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

# initialize tally dict
tally = dict.fromkeys(choices, 0)

# tally up 1000 weighted choices
for i in xrange(1000):
    tally[weighted_choice(choices)] += 1

print tally.items()

Baskılar:

[('WHITE', 904), ('GREEN', 22), ('RED', 74)]

Tüm ağırlıkların tamsayı olduğunu varsayar. 100'e kadar eklemek zorunda değiller, bunu test sonuçlarını daha kolay yorumlamak için yaptım. (Ağırlıklar kayan nokta sayılarıysa, tüm ağırlıklar> = 1 olana kadar hepsini tekrar tekrar 10 ile çarpın.)

weights = [.6, .2, .001, .199]
while any(w < 1.0 for w in weights):
    weights = [w*10 for w in weights]
weights = map(int, weights)

1
Güzel, yine de tüm ağırlıkların tamsayı olduğunu varsayabileceğimden emin değilim.
Colin

1
Görünüşe göre nesneleriniz bu örnekte çoğaltılacak. Bu verimsiz olurdu (ve ağırlıkları tamsayılara dönüştürme işlevi de öyle). Bununla birlikte, eğer tamsayı ağırlıkları küçükse, bu çözüm iyi bir tek astarlıdır.
wei2912

Temel öğeler çoğaltılır, ancak nesnelerin kendileri değil, yalnızca başvuruları çoğaltılır. (bu yüzden kullanarak bir liste listesi oluşturamazsınız [[]]*10- dış listedeki tüm öğeler aynı listeyi gösterir.
PaulMcG

@PaulMcG Hayır; referanslar dışında hiçbir şey kopyalanmayacaktır. Python'un tip sisteminin ilkel bir kavramı yoktur. Örneğin, bir ile bile , aynı çağrıya intbenzer bir şey yaparak çok sayıda referans aldığınızı doğrulayabilir [id(x) for x in ([99**99] * 100)]ve idher çağrıda aynı bellek adresini döndürdüğünü gözlemleyebilirsiniz .
Mark Amery

14

Liste yerine ağırlıklı bir sözlüğünüz varsa bunu yazabilirsiniz

items = { "a": 10, "b": 5, "c": 1 } 
random.choice([k for k in items for dummy in range(items[k])])

[k for k in items for dummy in range(items[k])]Bu listeyi oluşturan not['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']


10
Bu, küçük toplam nüfus değerleri için geçerlidir, ancak büyük veri kümeleri için geçerli değildir (örneğin, eyaletlere göre ABD nüfusu, içinde 300 milyon öğe içeren bir çalışma listesi oluşturacaktır).
Ryan

@Ryan Gerçekten. Ayrıca başka bir gerçekçi senaryo olan tamsayı olmayan ağırlıklar için de işe yaramaz (örneğin, ağırlıklarınızı seçim olasılıkları olarak ifade ettiyseniz).
Mark Amery

12

Python olarak v3.6, random.choicesbir dönüş için kullanılabilir lististeğe bağlı ağırlıkları ile verilen popülasyondan belirtilen boyutta elemanlarının.

random.choices(population, weights=None, *, cum_weights=None, k=1)

  • nüfus : listbenzersiz gözlemler içerir. (Boşsa yükseltir IndexError)

  • ağırlıklar : Seçim yapmak için daha hassas göreceli ağırlıklar gerekir.

  • cum_weights : seçim yapmak için gerekli toplam ağırlıklar.

  • k : çıktılanacak olanın boyutu ( len) list. (Varsayılan len()=1)


Birkaç Uyarı:

1) Çizilen ürünlerin daha sonra değiştirilmeleri için değiştirilmiş ağırlıklı örnekleme kullanır. Ağırlıklar dizisindeki değerler kendi içinde önemli değildir, ancak nispi oranları önemlidir.

np.random.choiceOlasılıkları sadece ağırlık olarak alabilen ve ayrıca 1 kritere kadar bireysel olasılıkların toplamını temin etmesi gerekenlerden farklı olarak, burada böyle bir düzenleme yoktur. Sayısal türlere (tür int/float/fractionhariç Decimal) ait oldukları sürece, bunlar yine de çalışır.

>>> import random
# weights being integers
>>> random.choices(["white", "green", "red"], [12, 12, 4], k=10)
['green', 'red', 'green', 'white', 'white', 'white', 'green', 'white', 'red', 'white']
# weights being floats
>>> random.choices(["white", "green", "red"], [.12, .12, .04], k=10)
['white', 'white', 'green', 'green', 'red', 'red', 'white', 'green', 'white', 'green']
# weights being fractions
>>> random.choices(["white", "green", "red"], [12/100, 12/100, 4/100], k=10)
['green', 'green', 'white', 'red', 'green', 'red', 'white', 'green', 'green', 'green']

2) Ne ağırlıklar ne de cum_weights belirtilmezse, seçimler eşit olasılıkla yapılır. Eğer bir ağırlık sekansı sağlanırsa, popülasyon sekansı ile aynı uzunlukta olmalıdır .

Her iki belirtme ağırlıkları ve cum_weights bir yükseltir TypeError.

>>> random.choices(["white", "green", "red"], k=10)
['white', 'white', 'green', 'red', 'red', 'red', 'white', 'white', 'white', 'green']

3) cum_weights genellikle itertools.accumulatebu gibi durumlarda gerçekten kullanışlı olan bir fonksiyonun sonucudur .

Bağlantılı belgelerden:

Dahili olarak, göreli ağırlıklar seçim yapmadan önce kümülatif ağırlığa dönüştürülür, böylece kümülatif ağırlıkların sağlanması işten tasarruf sağlar.

Yani, ya tedarik etmek weights=[12, 12, 4]ya da cum_weights=[12, 24, 28]bizim davamız için aynı sonucu üretir ve ikincisi daha hızlı / verimli görünür.


11

İşte Python 3.6 için standart kütüphanede bulunan sürüm:

import itertools as _itertools
import bisect as _bisect

class Random36(random.Random):
    "Show the code included in the Python 3.6 version of the Random class"

    def choices(self, population, weights=None, *, cum_weights=None, k=1):
        """Return a k sized list of population elements chosen with replacement.

        If the relative weights or cumulative weights are not specified,
        the selections are made with equal probability.

        """
        random = self.random
        if cum_weights is None:
            if weights is None:
                _int = int
                total = len(population)
                return [population[_int(random() * total)] for i in range(k)]
            cum_weights = list(_itertools.accumulate(weights))
        elif weights is not None:
            raise TypeError('Cannot specify both weights and cumulative weights')
        if len(cum_weights) != len(population):
            raise ValueError('The number of weights does not match the population')
        bisect = _bisect.bisect
        total = cum_weights[-1]
        return [population[bisect(cum_weights, random() * total)] for i in range(k)]

Kaynak: https://hg.python.org/cpython/file/tip/Lib/random.py#l340


2
import numpy as np
w=np.array([ 0.4,  0.8,  1.6,  0.8,  0.4])
np.random.choice(w, p=w/sum(w))

2

Muhtemelen faydalı herhangi bir şeye katkıda bulunmak için çok geç kaldım, ancak basit, kısa ve çok etkili bir snippet:

def choose_index(probabilies):
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

Olasılıklarınızı sıralamanıza veya cmf'nizle bir vektör oluşturmanıza gerek yoktur ve seçimini bulduğunda sona erer. Bellek: O (1), süre: O (N), ortalama çalışma süresi ~ N / 2 ile.

Ağırlıklarınız varsa, bir satır ekleyin:

def choose_index(weights):
    probabilities = weights / sum(weights)
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

1
Bu konuda bazı şeyler yanlış. Yüzeysel olarak, bazı yazılan değişken isimleri vardır ve bunu kullanmak için herhangi bir gerekçe yoktur np.random.choice. Ancak daha ilginç olarak, bunun bir istisna yarattığı bir hata modu var. Bunu yapmak 1'in toplamını probabilities = weights / sum(weights)garanti etmez probabilities; Örneğin, weightsbir [1,1,1,1,1,1,1]sonra probabilitiessadece mümkün olan en büyük dönüş değeri daha küçük, 0.9999999999999998 toplamı olacaktır random.random(0,9999999999999999 olan). O choice <= cmfzaman asla tatmin olmaz.
Mark Amery

2

Ağırlıklı seçenekler listeniz nispeten statikse ve sık örnekleme istiyorsanız, bir O (N) ön işleme adımı yapabilir ve ardından bu ilgili yanıttaki işlevleri kullanarak O (1) 'deki seçimi yapabilirsiniz .

# run only when `choices` changes.
preprocessed_data = prep(weight for _,weight in choices)

# O(1) selection
value = choices[sample(preprocessed_data)][0]

1

Ben sivri diğer iş parçacığı baktı ve benim kodlama tarzı bu varyasyon ile geldi, bu tallies amacıyla seçim indeksi döndürür, ancak dize (yorum dönüş alternatifi) dönmek basit:

import random
import bisect

try:
    range = xrange
except:
    pass

def weighted_choice(choices):
    total, cumulative = 0, []
    for c,w in choices:
        total += w
        cumulative.append((total, c))
    r = random.uniform(0, total)
    # return index
    return bisect.bisect(cumulative, (r,))
    # return item string
    #return choices[bisect.bisect(cumulative, (r,))][0]

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

tally = [0 for item in choices]

n = 100000
# tally up n weighted choices
for i in range(n):
    tally[weighted_choice(choices)] += 1

print([t/sum(tally)*100 for t in tally])

1

Dağılımı kaç kez örneklemek istediğinize bağlıdır.

K dağıtımını örneklemek istediğinizi varsayalım. Sonra kullanarak zaman karmaşıklığı np.random.choice()her zaman olduğu O(K(n + log(n)))zamann dağılımındaki öğelerin sayısıdır.

Benim durumumda, aynı dağılımı 10 ^ 3 derecesinin birden çok kez örneklemem gerekiyordu, burada n 10 ^ 6 mertebesinde. Kümülatif dağılımı önceden hesaplayan ve içinde örnekleri aşağıdaki kodu kullandım O(log(n)). Genel zaman karmaşıklığı O(n+K*log(n)).

import numpy as np

n,k = 10**6,10**3

# Create dummy distribution
a = np.array([i+1 for i in range(n)])
p = np.array([1.0/n]*n)

cfd = p.cumsum()
for _ in range(k):
    x = np.random.uniform()
    idx = cfd.searchsorted(x, side='right')
    sampled_element = a[idx]

1

Python 3'e sahipseniz ve numpykendi döngülerinizi kurmaktan veya yazmaktan korkuyorsanız , şunları yapabilirsiniz:

import itertools, bisect, random

def weighted_choice(choices):
   weights = list(zip(*choices))[1]
   return choices[bisect.bisect(list(itertools.accumulate(weights)),
                                random.uniform(0, sum(weights)))][0]

Eğer inşa Çünkü bir şey sıhhi tesisat adaptörleri bir çanta dışarı! Rağmen ... itiraf etmeliyim ki Ned'in cevabı biraz daha uzun olsa da anlaşılması daha kolay.


0

Genel bir çözüm:

import random
def weighted_choice(choices, weights):
    total = sum(weights)
    treshold = random.uniform(0, total)
    for k, weight in enumerate(weights):
        total -= weight
        if total < treshold:
            return choices[k]

0

Burada, numpy kullanan bir başka weightted_choice sürümü daha bulunmaktadır. Ağırlıklar vektörünü geçirin ve hangi kutunun seçildiğini gösteren 1 içeren bir 0 dizisi döndürür. Kod varsayılan olarak sadece tek bir çizim yapmayı gerektirir ancak yapılacak çekiliş sayısını girebilirsiniz ve çizilen kutu başına sayılar döndürülür.

Ağırlıklar vektörü 1'e eşit değilse, normalleştirilecek şekilde normalleştirilir.

import numpy as np

def weighted_choice(weights, n=1):
    if np.sum(weights)!=1:
        weights = weights/np.sum(weights)

    draws = np.random.random_sample(size=n)

    weights = np.cumsum(weights)
    weights = np.insert(weights,0,0.0)

    counts = np.histogram(draws, bins=weights)
    return(counts[0])

0

Bunu yapmanın başka bir yolu da, element dizisindeki elemanlarla aynı indekste ağırlıklarımız olduğunu varsayarsak.

import numpy as np
weights = [0.1, 0.3, 0.5] #weights for the item at index 0,1,2
# sum of weights should be <=1, you can also divide each weight by sum of all weights to standardise it to <=1 constraint.
trials = 1 #number of trials
num_item = 1 #number of items that can be picked in each trial
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# gives number of times an item was selected at a particular index
# this assumes selection with replacement
# one possible output
# selected_item_arr
# array([[0, 0, 1]])
# say if trials = 5, the the possible output could be 
# selected_item_arr
# array([[1, 0, 0],
#   [0, 0, 1],
#   [0, 0, 1],
#   [0, 1, 0],
#   [0, 0, 1]])

Şimdi varsayalım, 1 denemede 3 öğe örneklemeliyiz. Ağırlık dizisine göre verilen ağırlıklarına göre büyük miktarda üç adet R, G, B topu olduğunu varsayabilirsiniz, aşağıdakiler mümkün olabilir:

num_item = 3
trials = 1
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# selected_item_arr can give output like :
# array([[1, 0, 2]])

ayrıca seçilecek öğe sayısını bir kümedeki binom / çok terimli deneme sayısı olarak düşünebilirsiniz. Yani, yukarıdaki örnek hala

num_binomial_trial = 5
weights = [0.1,0.9] #say an unfair coin weights for H/T
num_experiment_set = 1
selected_item_arr = np.random.multinomial(num_binomial_trial, weights, num_experiment_set)
# possible output
# selected_item_arr
# array([[1, 4]])
# i.e H came 1 time and T came 4 times in 5 binomial trials. And one set contains 5 binomial trails.

0

Bu konuda Sebastien Thurn'un Robotbilim için ücretsiz Udacity AI dersinde ders var. Temelde mod operatörünü kullanarak indeksli ağırlıkların dairesel bir dizisini yapar% , değişken bir beta değerini 0'a ayarlar, rastgele bir dizin seçer, N'den döngüler için, burada N, endeks sayısıdır ve for döngüsünde ilk olarak beta'yı formüle göre artırır:

beta = {0 ... 2 * Weight_max} 'tan beta + tek tip örnek

ve sonra for döngüsüne, her biri için bir while döngüsüne yerleştirilir:

while w[index] < beta:
    beta = beta - w[index]
    index = index + 1

select p[index]

Daha sonra olasılıklara (veya derste sunulan durumda normalleştirilmiş olasılık) dayalı olarak yeniden örneklemek için bir sonraki dizine geçin.

Ders bağlantısı: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923

Okul hesabımla Udacity'ye giriş yaptım, bu yüzden bağlantı işe yaramazsa, Ders 8, parçacık filtreleri üzerine ders verdiği Robotik Yapay Zeka'nın video numarası 21'dir.


-1

Bunun bir yolu, tüm ağırlıkların toplamını randomize etmek ve daha sonra değerleri her bir var için sınır noktaları olarak kullanmaktır. İşte bir jeneratör olarak kaba bir uygulama.

def rand_weighted(weights):
    """
    Generator which uses the weights to generate a
    weighted random values
    """
    sum_weights = sum(weights.values())
    cum_weights = {}
    current_weight = 0
    for key, value in sorted(weights.iteritems()):
        current_weight += value
        cum_weights[key] = current_weight
    while True:
        sel = int(random.uniform(0, 1) * sum_weights)
        for key, value in sorted(cum_weights.iteritems()):
            if sel < value:
                break
        yield key

-1

Numpy kullanma

def choice(items, weights):
    return items[np.argmin((np.cumsum(weights) / sum(weights)) < np.random.rand())]

NumPy np.random.choice, 2014'ten beri burada bulunan kabul edilen cevapta belirtildiği gibi zaten var .
Mark Amery

-1

Sonunda bu şablonu inşa ettiğim fikirleri aramaktan gerçekten hızlı gerçekten basit bir şey yapmam gerekiyordu. Fikir, aptaldan bir json formundaki ağırlıklı değerleri almak, burada dikte tarafından simüle edilir.

Ardından, her bir değerin ağırlığına orantılı olarak tekrarlandığı bir listeye çevirin ve listeden bir değer seçmek için random.choice kullanın.

10, 100 ve 1000 yineleme ile çalışmayı denedim. Dağıtım oldukça sağlam görünüyor.

def weighted_choice(weighted_dict):
    """Input example: dict(apples=60, oranges=30, pineapples=10)"""
    weight_list = []
    for key in weighted_dict.keys():
        weight_list += [key] * weighted_dict[key]
    return random.choice(weight_list)

-1

Bunların hiçbirinin sözdizimini sevmedim. Gerçekten sadece öğelerin ne olduğunu ve her birinin ağırlığının ne olduğunu belirtmek istedim. Kullanabileceğimin farkındayım random.choicesama bunun yerine hızlı bir şekilde aşağıdaki sınıfı yazdım.

import random, string
from numpy import cumsum

class randomChoiceWithProportions:
    '''
    Accepts a dictionary of choices as keys and weights as values. Example if you want a unfair dice:


    choiceWeightDic = {"1":0.16666666666666666, "2": 0.16666666666666666, "3": 0.16666666666666666
    , "4": 0.16666666666666666, "5": .06666666666666666, "6": 0.26666666666666666}
    dice = randomChoiceWithProportions(choiceWeightDic)

    samples = []
    for i in range(100000):
        samples.append(dice.sample())

    # Should be close to .26666
    samples.count("6")/len(samples)

    # Should be close to .16666
    samples.count("1")/len(samples)
    '''
    def __init__(self, choiceWeightDic):
        self.choiceWeightDic = choiceWeightDic
        weightSum = sum(self.choiceWeightDic.values())
        assert weightSum == 1, 'Weights sum to ' + str(weightSum) + ', not 1.'
        self.valWeightDict = self._compute_valWeights()

    def _compute_valWeights(self):
        valWeights = list(cumsum(list(self.choiceWeightDic.values())))
        valWeightDict = dict(zip(list(self.choiceWeightDic.keys()), valWeights))
        return valWeightDict

    def sample(self):
        num = random.uniform(0,1)
        for key, val in self.valWeightDict.items():
            if val >= num:
                return key

-1

Önceden ağırlıklı bir liste ile random.choice () öğesini sağlayın:

Çözüm ve Test:

import random

options = ['a', 'b', 'c', 'd']
weights = [1, 2, 5, 2]

weighted_options = [[opt]*wgt for opt, wgt in zip(options, weights)]
weighted_options = [opt for sublist in weighted_options for opt in sublist]
print(weighted_options)

# test

counts = {c: 0 for c in options}
for x in range(10000):
    counts[random.choice(weighted_options)] += 1

for opt, wgt in zip(options, weights):
    wgt_r = counts[opt] / 10000 * sum(weights)
    print(opt, counts[opt], wgt, wgt_r)

Çıktı:

['a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'd', 'd']
a 1025 1 1.025
b 1948 2 1.948
c 5019 5 5.019
d 2008 2 2.008
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.