Module: AMEE::Analytics::TermsListAnalyticsSupport

Defined in:
lib/amee/analytics/terms_list_analytics_support.rb

Overview

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

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object

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 ... >


376
377
378
379
380
381
382
383
384
385
386
# 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

Instance Method Details

#+(other_list) ⇒ Object



75
76
77
# 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) ⇒ Object



79
80
81
82
# 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?Boolean

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

Returns:

  • (Boolean)


125
126
127
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 125

def all_numeric?
  all? { |term| term.has_numeric_value? }
end

#analogous?Boolean

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).

Returns:

  • (Boolean)


26
27
28
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 26

def analogous?
  labels.uniq.size == (1 or nil)
end

#first_of_each_typeObject



84
85
86
87
88
# 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?Boolean

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

Returns:

  • (Boolean)


44
45
46
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 44

def heterogeneous?
  !homogeneous?
end

#homogeneous?Boolean

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.

Returns:

  • (Boolean)


36
37
38
# 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?Boolean

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

Returns:

  • (Boolean)


61
62
63
64
65
66
# 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?Boolean

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

Returns:

  • (Boolean)


51
52
53
54
55
56
# 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) ⇒ Object

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.



249
250
251
# 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

#labelObject

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



71
72
73
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 71

def label
  first.label if analogous?
end

#mean(unit = nil, per_unit = nil) ⇒ Object

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.



197
198
199
200
201
# 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

#medianObject

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).



231
232
233
234
235
236
237
238
239
240
241
242
# 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

#modeObject

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.



212
213
214
215
216
217
218
219
220
221
222
# 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) ⇒ Object

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.



259
260
261
262
263
264
265
266
267
# 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

#nameObject



15
16
17
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 15

def name
  first.name if analogous?
end

#numeric_termsObject

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



135
136
137
# 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_unitObject

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



115
116
117
118
119
120
# 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_unitObject

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



99
100
101
102
103
104
# 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) ⇒ Boolean

Returns:

  • (Boolean)


334
335
336
337
338
339
340
341
342
343
344
# 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

#rotateObject

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.



271
272
273
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 271

def rotate
  push(self.shift)
end

#sort_by(attr) ⇒ Object

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 ... >


303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# 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) ⇒ Object

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 ... >


285
286
287
# 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) ⇒ Object

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>


156
157
158
159
160
161
162
163
# 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) ⇒ Object

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.



176
177
178
179
180
181
182
183
184
# 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

#typeObject

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.



330
331
332
# File 'lib/amee/analytics/terms_list_analytics_support.rb', line 330

def type
  AMEE::DataAbstraction::TermsList.new select{ |x| x.label == :type }
end