Código para generar números aleatorios gaussianos (normalmente distribuidos) en Ruby


¿Qué es un código para generar números aleatorios normalmente distribuidos en ruby?

(Nota: Respondí mi propia pregunta, pero esperaré unos días antes de aceptar para ver si alguien tiene una mejor respuesta.)

EDITAR:

Buscando esto, miré todas las páginas en LO que resulta de las dos búsquedas:

+ "distribución normal" ruby

Y

+ gaussian + random ruby

Author: Eponymous, 2011-04-29

4 answers

Python es aleatorio.gauss () y Boost normal_distribution ambos usan la transformada Box-Muller, por lo que debería ser suficiente para Ruby también.

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

El método se puede envolver en una clase que devuelve las muestras una por una.

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(CC0)

En la medida de lo posible por ley, antonakos ha renunciado a todos los derechos de autor y derechos relacionados o conexos a la clase RandomGaussian Ruby. Este trabajo se publica desde: Dinamarca.


La declaración de licencia no significa que me importe este código. Por el contrario, no uso el código, no lo he probado, y no programo en Ruby.

 46
Author: antonakos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:50

La pregunta original pedía código, pero el comentario de seguimiento del autor implicaba un interés en usar bibliotecas existentes. Yo estaba interesado en lo mismo, y mis búsquedas dieron como resultado estas dos gemas de rubí:

Gsl - "Interfaz Ruby para la Biblioteca Científica GNU" (requiere instalar GSL). La secuencia de llamada para números aleatorios normalmente distribuidos con media = 0 y una desviación estándar dada es

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

Rubystats - " un port de las bibliotecas de estadísticas from PHPMath " (pure ruby). La secuencia de llamada para números aleatorios normalmente distribuidos con una media y desviación estándar dada es

 gen = Rubystats::NormalDistribution.new(mean, sd)
 gen.rng               # a single random sample
 gen.rng(100)          # 100 random samples
 19
Author: ronen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-21 00:21:47

+1 en la respuesta de @antonakos. Aquí está la implementación de Box-Muller que he estado usando; es esencialmente idéntico pero un poco más estricto código:

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

Por supuesto, si realmente te importa la velocidad, deberías implementar el Algoritmo Zigurat :).

 10
Author: fearless_fool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-13 19:15:38

Otra opción, esta usando la gema distribution, escrita por uno de los SciRuby fellows.

Es un poco más simple de usar, creo.

require 'distribution'
normal = Distribution::Normal.rng(1)
norm_distribution = 1_000.times.map {normal.call}
 10
Author: Ryanmt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-14 00:58:18