指定された(数値)分布で乱数を生成します


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()?適切な発生回数でマスターリストを作成し、1つを選択します。もちろん、これは重複した質問です。
S.Lott、2010年


2
@ S.Lottは、ディストリビューションの大きな違いに対して、それほどメモリを消費しませんか?
Lucas Moeskops、2010年

2
@ S.Lott:少数のオカレンスでは、おそらく選択方法で十分ですが、不要な場合は、巨大なリストを作成することは避けたいと思います。
pafcu 2010年

5
@ S.Lott:OK、約10000 * 365 = 3650000 = 360万要素。Pythonのメモリ使用量はわかりませんが、少なくとも3.6M * 4B = 14.4MBです。それほど多くはありませんが、追加のメモリを必要としない同様に単純な方法がある場合は、無視してはいけないものではありません。
pafcu 2010年

回答:


118

scipy.stats.rv_discreteあなたが望むものかもしれません。valuesパラメータを介して確率を指定できます。その後rvs()、分布オブジェクトのメソッドを使用して乱数を生成できます。

コメントでEugene 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()している場合は、標準ライブラリから使用できます。MarkDickinsonの回答を参照してください。


9
私のマシンでnumpy.random.choice()は、約20倍高速です。
Eugene Pakhomov

9
元の質問とまったく同じです。例:numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
Eugene Pakhomov

1
@EugenePakhomovいいですね、知りませんでした。これについてさらに言及している回答があるのはわかりますが、サンプルコードは含まれておらず、賛成票も多くありません。見やすくするために、この回答にコメントを追加します。
Sven Marnach

2
驚いたことに、rv_discrete.rvs()はO(len(p)* size)の時間とメモリで動作します!choice()は最適なO(len(p)+ log(len(p))* size)時間で実行されるようです。
alyaxey 2017年

3
Python 3.6以降を使用している場合、アドオンパッケージを必要としない別の答えがあります。
Mark Ransom 2018

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サンプルを生成する前に、呼び出されるたびに準備作業が必要になるため、これは貴重です。一度に多くのサンプルを生成することで、その準備作業を1回行うだけで済みます。ここでは、100万サンプルを生成し、を使用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 2018

1
@ abbas786:組み込みではありませんが、この質問に対する他の回答はすべてPython 2.7で機能するはずです。また、必要に応じて、random.choicesのPython 3ソースを検索してコピーすることもできます。
Mark Dickinson

27

CDFを使用してリストを生成する利点は、バイナリ検索を使用できることです。前処理にはO(n)の時間とスペースが必要ですが、O(k log n)でk個の数値を取得できます。通常のPythonリストは非効率なので、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

リスト内の(item、prob)ペアの順序は、実装で重要ですよね?
stackoverflowuser2010 2013

1
@ stackoverflowuser2010:関係ありません(浮動小数点のモジュロエラー)
sdcvvc

いいね。これはscipy.stats.rv_discreteより30%速いことがわかりました。
アスペン

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 2013

5
numpy.random.choice()とは完全に異なり、random.choice()確率分布をサポートします。
Eugene 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回連続して実行した結果を以下に示します。 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 2015

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

@pafcu同意した。ちょうど解決策、私の頭に浮かんだ2番目(最初の1つは「重量確率python」のようなものを検索することでした:))。
khachik 2010年

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関数は、+ 10 20、20 + 60、20 + 60 + 10、20 + 60 + 10に20、60、10、10からそれを変換することになります

random.randint次に、20 + 60 + 10 + 10までの乱数を選択し、次にbisectを使用して実際の値をすばやく取得します


0

NumPyのランダムサンプリング分布を確認することをお勧めします


3
numpy関数は、限られた数のディストリビューションのみをサポートしており、独自のディストリビューションを指定することはできません。
pafcu 2010年


0

これらの答えのどれも、特に明確または単純ではありません。

動作することが保証されている明確でシンプルな方法を次に示します。

accumulate_normalize_probabilitiesは、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.