Создание списка случайных чисел с суммированием до 1


84

Как мне составить список из N (скажем, 100) случайных чисел, чтобы их сумма была равна 1?

Я могу составить список случайных чисел с помощью

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

Как мне изменить это так, чтобы сумма списка равнялась 1 (это для моделирования вероятности).


5
Если их сумма равна 1, они не являются полностью случайными.
fjarri 06

20
Разделите каждое число в списке на сумму в списке
aragaer

1
@ Богдан, это не проблема.
Tom Kealy

2
@ Богдан, это не так. Они случайны, но ограничение используется на одну степень свободы.
pjs 06

2
@pjs, что означает, что (в лучшем случае) 99 из них случайны, а 1 - нет. Другими словами, «не совсем случайно».
fjarri 06

Ответы:


155

Самое простое решение - взять 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 лет после первоначального ответа): Еще один полезный факт о распределении Дирихле заключается в том, что вы получаете его естественным образом, если сгенерируете набор случайных величин с гамма-распределением, а затем разделите их на их сумму.


5
+1 за то, что единственный, кто упомянул распределение Дирихле. Это должен быть ответ.
Тимоти Шилдс

2
Я изменил свой принятый ответ на этот, поскольку масштабирование не обязательно дает равномерное распределение.
Том Кили

1
@Tom, я не завидую свой выбор, и этот ответ хорош, но я хочу сделать что - то ясно: Scaling ли обязательно дает равномерное распределение (более [0,1/s)). Он будет точно таким же однородным, как и немасштабированное распределение, с которым вы начали, потому что масштабирование не изменяет распределение, а просто сжимает его. Этот ответ дает множество распределений, только одно из которых является однородным. Если это не имеет для вас смысла, запустите примеры и посмотрите на гистограммы, чтобы прояснить ситуацию. Также попробуйте то же самое с гауссовским распределением ( np.random.normal).
askewchan

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

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

3
Думаю, пора пива.
Tom Kealy

1
Это хорошее решение, но похоже, что должен быть способ сделать это за один проход, который хорошо распределяется по всему диапазону. Создание, суммирование, изменение - это трехпроходная операция. Вы можете, по крайней мере, оптимизировать один проход, суммируя по мере создания.
Сайлас Рэй

2
Масштабирование не обязательно хорошее. См. Мой ответ для получения дополнительной информации. Существует множество возможных отображений из [0,1) ^ n в целевое пространство (сумма x_i = 1), и все они не могут быть однородными!
Майк Хаски, 06

1
Это неправильно , по крайней мере, если вам
небезразличны

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 дает равномерное распределение вдоль геометрического отрезка прямой.

С тремя переменными вы выбираете случайную точку в кубе и проецируете (радиально, через начало координат), но точки рядом с центром треугольника будут более вероятными, чем точки рядом с вершинами. Полученные точки находятся на треугольнике в плоскости x + y + z. Если вам нужен объективный выбор точек в этом треугольнике, масштабирование бесполезно.

Проблема усложняется в 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.

Это стандартное масштабирование и сдвиг, используемое в теории генерации случайных чисел. Благодарность принадлежит моему другу Джилу Вайшнаву (я не уверен, есть ли у него профиль SO) и @sega_sai.



0

В духе «разделить каждый элемент в списке на сумму списка» это определение создаст список случайных чисел длины = ЧАСТИ, сумма = ИТОГО, с округлением каждого элемента до МЕСТА (или None):

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.