Generating a list of random numbers, summing to 1

  • This question is not a duplicate of Getting N random numbers whose sum is M because:
    1. Most answers there are about theory, not a specific coding solution in python to answer this question
    2. The accepted answer here is 5 years older than the one answer in the duplicate that answers this question.
    3. The duplicate accepted answer does not answer this question

How would I make a list of N (say 100) random numbers, so that their sum is 1?

I can make a list of random numbers with

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

How would I modify this so that the list sums to 1 (this is for a probability simulation).

98956 次浏览

你可以很容易地做到:

r.append(1 - sum(r))

做到这一点的最佳方法是简单地列出一个数字列表,根据您的需要,然后将它们全部除以总和。它们完全是随机的。

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

产生100个随机数并不重要。 将生成的数字相加,然后将每个个体除以总数。

将每个数字除以总数可能得不到所需的分布。例如,对于两个数字,对 x,y = Random. Random () ,Random. Random ()在平方0 < = x < 1,0 < = y < 1上均匀地选择一个点。除以指向从(x,y)到原点的直线 x + y = 1的和。接近(0.5,0.5)的点比接近(0.1,0.9)的点更有可能出现。

然后,对于两个变量,x = Random () ,y = 1-x 给出了沿几何线段的均匀分布。

使用3个变量,你可以在一个立方体中选择一个随机点并投影(从径向,穿过原点) ,但是靠近三角形中心的点比靠近顶点的点更有可能投影。得到的点在 x + y + z 平面上的一个三角形上。如果你需要在那个三角形中无偏差地选择点,缩放是没有好处的。

这个问题在 n 维中变得复杂,但是你可以得到一个低精度(但是高精度,对于所有的实验室科学爱好者!)估计的方法是从所有 n 元组的非负整数加起来为 N 的集合中一致挑选,然后将它们除以 N。

我最近想出了一个算法来处理中等大小的 n,N。当 n = 100和 N = 1,000,000时,应该可以得到6位随机数。我的回答是:

Create constrained random numbers?

创建一个由0和1组成的列表,然后添加99个随机数。把单子整理一下。连续的差异将是加起来为1的间隔长度。

我对 Python 不是很熟悉,所以请原谅我,如果有一个更 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))

最简单的解决方案实际上是取 N 个随机值并除以和。

一个更通用的解决方案是使用 < a href = “ http://en.wikipedia.org/wiki/Dirichlet _ distribution”rel = “ norefrer”> 狄利克雷分布 也就是 麻木不仁

通过改变分布的参数,你可以改变个别数字的“随机性”

>>> 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 是矢量的长度,或者给出矢量的大部分值是 ~ 0,并且只有一个1,或者在这些可能性之间给出一些东西。

编辑(最初答案后5年) : 关于这个狄利克雷分布的另一个有用的事实是,如果你生成一组伽马分布的随机变量,然后除以它们的和,你就自然而然地得到了它。

本着“用 list 的 sum 除列表中的每个元素”的精神,这个定义将创建一个长度 = PARTS,sum = TOTAL 的随机数列表,每个元素四舍五入为 PLACES (或 Nothing) :

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

In addition to @pjs's solution we can define a function with two parameters as well.

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]

如果您想为随机选择的数字设置一个最小阈值(即,生成的数字至少应该是 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 _ value (要生成的值的数量) ,这样就可以生成所需的数字(num_values <= 1/min_thesh)

所以基本上,我们把1的一部分固定为最小阈值,然后在另一部分中创建随机数。我们将 min_thesh加到所有数字中,得到和1。 例如,假设您想生成3个数字,min _ thresh = 0.2。我们创建一个用随机数填充的部分[1-(0.2 x3) = 0.4]。我们将这部分填满,并将0.2添加到所有值中,因此我们也可以得到0.6。

这是随机数生成理论中使用的标准缩放和移位。这要归功于我的朋友 Jeel Vaishnav (我不确定他是否有 SO 档案)和@sega _ sai。

另一种解决方案是使用随机选择和除以和:

import random
n = 5
rand_num = [random.choice(range(0,100)) for r in range(n)] # create random integers
rand_num = [i/sum(rand_num) for i in rand_num] # normalize them

Inspired by @sega_sai answer with an up-to-date and recommanded numpy implementation [2022年3月]

from numpy.random import default_rng


rng = default_rng()
rng.dirichlet(np.ones(10),size=1)
>>> array([[0.01279836, 0.16891858, 0.01136867, 0.17577222, 0.27944229,
0.06244618, 0.19878224, 0.02481954, 0.01478089, 0.05087103]])

参考文献: