109
  • 这个问题不是Getting N random numbers which sum is M的副本,因为:
    1. 大多数答案都是关于理论的,而不是 python 中的特定编码解决方案来回答这个问题
    2. 此处接受的答案比回答此问题的副本中的一个答案早 5 年。
    3. 重复接受的答案不回答这个问题

我将如何制作一个包含 N(比如 100)个随机数的列表,以使它们的总和为 1?

我可以制作一个随机数列表

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

我将如何修改它以使列表总和为 1(这是用于概率模拟)。

4

11 回答 11

196

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

更通用的解决方案是使用numpy 中可用的Dirichlet 分布

通过更改分布参数,您可以更改单个数字的“随机性”

>>> 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]]

根据主要参数,Dirichlet 分布将给出所有值都接近 1./N 的向量,其中 N 是向量的长度,或者给出向量的大多数值都为 ~0 的向量,并且有将是一个单一的 1,或者在这些可能性之间给出一些东西。

编辑(原始答案后 5 年):关于 D​​irichlet 分布的另一个有用事实是,如果您生成一组 Gamma 分布的随机变量然后将它们除以它们的总和,那么您自然会得到它。

于 2013-09-06T16:32:23.810 回答
43

最好的方法是简单地列出任意数量的数字,然后将它们全部除以总和。它们以这种方式完全随机。

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
于 2013-09-06T14:16:06.863 回答
8

将每个数字除以总数可能无法得到您想要的分布。例如,对于两个数字,对 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。它应该适用于 n = 100 和 N = 1,000,000 给你 6 位随机数。请参阅我的答案:

创建受约束的随机数?

于 2013-09-06T14:43:13.930 回答
6

创建一个由 0 和 1 组成的列表,然后添加 99 个随机数。对列表进行排序。连续的差异将是加起来为 1 的间隔长度。

我不流利地使用 Python,所以如果有更 Pythonic 的方式来做这件事,请原谅我。我希望意图很明确:

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))
于 2013-09-06T16:18:06.663 回答
5

除了@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]  
于 2018-09-04T08:39:54.180 回答
1

生成 100 个随机数与范围无关。将生成的数字相加,将每个人除以总数。

于 2013-09-06T14:16:44.180 回答
1

另一种解决方案是使用 random.choice 并除以总和:

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
于 2021-10-14T06:30:33.127 回答
1

如果您想为随机选择的数字设置一个最小阈值(即,生成的数字应该是 atleast 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。

于 2020-07-15T09:39:27.663 回答
0

本着“将列表中的每个元素除以列表的总和”的精神,此定义将创建一个长度 = 部分,总和 = 总和的随机数列表,每个元素四舍五入为 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
于 2014-09-04T18:51:13.690 回答
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
于 2014-09-05T02:18:57.137 回答
0

你可以很容易地做到:

r.append(1 - sum(r))
于 2013-09-06T14:13:07.303 回答