ruby - variacion - desviacion estandar y varianza
¿Cómo puedo hacer desviación estándar en Ruby? (9)
Tengo varios registros con un atributo dado, y quiero encontrar la desviación estándar.
¿Cómo puedo hacer eso?
Como una función simple, dada una lista de números:
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
En caso de que la gente esté usando postgres ... proporciona funciones agregadas para stddev_pop y stddev_samp - funciones agregadas postgresql
stddev (equivalente a stddev_samp) disponible desde al menos postgres 7.1, ya que se proporcionan 8.2 samp y pop.
La respuesta dada arriba es elegante pero tiene un ligero error. Al no ser un jefe de estadísticas, me senté y leí en detalle una serie de sitios web y encontré que este daba la explicación más comprensible de cómo derivar una desviación estándar. http://sonia.hubpages.com/hub/stddev
El error en la respuesta anterior está en el método sample_variance
.
Aquí está mi versión corregida, junto con una prueba de unidad simple que muestra que funciona.
en ./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
en ./test
usando números derivados de una hoja de cálculo simple.
#!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
Los cálculos presentados no son muy eficientes porque requieren varios (al menos dos, pero a menudo tres porque normalmente desea presentar un promedio además de std-dev) a través de la matriz.
Sé que Ruby no es el lugar para buscar eficiencia, pero aquí está mi implementación que calcula el promedio y la desviación estándar con una sola pasada sobre los valores de la lista:
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
No soy un gran fan de agregar métodos a Enumerable
ya que podría haber efectos secundarios no deseados. También proporciona métodos realmente específicos a una serie de números para cualquier clase heredada de Enumerable
, lo que no tiene sentido en la mayoría de los casos.
Si bien esto está bien para pruebas, scripts o aplicaciones pequeñas, es riesgoso para aplicaciones más grandes, así que aquí hay una alternativa basada en la respuesta de @tolitius que ya era perfecta. Esto es más para referencia que cualquier otra cosa:
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
Y luego lo usas como tal:
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
El comportamiento es el mismo, pero evita los gastos generales y los riesgos de agregar métodos a Enumerable
.
O ¿qué tal
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
Parece que Angela puede haber estado queriendo una biblioteca existente. Después de jugar con statsample, array-statisics y algunos otros, recomendaría la gema descriptive_statistics si intentas evitar reinventar la rueda.
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
No puedo hablar de su corrección estadística, ni de su comodidad con el parche de monos Enumerable; pero es fácil de usar y es fácil de contribuir.
Si los registros disponibles son de tipo Integer
o Rational
, es posible que desee calcular la varianza utilizando Rational
lugar de Float
para evitar errores introducidos por el redondeo.
Por ejemplo:
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
(Sería prudente agregar el manejo de casos especiales para listas vacías y otros casos de borde).
Entonces la raíz cuadrada se puede definir como:
def std_dev(list)
Math.sqrt(variance(list))
end
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
Probandolo
a = [ 20, 23, 23, 24, 25, 22, 12, 21, 29 ]
a.standard_deviation
# => 4.594682917363407
17/01/2012:
arreglando "sample_variance" gracias a Dave Sag