Versi acak dari random.choice


245

Saya perlu menulis versi acak dari random.choice (setiap elemen dalam daftar memiliki probabilitas berbeda untuk dipilih). Inilah yang saya pikirkan:

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

Fungsi ini tampaknya terlalu rumit bagi saya, dan jelek. Saya berharap semua orang di sini dapat menawarkan beberapa saran untuk memperbaikinya atau cara lain untuk melakukan ini. Efisiensi bagi saya tidak sepenting kebersihan kode dan keterbacaan.

Jawaban:


297

Sejak versi 1.7.0, NumPy memiliki choicefungsi yang mendukung distribusi probabilitas.

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

Perhatikan bahwa probability_distributionadalah urutan dalam urutan yang sama list_of_candidates. Anda juga dapat menggunakan kata kunci replace=Falseuntuk mengubah perilaku sehingga item yang ditarik tidak diganti.


11
Dengan pengujian saya, ini adalah urutan besarnya lebih lambat daripada random.choicesuntuk panggilan individu. Jika Anda membutuhkan banyak hasil acak, sangat penting untuk memilih semuanya sekaligus dengan menyesuaikan number_of_items_to_pick. Jika Anda melakukannya, ini adalah urutan besarnya lebih cepat.
jpmc26

2
Ini tidak bekerja dengan tuple dll ("ValueError: a harus 1 dimensi"), jadi dalam hal ini orang dapat meminta numpy untuk memilih indeks ke dalam daftar, yaitu len(list_of_candidates), dan kemudian lakukanlist_of_candidates[draw]
xjcl

218

Sejak Python 3.6 ada metode choicesdari randommodul.

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']]

Perhatikan bahwa random.choicessampel akan diganti dengan per dokumen :

Kembalikan kdaftar ukuran elemen yang dipilih dari populasi dengan penggantian.

Jika Anda perlu mengambil sampel tanpa penggantian, maka sebagai status jawaban brilian @ ronan-paixão , Anda dapat menggunakan numpy.choice, yang replaceargumennya mengontrol perilaku tersebut.


4
Ini jauh lebih cepat daripada numpy.random.choice. Memilih dari daftar 8 item berbobot 10.000 kali, numpy.random.choice mengambil 0,3286 detik sedangkan secara acak. Pilihan mengambil 0,0416 detik, sekitar 8x lebih cepat.
Anton Codes

@AntonCodes Contoh ini dipilih dengan ceri. numpy akan memiliki overhead konstan waktu yang random.choicestidak, jadi tentu saja itu lebih lambat pada daftar item 8 kecil, dan jika Anda memilih 10k kali dari daftar seperti itu, Anda benar. Tetapi untuk kasus-kasus ketika daftar lebih besar (tergantung pada bagaimana Anda menguji, saya melihat break point antara 100-300 elemen), np.random.choicemulai mengungguli random.choicesoleh celah yang cukup lebar. Sebagai contoh, termasuk langkah normalisasi bersama dengan panggilan numpy, saya mendapatkan speedup hampir 4x lebih random.choicesuntuk daftar elemen 10k.
ggorlen

Ini harus menjadi jawaban baru berdasarkan peningkatan kinerja yang dilaporkan oleh @AntonCodes.
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
Anda dapat menghentikan operasi dan menghemat waktu dengan membalik pernyataan di dalam for for loop:upto +=w; if upto > r
knite

5
simpan variabel dengan menghapus upto dan hanya mengurangi r dengan bobot setiap kali. Perbandingannya adalahif r < 0
JnBrymn

@ JnBrymn Anda perlu memeriksa r <= 0. Pertimbangkan satu set input 1 item, dan gulungan 1,0. Pernyataan itu akan gagal. Saya memperbaiki kesalahan itu dalam jawaban.
moooeeeep

1
@Sathathrion Anda dapat menggunakan pragma untuk menandai loop for sebagai parsial:# pragma: no branch
Ned Batchelder

1
@ mLstudent33 Saya tidak menggunakan Udacity.
Anton Codes

70
  1. Atur bobot menjadi distribusi kumulatif.
  2. Gunakan random.random () untuk memilih pelampung acak 0.0 <= x < total.
  3. Cari distribusi menggunakan bisect.bisect seperti yang ditunjukkan pada contoh di http://docs.python.org/dev/library/bisect.html#other-examples .
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'

Jika Anda perlu membuat lebih dari satu pilihan, bagi ini menjadi dua fungsi, satu untuk membangun bobot kumulatif dan lainnya untuk membagi dua ke titik acak.


5
Ini lebih efisien daripada jawaban Ned. Pada dasarnya, alih-alih melakukan pencarian linear (O (n)) melalui pilihan, dia melakukan pencarian biner (O (log n)). +1!
NHDaly

indeks tuple di luar kisaran jika acak () terjadi untuk mengembalikan 1,0
Jon Vaughan

10
Ini masih berjalan O(n)karena perhitungan distribusi kumulatif.
Lev Levitsky

6
Solusi ini lebih baik dalam kasus di mana beberapa panggilan ke weighted_choice diperlukan untuk set pilihan yang sama. Dalam hal ini Anda dapat membuat jumlah kumulatif sekali dan melakukan pencarian biner pada setiap panggilan.
Amos

1
@JonVaughan random() tidak dapat mengembalikan 1.0. Per dokumen, ia mengembalikan hasil dalam interval setengah-terbuka [0.0, 1.0), yang mengatakan bahwa ia dapat mengembalikan tepat 0,0, tetapi tidak dapat mengembalikan tepat 1,0. Nilai terbesar yang dapat dikembalikan adalah 0,99999999999999988897769753748434595763683319091796875 (yang dicetak Python sebagai 0,999999999999999999, dan merupakan float 64-bit terbesar kurang dari 1).
Mark Amery

21

Jika Anda tidak keberatan menggunakan numpy, Anda dapat menggunakan numpy.random.choice .

Sebagai contoh:

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])

Jika Anda tahu berapa banyak pilihan yang harus Anda buat sebelumnya, Anda bisa melakukannya tanpa loop seperti ini:

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

15

Mentah, tetapi mungkin cukup:

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

Apakah itu bekerja?

# 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()

Cetakan:

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

Asumsikan bahwa semua bobot adalah bilangan bulat. Mereka tidak perlu menambahkan hingga 100, saya hanya melakukan itu untuk membuat hasil tes lebih mudah diinterpretasikan. (Jika bobot adalah angka floating point, kalikan semuanya dengan 10 berulang hingga semua bobot> = 1.)

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
Bagus, saya tidak yakin saya bisa menganggap semua bobot adalah bilangan bulat.
Colin

1
Sepertinya objek Anda akan diduplikasi dalam contoh ini. Itu tidak efisien (dan begitu pula fungsi untuk mengubah bobot menjadi bilangan bulat). Namun demikian, solusi ini adalah satu-liner yang baik jika bobot bilangan bulat kecil.
wei2912

Primitif akan digandakan, tetapi objek hanya akan memiliki referensi yang digandakan, bukan objek itu sendiri. (inilah sebabnya Anda tidak dapat membuat daftar daftar menggunakan [[]]*10- semua elemen di daftar luar menunjuk ke daftar yang sama.
PaulMcG

@PaulMcG No; tidak ada tapi referensi yang akan digandakan. Sistem tipe Python tidak memiliki konsep primitif. Anda dapat mengonfirmasi bahwa bahkan dengan misalnya intAnda masih mendapatkan banyak referensi ke objek yang sama dengan melakukan sesuatu seperti [id(x) for x in ([99**99] * 100)]dan mengamati yang idmengembalikan alamat memori yang sama pada setiap panggilan.
Mark Amery

14

Jika Anda memiliki kamus berbobot alih-alih daftar, Anda dapat menulis ini

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

Catatan yang [k for k in items for dummy in range(items[k])]menghasilkan daftar ini['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']


10
Ini berfungsi untuk nilai total populasi kecil, tetapi tidak untuk dataset besar (mis. Populasi AS oleh negara pada akhirnya akan membuat daftar kerja dengan 300 juta item di dalamnya).
Ryan

@Ryan Memang. Ini juga tidak berfungsi untuk bobot non-integer, yang merupakan skenario realistis lainnya (misalnya jika Anda memiliki bobot yang dinyatakan sebagai probabilitas pemilihan).
Mark Amery

12

Pada Python v3.6, random.choicesdapat digunakan untuk mengembalikan listelemen ukuran tertentu dari populasi tertentu dengan bobot opsional.

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

  • populasi : listberisi pengamatan unik. (Jika kosong, naikkan IndexError)

  • bobot : Lebih tepatnya bobot relatif yang dibutuhkan untuk membuat pilihan.

  • cum_weights : bobot kumulatif diperlukan untuk membuat pilihan.

  • k : ukuran ( len) dari yang listakan dikeluarkan. (Default len()=1)


Beberapa Peringatan:

1) Itu menggunakan sampling tertimbang dengan penggantian sehingga barang yang ditarik akan diganti nanti. Nilai-nilai dalam urutan bobot itu sendiri tidak penting, tetapi rasio relatifnya tidak.

Tidak seperti np.random.choiceyang hanya dapat mengambil probabilitas sebagai bobot dan juga yang harus memastikan penjumlahan probabilitas individu hingga 1 kriteria, tidak ada peraturan seperti itu di sini. Selama mereka termasuk tipe numerik ( int/float/fractionkecuali Decimaltipe), ini akan tetap bekerja.

>>> 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) Jika bobot atau cum_weights tidak ditentukan, pemilihan dilakukan dengan probabilitas yang sama. Jika urutan bobot disediakan, panjangnya harus sama dengan urutan populasi .

Menentukan bobot dan cum_weights memunculkan a TypeError.

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

3) cum_weights biasanya merupakan hasil dari itertools.accumulatefungsi yang sangat berguna dalam situasi seperti itu.

Dari dokumentasi yang ditautkan:

Secara internal, bobot relatif dikonversi menjadi bobot kumulatif sebelum membuat pilihan, sehingga memasok bobot kumulatif akan menghemat pekerjaan.

Jadi, baik memasok weights=[12, 12, 4]atau cum_weights=[12, 24, 28]untuk kasus kami yang dibuat menghasilkan hasil yang sama dan yang terakhir tampaknya lebih cepat / efisien.


11

Berikut adalah versi yang disertakan dalam pustaka standar untuk Python 3.6:

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)]

Sumber: 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

Saya mungkin sudah terlambat untuk menyumbangkan sesuatu yang bermanfaat, tetapi di sini cuplikan sederhana, pendek, dan sangat efisien:

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]

Tidak perlu mengurutkan probabilitas Anda atau membuat vektor dengan cmf Anda, dan itu berakhir setelah menemukan pilihannya. Memori: O (1), waktu: O (N), dengan rata-rata waktu berjalan ~ N / 2.

Jika Anda memiliki bobot, cukup tambahkan satu baris:

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
Beberapa hal salah dengan ini. Secara dangkal, ada beberapa nama variabel yang salah ketik dan tidak ada alasan yang diberikan untuk menggunakan ini, katakanlah np.random.choice,. Tapi yang lebih menarik, ada mode kegagalan di mana ini menimbulkan pengecualian. Melakukan probabilities = weights / sum(weights)tidak menjamin bahwa probabilitiesakan berjumlah 1; misalnya, jika weightsini [1,1,1,1,1,1,1]kemudian probabilitieshanya akan berjumlah 0,9999999999999998, lebih kecil dari nilai pengembalian sebesar mungkin random.random(yaitu 0,999999999999999999). Maka choice <= cmftidak pernah puas.
Mark Amery

2

Jika daftar pilihan tertimbang Anda relatif statis, dan Anda ingin sering mengambil sampel, Anda dapat melakukan satu langkah preprocessing O (N), dan kemudian melakukan seleksi dalam O (1), menggunakan fungsi-fungsi dalam jawaban terkait ini .

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

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

1

Saya melihat utas lainnya yang runcing dan menghasilkan variasi dalam gaya pengkodean saya, ini mengembalikan indeks pilihan untuk tujuan penghitungan, tetapi mudah untuk mengembalikan string (komentar pengembalian alternatif):

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

Itu tergantung pada berapa kali Anda ingin sampel distribusi.

Misalkan Anda ingin mencicipi distribusi K kali. Kemudian, kompleksitas waktu yang digunakan np.random.choice()setiap waktu adalah O(K(n + log(n)))kapan njumlah item dalam distribusi.

Dalam kasus saya, saya perlu sampel distribusi yang sama beberapa kali dari urutan 10 ^ 3 di mana n adalah urutan 10 ^ 6. Saya menggunakan kode di bawah ini, yang mengkompilasi distribusi kumulatif dan sampel dalam O(log(n)). Kompleksitas waktu keseluruhan adalah 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

Jika Anda memiliki Python 3, dan takut menginstal numpyatau menulis loop Anda sendiri, Anda dapat melakukannya:

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]

Karena Anda dapat membangun apa pun dari sekantong adaptor pipa ledeng! Meskipun ... aku harus mengakui bahwa jawaban Ned, meski sedikit lebih lama, lebih mudah dimengerti.


0

Solusi umum:

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

Ini adalah versi lain dari weighted_choice yang menggunakan numpy. Lulus dalam vektor bobot dan akan mengembalikan array 0 yang berisi 1 yang menunjukkan bin mana yang dipilih. Kode default untuk hanya membuat satu pengundian tetapi Anda dapat meneruskan dalam jumlah pengundian yang akan dibuat dan jumlah per bin yang ditarik akan dikembalikan.

Jika vektor bobot tidak menjumlahkan ke 1, vektor akan dinormalisasi sehingga tidak.

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

Cara lain untuk melakukan ini, dengan asumsi kita memiliki bobot pada indeks yang sama dengan elemen dalam array elemen.

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]])

Sekarang mari kita asumsikan, kita harus mencicipi 3 item dalam 1 percobaan. Anda dapat mengasumsikan bahwa ada tiga bola R, G, B yang hadir dalam jumlah besar dalam perbandingan bobotnya yang diberikan oleh susunan bobot, berikut ini adalah hasil yang mungkin:

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]])

Anda juga bisa memikirkan jumlah item yang akan dipilih sebagai jumlah uji binomial / multinomial dalam satu set. Jadi, contoh di atas masih bisa berfungsi sebagai

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

Ada kuliah tentang hal ini oleh Sebastien Thurn dalam kursus Udacity gratis AI untuk Robotika. Pada dasarnya ia membuat array melingkar dari bobot yang diindeks menggunakan operator mod %, menetapkan variabel beta ke 0, secara acak memilih indeks, untuk loop melalui N di mana N adalah jumlah indeks dan dalam loop untuk kenaikan pertama beta dengan rumus:

beta = beta + sampel seragam dari {0 ... 2 * Weight_max}

dan kemudian bersarang di dalam for loop, loop sementara per di bawah ini:

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

select p[index]

Kemudian ke indeks berikutnya untuk sampel berdasarkan probabilitas (atau probabilitas normalisasi dalam kasus yang disajikan dalam kursus).

Tautan kuliah: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923

Saya masuk ke Udacity dengan akun sekolah saya jadi jika tautannya tidak berfungsi, itu adalah Pelajaran 8, video nomor 21 dari Kecerdasan Buatan untuk Robotika di mana dia memberi kuliah tentang filter partikel.


-1

Salah satu caranya adalah dengan mengacak total semua bobot dan kemudian menggunakan nilai-nilai sebagai titik batas untuk setiap var. Berikut ini adalah implementasi kasar sebagai generator.

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

Menggunakan numpy

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

NumPy sudah memiliki np.random.choice, sebagaimana disebutkan dalam jawaban yang diterima yang sudah ada di sini sejak 2014. Apa gunanya bergulir sendiri?
Mark Amery

-1

Saya perlu melakukan sesuatu seperti ini sangat cepat sangat sederhana, dari mencari ide saya akhirnya membuat template ini. Idenya adalah menerima nilai-nilai tertimbang dalam bentuk json dari api, yang di sini disimulasikan oleh dikt.

Kemudian terjemahkan ke dalam daftar di mana setiap nilai berulang secara proporsional dengan bobotnya, dan gunakan saja random.choice untuk memilih nilai dari daftar.

Saya mencoba menjalankannya dengan 10, 100 dan 1000 iterasi. Distribusi tampaknya cukup solid.

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

Saya tidak suka sintaksis dari semua itu. Saya benar-benar ingin menentukan item apa saja dan beratnya masing-masing. Saya menyadari bahwa saya dapat menggunakan random.choicestetapi sebaliknya saya dengan cepat menulis kelas di bawah ini.

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

Berikan random.choice () dengan daftar pra-tertimbang:

Solusi & Tes:

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)

Keluaran:

['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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.