生成具有加权概率的随机数 - 'Distribution' gem

Generating a random number with weighted probability - 'Distribution' gem

我想创建一个随机数生成器,生成一个随机十进制数:

我的数学非常差,但我的研究似乎告诉我,我想从类似于 Fisher–Snedecor (F) 模式的累积分布函数中提取一个随机数,有点像这个:

http://cdn.app.compendium.com/uploads/user/458939f4-fe08-4dbc-b271-efca0f5a2682/742d7708-efd3-492c-abff-6044d78e3bbd/Image/6303a2314437d8fcf2f72d9a56b1293a/f_distribution_probability.png

我正在使用名为 Distribution (https://github.com/sciruby/distribution) 的 Ruby gem 来尝试实现此目的。它看起来像是正确的工具,但我很难理解如何使用它来达到预期的结果:( 请提供任何帮助。

我收回去,没有rng呼吁F。所以,如果你想用Distributiongem,我建议用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}