class RailsDataExplorer::DataSeries

NOTE: DataSeries values are immutable once instantiated.

Responsibilities:

* Represent a data series
* Compute statistics
* Compute chart attributes
* Cache computed properties like values, statistics
* Provide modified versions of values
  (e.g., :limit_distinct_values, :compress_quantitative_values)

Collaborators:

* DataType

Attributes

chart_roles[R]

TODO: Add concept of significant figures for rounding values when displaying them en.wikipedia.org/wiki/Significant_figures

data_type[R]

TODO: Add concept of significant figures for rounding values when displaying them en.wikipedia.org/wiki/Significant_figures

name[R]

TODO: Add concept of significant figures for rounding values when displaying them en.wikipedia.org/wiki/Significant_figures

options[R]

TODO: Add concept of significant figures for rounding values when displaying them en.wikipedia.org/wiki/Significant_figures

Public Class Methods

large_dynamic_range_threshold() click to toggle source

Any data series with a dynamic range greater than this is considered having a large dynamic range We consider dynamic range the ratio between the largest and the smallest value.

# File lib/rails_data_explorer/data_series.rb, line 29
def self.large_dynamic_range_threshold
  10000.0
end
many_uniq_vals_threshold() click to toggle source

Any data series with more than this uniq vals is considered having many uniq values.

# File lib/rails_data_explorer/data_series.rb, line 35
def self.many_uniq_vals_threshold
  20
end
new(_name, _values, options={}) click to toggle source

options: :chart_roles, :data_type (all optional)

# File lib/rails_data_explorer/data_series.rb, line 40
def initialize(_name, _values, options={})
  options = { chart_roles: [], data_type: nil }.merge(options)
  @name = _name
  @values = _values
  @data_type = init_data_type(options[:data_type])
  @chart_roles = init_chart_roles(options[:chart_roles]) # after data_type!
  @options = options.symbolize_keys
end

Public Instance Methods

axis_scale(d3_or_vega, modification = {}) click to toggle source

@param d3_or_vega :d3 or :vega

# File lib/rails_data_explorer/data_series.rb, line 147
def axis_scale(d3_or_vega, modification = {})
  data_type.axis_scale(self, modification, d3_or_vega)
end
axis_tick_format(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 142
def axis_tick_format(modification = {})
  data_type.axis_tick_format(values(modification))
end
descriptive_statistics(modification = {}) click to toggle source

Returns descriptive_statistics as a flat Array (see values)

# File lib/rails_data_explorer/data_series.rb, line 51
def descriptive_statistics(modification = {})
  @cached_descriptive_statistics ||= {}
  @cached_descriptive_statistics[modification] ||= (
    data_type.descriptive_statistics(values(modification))
  )
end
descriptive_statistics_table(modification = {}) click to toggle source

Returns descriptive_statistics as a renderable table structure (see values)

# File lib/rails_data_explorer/data_series.rb, line 60
def descriptive_statistics_table(modification = {})
  @cached_descriptive_statistics_table ||= {}
  @cached_descriptive_statistics_table[modification] ||= (
    data_type.descriptive_statistics_table(values(modification))
  )
end
dynamic_range(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 176
def dynamic_range(modification = {})
  @cached_dynamic_range ||= {}
  @cached_dynamic_range[modification] ||= (
    divisor = [min_val(modification), max_val(modification)].min.to_f
    0 == divisor ? 0.0 : max_val / divisor
  )
end
has_large_dynamic_range?(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 185
def has_large_dynamic_range?(modification = {})
  @cached_has_large_dynamic_range ||= {}
  @cached_has_large_dynamic_range[modification] ||= (
    dynamic_range(modification) > self.class.large_dynamic_range_threshold
  )
end
inspect(indent=1, recursive=1000) click to toggle source
# File lib/rails_data_explorer/data_series.rb, line 127
def inspect(indent=1, recursive=1000)
  r = %(#<#{ self.class.to_s }\n)
  r << [
    "@name=#{ name.inspect }",
    "@data_type=#{ data_type.inspect }",
    "@chart_roles=#{ chart_roles.inspect }",
    "@values=<count: #{ values.count }, items: #{ values_summary }>",
  ].map { |e| "#{ '  ' * indent }#{ e }\n"}.join
  if recursive > 0
    # nothing to recurse
  end
  r << %(#{ '  ' * (indent-1) }>\n)
end
label_sorter(label_val_key, value_sorter) click to toggle source
# File lib/rails_data_explorer/data_series.rb, line 192
def label_sorter(label_val_key, value_sorter)
  data_type.label_sorter(label_val_key, self, value_sorter)
end
max_val(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 170
def max_val(modification = {})
  @cached_max_val ||= {}
  @cached_max_val[modification] ||= values(modification).compact.max
end
min_val(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 164
def min_val(modification = {})
  @cached_min_val ||= {}
  @cached_min_val[modification] ||= values(modification).compact.min
end
number_of_values(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 68
def number_of_values(modification = {})
  @cached_number_of_values ||= {}
  @cached_number_of_values[modification] ||= (
    values(modification).length
  )
end
uniq_vals(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 152
def uniq_vals(modification = {})
  @cached_uniq_vals ||= {}
  @cached_uniq_vals[modification] ||= values(modification).uniq
end
uniq_vals_count(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 158
def uniq_vals_count(modification = {})
  @cached_uniq_vals_count ||= {}
  @cached_uniq_vals_count[modification] ||= uniq_vals(modification).length
end
values(modification = {}) click to toggle source

Returns the values for this data series with an optional modification @param modification [Hash, optional] type of modification. {

name: :limit_distinct_values,
max_num_distinct_values: 20,
val_for_others: '[Other]',

} {

name: :compress_quantitative_values,

}

# File lib/rails_data_explorer/data_series.rb, line 98
def values(modification = {})
  @cached_values ||= {}
  @cached_values[modification] ||= (
    case modification[:name]
    when NilClass
      @values
    when :limit_distinct_values
      # Returns variant of self's values with number of distinct values limited
      # to :max_num_distinct_values. Less frequent values are mapped to
      # :val_for_others.
      # @param max_num_distinct_values [Integer, optional]
      data_type.limit_distinct_values(
        @values,
        (
          modification[:max_num_distinct_values] ||
          @options[:max_num_distinct_values] ||
          self.class.many_uniq_vals_threshold
        ),
        (
          modification[:val_for_others] ||
          @options[:val_for_others]
        )
      )
    else
      raise "Handle this modification: #{ modification.inspect }"
    end
  )
end
values_summary(modification = {}) click to toggle source

(see values)

# File lib/rails_data_explorer/data_series.rb, line 76
def values_summary(modification = {})
  @cached_values_summary ||= {}
  @cached_values_summary[modification] ||= (
    v = values(modification)
    if v.length < 3 || v.inspect.length < 80
      v.inspect
    else
      "[#{ v.first } ... #{ v.last }]"
    end
  )
end

Private Instance Methods

init_chart_roles(chart_role_overrides) click to toggle source

@return keys are chart_classes, and values are arrays with roles

# File lib/rails_data_explorer/data_series.rb, line 200
def init_chart_roles(chart_role_overrides)
  r = if chart_role_overrides.any?
    available_chart_types.inject(Hash.new([])) { |m,chart_type|
      subset = chart_type[:chart_roles] & chart_role_overrides
      next m if subset.empty?
      m[chart_type[:chart_class]] += subset
      m[chart_type[:chart_class]].uniq!
      m
    }
  else
    available_chart_types.inject(Hash.new([])) { |m,chart_type|
      m[chart_type[:chart_class]] += chart_type[:chart_roles]
      m[chart_type[:chart_class]].uniq!
      m
    }
  end
  r.freeze
end
init_data_type(data_type_override) click to toggle source
# File lib/rails_data_explorer/data_series.rb, line 219
def init_data_type(data_type_override)
  if data_type_override.nil?
    first_value = values.detect { |e| !e.nil? }
    case first_value
    when Integer, Bignum, Fixnum
      DataType::Quantitative::Integer.new
    when Float
      DataType::Quantitative::Decimal.new
    when String
      DataType::Categorical.new
    when Time, DateTime, ActiveSupport::TimeWithZone
      DataType::Quantitative::Temporal.new
    else
      raise(ArgumentError.new("Can't infer data type for value: #{ values.first.class.inspect }"))
    end
  else
    data_type_override
  end
end