numpy 如何在自己的特定范围内生成3个随机变量,但总和为1

zpgglvta  于 12个月前  发布在  其他
关注(0)|答案(6)|浏览(106)

我有三个变量:X,Y,Z。我想用Python生成X,Y,Z的随机数,其中X+Y+Z=1。
然而,每个变量的范围是不同的,例如,X必须介于0.71和0.72之间,Y必须介于0.23和0.24之间,Z必须介于0.05和0.06之间。
变量的精度无关紧要,因此您可以生成数字:电话:+86-021 - 8888888传真:+86-021 - 88888888

9q78igpj

9q78igpj1#

问题是
我们希望在特定范围内选择3个坐标。然而,一旦我们选择XYZ就固定了。

我们不能有完全的自由度,例如,如果我们选择X = 0.72Y = 0.24,那么Z不能存在于期望的范围内。
如果我们随机选择XY并计算Z,在许多情况下Z将是错误的:

size = 10_000

X = np.random.uniform(0.71, 0.72, size=size)
Y = np.random.uniform(0.23, 0.24, size=size)
Z = 1-(X+Y)

问题是:如何解决这个问题?

过采样

这通常不是推荐的方法,但我们可以使用试错法,这里我们需要采样两次以上,以希望有足够的有效点(在正方形上采样,保留一半的点):

target = 100_000
factor = 2.2  # the exact value depends on the target
              # there is always a risk that we miss a few points

size = int(target*factor)
X = np.random.uniform(0.71, 0.72, size=size)
Y = np.random.uniform(0.23, 0.24, size=size)
Z = 1-(X+Y)
idx = np.arange(size)[(Z>=0.05) & (Z<=0.06)][:target]
X = X[idx]
Y = Y[idx]
Z = Z[idx]

assert np.allclose(X+Y+Z, 1)

使用数学
我们所寻找的等价于在三角形上生成均匀点。
可以使用this formula

size = 10_000
limits = [(0.71, 0.72), (0.23, 0.24), (0.05, 0.06)]

MIN, MAX = np.array(limits).T
corners = np.repeat(MIN[None], 3, axis=0)
np.fill_diagonal(corners, MAX)

# or setting the corners manually:
# corners = np.array([[0.72, 0.23, 0.05],
#                     [0.71, 0.24, 0.05],
#                     [0.71, 0.23, 0.06]])

r1 = np.sqrt(np.random.random(size=size))
r2 = np.random.random(size=size)

X, Y, Z = (corners[:,None] * np.array([(1-r1), r1*(1-r2), r2*r1])[...,None]
          ).sum(axis=0).T

assert np.allclose(X+Y+Z, 1)

最后一步使用einsum

X, Y, Z = np.einsum('ik,ij->kj', corners, np.array([(1-r1), r1*(1-r2), r2*r1]))

(The黑点是三角形的角)

  • 注意,要使这种方法有效,两个坐标的最小边界+第三个坐标的最大边界之和必须等于1(Xmax+Ymin+Zmin == Xmin+Ymax+Zmin == Xmin+Ymin+Zmax == 1)。如果不是这样,忽略MAX并使用corners = (1-np.identity(3))*MIN ; np.fill_diagonal(corners, 1-corners.sum(axis=1))
eqqqjvef

eqqqjvef2#

这是生成真正随机变量的简单方法。

import random

def find_xyz():
    while True:
        x = random.uniform(0.71, 0.72)
        y = random.uniform(0.23, 0.24)
        z = 1 - x - y

        if 0.05 < z < 0.06:
            return x, y, z
    

if __name__ == "__main__":
    x, y, z = find_xyz()
    print(x, y, z)
3zwjbxry

3zwjbxry3#

您可以将每个范围转换为从最小值(自由范围)开始的可能增量的基于零的跨度。您知道每个最小值,因此可以从计算中排除这些值,并在从其基础(最小值)随机选择偏移量后将其添加回去。这使得问题变得更简单,因为所有最小值都设置为零,只有x,y,z偏移量需要加起来才能达到剩余的范围。

xMin,xMax = 0.71, 0.72
yMin,yMax = 0.23, 0.24
zMin,zMax = 0.05, 0.06

baseMin = xMin + yMin + zMin   # 0.99
total = 1 - baseMin            # 0.01
xSpan = xMax-xMin              # 0 ... 0.01
ySpan = yMax-yMin              # 0 ... 0.01
zSpan = zMax-zMin              # 0 ... 0.01

import random

xDelta = random.uniform(max(0,total-xSpan-zSpan), xSpan)
yDelta = random.uniform(max(0,total-xDelta-zSpan), min(ySpan, total-xDelta))
zDelta = total - xDelta - yDelta

X = xMin + xDelta
Y = yMin + yDelta
Z = zMin + zDelta

结果:

print(X,Y,Z,"=",X+Y+Z)
print("X is valid: ", xMin <= X <= xMax)
print("Y is valid: ", yMin <= Y <= yMax)
print("Y is valid: ", zMin <= Z <= zMax)

0.7170555223349833 0.23168125318306362 0.05126322448195306 = 1.0
X is valid:  True
Y is valid:  True
Y is valid:  True

第一个随机值(xDelta)受到其他两个值对总增量的最大贡献的约束。第二随机值(yDelta)受(现在已知的)第一值和剩余值的隐式范围的约束。最后一个值在该点没有自由度,必须是总增量的余数。最后一个值将在它指定的范围内,因为前两个值受到了约束。

  • 请注意,您的示例在所有3个值(0.上具有相同的(完全重叠的)自由度范围。0.01),但如果范围大小不同,并且其中一些范围与总自由度范围不完全重叠,则此解决方案也应该有效 *

不幸的是,这种方法并不能给予一个规则的分布(根据mozway的测量)。
在所有3个范围具有相同的间隔大小并且对应于与总最小值的差的特殊情况下(即,完全重叠在你的例子0.01),我能够找到一种方法来产生一个规则的分布,随机挑选两个点在0. 0.01间隔,并使用分割的3个部分作为delta值:

def randXYZ2(xRange,yRange,zRange):
        
    xMin,xMax = xRange
    yMin,yMax = yRange
    zMin,zMax = zRange

    baseMin = xMin + yMin + zMin   # 0.99
    total = 1 - baseMin            # 0.01

    p0 = random.uniform(0,total)
    p1 = random.uniform(0,total)
    if p0>p1: p0,p1 = p1,p0
    
    xDelta = p0       
    yDelta = p1-p0
    zDelta = total - xDelta - yDelta

    X = xMin + xDelta
    Y = yMin + yDelta
    Z = zMin + zDelta

    return X,Y,Z

产出:

size = 3000
xRange = 0.71, 0.72
yRange = 0.23, 0.24
zRange = 0.05, 0.06
samples = [ randXYZ2(xRange,yRange,zRange) for _ in range(size) ]
xs,ys,zs = zip(*samples)

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs,ys,zs,  marker="o", s=1)
plt.show()

niknxzdl

niknxzdl4#

np.random.dirichlet能用吗?

X, Y, Z = 0.01 * np.random.dirichlet([1, 1, 1]) + [0.71, 0.23, 0.05]
kxkpmulp

kxkpmulp5#

以下是节目单-

import random
X = random.uniform(0.71, 0.72)
Y = random.uniform(0.23, 0.24)
Z = 1 - X - Y

# Adjust Z if it is outside the range [0.05, 0.06]
if Z < 0.05:
    Z = 0.05
elif Z > 0.06:
    Z = 0.06

# Print the values of X, Y, and Z
print(f"X = {X:.8f}, Y = {Y:.8f}, Z = {Z:.8f}")

每一次,所有3个变量的值都发生变化,但总和为1。
希望这对你有帮助。

pxyaymoc

pxyaymoc6#

import random

X, Y, Z = 0.0, 0.0, 0.0

epsilon = 0.01

target = 1

while True:
    
    X = random.uniform(0.71, 0.72)
    Y = random.uniform(0.23, 0.24)
    Z = random.uniform(0.05, 0.06)
    
    if target - epsilon <= (X + Y + Z) <= target + epsilon:
        print(X, Y, Z)

upd:

import random

epsilon = 1000

x_d, x_u = 71 * epsilon, 72 * epsilon
y_d, y_u = 23 * epsilon, 24 * epsilon
z_d, z_u = 5 * epsilon, 6 * epsilon

target = 1

while True:
    
    X = random.randint(x_d, x_u)
    Y = random.randint(y_d, y_u)
    Z = random.randint(z_d, z_u)
    
    if (X + Y + Z) == target*epsilon:
        print(X / epsilon, Y / epsilon, Z / epsilon)

相关问题