Tạo số ngẫu nhiên với phân phối (số) nhất định


132

Tôi có một tệp với một số xác suất cho các giá trị khác nhau, ví dụ:

1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2

Tôi muốn tạo số ngẫu nhiên bằng cách sử dụng phân phối này. Có một mô-đun hiện có xử lý này tồn tại? Việc tự viết mã khá đơn giản (xây dựng hàm mật độ tích lũy, tạo giá trị ngẫu nhiên [0,1] và chọn giá trị tương ứng) nhưng có vẻ như đây là một vấn đề phổ biến và có lẽ ai đó đã tạo ra một hàm / mô-đun cho nó

Tôi cần điều này bởi vì tôi muốn tạo một danh sách các ngày sinh nhật (không tuân theo bất kỳ phân phối nào trong randommô-đun chuẩn ).


2
Khác hơn random.choice()? Bạn xây dựng danh sách tổng thể với số lần xuất hiện thích hợp và chọn một lần xuất hiện. Đây là một câu hỏi trùng lặp, tất nhiên.
S.Lott

1
có thể trùng lặp với lựa chọn có trọng số ngẫu nhiên
S.Lott

2
@ S.Lott không phải là rất nhiều bộ nhớ cho sự khác biệt lớn trong phân phối?
Lucas Moeskops

2
@ S.Lott: Phương pháp lựa chọn của bạn có thể sẽ tốt cho số lần xuất hiện nhỏ nhưng tôi muốn tránh tạo danh sách lớn khi không cần thiết.
pafcu

5
@ S.Lott: OK, khoảng 10000 * 365 = 3650000 = 3,6 triệu phần tử. Tôi không chắc chắn về việc sử dụng bộ nhớ trong Python, nhưng ít nhất là 3,6M * 4B = 14,4 MB. Không phải là một số tiền lớn, nhưng không phải là thứ bạn nên bỏ qua khi có một phương pháp đơn giản không kém mà không cần thêm bộ nhớ.
pafcu

Câu trả lời:


118

scipy.stats.rv_discretecó thể là những gì bạn muốn. Bạn có thể cung cấp xác suất của bạn thông qua valuestham số. Sau đó, bạn có thể sử dụng rvs()phương thức của đối tượng phân phối để tạo các số ngẫu nhiên.

Như được chỉ ra bởi Eugene Pakhomov trong các bình luận, bạn cũng có thể truyền ptham số từ khóa cho numpy.random.choice(), vd

numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])

Nếu bạn đang sử dụng Python 3.6 trở lên, bạn có thể sử dụng random.choices()từ thư viện chuẩn - xem câu trả lời của Mark Dickinson .


9
Trên máy của tôi numpy.random.choice()nhanh hơn gần 20 lần.
Eugene Pakhomov

9
nó thực hiện chính xác cùng một câu hỏi với câu hỏi ban đầu. Ví dụ:numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
Eugene Pakhomov

1
@EugenePakhomov Thật tuyệt, tôi không biết điều đó. Tôi có thể thấy có một câu trả lời đề cập đến vấn đề này hơn nữa, nhưng nó không chứa bất kỳ mã ví dụ nào và không có nhiều upvote. Tôi sẽ thêm một nhận xét cho câu trả lời này để nhìn rõ hơn.
Sven Marnach

2
Đáng ngạc nhiên, rv_discittle.rvs () hoạt động trong thời gian và bộ nhớ O (len (p) *)! Trong khi sự lựa chọn () dường như chạy trong thời gian tối ưu O (len (p) + log (len (p)) *).
alyaxey

3
Nếu bạn đang sử dụng Python 3.6 hoặc mới hơn, có một câu trả lời khác không yêu cầu bất kỳ gói addon nào.
Đánh dấu tiền chuộc

113

Kể từ Python 3.6, có một giải pháp cho điều này trong thư viện chuẩn của Python, cụ thể là random.choices.

Sử dụng ví dụ: hãy thiết lập một dân số và trọng số phù hợp với những người trong câu hỏi của OP:

>>> from random import choices
>>> population = [1, 2, 3, 4, 5, 6]
>>> weights = [0.1, 0.05, 0.05, 0.2, 0.4, 0.2]

Bây giờ choices(population, weights)tạo một mẫu duy nhất:

>>> choices(population, weights)
4

Đối số chỉ từ khóa tùy chọn kcho phép một yêu cầu nhiều hơn một mẫu cùng một lúc. Điều này rất có giá trị vì có một số công việc chuẩn bị random.choicesphải thực hiện mỗi khi được gọi, trước khi tạo bất kỳ mẫu nào; bằng cách tạo ra nhiều mẫu cùng một lúc, chúng ta chỉ phải thực hiện công việc chuẩn bị đó một lần. Ở đây chúng tôi tạo ra một triệu mẫu và sử dụng collections.Counterđể kiểm tra xem phân phối mà chúng tôi nhận được có phù hợp với các trọng số chúng tôi đưa ra không.

>>> million_samples = choices(population, weights, k=10**6)
>>> from collections import Counter
>>> Counter(million_samples)
Counter({5: 399616, 6: 200387, 4: 200117, 1: 99636, 3: 50219, 2: 50025})

Có phiên bản Python 2.7 nào không?
abbas786

1
@ abbas786: Không được tích hợp sẵn, nhưng tất cả các câu trả lời khác cho câu hỏi này đều nên hoạt động trên Python 2.7. Bạn cũng có thể tra cứu nguồn Python 3 cho Random.choices và sao chép nó, nếu có khuynh hướng.
Đánh dấu Dickinson

27

Một lợi thế để tạo danh sách bằng CDF là bạn có thể sử dụng tìm kiếm nhị phân. Trong khi bạn cần O (n) thời gian và không gian để tiền xử lý, bạn có thể nhận được k số trong O (k log n). Vì danh sách Python bình thường không hiệu quả, bạn có thể sử dụng arraymô-đun.

Nếu bạn nhấn mạnh vào không gian liên tục, bạn có thể làm như sau; O (n) thời gian, O (1) không gian.

def random_distr(l):
    r = random.uniform(0, 1)
    s = 0
    for item, prob in l:
        s += prob
        if s >= r:
            return item
    return item  # Might occur because of floating point inaccuracies

Thứ tự của các cặp (mục, thăm dò) trong danh sách có vấn đề trong quá trình thực hiện của bạn, phải không?
stackoverflowuser2010

1
@ stackoverflowuser2010: Không thành vấn đề (lỗi modulo ở dấu phẩy động)
sdcvvc

Đẹp. Tôi thấy điều này nhanh hơn 30% so với scipy.stats.rv_disc rời.
Aspen

1
Một vài lần chức năng này sẽ ném KeyError vì dòng cuối cùng.
imrek

@DrunkenMaster: Tôi không hiểu. Bạn có biết l[-1]trả về phần tử cuối cùng của danh sách?
sdcvvc

15

Có lẽ nó là loại muộn. Nhưng bạn có thể sử dụng numpy.random.choice(), truyền ptham số:

val = numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])

1
OP không muốn sử dụng random.choice()- xem các bình luận.
pobrelkey

5
numpy.random.choice()là hoàn toàn khác nhau random.choice()và hỗ trợ phân phối xác suất.
Eugene Pakhomov

14

(OK, tôi biết bạn đang yêu cầu thu nhỏ, nhưng có lẽ những giải pháp được trồng tại nhà đó không đủ gọn gàng theo ý thích của bạn. :-)

pdf = [(1, 0.1), (2, 0.05), (3, 0.05), (4, 0.2), (5, 0.4), (6, 0.2)]
cdf = [(i, sum(p for j,p in pdf if j < i)) for i,_ in pdf]
R = max(i for r in [random.random()] for i,c in cdf if c <= r)

Tôi đã xác nhận rằng nó hoạt động bằng cách đánh dấu đầu ra của biểu thức này:

sorted(max(i for r in [random.random()] for i,c in cdf if c <= r)
       for _ in range(1000))

Điều này có vẻ ấn tượng. Chỉ cần đặt mọi thứ vào ngữ cảnh, đây là kết quả từ 3 lần thực hiện liên tiếp của đoạn mã trên: ['Đếm 1 với đầu dò: 0,1 là: 113', 'Đếm 2 với đầu dò: 0,05 là: 55', 'Đếm 3 với đầu dò: 0,05 là: 50 ',' Đếm 4 với đầu dò: 0,2 là: 201 ',' Đếm 5 với đầu dò: 0,4 là: 388 ',' Đếm 6 với đầu dò: 0,2 là: 193 ']. ............. ['Đếm 1 với đầu dò: 0,1 là: 77', 'Đếm 2 với đầu dò: 0,05 là: 60', 'Đếm 3 với đầu dò: 0,05 là: 51 ',' Đếm 4 với đầu dò: 0,2 là: 193 ',' Đếm 5 với đầu dò: 0,4 là: 438 ',' Đếm 6 với đầu dò: 0,2 là: 181 '] ........ ..... và
Vaibhav

['Đếm 1 với đầu dò: 0,1 là: 84', 'Đếm 2 với đầu dò: 0,05 là: 52', 'Đếm 3 với đầu dò: 0,05 là: 53', 'Đếm 4 với đầu dò: 0,2 là: 210 ',' Đếm 5 với đầu dò: 0,4 là: 405 ',' Đếm 6 với đầu dò: 0,2 là: 196 ']
Vaibhav

Một câu hỏi, làm thế nào để tôi trả lại max (i ..., nếu 'i' là một đối tượng?
Vaibhav

@Vaibhav ikhông phải là một đối tượng.
Marcelo Cantos

6

Tôi đã viết một giải pháp để vẽ các mẫu ngẫu nhiên từ một phân phối liên tục tùy chỉnh .

Tôi cần điều này cho trường hợp sử dụng tương tự với trường hợp của bạn (tức là tạo ngày ngẫu nhiên với phân phối xác suất nhất định).

Bạn chỉ cần chức năng random_custDistvà dòng samples=random_custDist(x0,x1,custDist=custDist,size=1000). Phần còn lại là trang trí ^^.

import numpy as np

#funtion
def random_custDist(x0,x1,custDist,size=None, nControl=10**6):
    #genearte a list of size random samples, obeying the distribution custDist
    #suggests random samples between x0 and x1 and accepts the suggestion with probability custDist(x)
    #custDist noes not need to be normalized. Add this condition to increase performance. 
    #Best performance for max_{x in [x0,x1]} custDist(x) = 1
    samples=[]
    nLoop=0
    while len(samples)<size and nLoop<nControl:
        x=np.random.uniform(low=x0,high=x1)
        prop=custDist(x)
        assert prop>=0 and prop<=1
        if np.random.uniform(low=0,high=1) <=prop:
            samples += [x]
        nLoop+=1
    return samples

#call
x0=2007
x1=2019
def custDist(x):
    if x<2010:
        return .3
    else:
        return (np.exp(x-2008)-1)/(np.exp(2019-2007)-1)
samples=random_custDist(x0,x1,custDist=custDist,size=1000)
print(samples)

#plot
import matplotlib.pyplot as plt
#hist
bins=np.linspace(x0,x1,int(x1-x0+1))
hist=np.histogram(samples, bins )[0]
hist=hist/np.sum(hist)
plt.bar( (bins[:-1]+bins[1:])/2, hist, width=.96, label='sample distribution')
#dist
grid=np.linspace(x0,x1,100)
discCustDist=np.array([custDist(x) for x in grid]) #distrete version
discCustDist*=1/(grid[1]-grid[0])/np.sum(discCustDist)
plt.plot(grid,discCustDist,label='custom distribustion (custDist)', color='C1', linewidth=4)
#decoration
plt.legend(loc=3,bbox_to_anchor=(1,0))
plt.show()

Phân phối tùy chỉnh liên tục và phân phối mẫu rời rạc

Hiệu suất của giải pháp này là chắc chắn, nhưng tôi thích khả năng đọc hơn.


1

Lập danh sách các mục, dựa trên weights:

items = [1, 2, 3, 4, 5, 6]
probabilities= [0.1, 0.05, 0.05, 0.2, 0.4, 0.2]
# if the list of probs is normalized (sum(probs) == 1), omit this part
prob = sum(probabilities) # find sum of probs, to normalize them
c = (1.0)/prob # a multiplier to make a list of normalized probs
probabilities = map(lambda x: c*x, probabilities)
print probabilities

ml = max(probabilities, key=lambda x: len(str(x)) - str(x).find('.'))
ml = len(str(ml)) - str(ml).find('.') -1
amounts = [ int(x*(10**ml)) for x in probabilities]
itemsList = list()
for i in range(0, len(items)): # iterate through original items
  itemsList += items[i:i+1]*amounts[i]

# choose from itemsList randomly
print itemsList

Một tối ưu hóa có thể là bình thường hóa số tiền theo ước số chung lớn nhất, để làm cho danh sách mục tiêu nhỏ hơn.

Ngoài ra, điều này có thể thú vị.


Nếu danh sách các mục lớn, điều này có thể sử dụng rất nhiều bộ nhớ.
pafcu

@pafcu Đồng ý. Chỉ là một giải pháp, thứ hai xuất hiện trong đầu tôi (thứ nhất là tìm kiếm thứ gì đó như "trăn xác suất cân nặng" :)).
khachik

1

Một câu trả lời khác, có thể nhanh hơn :)

distribution = [(1, 0.2), (2, 0.3), (3, 0.5)]  
# init distribution  
dlist = []  
sumchance = 0  
for value, chance in distribution:  
    sumchance += chance  
    dlist.append((value, sumchance))  
assert sumchance == 1.0 # not good assert because of float equality  

# get random value  
r = random.random()  
# for small distributions use lineair search  
if len(distribution) < 64: # don't know exact speed limit  
    for value, sumchance in dlist:  
        if r < sumchance:  
            return value  
else:  
    # else (not implemented) binary search algorithm  

1
from __future__ import division
import random
from collections import Counter


def num_gen(num_probs):
    # calculate minimum probability to normalize
    min_prob = min(prob for num, prob in num_probs)
    lst = []
    for num, prob in num_probs:
        # keep appending num to lst, proportional to its probability in the distribution
        for _ in range(int(prob/min_prob)):
            lst.append(num)
    # all elems in lst occur proportional to their distribution probablities
    while True:
        # pick a random index from lst
        ind = random.randint(0, len(lst)-1)
        yield lst[ind]

Xác minh:

gen = num_gen([(1, 0.1),
               (2, 0.05),
               (3, 0.05),
               (4, 0.2),
               (5, 0.4),
               (6, 0.2)])
lst = []
times = 10000
for _ in range(times):
    lst.append(next(gen))
# Verify the created distribution:
for item, count in Counter(lst).iteritems():
    print '%d has %f probability' % (item, count/times)

1 has 0.099737 probability
2 has 0.050022 probability
3 has 0.049996 probability 
4 has 0.200154 probability
5 has 0.399791 probability
6 has 0.200300 probability

1

dựa trên các giải pháp khác, bạn tạo phân phối tích lũy (dưới dạng số nguyên hoặc thả nổi bất cứ thứ gì bạn thích), sau đó bạn có thể sử dụng bisect để làm cho nó nhanh

đây là một ví dụ đơn giản (tôi đã sử dụng số nguyên ở đây)

l=[(20, 'foo'), (60, 'banana'), (10, 'monkey'), (10, 'monkey2')]
def get_cdf(l):
    ret=[]
    c=0
    for i in l: c+=i[0]; ret.append((c, i[1]))
    return ret

def get_random_item(cdf):
    return cdf[bisect.bisect_left(cdf, (random.randint(0, cdf[-1][0]),))][1]

cdf=get_cdf(l)
for i in range(100): print get_random_item(cdf),

các get_cdf chức năng sẽ chuyển đổi nó từ 20, 60, 10, 10 thành 20, 20 + 60, 20 + 60 + 10, 20 + 60 + 10 + 10

bây giờ chúng tôi chọn một số ngẫu nhiên lên tới 20 + 60 + 10 + 10 bằng cách sử dụng random.randintsau đó chúng tôi sử dụng bisect để có được giá trị thực tế một cách nhanh chóng



0

Không có câu trả lời nào là đặc biệt rõ ràng hoặc đơn giản.

Dưới đây là một phương pháp rõ ràng, đơn giản được đảm bảo để làm việc.

tích lũy_n normalize_probabilities lấy một từ điển pánh xạ các ký hiệu theo xác suất HOẶC tần số. Nó đưa ra danh sách các bộ dữ liệu có thể sử dụng để lựa chọn.

def accumulate_normalize_values(p):
        pi = p.items() if isinstance(p,dict) else p
        accum_pi = []
        accum = 0
        for i in pi:
                accum_pi.append((i[0],i[1]+accum))
                accum += i[1]
        if accum == 0:
                raise Exception( "You are about to explode the universe. Continue ? Y/N " )
        normed_a = []
        for a in accum_pi:
                normed_a.append((a[0],a[1]*1.0/accum))
        return normed_a

Sản lượng:

>>> accumulate_normalize_values( { 'a': 100, 'b' : 300, 'c' : 400, 'd' : 200  } )
[('a', 0.1), ('c', 0.5), ('b', 0.8), ('d', 1.0)]

Tại sao nó hoạt động

Bước tích lũy biến mỗi ký hiệu thành một khoảng giữa chính nó và xác suất hoặc tần số ký hiệu trước đó (hoặc 0 trong trường hợp ký hiệu đầu tiên). Các khoảng này có thể được sử dụng để chọn từ (và do đó lấy mẫu phân phối được cung cấp) bằng cách chỉ cần bước qua danh sách cho đến khi số ngẫu nhiên trong khoảng 0,0 -> 1,0 (được chuẩn bị trước đó) nhỏ hơn hoặc bằng điểm cuối khoảng của ký hiệu hiện tại.

Việc chuẩn hóa giải phóng chúng ta khỏi nhu cầu đảm bảo mọi thứ tổng hợp đến một giá trị nào đó. Sau khi chuẩn hóa, "vectơ" của xác suất tính tổng thành 1.0.

Phần còn lại của mã để chọn và tạo mẫu dài tùy ý từ phân phối bên dưới:

def select(symbol_intervals,random):
        print symbol_intervals,random
        i = 0
        while random > symbol_intervals[i][1]:
                i += 1
                if i >= len(symbol_intervals):
                        raise Exception( "What did you DO to that poor list?" )
        return symbol_intervals[i][0]


def gen_random(alphabet,length,probabilities=None):
        from random import random
        from itertools import repeat
        if probabilities is None:
                probabilities = dict(zip(alphabet,repeat(1.0)))
        elif len(probabilities) > 0 and isinstance(probabilities[0],(int,long,float)):
                probabilities = dict(zip(alphabet,probabilities)) #ordered
        usable_probabilities = accumulate_normalize_values(probabilities)
        gen = []
        while len(gen) < length:
                gen.append(select(usable_probabilities,random()))
        return gen

Sử dụng :

>>> gen_random (['a','b','c','d'],10,[100,300,400,200])
['d', 'b', 'b', 'a', 'c', 'c', 'b', 'c', 'c', 'c']   #<--- some of the time

-1

Đây là một cách hiệu quả hơn để làm điều này:

Chỉ cần gọi hàm sau với mảng 'trọng số' của bạn (giả sử các chỉ số là các mục tương ứng) và không. các mẫu cần thiết. Chức năng này có thể dễ dàng sửa đổi để xử lý cặp theo thứ tự.

Trả về chỉ mục (hoặc vật phẩm) được lấy mẫu / chọn (có thay thế) bằng xác suất tương ứng của chúng:

def resample(weights, n):
    beta = 0

    # Caveat: Assign max weight to max*2 for best results
    max_w = max(weights)*2

    # Pick an item uniformly at random, to start with
    current_item = random.randint(0,n-1)
    result = []

    for i in range(n):
        beta += random.uniform(0,max_w)

        while weights[current_item] < beta:
            beta -= weights[current_item]
            current_item = (current_item + 1) % n   # cyclic
        else:
            result.append(current_item)
    return result

Một lưu ý ngắn về khái niệm được sử dụng trong vòng lặp while. Chúng tôi giảm trọng số của vật phẩm hiện tại khỏi beta tích lũy, là giá trị tích lũy được xây dựng đồng đều ngẫu nhiên và tăng chỉ số hiện tại để tìm vật phẩm, trọng số phù hợp với giá trị của beta.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.