特定の属性を持つ複数のレコードがあり、標準偏差を見つけたい。
それ、どうやったら出来るの?
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
「sample_variance」をDave Sagに修正
アンジェラは既存のライブラリを望んでいたようです。 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の快適さについて話すことはできません。しかし、使いやすく、貢献するのは簡単です。
上記の答えはエレガントですが、それにわずかなエラーがあります。私は統計の頭ではないので、座っていくつかのウェブサイトを詳細に読んだところ、このウェブサイトが標準偏差を導き出す方法について最も分かりやすい説明を与えていることがわかりました。 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
望ましくない副作用が生じる可能性があるため、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
にメソッドを追加するリスクを回避します。
単純な関数として、与えられた数字のリスト:
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
提示された計算は、アレイを複数回(通常はstd-devに加えて平均も提示したいため、多くの場合3回)必要なので、あまり効率的ではありません。
Rubyは効率を探す場所ではありませんが、リスト値の1回のパスで平均と標準偏差を計算する実装は次のとおりです。
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
手元のレコードのタイプが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
(空のリストやその他のEdgeの場合に特別な場合の処理を追加するのが賢明でしょう。)
次に、平方根を次のように定義できます。
def std_dev(list)
Math.sqrt(variance(list))
end
人々がpostgresを使用している場合...それはstddev_popとstddev_sampの集約関数を提供します- postgresql集約関数
stddev(stddev_sampの等量)は、少なくともpostgres 7.1以降で使用可能です。8.2はsampとpopの両方が提供されているためです。
またはどうですか:
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