web-dev-qa-db-fra.com

Comment puis-je faire l'écart-type dans Ruby?

J'ai plusieurs enregistrements avec un attribut donné et je veux trouver l'écart-type.

Comment je fais ça?

54
Timothy T.
module Enumerable

    def sum
      self.inject(0){|accum, i| accum + i }
    end

    def mean
      self.sum/self.length.to_f
    end

    def sample_variance
      m = self.mean
      sum = self.inject(0){|accum, i| accum +(i-m)**2 }
      sum/(self.length - 1).to_f
    end

    def standard_deviation
      Math.sqrt(self.sample_variance)
    end

end 

Le tester:

a = [ 20, 23, 23, 24, 25, 22, 12, 21, 29 ]
a.standard_deviation  
# => 4.594682917363407

17/01/2012:

correction de "sample_variance" grâce à Dave Sag

82
tolitius

Il semble qu'Angela ait pu vouloir une bibliothèque existante. Après avoir joué avec statsample, array-statisics et quelques autres, je recommanderais la gemme descriptive_statistics si vous essayez d'éviter de réinventer la roue.

gem install descriptive_statistics
$ irb
1.9.2 :001 > require 'descriptive_statistics'
 => true 
1.9.2 :002 > samples = [1, 2, 2.2, 2.3, 4, 5]
 => [1, 2, 2.2, 2.3, 4, 5] 
1.9.2p290 :003 > samples.sum
 => 16.5 
1.9.2 :004 > samples.mean
 => 2.75 
1.9.2 :005 > samples.variance
 => 1.7924999999999998 
1.9.2 :006 > samples.standard_deviation
 => 1.3388427838995882 

Je ne peux pas parler de son exactitude statistique ou de votre confort avec le patch de singe Enumerable; mais il est facile à utiliser et à contribuer.

34
eprothro

La réponse donnée ci-dessus est élégante mais contient une légère erreur. N'étant pas moi-même un responsable des statistiques, je me suis assis et j'ai lu en détail un certain nombre de sites Web et j'ai trouvé que celui-ci donnait l'explication la plus compréhensible de la façon de dériver un écart-type. http://sonia.hubpages.com/hub/stddev

L'erreur dans la réponse ci-dessus est dans le sample_variance méthode.

Voici ma version corrigée, ainsi qu'un simple test unitaire qui montre que cela fonctionne.

dans ./lib/enumerable/standard_deviation.rb

#!usr/bin/Ruby

module Enumerable

  def sum
    return self.inject(0){|accum, i| accum + i }
  end

  def mean
    return self.sum / self.length.to_f
  end

  def sample_variance
    m = self.mean
    sum = self.inject(0){|accum, i| accum + (i - m) ** 2 }
    return sum / (self.length - 1).to_f
  end

  def standard_deviation
    return Math.sqrt(self.sample_variance)
  end

end

dans ./test en utilisant des nombres dérivés d'une simple feuille de calcul.

Screen Snapshot of a Numbers spreadsheet with example data

#!usr/bin/Ruby

require 'enumerable/standard_deviation'

class StandardDeviationTest < Test::Unit::TestCase

  THE_NUMBERS = [1, 2, 2.2, 2.3, 4, 5]

  def test_sum
    expected = 16.5
    result = THE_NUMBERS.sum
    assert result == expected, "expected #{expected} but got #{result}"
  end

  def test_mean
    expected = 2.75
    result = THE_NUMBERS.mean
    assert result == expected, "expected #{expected} but got #{result}"
  end

  def test_sample_variance
    expected = 2.151
    result = THE_NUMBERS.sample_variance
    assert result == expected, "expected #{expected} but got #{result}"
  end

  def test_standard_deviation
    expected = 1.4666287874
    result = THE_NUMBERS.standard_deviation
    assert result.round(10) == expected, "expected #{expected} but got #{result}"
  end

end
30
Dave Sag

Je ne suis pas un grand fan de l'ajout de méthodes à Enumerable car il pourrait y avoir des effets secondaires indésirables. Il donne également des méthodes vraiment spécifiques à un tableau de nombres à n'importe quelle classe héritant de Enumerable, ce qui n'a pas de sens dans la plupart des cas.

Bien que cela soit bien pour les tests, les scripts ou les petites applications, c'est risqué pour les applications plus grandes, alors voici une alternative basée sur la réponse de @tolitius qui était déjà parfaite. C'est plus pour référence qu'autre chose:

module MyApp::Maths
  def self.sum(a)
    a.inject(0){ |accum, i| accum + i }
  end

  def self.mean(a)
    sum(a) / a.length.to_f
  end

  def self.sample_variance(a)
    m = mean(a)
    sum = a.inject(0){ |accum, i| accum + (i - m) ** 2 }
    sum / (a.length - 1).to_f
  end

  def self.standard_deviation(a)
    Math.sqrt(sample_variance(a))
  end
end

Et puis vous l'utilisez comme tel:

2.0.0p353 > MyApp::Maths.standard_deviation([1,2,3,4,5])
=> 1.5811388300841898

2.0.0p353 :007 > a = [ 20, 23, 23, 24, 25, 22, 12, 21, 29 ]
 => [20, 23, 23, 24, 25, 22, 12, 21, 29]

2.0.0p353 :008 > MyApp::Maths.standard_deviation(a)
 => 4.594682917363407

2.0.0p353 :043 > MyApp::Maths.standard_deviation([1,2,2.2,2.3,4,5])
 => 1.466628787389638

Le comportement est le même, mais il évite les frais généraux et les risques d'ajouter des méthodes à Enumerable.

9
marcgg

En tant que fonction simple, étant donné une liste de nombres:

def standard_deviation(list)
  mean = list.inject(:+) / list.length.to_f
  var_sum = list.map{|n| (n-mean)**2}.inject(:+).to_f
  sample_variance = var_sum / (list.length - 1)
  Math.sqrt(sample_variance)
end
2
tothemario

Les calculs présentés ne sont pas très efficaces car ils nécessitent plusieurs (au moins deux, mais souvent trois car vous voulez généralement présenter la moyenne en plus de std-dev) à travers le tableau.

Je sais Ruby n'est pas l'endroit idéal pour rechercher l'efficacité, mais voici mon implémentation qui calcule la moyenne et l'écart-type en un seul passage sur les valeurs de la liste:

module Enumerable

  def avg_stddev
    return nil unless count > 0
    return [ first, 0 ] if count == 1
    sx = sx2 = 0
    each do |x|
      sx2 += x**2
      sx += x
    end
    [ 
      sx.to_f  / count,
      Math.sqrt( # http://wijmo.com/docs/spreadjs/STDEV.html
        (sx2 - sx**2.0/count)
        / 
        (count - 1)
      )
    ]
  end

end
2
Guss

Si les enregistrements disponibles sont de type Integer ou Rational, vous souhaiterez peut-être calculer la variance à l'aide de Rational au lieu de Float pour éviter les erreurs introduites par l'arrondi. .

Par exemple:

def variance(list)
  mean = list.reduce(:+)/list.length.to_r
  sum_of_squared_differences = list.map { |i| (i - mean)**2 }.reduce(:+)
  sum_of_squared_differences/list.length
end

(Il serait prudent d'ajouter la gestion des cas spéciaux pour les listes vides et les autres cas Edge.)

La racine carrée peut alors être définie comme:

def std_dev(list)
  Math.sqrt(variance(list))
end
1
Peter Kagey

Dans le cas où les gens utilisent postgres ... il fournit des fonctions d'agrégation pour stddev_pop et stddev_samp - fonctions d'agrégation postgresql

stddev (équiv de stddev_samp) disponible depuis au moins postgres 7.1, depuis 8.2 samp et pop sont fournis.

0
Straff

Ou que diriez-vous:

class Stats
    def initialize( a )
        @avg = a.count > 0 ? a.sum / a.count.to_f : 0.0
        @stdev = a.count > 0 ? ( a.reduce(0){ |sum, v| sum + (@avg - v) ** 2 } / a.count ) ** 0.5 : 0.0
    end
end
0
Mads Boyd-Madsen