module Crunchr::ClassMethods

Public Instance Methods

as_table(list, opts = {}) click to toggle source

pass in a list off data-objects with and get a nice table

list = [ Object.data({ doors: 1, keys: 2}),
         Object.data({ doors: 1, keys: 3 },
         ...
       ]

table = Object.as_table(list, keys: %w[doors keys])
# => [ [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 3, 8 ] ]

Or use lists in lists

deep_list = [ list, list list ]
table = Object.as_table(
  deep_list, keys: %[doors keys], list_operator: delta
)
# => [ [ 2, 6 ] ]  (difference of max and min for both doors and keys)

Usage with dates/times

If you include Crunchr into something Active-Modely that has ‘created_at’ as a (sane) attribute, you can supply a :date key, it will add a column with the value of created_at into the table. If you do not supply :date_fmt, it will call to_date on the column

@param [Array] list List (1d or 2d) of data objects @param [Hash] opts Options @option opts [Array] keys List of keys to fetch, may contain

calculations, eg: ['doors', 'keys', 'doors / keys']

@option opts [Symbol] list_operator With a 2d list, what operator to

apply to each given list to determine the 1d value see #delta for
more info

@option opts [String] date_fmt Use as input to strftime for the value

in the date column

@option opts [String] str_fmt Use as input to sprintf for the value

in **every** column. (Cannot be used together with :delta)

@option opts [Boolean] delta After the first row, fill every other row

with the difference to the previous row. (Cannot be used with
:str_fmt)
# File lib/crunchr.rb, line 163
def as_table(list, opts = {})
  keys = opts[:keys] || raise("Need keys")

  if opts[:delta] && opts[:str_fmt]
    raise ":delta and :str_fmt cannot be supplied together"
  end

  table = []

  list.each do |statistic|
    iteration_keys = keys.dup

    if statistic.is_a?(Array)
      (iteration_keys, statistic) = flatten(statistic, opts)
    end

    row = []

    iteration_keys.each do |key|
      value = zero()

      if key == :date
        value = opts[:date_fmt] ?
          statistic.created_at.strftime(opts[:date_fmt]) :
          statistic.created_at.to_date

      else
        value = statistic.fetch(key)

        if value.respond_to? :round
          value = case opts[:round]
                  when nil
                    value
                  when 0
                    value.round rescue value
                  else
                    value.round(opts[:round])
                  end
        end

        value = opts[:str_fmt] % value if opts[:str_fmt]

        value = value.to_f if value.is_a?(BigDecimal)
      end

      row << checked(value)
    end

    if opts[:delta] && table.any?
      new_row = []
      row.each_with_index do |value, idx|
        next unless value.kind_of?(Numeric)
        new_row[idx] = checked(row[idx] - @prev[idx])
      end

      @prev = row.dup
      row = new_row
    else
      @prev = row
    end

    table << row
  end

  return table
end
checked(value) click to toggle source

Make sure the value is zero if it is NaN, infinite, or nil Turn the value into a float if it is a BigDecimal

@param value The value to check @return [Float, Integer] the improved value

# File lib/crunchr.rb, line 298
def checked(value)
  value = zero() if value.respond_to?(:nan?) && value.nan?
  value = zero() if value.respond_to?(:infinity?) && value.infinity?
  value = zero() if value.nil?
  value = value.to_f if value.is_a? BigDecimal

  value
end
flatten(array, opts) click to toggle source

flatten an array of rows by applying an operator vertically on each column and accepting the result as a single row

@param [Array] array List of lists @param [Hash] opts Options @option opts [Symbol] list_operator What operator to apply to the array

to get a single value, defaults to :mean, should be any of
   - :mean
   - :stddev
   - :median
   - :range
   - :mode
   - :sum
   - :min
   - :max
   - :delta (takes the difference of max and min)
# File lib/crunchr.rb, line 247
def flatten(array, opts)
  keys = opts[:keys].dup

  # this must be an interval period : find the mean, sum, max, whatever
  opts[:list_operator] ||= :mean

  collection = self.new( :data => {} )

  keys.each_with_index do |key, idx|
    if key == :date
      collection.created_at = array.first.created_at
      next
    end

    collection_key = key.to_s.gsub(/[\s*\/:x+-]+/, '_')
    keys[idx] = collection_key if collection_key != key

    array.each do |item|
      collection.data[collection_key] ||= []
      collection.data[collection_key] << (item.fetch(key) || 0)
    end

    # turn the collection into a single value
    value = if opts[:list_operator] == :delta
      collection.data[collection_key].max -
      collection.data[collection_key].min

    else
       collection.data[collection_key].send(opts[:list_operator])

    end

    collection.data[collection_key] = value
  end

  collection
  collection.readonly! if collection.respond_to?(:readonly!)

  return [keys, collection]
end
zero() click to toggle source

Return a BigDecimal zero value

# File lib/crunchr.rb, line 289
def zero
  BigDecimal.new("0.0")
end