生成具有加权概率的随机数 - 'Distribution' gem
Generating a random number with weighted probability - 'Distribution' gem
我想创建一个随机数生成器,生成一个随机十进制数:
- 大于 0.0
- 小于 15.0
- 其中该数字接近 2.0 的概率相对较高
- 它接近15.0或非常接近于零的概率很低
我的数学非常差,但我的研究似乎告诉我,我想从类似于 Fisher–Snedecor (F) 模式的累积分布函数中提取一个随机数,有点像这个:
我正在使用名为 Distribution (https://github.com/sciruby/distribution) 的 Ruby gem 来尝试实现此目的。它看起来像是正确的工具,但我很难理解如何使用它来达到预期的结果:( 请提供任何帮助。
我收回去,没有rng
呼吁F
。所以,如果你想用Distribution
gem,我建议用4个自由度的Chi2。
具有 k
自由度的 Chi2 的众数等于 k-2
,因此对于 4 d.f。您将在 2 获得模式,请参阅 here。我的Ruby生锈了,多多包涵
require 'distribution'
normal = Distribution::Normal.rng(0)
g1 = normal.call
g2 = normal.call
g3 = normal.call
g4 = normal.call
chi2 = g1*g1 + g2*g2 + g3*g3 + g4*g4
更新
你必须在 15 处截断它,所以如果生成的 chi2 大于 15 就拒绝它并生成另一个。虽然我会说你不会看到很多
值高于 15,检查图表 PDF/CDF。
更新二
如果你想从 F
中获取样本,请从上面的代码中为 d
自由度制作通用 Chi2 生成器,然后只采样chi2 比率,检查 here
chi2_d1 = DChi2(d1)
chi2_d2 = DChi2(d2)
f = (chi2_d1.call / d1) / (chi2_d2.call / d2)
更新三
而且,坦率地说,我不明白如何让 F
分发为您工作。在0
还可以,但是众数等于(d1-2)/d1 * d2/(d2 + 2)
,很难看出它等于2。你提供的图表在1/3左右有众数。
这是一个非常粗略、不科学、非数学的尝试,尝试将 F 分布与您在 F 函数图像(3 和 36)中给出的参数一起使用。
首先,我计算 CDF 所需的 F 值为 0.975(100% - 2.5% 为您的数字 15 范围的上限):
计算我们可以使用p_value
这样的方法:
> F_15 = Distribution::F.p_value(0.975, 3, 36)
=> 3.5046846420861977
接下来我们简单地使用一个乘数,这样当我们计算 CDF 时,当 F 值为 F_15
.
时,它会 return 值 15
> M = 15 / F_15
=> 4.27998565687528
现在我们可以使用 rand
生成随机数,其范围为 0..1,如下所示:
[M * Distribution::F.p_value(rand, 3, 36), 15].min
问题是这个函数会以 45% 的概率接近数字 2 吗?好吧..有点。您需要为 F 分布选择正确的参数来调整曲线(或者只调整乘数 M
)。但这里有一个示例,其中包含您图像中的参数:
0.step(0.99, 0.02).map { |n|
sprintf("%0.2f", M * Distribution::F.p_value(n, 3, 36))
}
给你:
["0.00", "0.26", "0.42", "0.57", "0.70", "0.83", "0.95", "1.07",
"1.20", "1.31", "1.43", "1.55", "1.67", "1.80", "1.92", "2.04",
"2.17", "2.30", "2.43", "2.56", "2.70", "2.84", "2.98", "3.13",
"3.28", "3.44", "3.60", "3.77", "3.95", "4.13", "4.32", "4.52",
"4.73", "4.95", "5.18", "5.43", "5.69", "5.97", "6.28", "6.61",
"6.97", "7.37", "7.81", "8.32", "8.90", "9.60", "10.45", "11.56",
"13.14", "15.90"]
有时您会因为数据的性质而知道适用哪种分布。例如,如果随机变量是独立的、相同的伯努利(双态)随机变量的总和,您知道前者具有二项分布,可以用正态分布来近似。当此处不适用时,您可以使用由其参数塑造的连续分布,或简单地使用离散分布。其他人对使用各种连续分布提出了建议,所以我将传递一些关于使用离散分布的评论。
假设离散概率密度函数如下:
pdf = [[0.5, 0.03], [1.0, 0.06], [1.5, 0.10], [ 2.0, 0.15], [2.5 , 0.15], [ 3.0, 0.10],
[4.0, 0.11], [6.0, 0.14], [9.0, 0.10], [12.0, 0.03], [14.0, 0.02], [15.0, 0.01]]
pdf.map(&:last).reduce(:+)
#=> 1.0
这可以解释为随机变量小于 0.5 的概率为 0.03,随机变量大于或等于 0.5 且小于 1.0 的概率为 0.06,依此类推。
离散 pdf 可能是根据历史数据或通过抽样构建的,与使用连续分布相比,它具有优势。可以通过增加间隔数来任意调整。
接下来将pdf转换为累积分布函数:
cum = 0.0
cdf = pdf.map { |k,v| [k, cum += v] }
#=> [[0.5, 0.03], [1.0, 0.09], [1.5, 0.19], [2.0, 0.34], [2.5, 0.49], [3.0, 0.59],
# [4.0, 0.7], [6.0, 0.84], [9.0, 0.94], [12.0, 0.97], [14.0, 0.99], [15.0, 1.0]]
现在使用 Kernel#rand to generate pseudo random variates between 0.0
and 1.0
and use Enumerable#find 将随机变量与 cdf
键相关联:
def rnd(cdf)
r = rand
cdf.find { |k,v| r < v }.first
end
请注意,cdf.find { |k,v| rand < v }.first
会产生错误的结果,因为 rand
是针对 cdf
的每个键值对执行的。
让我们尝试100,000次,记录相对频率
n = 100_000
inc = 1.0/n
n.times.with_object(Hash.new(0.0)) { |_, h| h[rnd(cdf)] += inc }.
sort.
map { |k,v| [k, v.round(5)] }.to_h
#=> { 0.5=>0.03053, 1.0=>0.05992, 1.5=>0.10084, 2.0=>0.14959, 2.5=>0.15024,
# 3.0=>0.10085, 4.0=>0.10946, 6.0=>0.13923, 9.0=>0.09919, 12.0=>0.03073,
# 14.0=>0.01931, 15.0=>0.01011}
我想创建一个随机数生成器,生成一个随机十进制数:
- 大于 0.0
- 小于 15.0
- 其中该数字接近 2.0 的概率相对较高
- 它接近15.0或非常接近于零的概率很低
我的数学非常差,但我的研究似乎告诉我,我想从类似于 Fisher–Snedecor (F) 模式的累积分布函数中提取一个随机数,有点像这个:
我正在使用名为 Distribution (https://github.com/sciruby/distribution) 的 Ruby gem 来尝试实现此目的。它看起来像是正确的工具,但我很难理解如何使用它来达到预期的结果:( 请提供任何帮助。
我收回去,没有rng
呼吁F
。所以,如果你想用Distribution
gem,我建议用4个自由度的Chi2。
具有 k
自由度的 Chi2 的众数等于 k-2
,因此对于 4 d.f。您将在 2 获得模式,请参阅 here。我的Ruby生锈了,多多包涵
require 'distribution'
normal = Distribution::Normal.rng(0)
g1 = normal.call
g2 = normal.call
g3 = normal.call
g4 = normal.call
chi2 = g1*g1 + g2*g2 + g3*g3 + g4*g4
更新
你必须在 15 处截断它,所以如果生成的 chi2 大于 15 就拒绝它并生成另一个。虽然我会说你不会看到很多 值高于 15,检查图表 PDF/CDF。
更新二
如果你想从 F
中获取样本,请从上面的代码中为 d
自由度制作通用 Chi2 生成器,然后只采样chi2 比率,检查 here
chi2_d1 = DChi2(d1)
chi2_d2 = DChi2(d2)
f = (chi2_d1.call / d1) / (chi2_d2.call / d2)
更新三
而且,坦率地说,我不明白如何让 F
分发为您工作。在0
还可以,但是众数等于(d1-2)/d1 * d2/(d2 + 2)
,很难看出它等于2。你提供的图表在1/3左右有众数。
这是一个非常粗略、不科学、非数学的尝试,尝试将 F 分布与您在 F 函数图像(3 和 36)中给出的参数一起使用。
首先,我计算 CDF 所需的 F 值为 0.975(100% - 2.5% 为您的数字 15 范围的上限):
计算我们可以使用p_value
这样的方法:
> F_15 = Distribution::F.p_value(0.975, 3, 36)
=> 3.5046846420861977
接下来我们简单地使用一个乘数,这样当我们计算 CDF 时,当 F 值为 F_15
.
> M = 15 / F_15
=> 4.27998565687528
现在我们可以使用 rand
生成随机数,其范围为 0..1,如下所示:
[M * Distribution::F.p_value(rand, 3, 36), 15].min
问题是这个函数会以 45% 的概率接近数字 2 吗?好吧..有点。您需要为 F 分布选择正确的参数来调整曲线(或者只调整乘数 M
)。但这里有一个示例,其中包含您图像中的参数:
0.step(0.99, 0.02).map { |n|
sprintf("%0.2f", M * Distribution::F.p_value(n, 3, 36))
}
给你:
["0.00", "0.26", "0.42", "0.57", "0.70", "0.83", "0.95", "1.07",
"1.20", "1.31", "1.43", "1.55", "1.67", "1.80", "1.92", "2.04",
"2.17", "2.30", "2.43", "2.56", "2.70", "2.84", "2.98", "3.13",
"3.28", "3.44", "3.60", "3.77", "3.95", "4.13", "4.32", "4.52",
"4.73", "4.95", "5.18", "5.43", "5.69", "5.97", "6.28", "6.61",
"6.97", "7.37", "7.81", "8.32", "8.90", "9.60", "10.45", "11.56",
"13.14", "15.90"]
有时您会因为数据的性质而知道适用哪种分布。例如,如果随机变量是独立的、相同的伯努利(双态)随机变量的总和,您知道前者具有二项分布,可以用正态分布来近似。当此处不适用时,您可以使用由其参数塑造的连续分布,或简单地使用离散分布。其他人对使用各种连续分布提出了建议,所以我将传递一些关于使用离散分布的评论。
假设离散概率密度函数如下:
pdf = [[0.5, 0.03], [1.0, 0.06], [1.5, 0.10], [ 2.0, 0.15], [2.5 , 0.15], [ 3.0, 0.10],
[4.0, 0.11], [6.0, 0.14], [9.0, 0.10], [12.0, 0.03], [14.0, 0.02], [15.0, 0.01]]
pdf.map(&:last).reduce(:+)
#=> 1.0
这可以解释为随机变量小于 0.5 的概率为 0.03,随机变量大于或等于 0.5 且小于 1.0 的概率为 0.06,依此类推。
离散 pdf 可能是根据历史数据或通过抽样构建的,与使用连续分布相比,它具有优势。可以通过增加间隔数来任意调整。
接下来将pdf转换为累积分布函数:
cum = 0.0
cdf = pdf.map { |k,v| [k, cum += v] }
#=> [[0.5, 0.03], [1.0, 0.09], [1.5, 0.19], [2.0, 0.34], [2.5, 0.49], [3.0, 0.59],
# [4.0, 0.7], [6.0, 0.84], [9.0, 0.94], [12.0, 0.97], [14.0, 0.99], [15.0, 1.0]]
现在使用 Kernel#rand to generate pseudo random variates between 0.0
and 1.0
and use Enumerable#find 将随机变量与 cdf
键相关联:
def rnd(cdf)
r = rand
cdf.find { |k,v| r < v }.first
end
请注意,cdf.find { |k,v| rand < v }.first
会产生错误的结果,因为 rand
是针对 cdf
的每个键值对执行的。
让我们尝试100,000次,记录相对频率
n = 100_000
inc = 1.0/n
n.times.with_object(Hash.new(0.0)) { |_, h| h[rnd(cdf)] += inc }.
sort.
map { |k,v| [k, v.round(5)] }.to_h
#=> { 0.5=>0.03053, 1.0=>0.05992, 1.5=>0.10084, 2.0=>0.14959, 2.5=>0.15024,
# 3.0=>0.10085, 4.0=>0.10946, 6.0=>0.13923, 9.0=>0.09919, 12.0=>0.03073,
# 14.0=>0.01931, 15.0=>0.01011}