[英]How to select an item from a list with known percentages in Python
我希望從列表中選擇一個隨機單詞,其中每個單詞的概率是已知的,例如:
概率果實
橙 0.10 蘋果 0.05 芒果 0.15 等
實現這一點的最佳方式是什么? 我要從中獲取的實際列表最多有 100 個項目,並且百分比並不全部達到 100%,因為它們確實不足,以說明發生幾率非常低的項目。 理想情況下,我想從 CSV 中獲取它,這是我存儲這些數據的地方。 這不是一項時間緊迫的任務。
感謝您提供有關如何最好地進行的任何建議。
如果您為每個項目分配一個與其概率成正比的數字范圍,在零和范圍總和之間選擇一個隨機數並找到與它匹配的項目,則您可以選擇具有加權概率的項目。 下面的類正是這樣做的:
from random import random
class WeightedChoice(object):
def __init__(self, weights):
"""Pick items with weighted probabilities.
weights
a sequence of tuples of item and it's weight.
"""
self._total_weight = 0.
self._item_levels = []
for item, weight in weights:
self._total_weight += weight
self._item_levels.append((self._total_weight, item))
def pick(self):
pick = self._total_weight * random()
for level, item in self._item_levels:
if level >= pick:
return item
然后,您可以使用csv
模塊加載 CSV 文件並將其提供給WeightedChoice
類:
import csv
weighed_items = [(item,float(weight)) for item,weight in csv.reader(open('file.csv'))]
picker = WeightedChoice(weighed_items)
print(picker.pick())
您想要的是從多項分布中提取。 假設您有兩個項目和概率列表,並且概率總和為 1(如果不是,只需添加一些默認值來覆蓋額外的值):
def choose(items,chances):
import random
p = chances[0]
x = random.random()
i = 0
while x > p :
i = i + 1
p = p + chances[i]
return items[i]
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]
x = 0.0
lst2 = []
for fruit, chance in lst:
tup = (x, fruit)
lst2.append(tup)
x += chance
tup = (x, None)
lst2.append(tup)
import random
def pick_one(lst2):
if lst2[0][1] is None:
raise ValueError, "no valid values to choose"
while True:
r = random.random()
for x, fruit in reversed(lst2):
if x <= r:
if fruit is None:
break # try again with a different random value
else:
return fruit
pick_one(lst2)
這將構建一個新列表,其中升序值表示選擇水果的值范圍; 然后 pick_one() 沿着列表向后走,尋找 <= 當前隨機值的值。 我們在列表的末尾放置了一個“哨兵”值; 如果值未達到 1.0,則有可能出現不應該匹配任何內容的隨機值,它將匹配標記值然后被拒絕。 random.random() 返回 [0.0, 1.0) 范圍內的隨機值,因此最終肯定會匹配列表中的某些內容。
這里的好處是,您應該能夠有一個匹配機會為 0.000001 的值,並且它實際上應該與該頻率匹配; 在其他解決方案中,您制作一個包含重復項的列表並僅使用 random.choice() 來選擇一個列表,則需要一個包含一百萬項的列表來處理這種情況。
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]
x = 0.0
lst2 = []
for fruit, chance in lst:
low = x
high = x + chance
tup = (low, high, fruit)
lst2.append(tup)
x += chance
if x > 1.0:
raise ValueError, "chances add up to more than 100%"
low = x
high = 1.0
tup = (low, high, None)
lst2.append(tup)
import random
def pick_one(lst2):
if lst2[0][2] is None:
raise ValueError, "no valid values to choose"
while True:
r = random.random()
for low, high, fruit in lst2:
if low <= r < high:
if fruit is None:
break # try again with a different random value
else:
return fruit
pick_one(lst2)
# test it 10,000 times
d = {}
for i in xrange(10000):
x = pick_one(lst2)
if x in d:
d[x] += 1
else:
d[x] = 1
我覺得這更清楚一些。 我們只是保留范圍,而不是將范圍表示為升序值的棘手方法。 因為我們正在測試范圍,所以我們可以簡單地向前遍歷 lst2 值; 無需使用reversed()
。
from numpy.random import multinomial
import numpy as np
def pickone(dist):
return np.where(multinomial(1, dist) == 1)[0][0]
if __name__ == '__main__':
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.70) ]
dist = [p[1] for p in lst]
N = 10000
draws = np.array([pickone(dist) for i in range(N)], dtype=int)
hist = np.histogram(draws, bins=[i for i in range(len(dist)+1)])[0]
for i in range(len(lst)):
print(f'{lst[i]} {hist[i]/N}')
一種解決方案是將概率歸一化為整數,然后對每個值重復每個元素一次(例如,包含 2 個橙子、1 個蘋果、3 個芒果的列表)。 這非常容易做到( from random import choice
)。 如果這不切實際,請嘗試此處的代碼。
import random
d= {'orange': 0.10, 'mango': 0.15, 'apple': 0.05}
weightedArray = []
for k in d:
weightedArray+=[k]*int(d[k]*100)
random.choice(weightedArray)
編輯
這基本上就是布賴恩上面所說的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.