【问题标题】:How can I do standard deviation in Ruby?如何在 Ruby 中做标准差?
【发布时间】:2011-10-13 04:47:29
【问题描述】:

我有几个具有给定属性的记录,我想找到标准差。

我该怎么做?

【问题讨论】:

    标签: ruby standard-deviation


    【解决方案1】:
    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
    

    2012 年 1 月 17 日:

    感谢 Dave Sag,修复了“sample_variance”

    【讨论】:

    • 您的sample_variance 方法中存在错误。请参阅下面的答案。
    • 你不需要在Ruby中写那么多“self”或“return”。
    • sum/(self.length - 1).to_f这行你为什么要从Enumerable的长度中减去1?
    • 我认为 sum/(self.length - 1).to_f 应该是 sum/length,我不认为 -1 是必要的并且会导致问题。
    • @moger777 代码是在做样本标准差,而不是总体标准差,所以 (n-1) 是正确的:macroption.com/population-sample-variance-standard-deviation
    【解决方案2】:

    看来安吉拉可能一直想要一个现有的图书馆。在使用 statsample、array-statisics 和其他一些工具之后,如果您想避免重新发明轮子,我建议您使用 descriptive_statistics gem。

    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 的舒适度;但它易于使用且易于贡献。

    【讨论】:

    • 这正是我正在寻找的快速解决方案。我对统计数据的了解不足以检查这项工作,但对于任何只需要以最小的努力获得一些基本统计数据的人来说,这是一个胜利。
    • Rails 用户的重要提示。此时,descriptive_statistics gem 似乎破坏了 ActiveRecord::Relation - 你会遇到NoMethodError: undefined method zero?对于 nil:NilClass` 和 (Object doesn't support #inspect).
    • 我也无法使用 descriptive_statistics gem,因为它使用长度方法而不是 size 或 count 来获取可枚举对象的大小,但是像 Vector 这样的一些常见可枚举对象没有实现长度。
    【解决方案3】:

    上面给出的答案很优雅,但有一点错误。我自己不是统计负责人,我坐下来详细阅读了许多网站,发现这个网站对如何得出标准偏差给出了最容易理解的解释。 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 中使用来自简单电子表格的数字。

    #!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
    

    【讨论】:

    • 对于 ruby​​ 1.8.7,我将最后一个断言更改为 assert result - expected &lt; 1e-10,添加了 require test/unit 并将第一个要求更改为 `require 'enumerable'。
    • 我将此代码复制到我的控制台中,并得到 1.3388427838995882 作为给定数组的标准偏差......???
    【解决方案4】:

    我不太喜欢向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 添加方法的开销和风险。

    【讨论】:

      【解决方案5】:

      所呈现的计算效率不是很高,因为它们需要多次(至少两个,但通常是三个,因为除了标准差之外,您通常还想呈现平均值)通过数组。

      我知道 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
      

      【讨论】:

      • 这就是服务100人和1000人的区别。如果你摆脱了注入和它的块内的数组,它可能是 10.000 人:-)
      • 这样的?我不熟悉 inject 的低效率,而且我不确定你对数组有什么好处,除了它创建了 n 个对象 - 但这些是短暂的对象,不应该是一个巨大的资源消耗。跨度>
      • 根据我的经验,从代码中删除短期对象可以极大地提高性能,这是我遇到瓶颈时首先考虑的地方。可能是因为JVM的堆分配时间(因为我大部分时间都使用JRuby)
      【解决方案6】:

      作为一个简单的函数,给定一个数字列表:

      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
      

      【讨论】:

        【解决方案7】:

        如果手头的记录是IntegerRational 类型,您可能希望使用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
        

        【讨论】:

          【解决方案8】:

          如果人们使用 postgres ...它为 stddev_pop 和 stddev_samp 提供聚合函数 - postgresql aggregate functions

          stddev(相当于 stddev_samp)至少从 postgres 7.1 开始可用,因为 8.2 提供了 samp 和 pop。

          【讨论】:

            【解决方案9】:

            或者怎么样:

            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
            

            【讨论】:

              【解决方案10】:

              您可以将此作为辅助方法并在任何地方对其进行评估。

              def calc_standard_deviation(arr)
                  mean = arr.sum(0.0) / arr.size
                  sum = arr.sum(0.0) { |element| (element - mean) ** 2 }
                  variance = sum / (arr.size - 1)
                  standard_deviation = Math.sqrt(variance)
              end
              

              【讨论】:

                猜你喜欢
                • 2022-01-21
                • 1970-01-01
                • 2012-06-29
                • 1970-01-01
                • 2014-02-27
                • 1970-01-01
                • 2020-06-11
                • 1970-01-01
                • 2017-01-15
                相关资源
                最近更新 更多