class StatisticalSpread < Struct.new(:values) def sum(identity=0, &block) if block_given? StatisticalSpread.new(values.map(&block)).sum(identity) else values.inject(:+) || identity end end def mean return if values.length < 1 sum / values.length.to_f end def median return if values.length < 1 sorted = values.sort if values.length % 2 == 0 (sorted[(values.length / 2) -1] + sorted[values.length / 2]) / 2.0 else sorted[values.length / 2] end end def mode return if values.length < 1 frequency_distribution = values.inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash } top_2 = frequency_distribution.sort { |a,b| b[1] <=> a[1] } .take(2) if top_2.length == 1 top_2.first.first elsif top_2.first.last == top_2.last.last nil else top_2.first.first end end def range return if values.length < 1 sorted = values.sort sorted.last - sorted.first end def max return if values.length < 1 values.sort.last end def min return if values.length < 1 values.sort.first end def percentile_from_value(value) return if values.length < 1 (values.sort.index(value) / values.length.to_f * 100).ceil end def value_from_percentile(percentile) return if values.length < 1 value_index = (percentile.to_f / 100 * values.length).ceil values.sort[value_index] end def variance return if values.length < 1 precalculated_mean = StatisticalSpread.new(values).mean sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 } sum / (values.length.to_f - 1) end def population_variance return if values.length < 1 precalculated_mean = StatisticalSpread.new(values).mean sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 } sum / values.length.to_f end def standard_deviation return if values.length < 2 Math.sqrt(StatisticalSpread.new(values).variance) end def relative_standard_deviation return if values.length < 1 precalculated_mean = StatisticalSpread.new(values).mean (StatisticalSpread.new(values).population_standard_deviation / precalculated_mean) * 100.0 end def population_standard_deviation return if values.length < 1 Math.sqrt(StatisticalSpread.new(values).population_variance) end def skewness return if values.length == 0 return 0 if values.length == 1 StatisticalSpread.new(values).sum_cubed_deviation / ((values.length - 1) * StatisticalSpread.new(values).cubed_standard_deviation.to_f) end def kurtosis return if values.length == 0 return 0 if values.length == 1 StatisticalSpread.new(values).sum_quarted_deviation / ((values.length - 1) * StatisticalSpread.new(values).quarted_standard_deviation.to_f) end protected def sum_cubed_deviation precalculated_mean = StatisticalSpread.new(values).mean values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 3} end def cubed_standard_deviation StatisticalSpread.new(values).standard_deviation ** 3 end def sum_quarted_deviation precalculated_mean = StatisticalSpread.new(values).mean values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 4} end def quarted_standard_deviation StatisticalSpread.new(values).standard_deviation ** 4 end end