Belirli bir (sayısal) dağılımla rastgele sayılar oluşturun


133

Farklı değerler için bazı olasılıklar içeren bir dosyam var, örneğin:

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

Bu dağılımı kullanarak rastgele sayılar üretmek istiyorum. Bunu ele alan mevcut bir modül var mı? Kendi başınıza kodlamak oldukça basittir (kümülatif yoğunluk işlevini oluşturun, rastgele bir değer oluşturun [0,1] ve karşılık gelen değeri seçin), ancak bu yaygın bir sorun gibi görünüyor ve muhtemelen birisi için bir işlev / modül oluşturmuştur. o.

Buna ihtiyacım var çünkü doğum günlerinin bir listesini oluşturmak istiyorum (standart randommodüldeki herhangi bir dağıtımı takip etmeyen ).


2
Dışında random.choice()? Ana listeyi uygun sayıda oluşumla oluşturursunuz ve birini seçersiniz. Bu elbette yinelenen bir sorudur.
S.Lott


2
@ S.Lott, dağıtımdaki büyük farklılıklar için bellek yoğun değil mi?
Lucas Moeskops

2
@ S.Lott: Seçim yönteminiz muhtemelen az sayıdaki oluşum için iyi olacaktır, ancak gerekli olmadığında büyük listeler oluşturmaktan kaçınmayı tercih ederim.
pafcu

5
@ S.Lott: Tamam, yaklaşık 10000 * 365 = 3650000 = 3,6 milyon öğe. Python'daki bellek kullanımından emin değilim, ancak en az 3.6M * 4B = 14.4MB. Çok büyük bir miktar değil, ancak fazladan bellek gerektirmeyen eşit derecede basit bir yöntem olduğunda da görmezden gelmeniz gereken bir şey değil.
pafcu

Yanıtlar:


119

scipy.stats.rv_discreteistediğin şey olabilir. Olasılıklarınızı valuesparametre üzerinden sağlayabilirsiniz . Ardından, rvs()rastgele sayılar oluşturmak için dağıtım nesnesinin yöntemini kullanabilirsiniz .

Eugene Pakhomov'un yorumlarda işaret ettiği gibi, bir panahtar kelime parametresini numpy.random.choice(), örneğin

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

Python 3.6 veya üstünü kullanıyorsanız random.choices(), standart kitaplıktan kullanabilirsiniz - Mark Dickinson'ın cevabına bakın .


9
Makinemde numpy.random.choice()neredeyse 20 kat daha hızlı.
Eugene Pakhomov

9
orijinal soru için tam olarak aynı şeyi yapar. Örneğin:numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
Eugene Pakhomov

1
@EugenePakhomov Bu güzel, bunu bilmiyordum. Bundan daha fazla bahseden bir cevap olduğunu görebiliyorum, ancak herhangi bir örnek kod içermiyor ve çok fazla olumlu oyu yok. Daha iyi görünürlük için bu yanıta bir yorum ekleyeceğim.
Sven Marnach

2
Şaşırtıcı bir şekilde, rv_discrete.rvs () O (len (p) * boyut) zamanda ve bellekte çalışır! Choice () en uygun O (len (p) + log (len (p)) * boyut) süresinde çalışıyor gibi görünmektedir.
alyaxey

3
Eğer kullanıyorsanız Python 3.6 veya daha yeni var başka cevap herhangi eklenti paketlerini gerektirmez.
Mark Ransom

114

Python 3.6'dan beri, Python'un standart kütüphanesinde bunun için bir çözüm var, yani random.choices.

Örnek kullanım: OP'nin sorusundakilerle eşleşen bir popülasyon ve ağırlıklar ayarlayalım:

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

Şimdi choices(population, weights)tek bir örnek oluşturur:

>>> choices(population, weights)
4

İsteğe bağlı yalnızca anahtar kelime argümanı k, bir kişinin aynı anda birden fazla örnek talep etmesine izin verir. Bu değerlidir, çünkü random.choicesher çağrıldığında, herhangi bir örnek oluşturmadan önce yapılması gereken bazı hazırlık çalışmaları vardır; aynı anda birçok numune üreterek, bu hazırlık çalışmasını yalnızca bir kez yapmamız gerekiyor. Burada bir milyon numune üretiyoruz ve collections.Counterelde ettiğimiz dağılımın verdiğimiz ağırlıklarla kabaca eşleşip eşleşmediğini kontrol etmek için kullanıyoruz .

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

Bunun bir Python 2.7 sürümü var mı?
abbas786

1
@ abbas786: Yerleşik değil, ancak bu sorunun diğer yanıtlarının tümü Python 2.7 üzerinde çalışmalıdır. Ayrıca random.choices için Python 3 kaynağına bakabilir ve eğilimliyse bunu kopyalayabilirsiniz.
Mark Dickinson

27

CDF kullanarak liste oluşturmanın bir avantajı, ikili aramayı kullanabilmenizdir. Ön işleme için O (n) zamanına ve boşluğuna ihtiyacınız olsa da, O (k log n) 'de k sayı alabilirsiniz. Normal Python listeleri verimsiz olduğu için arraymodülü kullanabilirsiniz .

Sabit boşlukta ısrar ediyorsanız, şunları yapabilirsiniz; O (n) zaman, O (1) uzay.

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

Listedeki (öğe, sonda) çiftlerinin sırası, uygulamanızda önemlidir, değil mi?
stackoverflowuser2010

1
@ stackoverflowuser2010:
Önemli

Güzel. Bunu scipy.stats.rv_discrete'den% 30 daha hızlı buldum.
Aspen

1
Birkaç kez bu işlev bir KeyError atar çünkü son satır.
imrek

@DrunkenMaster: Anlamıyorum. l[-1]Listenin son öğesini döndürdüğünün farkında mısınız ?
sdcvvc

15

Belki biraz geç kalmıştır. Ancak parametreyi numpy.random.choice()geçerek kullanabilirsiniz p:

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

1
OP kullanmak istemiyor random.choice()- yorumlara bakın.
pobrelkey

5
numpy.random.choice()tamamen farklıdır random.choice()ve olasılık dağılımını destekler.
Eugene Pakhomov

14

(Tamam, shrink-wrap istediğinizi biliyorum, ama belki de evde yetiştirilen çözümler beğeninize yetecek kadar özlü değildi. :-)

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)

Bu ifadenin çıktısına göz atarak bunun işe yaradığını sözde doğruladım:

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

Bu etkileyici görünüyor. Bir şeyleri bağlam içine koymak için, işte yukarıdaki kodun art arda 3 kez çalıştırılmasının sonuçları: ['Prob ile 1'in sayısı: 0.1 is: 113', 'Problu 2'nin sayısı: 0.05'tir: 55', ' 3 sonda ile: 0,05: 50 ',' Sonda ile 4 sayısı: 0,2: 201 ',' Sonda ile 5 sayısı: 0,4: 388 ',' Sonda ile 6 sayısı: 0,2: 193 ']. ............. ['Sonda ile 1'in sayısı: 0,1: 77', 'Sonda ile 2'nin sayısı: 0,05: 60', 'Sondalı 3'ün sayısı: 0,05: 51 ',' Sonda ile 4'ün sayısı: 0.2: 193 ',' Sonda ile 5'in sayısı: 0.4: 438 ',' Sonda ile 6'nın sayısı: 0.2: 181 '] ........ ..... ve
Vaibhav

['Sonda ile 1'in sayısı: 0,1: 84', '2'nin problu sayısı: 0,05: 52', 'Sonda ile 3'ün sayısı: 0,05', 'Problu 4'ün sayısı: 0,2: 210 ',' Sonda ile 5'in sayısı: 0.4: 405 ',' Sonda ile 6'nın sayısı: 0.2: 196 ']
Vaibhav

Bir soru, nasıl max döndürürüm (i ..., eğer 'i' bir nesneyse?
Vaibhav

@Vaibhav ibir nesne değildir.
Marcelo Cantos

6

Özel bir sürekli dağıtımdan rastgele örnekler çizmek için bir çözüm yazdım .

Sizinkine benzer bir kullanım durumu için buna ihtiyacım vardı (yani belirli bir olasılık dağılımıyla rastgele tarihler oluşturmak).

Sadece işleve random_custDistve çizgiye ihtiyacın var samples=random_custDist(x0,x1,custDist=custDist,size=1000). Gerisi dekorasyon ^^.

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

Sürekli özel dağıtım ve ayrık numune dağıtımı

Bu çözümün performansı kesinlikle iyileştirilebilir, ancak okunabilirliği tercih ediyorum.


1

Aşağıdakilere göre öğelerin bir listesini yapı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

Bir optimizasyon, hedef listeyi küçültmek için en büyük ortak bölen tarafından miktarları normalleştirmek olabilir.

Ayrıca bu ilginç olabilir.


Öğe listesi büyükse, bu çok fazla bellek kullanabilir.
pafcu

@pafcu Kabul edildi. Sadece bir çözüm, aklıma gelen ikincisi (ilki "ağırlık olasılık pitonu" gibi bir şey aramaktı :)).
khachik

1

Başka bir cevap, muhtemelen daha hızlı :)

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]

Doğrulama:

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

diğer çözümlere dayalı olarak, birikimli dağıtım (tam sayı veya yüzer olarak) oluşturursunuz, ardından bisect'i hızlı hale getirmek için kullanabilirsiniz.

bu basit bir örnek (burada tamsayı kullandım)

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_cdfişlev, 20 içine 20, 60, 10, 10 dönüştürmek olur + 60 20, 20 + 60 + 10, 20 + 60 + 10 + 10

Şimdi 20 + 60 + 10 + 10'a kadar rastgele bir sayı seçiyoruz ve random.randintardından gerçek değeri hızlı bir şekilde elde etmek için bisect kullanıyoruz



0

Bu cevapların hiçbiri özellikle açık veya basit değil.

İşte çalışması garantili net, basit bir yöntem.

biriktirmek_normalize_probabilities , psembolleri olasılık VEYA frekanslara eşleyen bir sözlük alır . Seçim yapılabilecek kullanılabilir tuplelar listesini çıkarır.

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

Verim:

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

Neden işe yarıyor

Birikim aşaması (birinci sembolün halinde veya 0) kendisi ve önceki semboller olasılık ya da frekans arasında bir aralık her bir sembol döner. Bu aralıklar, 0.0 -> 1.0 aralığındaki rastgele sayı (daha önce hazırlanmış olan) mevcut sembolün aralığının bitiş noktasına eşit veya daha az olana kadar basitçe listede adım adım ilerleyerek arasından seçim yapmak (ve böylece sağlanan dağıtımı örneklemek) için kullanılabilir.

Normalleştirme bazı değere her şeyin meblağlar yapmak gerek bizi serbest bırakır. Normalleştirmeden sonra olasılıkların "vektörü" toplamı 1.0'a çıkar.

Kodun kalan seçimi ve dağılımından bir isteğe bağlı olarak uzun bir örnek oluşturmak için aşağıdaki gibidir:

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

Kullanım:

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

İşte bunu yapmanın daha etkili bir yolu :

Aşağıdaki işlevi 'ağırlık' dizinizle (indisleri karşılık gelen öğeler olarak varsayarak) ve no. gerekli örneklerin. Bu işlev, sipariş edilen çiftleri işlemek için kolayca değiştirilebilir.

Örneklenen / alınan (değiştirilerek) dizinleri (veya öğeleri) ilgili olasılıklarını kullanarak döndürür:

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 döngüsünde kullanılan kavram hakkında kısa bir not. Mevcut öğenin ağırlığını rastgele oluşturulmuş kümülatif bir değer olan kümülatif betadan düşürüyoruz ve ağırlığı beta değeriyle eşleşen öğeyi bulmak için mevcut endeksi artırıyoruz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.