Code pour générer des nombres aléatoires gaussiens (normalement distribués) dans Ruby

Quel est le code pour générer des nombres aléatoires normalement distribués dans ruby?

(Note: j'ai répondu à ma propre question, mais j'attendrai quelques jours avant d'accepter de voir si quelqu'un a une meilleure réponse.)

Modifier:

En cherchant ceci, j'ai regardé toutes les pages sur SO résultant des deux recherches:

+"distribution normale" ruby

Et

+ gaussien + Ruby aléatoire

27
demandé sur Eponymous 2011-04-29 02:16:58

4 réponses

Python aléatoire.gauss () et Boost normal_distribution utilisent tous les deux la transformation Box-Muller , donc cela devrait aussi être suffisant pour 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

La méthode peut être enveloppée dans une classe qui renvoie les échantillons un par un.

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)

Dans la mesure du possible en vertu de la loi, antonakos a renoncé à tous les droits d'auteur et droits connexes ou voisins à la classe RandomGaussian Ruby. Ce travail est publié à partir de: Danemark.


La déclaration de licence ne signifie pas que je me soucie de ce code. Au contraire, je n'utilise pas le code, je ne l'ai pas testé, et je ne Programme pas en Ruby.

46
répondu antonakos 2017-05-23 11:54:50

La question initiale demandait du code, mais le commentaire de suivi de l'auteur impliquait un intérêt à utiliser les bibliothèques existantes. J'étais intéressé par la même chose, et mes recherches ont révélé ces deux gemmes de rubis:

Gsl - "Interface Ruby à la bibliothèque scientifique GNU" (nécessite l'installation de GSL). La séquence d'appel pour les nombres aléatoires normalement distribués avec moyenne = 0 et un écart type donné est

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

Rubystats - " un port des bibliothèques de statistiques de PHPMath" (ruby pur). La séquence d'appel pour les nombres aléatoires normalement distribués avec une moyenne et un écart type donnés est

 gen = Rubystats::NormalDistribution.new(mean, sd)
 gen.rng               # a single random sample
 gen.rng(100)          # 100 random samples
19
répondu ronen 2011-11-21 00:21:47

+ 1 sur la réponse de @antonakos. Voici l'implémentation de Box-Muller que j'ai utilisée; c'est essentiellement identique mais un code légèrement plus serré:

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

Bien sûr, si vous vous souciez vraiment de la vitesse, vous devriez implémenter l'algorithme Ziggurat :).

10
répondu fearless_fool 2012-02-13 19:15:38

Une autre option, celle-ci utilisant la gem distribution , écrite par L'un des boursiers SciRuby.

C'est un peu plus simple à utiliser, je pense.

require 'distribution'
normal = Distribution::Normal.rng(1)
norm_distribution = 1_000.times.map {normal.call}
10
répondu Ryanmt 2015-09-14 00:58:18