Утворіть випадкові числа із заданим (числовим) розподілом


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 - це не дуже велика пам'ять для великих відмінностей у розподілі?
Лукас Moeskops

2
@ S.Lott: Ваш метод вибору, ймовірно, буде добре для невеликої кількості подій, але я б краще уникати створення величезних списків, коли це не потрібно.
pafcu

5
@ S.Lott: Гаразд, приблизно 10000 * 365 = 3650000 = 3,6 мільйона елементів. Я не впевнений у використанні пам'яті в Python, але це принаймні 3,6 М * 4B = 14,4 МБ. Не величезна кількість, але не те, що ви повинні ігнорувати, коли існує не менш простий метод, який не потребує додаткової пам'яті.
pafcu

Відповіді:


118

scipy.stats.rv_discreteможе бути те, що ви хочете. Ви можете надати свої ймовірності за допомогою valuesпараметра. Потім можна використовувати rvs()метод об’єкта розподілу для генерації випадкових чисел.

Як вказував Євген Пахомов у коментарях, ви також можете передати 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()зі стандартної бібліотеки - див. Відповідь Марка Дікінсона .


9
На моїй машині numpy.random.choice()майже в 20 разів швидше.
Євген Пахомов

9
це робить точно так само, як і початкове запитання. Напр .:numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
Євген Пахомов

1
@EugenePakhomov Це добре, я цього не знав. Я можу побачити, що є відповідь, в якій згадується далі, але він не містить коду прикладу і не має великої кількості результатів. Я додам коментар до цієї відповіді для кращої наочності.
Свен Марнах

2
Дивно, але rv_discrete.rvs () працює в O (len (p) * size) час і пам'ять! У той час як вибір (), здається, працює в оптимальний час O (len (p) + log (len (p)) * *).
alyaxey

3
Якщо ви використовуєте Python 3.6 або новішу версію, є ще одна відповідь , яка не потребує додаткових пакунків.
Марк Рансом

113

Оскільки Python 3.6, у стандартній бібліотеці Python є рішення для цього, а саме random.choices.

Приклад використання: давайте встановимо сукупність та ваги, які відповідають тим, що стосуються питання ОП:

>>> 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. Ви також можете шукати джерело Python 3 для random.choices і скопіювати його, якщо це так схильно.
Марк Дікінсон

27

Перевагою для створення списку за допомогою CDF є те, що ви можете використовувати двійковий пошук. Поки вам потрібен O ​​(n) час і простір для попередньої обробки, ви можете отримати k числа в O (k log n). Оскільки звичайні списки 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

1
@ stackoverflowuser2010: Це не повинно мати значення (помилки модуля в плаваючій точці)
sdcvvc

Приємно. Я виявив, що це на 30% швидше, ніж scipy.stats.rv_discrete.
Аспен

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
ОП не хоче використовувати random.choice()- дивіться коментарі.
pobrelkey

5
numpy.random.choice()повністю відрізняється від random.choice()та підтримує розподіл ймовірностей.
Євген Пахомов

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-х послідовних виконання вищевказаного коду: ['Кількість 1 з проблемою: 0,1 є: 113', 'Кількість 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 '] ........ ..... і
Вайбхав

['Кількість 1 із зондом: 0,1 дорівнює: 84', 'Кількість 2 із зондуванням: 0,05 є: 52', 'Кількість 3 із зонду: 0,05 є: 53', 'Кількість 4 із зондом: 0,2 становить: 210 ',' Кількість 5 із зондом: 0,4 становить: 405 ',' Графа 6 із зондом: 0,2 є: 196 ']
Вайбхав

Питання, як мені повернути max (я ..., якщо 'я' є об’єктом?
Vaibhav

@Vaibhav iне є об’єктом.
Марсело Кантос

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 Погодився. Просто рішення, друге яке мені прийшло в голову (перше було шукати щось на кшталт "вагова ймовірність пітона" :)).
хачик

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

виходячи з інших рішень, ви генеруєте накопичувальний розподіл (як ціле чи плаваюче все, що вам подобається), то ви можете використовувати бісект, щоб зробити це швидко

це простий приклад (я тут використовував цілі числа)

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потім бісект, щоб швидко отримати фактичне значення


0

ви можете ознайомитись з розподілами вибіркової вибірки NumPy Random


3
Функції nummy також, здається, підтримують лише обмежену кількість дистрибутивів, не підтримуючи вказівку власних.
pafcu


0

Жоден із цих відповідей не є особливо зрозумілим чи простим.

Ось чіткий, простий метод, який гарантовано працює.

umule_normalize_probables приймає словник, 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

Ось більш ефективний спосіб зробити це:

Просто зателефонуйте на наступну функцію за допомогою масиву "ваг" (вважаючи індекси відповідними елементами) та "ні". необхідних зразків. Цю функцію можна легко змінити для обробки упорядкованої пари.

Повертає індекси (або елементи), відібрані / відібрані (із заміною), використовуючи відповідні ймовірності:

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. Ми зменшуємо вагу поточного елемента з кумулятивної бета-версії, що є кумулятивною величиною, побудованою рівномірно, рівномірно, та індексом поточного індексу, щоб знайти предмет, вага якого відповідає значенню бета-версії.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.