【问题标题】:Code to generate Gaussian (normally distributed) random numbers in Ruby在 Ruby 中生成高斯(正态分布)随机数的代码
【发布时间】:2011-04-28 22:16:58
【问题描述】:

什么是在 ruby​​ 中生成正态分布随机数的代码?

(注意:我回答了我自己的问题,但我会等几天再接受,看看是否有人有更好的答案。)

编辑:

搜索此内容时,我查看了由两次搜索产生的 SO 上的所有页面:

+“正态分布”红宝石

+高斯+随机红宝石

【问题讨论】:

  • 您是否检查过相关问题(见右侧面板)?
  • 是的,我查过,虽然有些地方有算法,但没有人用 Ruby 编写过代码。这是一个非常常见的任务,它真的应该在标准库中。但如果做不到这一点,我认为应该可以在 StackOverflow 上找到复制粘贴代码。
  • 提及您检查的内容可能是个好主意,这样想回答的人就不会检查它们,除非他们认为您错过了什么。

标签: ruby random normal-distribution


【解决方案1】:

Python 的 random.gauss() 和 Boost 的 normal_distribution 都使用 Box-Muller transform,所以这对于 Ruby 来说也应该足够了。

def gaussian(mean, stddev, rand)
  theta = 2 * Math::PI * rand.call
  rho = Math.sqrt(-2 * Math.log(1 - rand.call))
  scale = stddev * rho
  x = mean + scale * Math.cos(theta)
  y = mean + scale * Math.sin(theta)
  return x, y
end

该方法可以封装在一个类中,逐个返回样本。

class RandomGaussian
  def initialize(mean, stddev, rand_helper = lambda { Kernel.rand })
    @rand_helper = rand_helper
    @mean = mean
    @stddev = stddev
    @valid = false
    @next = 0
  end

  def rand
    if @valid then
      @valid = false
      return @next
    else
      @valid = true
      x, y = self.class.gaussian(@mean, @stddev, @rand_helper)
      @next = y
      return x
    end
  end

  private
  def self.gaussian(mean, stddev, rand)
    theta = 2 * Math::PI * rand.call
    rho = Math.sqrt(-2 * Math.log(1 - rand.call))
    scale = stddev * rho
    x = mean + scale * Math.cos(theta)
    y = mean + scale * Math.sin(theta)
    return x, y
  end
end

(CC0)

在法律允许的范围内,antonakos 放弃了对RandomGaussian Ruby 类的所有版权和相关或邻近权利。本作品发表于:丹麦。

【讨论】:

  • 您能否为您的代码添加一个许可许可证(BSD/CC-0 或类似的)(因为它旨在用于剪切粘贴重用)
  • @antonakos 在我的 openoffice calc 我有一个名为 NORMDIST 的函数,带有参数(数字、平均值、STDEV、C),我如何使用您的代码给出 4 个参数? (您的代码接受 mean, stddev, rand 这与我在 excel/openoffice calc 中使用的不同)
  • 我想知道你为什么要使用 Math.log(1 - rand.call) 而不是 Math.log(rand.call),因为 rand.call 返回一个介于 0 和 1 之间的数字。我检查了 python 和 boost 代码,他们也这样做了...但是为什么呢?
  • Ruby 中最小的非零浮点数是0.0.next_float => 5.0e-3241.0 以下的最大浮点数是1.0.prev_float => 0.9999999999999999。我不知道Kernel.rand 是否可以返回5.0e-324,但如果可以,那么减法会截断随机变量的尾部。
【解决方案2】:

最初的问题要求提供代码,但作者的后续评论暗示了对使用现有库的兴趣。我对此也很感兴趣,我的搜索发现了这两个红宝石:

gsl - “GNU 科学库的 Ruby 接口”(需要您安装 GSL)。均值为 0 且给定标准差的正态分布随机数的调用序列为

 rng = GSL::Rng.alloc
 rng.gaussian(sd)      # a single random sample
 rng.gaussian(sd, 100) # 100 random samples

rubystats - “来自 PHPMath 的统计库的一个端口”(纯 ruby​​)。给定均值和标准差的正态分布随机数的调用序列为

 gen = Rubystats::NormalDistribution.new(mean, sd)
 gen.rng               # a single random sample
 gen.rng(100)          # 100 random samples

【讨论】:

    【解决方案3】:

    +1 对@antonakos 的回答。这是我一直在使用的 Box-Muller 的实现;它本质上是相同的,但代码更紧凑:

    class RandomGaussian
      def initialize(mean = 0.0, sd = 1.0, rng = lambda { Kernel.rand })
        @mean, @sd, @rng = mean, sd, rng
        @compute_next_pair = false
      end
    
      def rand
        if (@compute_next_pair = !@compute_next_pair)
          # Compute a pair of random values with normal distribution.
          # See http://en.wikipedia.org/wiki/Box-Muller_transform
          theta = 2 * Math::PI * @rng.call
          scale = @sd * Math.sqrt(-2 * Math.log(1 - @rng.call))
          @g1 = @mean + scale * Math.sin(theta)
          @g0 = @mean + scale * Math.cos(theta)
        else
          @g1
        end
      end
    end
    

    当然,如果你真的关心速度,你应该实现Ziggurat Algorithm :)。

    【讨论】:

      【解决方案4】:

      另一个选项,这个使用 distribution gem,由 SciRuby 研究员之一编写。

      我认为它使用起来更简单一些。

      require 'distribution'
      normal = Distribution::Normal.rng(1)
      norm_distribution = 1_000.times.map {normal.call}
      

      【讨论】:

      • 看这段代码,我不知道预期的标准差应该是多少。
      • 转到此处:rubydoc.info/gems/distribution/0.7.0/Distribution/Normal/Ruby_ 您会看到 rng(1) 指定平均值,并且您可以通过传递您调用的附加参数 Distribution::Normal.rng(mean, standard_deviation) 来指定所需的标准差在该分布中提供一个随机值。
      猜你喜欢
      • 2011-06-12
      • 2020-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-26
      • 2012-12-11
      • 2018-06-22
      • 1970-01-01
      相关资源
      最近更新 更多