生成带有条件的随机数列表 - numpy [重复]

Posted

技术标签:

【中文标题】生成带有条件的随机数列表 - numpy [重复]【英文标题】:Generate list of random number with condition - numpy [duplicate] 【发布时间】:2020-01-22 16:09:06 【问题描述】:

我想生成一个包含 15 个整数的列表,总和为 12,最小值为 0,最大值为 6。

我尝试了以下代码

def generate(low,high,total,entity):
   while sum(entity)!=total:
       entity=np.random.randint(low, high, size=15)
   return entity

但上述功能无法正常工作。这太费时间了。 请告诉我生成此类数字的有效方法?

【问题讨论】:

严格来说它可以工作,但是生成和测试通常不是很有效。在您生成正确的序列之前,它通常需要一万次生成。 @WillemVanOnsem 还有其他快捷的方法吗? 【参考方案1】:

严格来说,以上将起作用。但是对于 0 到 6 之间的 15 个数字,生成 12 个的几率并没有那么高。事实上,我们可以通过以下方式计算可能性的数量:

F(s, 1) = 1 对于 0≤s≤6

F(s, n) = Σ6i=0F(s-i, n-1).

我们可以用一个值来计算:

from functools import lru_cache

@lru_cache()
def f(s, n, mn, mx):
    if n < 1:
        return 0
    if n == 1:
        return int(mn <= s <= mx)
    else:
        if s < mn:
            return 0
        return sum(f(s-i, n-1, mn, mx) for i in range(mn, mx+1))

这意味着有 9'483'280 种可能性,在 4'747'561'509'943 种可能性中产生 12 的总和,即 0.00019975%。因此,大约需要 500'624 次迭代才能得出这样的解决方案。

因此,我们应该更好地寻找一种直接的方式来生成这样的序列。我们可以通过每次计算生成一个数字的概率来做到这一点:生成 i 作为数字作为 n 个数字序列中的第一个数字的概率,总和为 s 是 F(si, n-1, 0, 6)/F(s, n, 0, 6)。这将保证我们在可能性列表上生成一个 uniform 列表,如果我们每次都绘制一个统一的数字,那么它将不会匹配与给定值匹配的整个值列表上的统一分布条件:

我们可以递归地做到这一点:

from numpy import choice

def sumseq(n, s, mn, mx):
    if n > 1:
        den = f(s, n, mn, mx)
        val, = choice(
            range(mn, mx+1),
            1,
            p=[f(s-i, n-1, mn, mx)/den for i in range(mn, mx+1)]
        )
        yield val
        yield from sumseq(n-1, s-val, mn, mx)
    elif n > 0:
        yield s

通过上面的函数,我们可以生成numpy数组:

>>> np.array(list(sumseq(15, 12, 0, 6)))
array([0, 0, 0, 0, 0, 4, 0, 3, 0, 1, 0, 0, 1, 2, 1])
>>> np.array(list(sumseq(15, 12, 0, 6)))
array([0, 0, 1, 0, 0, 1, 4, 1, 0, 0, 2, 1, 0, 0, 2])
>>> np.array(list(sumseq(15, 12, 0, 6)))
array([0, 1, 0, 0, 2, 0, 3, 1, 3, 0, 1, 0, 0, 0, 1])
>>> np.array(list(sumseq(15, 12, 0, 6)))
array([5, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1])
>>> np.array(list(sumseq(15, 12, 0, 6)))
array([0, 0, 0, 0, 4, 2, 3, 0, 0, 0, 0, 0, 3, 0, 0])

【讨论】:

这不是以某种奇怪的方式定义的多项分布吗? @SeverinPappadeux:可能是这样,我会尝试看看。我的想法是(a)实现应该是直截了当的,所以没有“拒绝”,并且(2)好像你会从统一抽样中抽取,然后拒绝。如果我看一下公式,可能就是这种情况,尽管证明它可能需要一些工作:) 如果您发现有趣的事情,请给我留言,谢谢 我更新了我的答案,相信我们提出了不同的解决方案,请看一下【参考方案2】:

您可以尝试以不同的方式实现它。

import random
def generate(low,high,goal_sum,size=15):
    output = []
    for i in range(size):
        new_int = random.randint(low,high)
        if sum(output) + new_int <= goal_sum:
            output.append(new_int)
        else:
            output.append(0)
    random.shuffle(output)
    return output

另外,如果你使用 np.random.randint,你的 high 实际上是 high-1

【讨论】:

【参考方案3】:

嗯,有一个简单而自然的解决方案 - 使用分布,它根据定义为您提供具有固定总和的值数组。最简单的是Multinomial Distribution。唯一要添加的代码是如果某个采样值高于最大值,则检查并拒绝(并重复采样)。

顺理成章

import numpy as np

def sample_sum_interval(n, p, maxv):
    while True:
        q = np.random.multinomial(n, p)
        v = np.where(q > maxv)
        if len(v[0]) == 0: # if len(v) > 0, some values are outside the range, reject
            return q
    return None

np.random.seed(32345)

k    = 15
n    = 12
maxv = 6
p = np.full((k), np.float64(1.0)/np.float64(k), dtype=np.float64) # probabilities

q = sample_sum_interval(n, p, maxv)
print(q)
print(np.sum(q))

q = sample_sum_interval(n, p, maxv)
print(q)
print(np.sum(q))

q = sample_sum_interval(n, p, maxv)
print(q)
print(np.sum(q))

更新

我快速查看了@WillemVanOnsem 提出的方法,我相信它与我自己使用的多项式不同。

如果我们查看多项 PMF,并假设所有 k 数字的概率相等, p1 = ... = pk = 1/k,那么我们可以把PMF写成

PMF(x1,...xk)=n!/(x1!...x k!) p1x1...pkxk = n!/(x1!...xk!) k-x1...k -xk = n!/(x1!...xk!) k -Sumixi = n!/(x1!...xk !) k-n

显然,特定 x1...xk 组合的概率会因分母中的阶乘(当然是模排列)而彼此不同,这与@WillemVanOnsem 方法不同,我相信它们都有相同的出现概率。

故事的寓意 - 这些方法产生不同的分布。

【讨论】:

以上是关于生成带有条件的随机数列表 - numpy [重复]的主要内容,如果未能解决你的问题,请参考以下文章

numpy中的非重复随机数

Numpy randint 附加

使用 numpy 生成具有 case-when 条件的随机数据

numpy.random

带有双条件和 || 的 While 循环Swift 中的逻辑运算符

numpy生成随机数组,超几何分布与连续分布