주어진 (숫자) 분포로 난수 생성


132

다른 값에 대한 확률이있는 파일이 있습니다.

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

이 분포를 사용하여 난수를 생성하고 싶습니다. 이를 처리하는 기존 모듈이 있습니까? 자체적으로 코딩하는 것은 매우 간단합니다 (누적 밀도 함수 작성, 임의의 값 [0,1] 생성 및 해당 값 선택). 이것은 일반적인 문제 여야하며 누군가가 함수 / 모듈을 생성 한 것 같습니다. 그것.

생일 목록 (표준 random모듈의 배포를 따르지 않음)을 생성하고 싶기 때문에 이것이 필요합니다 .


2
이외에 random.choice()? 적절한 발생 횟수로 마스터 목록을 작성하고 하나를 선택하십시오. 물론 이것은 중복 질문입니다.
S.Lott


2
@ S.Lott는 배포판의 큰 차이에 대해 메모리를 많이 사용하지 않습니까?
Lucas Moeskops

2
@ S.Lott : 당신의 선택 방법은 아마도 소수의 경우에는 좋을 것입니다.하지만 필요하지 않을 때 큰 목록을 만드는 것을 피하고 싶습니다.
pafcu

5
@ S.Lott : OK, 약 10000 * 365 = 3650000 = 360 만 요소. 파이썬의 메모리 사용량에 대해서는 잘 모르겠지만 최소 3.6M * 4B = 14.4MB입니다. 엄청난 양은 아니지만 여분의 메모리가 필요하지 않은 똑같이 간단한 방법이있을 때 무시해야 할 것이 아닙니다.
pafcu

답변:


118

scipy.stats.rv_discrete당신이 원하는 것일 수도 있습니다. values매개 변수 를 통해 확률을 제공 할 수 있습니다 . 그런 다음 rvs()분포 객체 의 방법 을 사용하여 난수를 생성 할 수 있습니다.

코멘트에 유진 Pakhomov에 의해 지적, 당신은 또한 전달할 수 p에 키워드 매개 변수 numpy.random.choice(), 예를

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

Python 3.6 이상을 사용 random.choices()하는 경우 표준 라이브러리에서 사용할 수 있습니다 . Mark Dickinson답변을 참조하십시오 .


9
내 컴퓨터 numpy.random.choice()에서 거의 20 배 더 빠릅니다.
유진 Pakhomov

9
그것은 원래의 질문과 정확히 똑같습니다. 예 :numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
Eugene Pakhomov 2016 년

1
@EugenePakhomov 좋은 데요, 몰랐습니다. 이에 대한 답변이 더 있음을 알 수 있지만 예제 코드는 포함되어 있지 않으며 많은 찬사를받지 못했습니다. 더 나은 가시성을 위해이 답변에 의견을 추가하겠습니다.
Sven Marnach 2016 년

2
놀랍게도 rv_discrete.rvs ()는 O (len (p) * size) 시간과 메모리에서 작동합니다! choice ()는 최적의 O (len (p) + log (len (p)) * size) 시간에 실행되는 것처럼 보입니다.
alyaxey

3
Python 3.6 이상을 사용 하는 경우 애드온 패키지가 필요없는 다른 답변 이 있습니다.
Mark Ransom

113

Python 3.6부터는 Python의 표준 라이브러리에 솔루션이 random.choices있습니다.

사용법 예 : 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]

이제 choices(population, weights)단일 샘플을 생성합니다.

>>> choices(population, weights)
4

선택적 키워드 전용 인수를 k사용하면 한 번에 둘 이상의 샘플을 요청할 수 있습니다. 이것은 random.choices샘플을 생성하기 전에 호출 될 때마다 수행 해야하는 준비 작업이 있기 때문에 유용 합니다. 한 번에 많은 샘플을 생성하면 준비 작업을 한 번만 수행하면됩니다. 여기서 우리는 백만 개의 샘플을 생성하고 collections.Counter우리가 얻는 분포가 우리가 준 가중치와 대략 일치하는지 확인하는 데 사용 합니다.

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

이것에 Python 2.7 버전이 있습니까?
abbas786

1
@ abbas786 : 내장되어 있지 않지만이 질문에 대한 다른 답변은 모두 Python 2.7에서 작동해야합니다. 또한 random.choices에 대한 Python 3 소스를 찾아보고 기울어지면 복사 할 수 있습니다.
Mark Dickinson

27

CDF를 사용하여 목록을 생성하면 이진 검색을 사용할 수 있다는 이점이 있습니다. 전처리를 위해 O (n) 시간과 공간이 필요하지만 O (k log n)에서 k 개의 숫자를 얻을 수 있습니다. 일반적인 파이썬리스트는 비효율적이기 때문에 array모듈 을 사용할 수 있습니다 .

일정한 공간을 고집하면 다음을 수행 할 수 있습니다. O (n) 시간, O (1) 공간

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

목록의 (항목, prob) 쌍의 순서는 구현에 중요합니다.
stackoverflowuser2010

1
@ stackoverflowuser2010 : 중요하지 않습니다 (부동 소수점의 모듈로 오류)
sdcvvc

좋은. 나는 이것이 scipy.stats.rv_discrete보다 30 % 빠릅니다.
Aspen

1
이 함수는 마지막 줄 때문에 KeyError를 던질 것입니다.
imrek

@ DrunkenMaster : 이해가 안됩니다. l[-1]목록의 마지막 요소를 반환 한다는 것을 알고 있습니까?
sdcvvc

15

아마 늦었을 수도 있습니다. 그러나 매개 변수를 numpy.random.choice()전달하여을 사용할 수 있습니다 p.

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

1
OP는 사용하고 싶지 않습니다 random.choice()-의견을 참조하십시오.
pobrelkey

5
numpy.random.choice()random.choice()확률 분포와 완전히 다르며 이를 지원합니다.
유진 Pakhomov

14

(좋아요, 나는 당신이 수축 포장을 요구한다는 것을 알고 있지만, 아마도 자체 개발 솔루션은 당신의 취향에 충분히 간결하지 않았을 것입니다. :-)

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)

나는이 표현의 결과를 눈으로 보아 이것이 효과가 있는지 의사에게 확인했다.

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

인상적입니다. 상황을 이해하기 위해 위 코드를 3 번 ​​연속 실행 한 결과는 다음과 같습니다. [ 'prob을 사용한 1의 개수 : 0.1은 : 113', 'prob를 가진 2의 개수 : 0.05는 : 55', ' 프로브가있는 3 : 0.05는 : 50 ','프로브가있는 4의 카운트 : 0.2는 : 201 ','프로브가있는 5의 카운트 : 0.4는 : 388 ','프로브가있는 6의 카운트 : 0.2는 : 193 ']. ............. [ '프로브가있는 1의 카운트 : 0.1은 : 77', '프로브를 가진 2의 카운트 : 0.05는 : 60', '프로브를 가진 3의 카운트 : 0.05는 : 51 ','프로브가있는 4의 개수 : 0.2는 : 193 ','프로브가있는 5의 개수 : 0.4는 : 438 ','프로브가있는 6의 카운트 : 0.2는 : 181 '] ........ .....
Vaibhav

[ '프로브가있는 1의 카운트 : 0.1은 : 84', '프로브가있는 2의 카운트 : 0.05는 : 52', '프로브가있는 3의 카운트 : 0.05는 : 53', '프로브를 가진 4의 카운트 : 0.2는 : 210 ','프로브 포함 5의 개수 : 0.4는 405 ','프로브 포함 6의 개수 : 0.2는 : 196 ']
Vaibhav

질문, 'i'가 객체 인 경우 어떻게 max (i ...)를 반환합니까?
Vaibhav

@Vaibhav i는 객체가 아닙니다.
Marcelo Cantos

6

나는 커스텀 연속 분포에서 무작위 샘플그리는 솔루션을 썼습니다 .

나는 당신과 비슷한 유스 케이스 (예 : 주어진 확률 분포로 임의의 날짜를 생성)에 이것을 필요로했습니다.

당신은 단지 기능 random_custDist과 라인이 필요합니다 samples=random_custDist(x0,x1,custDist=custDist,size=1000). 나머지는 장식입니다 ^^.

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

지속적인 사용자 정의 배포 및 개별 샘플 배포

이 솔루션의 성능은 확실하지 않지만 가독성을 선호합니다.


1

다음을 기준으로 항목 목록을 작성하십시오 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

목표 목록을 더 작게 만들기 위해 최대 공약수로 양을 정규화하는 것이 최적화 일 수 있습니다.

또한 이것은 흥미로울 수 있습니다.


항목 목록이 크면 추가 메모리가 많이 사용될 수 있습니다.
pafcu

@pafcu 동의합니다. 그냥 해결책, 두 번째는 내 마음에 왔습니다 (첫 번째는 "weight weight python"과 같은 것을 검색하는 것입니다 :)).
khachik

1

아마 더 빠른 대답 :)

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]

확인:

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

다른 솔루션을 기반으로 누적 분포 (정수 또는 원하는대로 부동 소수점)를 생성 한 다음 bisect를 사용하여 빠르게 만들 수 있습니다

이것은 간단한 예입니다 (여기서는 정수를 사용했습니다)

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

get_cdf함수는 20, 60, 10, 10에서 20, 20 + 60, 20 + 60 + 10, 20 + 60 + 10 + 10으로 변환합니다.

이제 우리는 최대 20 + 60 + 10 + 10의 난수 random.randint를 사용하여 bisect를 사용하여 실제 값을 빠른 방법으로 얻습니다.



0

이러한 답변 중 특히 명확하거나 간단한 것은 없습니다.

작동이 보장되는 명확하고 간단한 방법입니다.

축적p _ 정규화 확률은 기호를 확률 또는 빈도로 매핑 하는 사전 을 사용합니다 . 선택을 수행 할 수있는 사용 가능한 튜플 목록을 출력합니다.

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

수율 :

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

작동하는 이유

퇴적 단계는 (첫 번째 심볼의 경우는 0) 자체 이전 심볼 확률 또는 주파수 간격으로 각각의 기호를 온. 이 간격은 간격 0.0-> 1.0 (이전 준비)의 난수가 현재 심볼의 간격 끝점보다 작거나 같아 질 때까지 간단히 목록을 단계별로 선택하여 선택하여 제공된 분포를 샘플링하는 데 사용할 수 있습니다.

정상화는 어떤 값으로 확인 모든 합계를 만들기 위해 필요에서 우리를 해제합니다. 정규화 후 확률의 "벡터"는 1.0이됩니다.

코드의 나머지 선택 및 분포로부터 임의의 길이의 샘플을 생성하기위한 이하이다 :

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

사용법 :

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

보다 효과적인 방법 은 다음과 같습니다 .

'weights'배열 (지표를 해당 항목으로 가정)과 no를 사용하여 다음 함수를 호출하십시오. 필요한 샘플. 이 기능은 순서 쌍을 처리하기 위해 쉽게 수정할 수 있습니다.

각각의 확률을 사용하여 샘플링 / 피킹 (대체) 된 인덱스 (또는 항목)를 반환합니다.

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

while 루프에서 사용되는 개념에 대한 간단한 설명. 무작위로 균일하게 구성된 누적 값인 누적 베타에서 현재 항목의 무게를 줄이고, 가중치가 베타의 값과 일치하는 항목을 찾기 위해 현재 색인을 증가시킵니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.