生成一个随机数列表,总计为1


84

我将如何列出N个(例如100个)随机数,使其总和为1?

我可以用

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

我将如何修改此列表,使其总数为1(这是用于概率模拟)。


5
如果它们的总和为1,则它们不是完全随机的。
fjarri 2013年

19
将列表中的每个数字除以列表的总和
aragaer 2013年

1
@Bogdan并不是一个真正的问题。
汤姆·基利2013年

2
@Bogdan不正确。它们是随机的,但约束使用了一个自由度。
pjs 2013年

2
@pjs,这意味着(最多)其中99个是随机的,而1个不是。换句话说,“不是完全随机的”。
fjarri 2013年

Answers:


151

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

更为通用的解决方案是使用Dirichlet分布 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年):关于Dirichlet分布的另一个有用事实是,如果您生成Gamma分布的一组随机变量,然后将它们除以它们的总和,就自然可以得到它。


4
+1是唯一提及Dirichlet分布的人。这应该是答案。
蒂莫西·希尔兹

2
我已更改了对此答案的接受答案,因为缩放不一定会给出均匀的分布。
汤姆·基利2013年

1
@Tom,我不介意您的选择,这个答案很好,但是我想说清楚一点:缩放比例的确会产生均匀的分布(超过[0,1/s))。它与开始时的未缩放分布完全一样,因为缩放不会更改分布,而只会压缩它。这个答案给出了各种各样的分布,其中只有一个是均匀的。如果这对您没有意义,请运行示例并查看一些直方图以使其清楚。也可以使用高斯分布(np.random.normal)尝试相同的操作。
askewchan

@askewchan,您在这里不正确。取随机数并除以总和将不会给出均匀分布(对于非常大的N,它将接近均匀,但从不严格均匀,并且对于较小的N也不会完全均匀)。Dirichlet分布也不会给出均匀分布(因为不可能获得均匀分布和1的和)。
sega_sai 2013年

@sega_sai在这种情况下,没有可以伪随机生成的严格一致的分布。我的意思是重新规范化“均匀”分布不会使其变得不那么均匀。我正在回应汤姆的评论,这暗示选择了此答案是因为他想要统一分配。除非我从根本上弄错了?
askewchan 2013年

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 2013年

3
我认为该喝啤酒了。
汤姆·基利2013年

1
这是一个很好的解决方案,但似乎应该有一种方法可以通过一次遍历就可以在整个范围内获得良好的分布。创建,求和,修改是一个三遍操作。您至少可以通过在生成时进行求和来优化一次通过。
Silas Ray

2
缩放不一定是好的。看到我的答案更多。从[0,1)^ n到目标空间(x_i = 1的总和)有很多可能的映射,它们不可能都是统一的!
Mike Housky 2013年

1
这是错误的,至少在您关心实际的均匀分布的情况下stackoverflow.com/a/8068956/2075003
n1000

7

将每个数字除以总数可能无法获得所需的分布。例如,有两个数字,对x,y = random.random(),random.random()在0 <= x <1,0 <= y <1的正方形上均匀地选取一个点。用总和“投影”除以沿(x,y)到原点的点(x,y)指向线x + y = 1的总和。(0.5,0.5)附近的点比(0.1,0.9)附近的点更有可能。

对于两个变量,则x = random.random(),y = 1-x沿几何线段给出均匀分布。

使用3个变量,您将在立方体中选择一个随机点并进行投影(径向地通过原点),但是靠近三角形中心的点比靠近顶点的点更有可能。所得点在x + y + z平面上的三角形上。如果需要在该三角形中无偏地选择点,则缩放是不好的。

问题在n维中变得复杂,但是您可以通过从所有非负整数n元组的集合中统一选取,得到一个低精度(但对您的实验室科学迷来说是高精度!)的估计值。 N,然后将它们除以N。

我最近想出了一种算法,可以对中等大小的n做此处理。它应在n = 100和N = 1,000,000的情况下起作用,才能为您提供6位数的随机数。请参阅我的回答:

创建受约束的随机数?


您应该检查Dirichlet分布
乔纳森·H

6

创建一个包含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))

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。

这是随机数生成理论中使用的标准缩放和移位。感谢我的朋友Jeel Vaishnav(我不确定是否有SO个人资料)和@sega_sai。



0

本着“将列表中的每个元素除以列表总和”的精神,此定义将创建一个长度为PARTS,总和为TOTAL的随机数列表,每个元素均四舍五入为PLACES(或无):

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.