module AMEE::Analytics::TermsListAnalyticsSupport

Mixin module for the AMEE::DataAbstraction::Term class, providing methods for handling collections of calculations.

Public Instance Methods

+(other_list) click to toggle source
# File lib/amee/analytics/terms_list_analytics_support.rb, line 75
def +(other_list)
  self.class.new(self.to_a + other_list.to_a)
end
-(other_list) click to toggle source
# File lib/amee/analytics/terms_list_analytics_support.rb, line 79
def -(other_list)
  other_list = [other_list].flatten
  self.delete_if { |term| other_list.include?(term) }
end
all_numeric?() click to toggle source

Returns true if all terms in the list have numeric values. Otherwise, returns false.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 125
def all_numeric?
  all? { |term| term.has_numeric_value? }
end
analogous?() click to toggle source

Returns true if all terms within the list have the same label. Otherwise, returns false.

This enables a check as to whether all terms represent the same thing, i.e. same calculation component (i.e. the same drill choice, or profile item value, or return value, or metadata type).

# File lib/amee/analytics/terms_list_analytics_support.rb, line 26
def analogous?
  labels.uniq.size == (1 or nil)
end
first_of_each_type() click to toggle source
# File lib/amee/analytics/terms_list_analytics_support.rb, line 84
def first_of_each_type
  labels = self.labels.uniq
  terms = labels.map {|label| find { |term| term.label == label } }
  AMEE::DataAbstraction::TermsList.new(terms)
end
heterogeneous?() click to toggle source

Returns true if TermsList is NOT homogeneous, i.e. it does NOT contain all analogous terms with corresponding units. Otherwise, returns false.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 44
def heterogeneous?
  !homogeneous?
end
homogeneous?() click to toggle source

Returns true if all terms within the list have the same label AND contain consistent units. Otherwise, returns false.

This enables a term list to be manipulated numerically, for example, by producing a sum or a mean across all terms.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 36
def homogeneous?
  analogous? and homogeneous_units? and homogeneous_per_units?
end
homogeneous_per_units?() click to toggle source

Returns true if all terms within the list are represented by the same PER unit or are all nil. Otherwise, returns false.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 61
def homogeneous_per_units?
  return true if all? { |term| term.per_unit.nil? } or
    ( all? { |term| term.per_unit.is_a? Quantity::Unit::Base } and
      map { |term| term.per_unit.label }.uniq.size == 1 )
  return false
end
homogeneous_units?() click to toggle source

Returns true if all terms within the list are represented by the same unit or are all nil. Otherwise, returns false.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 51
def homogeneous_units?
  return true if all? { |term| term.unit.nil? } or
    ( all? { |term| term.unit.is_a? Quantity::Unit::Base } and
      map { |term| term.unit.label }.uniq.size == 1 )
  return false
end
initialize_result(label,value,unit=nil,per_unit=nil) click to toggle source

Convenience method for initializing instances of the Result class. Intialize the new object with the attributes described by label, value, unit and per_unit. The unit and per_unit attributes default to nil if left unspecified.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 249
def initialize_result(label,value,unit=nil,per_unit=nil)
  Result.new { label label; value value; unit unit; per_unit per_unit }
end
label() click to toggle source

Returns the label which defines all terms in contained within self, if they are all the same. Otherwise, returns nil.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 71
def label
  first.label if analogous?
end
mean(unit=nil,per_unit=nil) click to toggle source

Returns a new instance of Result which represents the mean of all term values within the list.

Any terms within self which contain non-numeric values are ignored.

If the terms within self do not contain consistent units, they are standardized by default to the unit (and per unit) which predominate in the list. Alternatively, the required unit and per units can be specified as arguments using the same conventions as the #standardize_units method.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 197
def mean(unit=nil,per_unit=nil)
  list = numeric_terms
  sum = list.sum(unit,per_unit)
  Result.new { label sum.label; value (sum.value/list.size); unit sum.unit; per_unit sum.per_unit; name sum.name }
end
median() click to toggle source

Returns a representation of the term with median value in self. This method considers both numerical and text values.

If self has an even-numbered size, the median is caluclated as the mean of the values of the two centrally placed terms (having been sorted according to their value attributes).

# File lib/amee/analytics/terms_list_analytics_support.rb, line 231
def median
  new_list = standardize_units
  midpoint = new_list.size/2
  if new_list.size % 2.0 == 1
    median_term = new_list.sort_by_value[midpoint]
  elsif new_list.size % 2.0 == 0
    median_term = new_list.sort_by_value[midpoint-1, 2].mean
  else
    raise
  end
  median_term.to_result
end
method_missing(method, *args, &block) click to toggle source

Syntactic sugar for several instance methods.


Call a method on self which named after a specific term label contained within self and return a new instance of the TermsList class containing each of those terms. E.g.,

my_terms = my_terms_list.type              #=> <AMEE::DataAbstraction::TermsList>
my_terms.label                             #=> :type

my_terms = my_terms_list.mass              #=> <AMEE::DataAbstraction::TermsList>
my_terms.label                             #=> :mass

my_terms = my_terms_list.co2               #=> <AMEE::DataAbstraction::TermsList>
my_terms.label                             #=> :co2

Call either the #sort_by or #sort_by! methods including the argument term as part of the method name, e.g.,

my_calculation_collection.sort_by_value

                #=> <AMEE::DataAbstraction::TermsList ... >

my_calculation_collection.sort_by_name!

                #=> <AMEE::DataAbstraction::TermsList ... >
Calls superclass method
# File lib/amee/analytics/terms_list_analytics_support.rb, line 376
def method_missing(method, *args, &block)
  if labels.include? method
    AMEE::DataAbstraction::TermsList.new select{ |x| x.label == method }
  elsif method.to_s =~ /sort_by_(.*)!/ and self.class::TermProperties.include? $1.to_sym
    sort_by! $1.to_sym
  elsif method.to_s =~ /sort_by_(.*)/ and self.class::TermProperties.include? $1.to_sym
    sort_by $1.to_sym
  else
    super
  end
end
mode() click to toggle source

Returns a representation of the term with most prevalent value in self, i.e. the modal value. This method considers both numerical and text values.

If only a single modal value is discovered an instance of the class Result is returning representing the modal value. Where multiple modal values occur a new instance of TermsList is returned containing Result representations of each modal value.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 212
def mode
  groups = standardize_units.reject { |term| term.value.nil? }.
    group_by { |term| term.value }.map(&:last)
  max_group_size = groups.max {|a,b| a.size <=> b.size }.size
  max_groups = groups.select {|a| a.size == max_group_size}
  if max_groups.size == 1
    max_groups.first.first.to_result
  else
    AMEE::DataAbstraction::TermsList.new max_groups.map { |group| group.first.to_result }
  end
end
move_by(attr,value,index) click to toggle source

Move an individual term to a specified location (index) within the list. The specific term is selected on the basis of one of it’s attributes values, with the attribute to use (e.g. :value, :unit) given by attr</attr> and value by <tt>value. The location within the list to move the term is given as an index integer value as the final argument.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 259
def move_by(attr,value,index)
  if attr == :unit || attr == :per_unit
    value = Unit.for value
  end
  term = find {|t| t.send(attr) == value }
  return if term.nil?
  delete(term)
  insert(index, term)
end
name() click to toggle source
# File lib/amee/analytics/terms_list_analytics_support.rb, line 15
def name
  first.name if analogous?
end
numeric_terms() click to toggle source

Returns a new instance of TermsList comprising only those terms belongong to self which have numeric values.

This is useful for establishing which terms in a list to perform numerical operations on

# File lib/amee/analytics/terms_list_analytics_support.rb, line 135
def numeric_terms
  AMEE::DataAbstraction::TermsList.new select { |term| term.has_numeric_value? }
end
predominant_per_unit() click to toggle source

Returns the label of the per unit which is predominantly used across all terms in the list, e.g.

list.predominant_per_unit      #=> h

list.predominant_per_unit      #=> kWh

Returns nil if all per units are blank

# File lib/amee/analytics/terms_list_analytics_support.rb, line 115
def predominant_per_unit
  terms = reject { |term| term.per_unit.nil? }
  unit = terms.group_by { |term| term.per_unit.label }.
    max {|a,b| a.last.size <=> b.last.size }.first unless terms.blank?
  return unit
end
predominant_unit() click to toggle source

Returns the label of the unit which is predominantly used across all terms in the list, e.g.

list.predominant_unit      #=> kg

list.predominant_unit      #=> kWh

Returns nil if all units are blank

# File lib/amee/analytics/terms_list_analytics_support.rb, line 99
def predominant_unit
  terms = reject { |term| term.unit.nil? }
  unit = terms.group_by { |term| term.unit.label }.
    max {|a,b| a.last.size <=> b.last.size }.first unless terms.blank?
  return unit
end
respond_to?(method) click to toggle source
Calls superclass method
# File lib/amee/analytics/terms_list_analytics_support.rb, line 334
def respond_to?(method)
  if labels.include? method.to_sym
    return true
  elsif method.to_s =~ /sort_by_(.*)!/ and self.class::TermProperties.include? $1.to_sym
    return true
  elsif method.to_s =~ /sort_by_(.*)/ and self.class::TermProperties.include? $1.to_sym
    return true
  else
    super
  end
end
rotate() click to toggle source

Rotate the list terms by one element - shifts the first-placed term to the end of the list, advancing all terms forward by one place.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 271
def rotate
  push(self.shift)
end
sort_by(attr) click to toggle source

Similar to #sort_by! but returns a new instance of TermsList arranged according to the values on the attribute attr.

If differences in units exist between terms, sorting occur based on the absolute quantities implied.

E.g.

my_terms_list.sort_by :value

                #=> <AMEE::DataAbstraction::TermsList ... >
# File lib/amee/analytics/terms_list_analytics_support.rb, line 303
def sort_by(attr)
  # 1. Remove unset terms before sort and append at end
  #
  # 2. Establish set terms
  #
  # 3. Zip together with corresponding standardized units list creating a
  # list of Term pairs
  #
  # 4. Sort list according to standardized Terms
  #
  # 5. Return map of original (now sorted) Terms

  unset_terms, set_terms = self.partition { |term| term.unset? || term.value.nil? }
  standardized_set_terms = AMEE::DataAbstraction::TermsList.new(set_terms).standardize_units
  ordered_set_terms = set_terms.zip(standardized_set_terms).sort! do |term,other_term|
    term[1].send(attr) <=> other_term[1].send(attr)
  end.map {|term_array| term_array[0]}
  AMEE::DataAbstraction::TermsList.new(ordered_set_terms + unset_terms)
end
sort_by!(attr) click to toggle source

Sorts the terms list in place according to the term attribute indicated by attr, returning self.

If differences in units exist between terms, sorting occur based on the absolute quantities implied.

my_terms_list.sort_by! :value

                #=> <AMEE::DataAbstraction::TermsList ... >
# File lib/amee/analytics/terms_list_analytics_support.rb, line 285
def sort_by!(attr)
  replace(sort_by(attr))
end
standardize_units(unit=nil,per_unit=nil) click to toggle source

Returns a new instance of TermsList with all units standardized and the respective term values adjusted accordingly.

The unit and per units to be standardized to can be specified as the first and second arguments respectively. Either the unit name, symbol or label (as defined in the Quantify gem) can be used. If no arguments are specified, the standardized units represent those which are predominant in the list, e.g.

list.standardize_units                  #=> <TermsList>

list.standardize_units(:t,:kWh)         #=> <TermsList>

list.standardize_units('pound')         #=> <TermsList>

list.standardize_units(nil, 'BTU')      #=> <TermsList>
# File lib/amee/analytics/terms_list_analytics_support.rb, line 156
def standardize_units(unit=nil,per_unit=nil)
  return self if homogeneous? && ((unit.nil? or (first.unit && first.unit.label == unit)) &&
     (per_unit.nil? || (first.per_unit && first.per_unit.label == per_unit)))
  unit = predominant_unit if unit.nil?
  per_unit = predominant_per_unit if per_unit.nil?
  new_terms = map { |term| term.convert_unit(:unit => unit, :per_unit => per_unit) }
  AMEE::DataAbstraction::TermsList.new new_terms
end
sum(unit=nil,per_unit=nil) click to toggle source

Returns a new instance of Result which represents the sum of all term values within the list.

Any terms within self which contain non-numeric values are ignored.

If the terms within self do not contain consistent units, they are standardized by default to the unit (and per unit) which predominate in the list. Alternatively, the required unit and per units can be specified as arguments using the same conventions as the #standardize_units method.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 176
def sum(unit=nil,per_unit=nil)
  unit = predominant_unit if unit.nil?
  per_unit = predominant_per_unit if per_unit.nil?
  value = numeric_terms.standardize_units(unit,per_unit).inject(0.0) do |sum,term|
    sum + term.value
  end
  template = self
  Result.new { label template.label; value value; unit unit; per_unit per_unit; name template.name }
end
type() click to toggle source

Return an instance of TermsList containing only terms labelled :type.

This method overrides the standard type method (which is deprecated) and mimics the functionality provied by the first method_missing method in dynamically retrieving a subset of terms according their labels.

# File lib/amee/analytics/terms_list_analytics_support.rb, line 330
def type
  AMEE::DataAbstraction::TermsList.new select{ |x| x.label == :type }
end