Створення списку випадкових чисел, підсумовуючи до 1


84

Як скласти список N (скажімо 100) випадкових чисел, щоб їх сума дорівнювала 1?

Я можу скласти список випадкових чисел за допомогою

r = [ran.random() for i in range(1,100)]

Як би я змінив це так, щоб список складав 1 (це для моделювання ймовірності).


5
Якщо їх сума дорівнює 1, вони не є абсолютно випадковими.
fjarri

19
Поділіть кожне число в списку на суму списку
aragaer

1
@Bogdan це насправді не проблема.
Tom Kealy

2
@Bogdan це неправильно. Вони випадкові, але обмеження використовує один ступінь свободи.
pjs

2
@pjs, що означає, що (у кращому випадку) 99 з них є випадковими, а 1 - ні. Іншими словами, "не зовсім випадковий".
fjarri

Відповіді:


151

Найпростішим рішенням є справді взяти N випадкових значень і розділити на суму.

Більш загальним рішенням є використання розподілу Діріхле http://en.wikipedia.org/wiki/Dirichlet_distribution, яке доступне в numpy.

Змінюючи параметри розподілу, ви можете змінити "випадковість" окремих чисел

>>> import numpy as np, numpy.random
>>> print np.random.dirichlet(np.ones(10),size=1)
[[ 0.01779975  0.14165316  0.01029262  0.168136    0.03061161  0.09046587
   0.19987289  0.13398581  0.03119906  0.17598322]]

>>> print np.random.dirichlet(np.ones(10)/1000.,size=1)
[[  2.63435230e-115   4.31961290e-209   1.41369771e-212   1.42417285e-188
    0.00000000e+000   5.79841280e-143   0.00000000e+000   9.85329725e-005
    9.99901467e-001   8.37460207e-246]]

>>> print np.random.dirichlet(np.ones(10)*1000.,size=1)
[[ 0.09967689  0.10151585  0.10077575  0.09875282  0.09935606  0.10093678
   0.09517132  0.09891358  0.10206595  0.10283501]]

Залежно від основного параметра розподіл Діріхле дасть або вектори, де всі значення близькі до 1./N, де N - довжина вектора, або дасть вектори, де більшість значень векторів буде ~ 0, і там буде одиничним 1, або дайте щось середнє між цими можливостями.

РЕДАГУВАТИ (5 років після вихідної відповіді): Ще один корисний факт щодо розподілу Діріхле - це те, що ви отримуєте його природним чином, якщо генеруєте гамма-розподілений набір випадкових величин, а потім ділите їх на їх суму.


4
+1 за те, що єдиний згадав про розподіл Діріхле. Це має бути відповіддю.
Тімоті Шилдс

2
Я змінив прийняту відповідь на цю, оскільки масштабування не обов'язково дає рівномірний розподіл.
Tom Kealy

1
@Tom, я не дорікую вашому вибору, і ця відповідь приємна, але я хочу пояснити щось: масштабування дійсно дає рівномірний розподіл (над [0,1/s)). Він буде настільки рівномірним, як розподіл без масштабу, з якого ви розпочали, оскільки масштабування не змінює розподіл, а просто стискає його. Ця відповідь дає різноманітні розподіли, лише один з яких є рівномірним. Якщо для вас це не має сенсу, запустіть приклади та перегляньте деякі гістограми, щоб це було зрозуміло. Також спробуйте те ж саме з гауссовим розподілом ( np.random.normal).
askewchan

@askewchan, ти тут не маєш рації. взяття випадкових чисел і ділення на суму НЕ дасть рівномірного розподілу (він буде близьким до рівномірного для дуже великих N, але ніколи не буде строго рівномірним і також зовсім не рівномірним при меншому N). Розподіл Діріхле також не дасть рівномірних розподілів (оскільки неможливо отримати рівномірний розподіл та суму 1).
sega_sai

@sega_sai У цьому ключі немає строго рівномірного розподілу, який можна було б генерувати псевдовипадково. Я маю на увазі, що перенормування "рівномірного" розподілу не робить його менш рівномірним. Я відповідав на коментар Тома, який передбачав, що цю відповідь було вибрано, оскільки він хотів однакового розподілу. Хіба що я більш глибоко помиляюся?
askewchan

39

Найкращий спосіб зробити це - просто скласти список із скільки завгодно чисел, а потім розділити їх на суму. Таким чином вони абсолютно випадкові.

r = [ran.random() for i in range(1,100)]
s = sum(r)
r = [ i/s for i in r ]

або, як пропонує @TomKealy, зберігати суму та створення в одному циклі:

rs = []
s = 0
for i in range(100):
    r = ran.random()
    s += r
    rs.append(r)

Для найшвидшої роботи використовуйте numpy:

import numpy as np
a = np.random.random(100)
a /= a.sum()

І ви можете дати випадкові числа будь-який розподіл, який ви хочете, для розподілу ймовірностей:

a = np.random.normal(size=100)
a /= a.sum()

---- Час ----

In [52]: %%timeit
    ...: r = [ran.random() for i in range(1,100)]
    ...: s = sum(r)
    ...: r = [ i/s for i in r ]
   ....: 
1000 loops, best of 3: 231 µs per loop

In [53]: %%timeit
   ....: rs = []
   ....: s = 0
   ....: for i in range(100):
   ....:     r = ran.random()
   ....:     s += r
   ....:     rs.append(r)
   ....: 
10000 loops, best of 3: 39.9 µs per loop

In [54]: %%timeit
   ....: a = np.random.random(100)
   ....: a /= a.sum()
   ....: 
10000 loops, best of 3: 21.8 µs per loop

2
@Tom Не хвилюйтеся, легко застрягти, намагаючись зробити ці речі набагато складнішими, ніж вони є. Тепер це для наступної людини.
askewchan

3
Думаю, настав час пива.
Tom Kealy

1
Це хороше рішення, але, схоже, повинен бути спосіб зробити це за один прохід, який отримує хороший розподіл по діапазону. Створення, підсумовування, модифікація - це 3-прохідна операція. Ви можете хоча б оптимізувати один прохід шляхом підсумовування, коли ви генеруєте.
Сайлас Рей

2
Масштабування не обов’язково добре. Більше див. У моїй відповіді. Існує багато можливих відображень від [0,1) ^ n на цільовий простір (сума x_i = 1), і всі вони не можуть бути рівномірними!
Mike Housky

1
Це неправильно , принаймні на той випадок, якщо ви дбаєте про фактичний рівномірний розподіл stackoverflow.com/a/8068956/2075003
n1000

7

Поділивши кожне число на загальне, можливо, ви не отримаєте бажаного розподілу. Наприклад, з двома числами пара x, y = random.random (), random.random () обирає точку рівномірно на квадраті 0 <= x <1, 0 <= y <1. Поділивши на суму "проектує" цю точку (x, y) на пряму x + y = 1 вздовж прямої від (x, y) до початку координат. Точки поблизу (0,5,0,5) будуть набагато більш імовірними, ніж точки поблизу (0,1,0,9).

Тоді для двох змінних x = random.random (), y = 1-x дає рівномірний розподіл по геометричному відрізку лінії.

З 3 змінними ви вибираєте випадкову точку в кубі та проектуєте (радіально, через початок координат), але точки поблизу центру трикутника будуть вірогіднішими, ніж точки біля вершин. Отримані точки знаходяться на трикутнику в площині x + y + z. Якщо вам потрібен неупереджений вибір точок у цьому трикутнику, масштабування не годиться.

Проблема ускладнюється в n-вимірах, але ви можете отримати низьку точність (але високу точність для всіх шанувальників лабораторії!), Рівномірно вибравши з набору всіх n-крапок невід'ємних цілих чисел, додаючи до N, а потім розділивши кожен з них на N.

Нещодавно я придумав алгоритм, щоб зробити це для скромних n, N. Він повинен працювати при n = 100 і N = 1 000 000, щоб дати вам 6-значні випадки. Дивіться мою відповідь за адресою:

Створити обмежені випадкові числа?


Вам слід перевірити розподіл Діріхле .
Джонатан Х

6

Створіть список, що складається з 0 та 1, а потім додайте 99 випадкових чисел. Сортувати список. Послідовними різницями будуть довжини інтервалів, які складають до 1.

Я не вільно володію Python, тому пробачте мене, якщо існує більш пітонічний спосіб зробити це. Я сподіваюся, що намір ясний, хоча:

import random

values = [0.0, 1.0]
for i in range(99):
    values.append(random.random())
values.sort()
results = []
for i in range(1,101):
    results.append(values[i] - values[i-1])
print results

Ось оновлена ​​реалізація в Python 3:

import random

def sum_to_one(n):
    values = [0.0, 1.0] + [random.random() for _ in range(n - 1)]
    values.sort()
    return [values[i+1] - values[i] for i in range(n)]

print(sum_to_one(100))

3

На додаток до рішення @ pjs ми можемо також визначити функцію з двома параметрами.

import numpy as np

def sum_to_x(n, x):
    values = [0.0, x] + list(np.random.uniform(low=0.0,high=x,size=n-1))
    values.sort()
    return [values[i+1] - values[i] for i in range(n)]

sum_to_x(10, 0.6)
Out: 
[0.079058655684546,
 0.04168649034779022,
 0.09897491411670578,
 0.065152293196646,
 0.000544800901222664,
 0.12329662037166766,
 0.09562168167787738,
 0.01641359261155284,
 0.058273232428072474,
 0.020977718663918954]  

1

генерувати 100 випадкових чисел не має значення, якого діапазону. підсумуйте отримані числа, розділіть кожного на загальну кількість.


1

Якщо ви хочете мати мінімальний поріг для випадково вибраних чисел (тобто сформовані числа повинні бути принаймні min_thresh),

rand_prop = 1 - num_of_values * min_thresh
random_numbers = (np.random.dirichlet(np.ones(10),size=1)[0] * rand_prop) + min_thresh

Просто переконайтеся, що у вас є num_of_values ​​(кількість значень, які потрібно згенерувати), щоб можна було генерувати необхідні числа (num_values <= 1/min_thesh )

Отже, в основному ми фіксуємо частину 1 для мінімального порогу, тоді ми створюємо випадкові числа в іншій частині. Додаємоmin_thesh до всіх чисел, щоб отримати суму 1. Наприклад, скажімо: ви хочете сформувати 3 числа, з min_thresh = 0,2. Створюємо порцію для заповнення випадковими числами [1 - (0,2x3) = 0,4]. Ми заповнюємо цю частину і додаємо 0,2 до всіх значень, щоб ми також могли отримати 0,6.

Це стандартне масштабування та зсув, що використовується в теорії генерації випадкових чисел. Заслуга припадає моєму другу Джилу Вайшнаву (я не впевнений, чи має такий профіль) та @sega_sai.


0

Ви можете легко зробити:

r.append(1 - sum(r))

1
Потім останнє число співвідноситься з першими N-1числами.
askewchan

0

У дусі "розділити кожен елемент у списку на суму списку", це визначення створить список випадкових чисел довжиною = ЧАСТИНИ, сума = ВСЬОГО, з кожним елементом, округленим до МІСЦЕВ (або Жодного):

import random
import time

PARTS       = 5
TOTAL       = 10
PLACES      = 3

def random_sum_split(parts, total, places):

    a = []
    for n in range(parts):
        a.append(random.random())
    b = sum(a)
    c = [x/b for x in a]    
    d = sum(c)
    e = c
    if places != None:
        e = [round(x*total, places) for x in c]
    f = e[-(parts-1):]
    g = total - sum(f)
    if places != None:
        g = round(g, places)
    f.insert(0, g)

    log(a)
    log(b)
    log(c)
    log(d)
    log(e)
    log(f)
    log(g)

    return f   

def tick():

    if info.tick == 1:

        start = time.time()

        alpha = random_sum_split(PARTS, TOTAL, PLACES)

        log('********************')
        log('***** RESULTS ******')
        log('alpha: %s' % alpha)
        log('total: %.7f' % sum(alpha))
        log('parts: %s' % PARTS)
        log('places: %s' % PLACES)

        end = time.time()  

        log('elapsed: %.7f' % (end-start))

результат:

Waiting...
Saved successfully.
[2014-06-13 00:01:00] [0.33561018369775897, 0.4904215932650632, 0.20264927800402832, 0.118862130636748, 0.03107818050878819]
[2014-06-13 00:01:00] 1.17862136611
[2014-06-13 00:01:00] [0.28474809073311597, 0.41609766067850096, 0.17193755673414868, 0.10084844382959707, 0.02636824802463724]
[2014-06-13 00:01:00] 1.0
[2014-06-13 00:01:00] [2.847, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] 2.848
[2014-06-13 00:01:00] ********************
[2014-06-13 00:01:00] ***** RESULTS ******
[2014-06-13 00:01:00] alpha: [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] total: 10.0000000
[2014-06-13 00:01:00] parts: 5
[2014-06-13 00:01:00] places: 3
[2014-06-13 00:01:00] elapsed: 0.0054131

0

У дусі методу pjs:

a = [0, total] + [random.random()*total for i in range(parts-1)]
a.sort()
b = [(a[i] - a[i-1]) for i in range(1, (parts+1))]

Якщо ви хочете, щоб їх округлили до десяткових знаків:

if places == None:
    return b
else:    
    b.pop()
    c = [round(x, places) for x in b]  
    c.append(round(total-sum(c), places))
    return c
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.