【问题标题】:How to generate random values in range (-1, 1) such that the total sum is 0?如何在 (-1, 1) 范围内生成随机值,使总和为 0?
【发布时间】:2020-02-12 21:46:24
【问题描述】:

如果总和为 1,我可以将这些值除以它们的总和。但是,当总和为0时,这种方法不适用。

也许我可以计算我采样的每个值的相反值,所以我总是会有一对数字,这样它们的总和为 0。但是这种方法减少了我希望在我的随机数组中拥有的“随机性”。

有更好的方法吗?

编辑:数组长度可以变化(从 3 到几百),但必须在采样前固定。

【问题讨论】:

  • 您的零和需要达到多少零?超过多少个数字?
  • @norok2 数组长度可以是可变的(从 3 到几百)。如果无法得到精确的总和,则总和应至少尽可能接近零。
  • 您可以通过生成相反的数字来使用您的方法的变体,然后向这些数字添加随机噪声。

标签: python random


【解决方案1】:

有一个Dirichlet-Rescale (DRS) algorithm 生成随机数,总和为给定数。正如它所说,它具有

向量均匀分布在 所有可能向量的域,以约束为界。

还有一个Python library

【讨论】:

  • 很好的参考。要回答具体问题,可以使用result = drs(n, 0, [1 for _ in range(n)], [-1 for _ in range(n)])
【解决方案2】:

您可以使用 sklearns Standardscaler。它将您的数据缩放为方差为 1,均值为 0。0 的均值等于 0 的总和。

from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
rand_numbers = StandardScaler().fit_transform(np.random.rand(100,1, ))

如果你不想用sklearn你可以手动标准化,公式很简单:

rand_numbers = np.random.rand(1000,1, )
rand_numbers = (rand_numbers - np.mean(rand_numbers)) / np.std(rand_numbers)  

这里的问题是 1 的方差,它导致数字大于 1 或小于 -1。因此,您可以通过其最大绝对值来划分数组。

rand_numbers = rand_numbers*(1/max(abs(rand_numbers)))

现在您有了一个值介于 -1 和 1 之间且总和非常接近于零的数组。

print(sum(rand_numbers))
print(min(rand_numbers))
print(max(rand_numbers))

输出:

[-1.51822999e-14]
[-0.99356294]
[1.]

使用此解决方案,您的数据始终是一个 1 或一个 -1。如果您想避免这种情况,您可以通过最大 abs 为除法添加一个正随机因子。 rand_numbers*(1/(max(abs(rand_numbers))+randomfactor))

编辑

正如@KarlKnechtel 提到的,除以标准偏差与除以最大绝对值是多余的。

以上可以通过以下方式简单完成:

rand_numbers = np.random.rand(100000,1, )
rand_numbers = rand_numbers - np.mean(rand_numbers)
rand_numbers = rand_numbers / max(abs(rand_numbers))

【讨论】:

  • 谢谢!上下限不是严格的限制,所以我绝对可以使用你提出的方法。
  • 请注意,这不是均匀分布的,而是高斯分布。
  • @norok 是什么让你得出这个结论?我不明白为什么它会变成高斯分布。我刚刚绘制了使用上述方法创建的 100 万个随机数及其正态分布的直方图。
  • 除以标准差与除以最大绝对值是多余的。
  • @FlorianH 实际上,你是对的。该分布将只是您传递的任何分布的缩放。我的错。
【解决方案3】:

我会尝试以下解决方案:

def draw_randoms_while_sum_not_zero(eps):
    r = random.uniform(-1, 1)
    sum = r
    yield r
    while abs(sum) > eps:
        if sum > 0:
            r = random.uniform(-1, 0)
        else:
            r = random.uniform(0,1)
        sum += r
        yield r

由于浮点数并不完全准确,因此您永远无法确定您绘制的数字总和可能为 0。您需要确定可接受的边距并调用上述生成器。

只要它们的总和不等于0 ± eps,它就会根据您的需要生成(延迟返回)随机数

epss = [0.1, 0.01, 0.001, 0.0001, 0.00001]

for eps in epss:
    lengths = []
    for _ in range(100):
        lengths.append(len(list(draw_randoms_while_sum_not_zero(eps))))
    print(f'{eps}: min={min(lengths)}, max={max(lengths)}, avg={sum(lengths)/len(lengths)}')

结果:

0.1: min=1, max=24, avg=6.1
0.01: min=1, max=174, avg=49.27
0.001: min=4, max=2837, avg=421.41
0.0001: min=5, max=21830, avg=4486.51
1e-05: min=183, max=226286, avg=48754.42

【讨论】:

  • 这对于小尺寸和非常低的 eps 需要很长时间,并且无法控制序列的长度。
  • 谢谢!这是一个有趣的方法,但正如前面评论中提到的,它没有考虑数组的长度。
  • @phoenix 只需添加计数
【解决方案4】:

既然您对生成大量数字并除以总和的方法很好,为什么不生成 n/2 个正数除以总和。然后生成n/2个负数并除以和?

想要随机的正负混合?首先随机生成该混合然后继续。

【讨论】:

    【解决方案5】:

    生成此类列表的一种方法是使用相反的数字。 如果这不是一个理想的属性,您可以通过向不同的相反对添加/减去相同的随机值来引入一些额外的随机性,例如:

    def exact_sum_uniform_random(num, min_val=-1.0, max_val=1.0, epsilon=0.1):
        items = [random.uniform(min_val, max_val) for _ in range(num // 2)]
        opposites = [-x for x in items]
        if num % 2 != 0:
            items.append(0.0)
        for i in range(len(items)):
            diff = random.random() * epsilon
            if items[i] + diff <= max_val \
                    and any(opposite - diff >= min_val for opposite in opposites):
                items[i] += diff
                modified = False
                while not modified:
                    j = random.randint(0, num // 2 - 1)
                    if opposites[j] - diff >= min_val:
                        opposites[j] -= diff
                        modified = True
        result = items + opposites
        random.shuffle(result)
        return result
    
    
    random.seed(0)
    x = exact_sum_uniform_random(3)
    print(x, sum(x))
    # [0.7646391433441265, -0.7686875811622043, 0.004048437818077755] 2.2551405187698492e-17
    

    编辑

    如果上限和下限不严格,构建零和序列的一种简单方法是将两个单独的序列和归一化为 1 和 -1 并将它们连接在一起:

    def norm(items, scale):
        return [item / scale for item in items]
    
    
    def zero_sum_uniform_random(num, min_val=-1.0, max_val=1.0):
        a = [random.uniform(min_val, max_val) for _ in range(num // 2)]
        a = norm(a, sum(a))
        b = [random.uniform(min_val, max_val) for _ in range(num - len(a))]
        b = norm(b, -sum(b))
        result = a + b
        random.shuffle(result)
        return result
    
    
    random.seed(0)
    n = 3
    x = exact_mean_uniform_random(n)
    print(exact_mean_uniform_random(n), sum(x))
    # [1.0, 2.2578843364303585, -3.2578843364303585] 0.0
    

    请注意,这两种方法通常不会具有均匀分布。

    【讨论】:

    • 谢谢!但是只有当我采取大量样本时,总和才会收敛到 0。例如,如果我只有 3 个样本,我的总和仍然会很大。
    • @phoenix Uhm,你指的是exact_sum_uniform_random()吗?
    • @phoenix 在示例中,我展示了 3 个样本,总和接近 0 (2.2551405187698492e-17)。但如果上限/下限不严格,则使用zero_sum_uniform_random() 中的方法可能会更好。
    • 不,我指的是您之前的回答。关于您当前的答案,总和是准确的,但在我的情况下随机性更为重要。
    • @phoenix 但这和random.uniform() 一样随机......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    • 2011-10-20
    • 2023-03-28
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多