Как я могу сделать стандартное отклонение в Ruby? - PullRequest
54 голосов
/ 13 октября 2011

У меня есть несколько записей с данным атрибутом, и я хочу найти стандартное отклонение.

Как мне это сделать?

Ответы [ 9 ]

82 голосов
/ 13 октября 2011
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 

Тестирование:

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

01/17/2012:

исправление "sample_variance" благодаря Дэйву Сэгу

34 голосов
/ 06 сентября 2012

Похоже, что Анжела, возможно, хотела существующую библиотеку. После игры со statsample, array-statisics и некоторыми другими, я бы порекомендовал гем descriptive_statistics , если вы пытаетесь не изобретать велосипед заново.

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 

Я не могу говорить ни о его статистической корректности, ни о вашем удобстве с мартышками-патчами Enumerable; но его легко использовать и вносить вклад.

30 голосов
/ 24 ноября 2011

Ответ, приведенный выше, элегантен, но содержит небольшую ошибку.Не будучи статистическим руководителем, я сел и подробно прочитал несколько веб-сайтов и обнаружил, что этот сайт дает наиболее понятное объяснение того, как получить стандартное отклонение.http://sonia.hubpages.com/hub/stddev

Ошибка в ответе выше в методе sample_variance.

Вот моя исправленная версия вместе с простым модульным тестом, который показывает, что он работает.

в ./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

в ./test с использованием чисел, полученных из простой электронной таблицы.

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
9 голосов
/ 15 января 2014

Я не большой поклонник добавления методов к Enumerable, так как могут быть нежелательные побочные эффекты. Он также предоставляет методы, действительно специфичные для массива чисел, для любого класса, наследуемого от Enumerable, что в большинстве случаев не имеет смысла.

Хотя это хорошо для тестов, сценариев или небольших приложений, это опасно для более крупных приложений, поэтому вот альтернатива, основанная на ответе @tolitius, которая уже была идеальной. Это больше для справки, чем что-либо еще:

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

И тогда вы используете его как таковой:

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

Поведение такое же, но оно позволяет избежать накладных расходов и рисков при добавлении методов в Enumerable.

2 голосов
/ 29 июля 2016

Как простая функция, дан список чисел:

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 голосов
/ 06 августа 2015

Представленные вычисления не очень эффективны, потому что они требуют несколько (по крайней мере, два, но часто три, потому что вы обычно хотите представить среднее в дополнение к std-dev) проходов через массив.

Я знаю, что Ruby не место для поиска эффективности, но вот моя реализация, которая вычисляет среднее и стандартное отклонение за один проход значений списка:

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
1 голос
/ 06 февраля 2017

Если имеющиеся записи имеют тип Integer или Rational, вы можете вычислить дисперсию, используя Rational вместо Float, чтобы избежать ошибок, возникающих при округлении.

Например,:

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

(Было бы целесообразно добавить обработку специального случая для пустых списков и других крайних случаев.)

Тогда корень квадратный может быть определен как:

def std_dev(list)
  Math.sqrt(variance(list))
end
0 голосов
/ 02 июня 2015

Или как насчет:

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 голосов
/ 27 февраля 2015

В случае, если люди используют postgres ... он предоставляет агрегатные функции для stddev_pop и stddev_samp - postgresql агрегатные функции

stddev (эквивалент stddev_samp) доступны, по крайней мере, с postgres 7.1, так как8.2 предоставляются как сэмпл, так и поп.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...