J'ai plusieurs enregistrements avec un attribut donné et je veux trouver l'écart-type.
Comment je fais ça?
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
correction de "sample_variance" grâce à Dave Sag
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.
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.
#!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
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
.
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
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
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
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.
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