Как мне составить список из N (скажем, 100) случайных чисел, чтобы их сумма была равна 1?
Я могу составить список случайных чисел с помощью
r = [ran.random() for i in range(1,100)]
Как мне изменить это так, чтобы сумма списка равнялась 1 (это для моделирования вероятности).
Ответы:
Самое простое решение - взять 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 лет после первоначального ответа): Еще один полезный факт о распределении Дирихле заключается в том, что вы получаете его естественным образом, если сгенерируете набор случайных величин с гамма-распределением, а затем разделите их на их сумму.
источник
[0,1/s)
). Он будет точно таким же однородным, как и немасштабированное распределение, с которым вы начали, потому что масштабирование не изменяет распределение, а просто сжимает его. Этот ответ дает множество распределений, только одно из которых является однородным. Если это не имеет для вас смысла, запустите примеры и посмотрите на гистограммы, чтобы прояснить ситуацию. Также попробуйте то же самое с гауссовским распределением (np.random.normal
).Лучший способ сделать это - просто составить список из любого количества чисел, а затем разделить их на сумму. Таким образом, они совершенно случайны.
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
источник
Разделение каждого числа на общее количество может не дать желаемого распределения. Например, с двумя числами пара 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-значные случайные числа. Смотрите мой ответ по адресу:
Создавать ограниченные случайные числа?
источник
Создайте список, состоящий из 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))
источник
В дополнение к решению @ 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]
источник
генерировать 100 случайных чисел независимо от диапазона. суммируйте полученные числа, разделите каждое на общее.
источник
Если вы хотите иметь минимальный порог для случайно выбранных чисел (т.е. сгенерированные числа должны быть как минимум
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.
источник
Вы можете легко справиться с:
r.append(1 - sum(r))
источник
N-1
числами.В духе «разделить каждый элемент в списке на сумму списка» это определение создаст список случайных чисел длины = ЧАСТИ, сумма = ИТОГО, с округлением каждого элемента до МЕСТА (или 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
источник
В духе метода 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
источник