# frozen_string_literal: true # Extensions to the Array class to calculate quartiles, outliers, and statistics class Array def compact_empty compact.reject { |a| a.is_a?(Spout::Models::Empty) } end def n compact_empty.count end def mean array = compact_empty return nil if array.size == 0 array.inject(:+).to_f / array.size end def sample_variance array = compact_empty m = array.mean sum = array.inject(0) { |a, e| a + (e - m)**2 } sum / (array.length - 1).to_f end def standard_deviation array = compact_empty return nil if array.size < 2 Math.sqrt(array.sample_variance) end def median array = compact_empty.sort return nil if array.size == 0 len = array.size len.odd? ? array[len / 2] : (array[len / 2 - 1] + array[len / 2]).to_f / 2 end def unknown count { |a| a.is_a?(Spout::Models::Empty) } end def quartile_sizes quartile_size = count / 4 quartile_fraction = count % 4 quartile_sizes = [quartile_size] * 4 (0..quartile_fraction - 1).to_a.each do |index| quartile_sizes[index] += 1 end quartile_sizes end def quartile_one self[0..(quartile_sizes[0] - 1)] end def quartile_two sizes = quartile_sizes start = sizes[0] stop = start + sizes[1] - 1 self[start..stop] end def quartile_three sizes = quartile_sizes start = sizes[0] + sizes[1] stop = start + sizes[2] - 1 self[start..stop] end def quartile_four sizes = quartile_sizes start = sizes[0] + sizes[1] + sizes[2] stop = start + sizes[3] - 1 self[start..stop] end def compact_min compact_empty.min end def compact_max compact_empty.max end def outliers array = compact_empty.sort.select { |v| v.is_a?(Numeric) } q1 = (array.quartile_one + array.quartile_two).median q3 = (array.quartile_three + array.quartile_four).median return [] if q1.nil? || q3.nil? iq_range = q3 - q1 inner_fence_lower = q1 - iq_range * 1.5 inner_fence_upper = q3 + iq_range * 1.5 array.select { |v| v > inner_fence_upper || v < inner_fence_lower } end def major_outliers array = compact_empty.sort.select { |v| v.is_a?(Numeric) } q1 = (array.quartile_one + array.quartile_two).median q3 = (array.quartile_three + array.quartile_four).median return [] if q1.nil? || q3.nil? iq_range = q3 - q1 outer_fence_lower = q1 - iq_range * 3 outer_fence_upper = q3 + iq_range * 3 array.select { |v| v > outer_fence_upper || v < outer_fence_lower } end def minor_outliers outliers - major_outliers end end module Spout module Helpers class ArrayStatistics def self.calculations [["N", :n, :count], ["Mean", :mean, :decimal], ["StdDev", :standard_deviation, :decimal, "± %s"], ["Median", :median, :decimal], ["Min", :compact_min, :decimal], ["Max", :compact_max, :decimal], ["Unknown", :unknown, :count]] end end end end