非偏置返回n个随机正数(> = 0)的列表,使得它们的和= = total_sum

时间:2010-10-18 12:17:23

标签: python algorithm

我正在寻找一种算法或建议来改进我的代码,以生成一个随机数列表,其总和等于某个任意数。使用我的代码,它总是有偏见,因为第一个数字往往会更高。

有没有办法让数字选择更有效率?

#!/usr/bin/python
'''
  Generate a list of 'numbs' positive random numbers whose sum = 'limit_sum'
'''

import random


def gen_list(numbs, limit_sum):
  my_sum = []
  for index in range(0, numbs):
    if index == numbs - 1:
      my_sum.append(limit_sum - sum(my_sum))
    else:
      my_sum.append(random.uniform(0, limit_sum - sum(my_sum)))

  return my_sum

#test
import pprint
pprint.pprint(gen_list(5, 20))
pprint.pprint(gen_list(10, 200))
pprint.pprint(gen_list(0, 30))
pprint.pprint(gen_list(1, 10))

输出

## output

[0.10845093828525609,
 16.324799712999706,
 0.08200162072303821,
 3.4534885160590041,
 0.031259211932997744]

[133.19609626532952,
 47.464880208741029,
 8.556082341110228,
 5.7817325913462323,
 4.6342577008233716,
 0.22532341156764768,
 0.0027495225618908918,
 0.064738336208217895,
 0.028888697891734455,
 0.045250924420116689]

[]

[10]

7 个答案:

答案 0 :(得分:12)

为什么不生成正确数量的均匀分布的随机数,将它们加起来并进行缩放?

编辑:要更清楚一点:你想要N个数字加到S?因此,在区间[0,1)或RNG产生的任何内容上生成N个均匀分布的随机数。添加它们,它们将总计s(比如说),而你希望它们总计为S,所以将每个数字乘以S / s。现在我认为数字在[0,S / s]上均匀随机分布。

答案 1 :(得分:9)

我将如何做到这一点:

  1. 生成n-1个随机数,全部在[0,max]
  2. 范围内
  3. 对这些数字进行排序
  4. 对于由排序列表中的第i个和第(i + 1)个数字组成的每对,创建一个区间(i,i + 1)并计算其长度。最后一个时间间隔将从最后一个数字开始,以max结束,第一个时间间隔将从0开始,并以列表中的第一个数字结束。
  5. 现在,这些区间的长度总是总计为max,因为它们只是代表[0,max]内的段。

    代码(在Python中):

    #! /usr/bin/env python
    import random
    
    def random_numbers(n,sum_to):
        values=[0]+[random.randint(0,sum_to) for i in xrange(n-1)]+[sum_to]
        values.sort()
        intervals=[values[i+1]-values[i] for i in xrange(len(values)-1)]
        return intervals
    
    if __name__=='__main__':
        print random_numbers(5,100)
    

答案 2 :(得分:6)

如果您正在寻找具有尽可能少的相关性的正态分布数字,并且需要严格*,我建议您采用以下数学方法并转换为代码。

(*严谨:其他方法的问题在于你的发行版中可以得到“长尾巴” - 换句话说,它很少但可能会有与你预期的输出非常不同的异常值)

  • 生成N-1个独立且相同分布(IID)的高斯随机变量v 0 ,v 1 ,v 2 ,... v N-1 以匹配问题的N-1自由度。
  • 创建列向量V,其中V = [0 v 0 ,v 1 ,v 2 ,... v N-1 ] Ť
  • 使用固定加权矩阵W,其中W由正交矩阵**组成,其顶行为[1 1 1 1 1 1 1 1 ... 1] / sqrt(N)。
  • 您的输出向量是乘积WV + SU / N,其中S是所需的和,U是1的列向量。换句话说,第i个输出变量=(矩阵W的行#i)和列向量V的点积,加到S / N.

每个输出变量的标准差将是(我相信,现在无法验证)sqrt(N / N-1)*输入随机变量的标准差。

**正交矩阵:这是困难的部分,我放入a question at math.stackexchange.com并且有一个简单的矩阵W可以工作,并且可以通过算法定义只有3个不同的值,所以你实际上没有构建矩阵。

W是Vw的Householder反映,其中v = [sqrt(N),0,0,0,...]和w = [1 1 1 1 1 ... 1]可以定义为:

W(1,i) = W(i,1) = 1/sqrt(N)
W(i,i) = 1 - K   for i >= 2 
W(i,j) = -K      for i,j >= 2, i != j
K = 1/sqrt(N)/(sqrt(N)-1)

马克方法的问题:

  

为什么不生成正确数量的均匀分布的随机数,将它们加起来并进行缩放?

如果你这样做,就会得到一个“长尾”分布。这是MATLAB中的一个例子:

 >> X = rand(100000,10);
 >> Y = X ./ repmat(sum(X,2),1,10);
 >> plot(sort(Y))

我在矩阵X中生成了100,000组N = 10个数,并创建了矩阵Y,其中Y的每一行是X的相应行除以其总和(因此Y的每一行总和为1.0)

绘制Y的排序值(每个列分别排序)产生大致相同的累积分布:

alt text

真正的均匀分布将产生从0到最大值的直线。你会注意到它与真正的均匀分布有点类似,除了在有长尾的末端。在0.2和0.5之间产生了过多的数字。对于较大的N值,尾部变得更糟,因为虽然数字的平均值下降(平均值= 1 / N),但最大值保持为1.0:由9个值0.0和1值1.0组成的向量是有效的并且可以通过这种方式生成,但在病理上很少见。

如果您不关心这一点,请继续使用此方法。并且可能有方法生成具有所需总和的“几乎” - 均匀或“几乎” - 高斯分布,这比我上面描述的更简单和更有效。但我提醒你要小心并理解你选择的算法的后果。


一个没有长尾分布均匀分布的修正如下:

  1. 生成向量V = N均匀分布的从0.0到1.0的随机数。
  2. 找出它们的和S及其最大值M。
  3. 如果S< k * M(最大值超出异常值),回到步骤1.我不确定k用什么值,也许k = N / 2?
  4. 输出向量V * S desired / S
  5. MATLAB中N = 10的示例:

     >> X = rand(100000,10);
     >> Y = X ./ repmat(sum(X,2),1,10);
     >> i = sum(X,2)>(10/2)*max(X,[],2);
     >> plot(sort(Y(i,:)))
    

    alt text

答案 3 :(得分:4)

好的,我们将解决这个问题,假设要求生成一个长度为N的随机向量,该向量在允许的空间上均匀分布,重述如下:

给出

  • 期望的长度L,
  • 期望的总和S,
  • 每个标量值的允许值[0,B]范围

生成长度为N的随机向量V,使得随机变量V在其允许的空间内均匀分布。


我们可以通过注意我们可以计算V = U * S来简化问题,其中U是具有期望总和1的类似随机向量,以及允许值[0,b]的范围,其中b = B / S.值b必须介于1 / N和1之间。


首先考虑N = 3.允许值{U}的空间是垂直于矢量[111]的平面的一部分,该平面穿过点[1/3 1/3 1/3]并且位于多维数据集内,其组件范围介于0和b之间。这组点{U}的形状像六角形。

(TBD:图片。我现在无法生成一个,我需要访问MATLAB或其他可以执行3D绘图的程序。我的Octave安装不能。)

最好使用一个矢量= [1 1 1] / sqrt(3)的正交加权矩阵W(参见我的另一个答案)。一个这样的矩阵是

octave-3.2.3:1> A=1/sqrt(3)
   A =  0.57735
octave-3.2.3:2> K=1/sqrt(3)/(sqrt(3)-1)
   K =  0.78868
octave-3.2.3:3> W = [A A A; A 1-K -K; A -K 1-K]
   W =

     0.57735   0.57735   0.57735
     0.57735   0.21132  -0.78868
     0.57735  -0.78868   0.21132

再次是正交(W * W = I)

如果考虑立方体[0 0 b],[0 bb],[0 b 0],[bb 0],[b 0 0]和[b 0 b]的点,则这些点形成六边形距离立方体的对角线都是b * sqrt(2/3)的距离。这些不能满足问题,但在一分钟内有用。另外两个点[0 0 0]和[b b b]位于立方体的对角线上。

正交加权矩阵W允许我们生成在{U}内均匀分布的点,因为正交矩阵是旋转/反射的坐标变换,不会缩放或倾斜。

我们将生成均匀分布在由W的3个向量定义的坐标系中的点。第一个分量是立方体对角线的轴。 U的分量之和完全取决于该轴,而完全不取决于其他轴。因此,沿该轴的坐标被强制为1 / sqrt(3),这对应于点[1 / 3,1 / 3,1 / 3]。

另外两个组件的方向垂直于立方体的对角线。由于距对角线的最大距离是b * sqrt(2/3),我们将在-b * sqrt(2/3)和+ b * sqrt(2/3)之间生成均匀分布的数字(u,v)。 / p>

这给了我们一个随机变量U'= [1 / sqrt(3)u v]。然后我们计算U = U'* W.一些结果点将超出允许范围(U的每个分量必须在0和b之间),在这种情况下我们拒绝并重新开始。

换句话说:

  1. 生成独立的随机变量u和v,它们均匀分布在-b * sqrt(2/3)和+ b * sqrt(3)之间。
  2. 计算向量U'= [1 / sqrt(3)u v]
  3. 计算U = U'* W。
  4. 如果U的任何组件超出范围[0,b],请拒绝此值并返回步骤1.
  5. 计算V = U * S。

  6. 解决方案类似于更高的尺寸(超平面的一部分内垂直于超立方体主对角线的均匀分布点):

    预先计算等级N的加权矩阵W.

    1. 生成独立的随机变量u 1 ,u 2 ,... u N-1 ,每个均匀分布在-b * k之间( N)和+ b * k(N)。
    2. 计算向量U'= [1 / N u 1 ,u 2 ,... u N-1 ]
    3. 计算U = U'* W.(有实际必须构造和乘以W的快捷方式。)
    4. 如果U的任何组件超出范围[0,b],请拒绝此值并返回步骤1.
    5. 计算V = U * S。
    6. 范围k(N)是N的函数,其表示边1的超立方体的顶点与其主对角线的最大距离。我不确定通用公式,但是对于N = 5,它是sqrt(2/3),对于N = 5,它是sqrt(6/5),在某个地方可能有一个公式。

答案 4 :(得分:2)

我遇到了这个问题,特别需要整数。答案是使用多项式。

import numpy.random, numpy
total_sum = 20
n = 6

v = numpy.random.multinomial(total_sum, numpy.ones(n)/n)

正如multinomial documentation所解释的那样,你已经推出了一个公平的六面骰子二十次。 v包含六个数字,表示骰子每一侧出现的次数。自然地,v的元素必须总和为二十。这里有六个n,二十个是total_sum

使用多项式,您也可以模拟不公平的骰子,这在某些情况下非常有用。

答案 5 :(得分:1)

以下内容非常简单,并返回统一的结果:

def gen_list(numbs, limit_sum):
    limits = sorted([random.uniform(0, limit_sum) for _ in xrange(numbs-1)])
    limits = [0] + limits + [limit_sum]
    return [x1-x0 for (x0, x1) in zip(limits[:-1], limits[1:])]

这个想法很简单,如果你需要比较0到20之间的5个数字,你可以简单地在0到20之间加上4个“限制”,然后得到(0,20)区间的分区。您想要的随机数只是排序列表中的5个区间的长度[0,random1,random2,random3,random4,20]。

PS:哎呀!看起来它和MAK的响应是一样的,尽管没有使用索引编码!

答案 6 :(得分:0)

您可以保留一个总计,而不必反复拨打sum(my_sum)