生成具有加权概率的随机数 - '分布'宝石

时间:2016-07-24 17:27:59

标签: ruby probability distribution

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

  • 大于0.0
  • 小于15.0
  • 该数字接近2.0的概率相对较高
  • 接近15.0或非常接近于零的概率非常低

我在数学上非常匮乏,但我的研究似乎告诉我,我想从类似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来尝试实现这一目标。它看起来像是正确的工具,但我正在努力了解如何使用它来达到预期的结果:(请帮助。

3 个答案:

答案 0 :(得分:2)

我会收回它,rng没有F号召唤。所以,如果你想使用Distribution gem,我建议使用4个自由度的Chi 2

具有k自由度的Chi 2 的模式等于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的图表。

更新II

如果您想从F获取样本,请从上面的代码中为d自由度制作通用Chi 2 生成器,并且只采样chi2的比例,检查here

chi2_d1 = DChi2(d1)
chi2_d2 = DChi2(d2)

f = (chi2_d1.call / d1) / (chi2_d2.call / d2)

更新III

而且,坦率地说,我不知道如何让F发行版为你工作。可以0,但模式等于(d1-2)/d1 * d2/(d2 + 2),很难看出它等于2.你提供的图表的模式大约为1/3。

答案 1 :(得分:2)

这是一个非常粗糙,不科学,非拙劣的尝试,使用你在F函数图像(3和36)中给出的参数进行F分布。

首先,我计算出CDF需要的F值为0.975(对于15号的范围的上限,100% - 2.5%):

要计算我们可以使用p_value方法,如下所示:

> F_15 = Distribution::F.p_value(0.975, 3, 36)
=> 3.5046846420861977

接下来我们只使用一个乘数,这样当我们计算CDF时,当F值为F_15时,它将返回值15.

> M = 15 / F_15
=> 4.27998565687528

现在我们可以使用rand生成随机数,其范围为0..1,如下所示:

[M * Distribution::F.p_value(rand, 3, 36), 15].min

问题是这个函数是否接近2号,概率为45%?嗯..很多。您需要为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"]

答案 2 :(得分:1)

有时您会因为数据的性质而知道哪个分布适用。例如,如果随机变量是独立的,相同的伯努利(两态)随机变量的总和,则您知道前者具有二项分布,其可以通过正态分布近似。当这里不适用时,您可以使用由其参数形成的连续分布,或者仅使用离散分布。其他人已经提出了使用各种连续分布的建议,因此我将传递关于使用离散分布的一些评论。

假设离散概率密度函数如下:

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,等等。

离散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#rand0.01.0之间生成伪随机变量,并使用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}