簡體   English   中英

如何從 Python 中已知百分比的列表中選擇項目

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM