rails-data-explorer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +18 -0
  4. data/doc/how_to/release.md +2 -5
  5. data/lib/rails-data-explorer-no-rails.rb +1 -0
  6. data/lib/rails-data-explorer.rb +5 -0
  7. data/lib/rails-data-explorer/chart.rb +11 -11
  8. data/lib/rails-data-explorer/chart/box_plot_group.rb +10 -3
  9. data/lib/rails-data-explorer/chart/contingency_table.rb +58 -45
  10. data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +2 -2
  11. data/lib/rails-data-explorer/chart/histogram_categorical.rb +10 -4
  12. data/lib/rails-data-explorer/chart/histogram_quantitative.rb +12 -5
  13. data/lib/rails-data-explorer/chart/histogram_temporal.rb +6 -0
  14. data/lib/rails-data-explorer/chart/parallel_coordinates.rb +4 -4
  15. data/lib/rails-data-explorer/chart/parallel_set.rb +3 -3
  16. data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +3 -3
  17. data/lib/rails-data-explorer/constants.rb +5 -0
  18. data/lib/rails-data-explorer/data_series.rb +20 -14
  19. data/lib/rails-data-explorer/data_set.rb +4 -0
  20. data/lib/rails-data-explorer/data_type/categorical.rb +84 -72
  21. data/lib/rails-data-explorer/data_type/quantitative.rb +46 -48
  22. data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +23 -17
  23. data/lib/rails-data-explorer/exploration.rb +20 -4
  24. data/lib/rails-data-explorer/statistics/rng_category.rb +1 -1
  25. data/lib/rails-data-explorer/utils/data_binner.rb +20 -10
  26. data/lib/rails-data-explorer/utils/data_quantizer.rb +6 -6
  27. data/lib/rails-data-explorer/utils/rde_table.rb +62 -0
  28. data/lib/rails_data_explorer.rb +35 -20
  29. data/rails-data-explorer.gemspec +1 -1
  30. data/spec/rails-data-explorer/exploration_spec.rb +4 -4
  31. data/spec/rails-data-explorer/utils/data_binner_spec.rb +3 -3
  32. metadata +30 -50
@@ -14,6 +14,9 @@ class RailsDataExplorer
14
14
  m[key] += 1
15
15
  m
16
16
  }
17
+ histogram_values_ds = DataSeries.new('_', h.values)
18
+ y_scale_type = histogram_values_ds.axis_scale(:vega)
19
+ bar_y2_val = 'log' == y_scale_type ? histogram_values_ds.min_val / 10.0 : 0
17
20
  width = 800
18
21
  {
19
22
  values: h.map { |k,v| { x: k, y: v } },
@@ -25,6 +28,9 @@ class RailsDataExplorer
25
28
  bar_width: 2,
26
29
  y_axis_label: 'Frequency',
27
30
  y_axis_tick_format: "d3.format('r')",
31
+ y_scale_type: y_scale_type,
32
+ y_scale_domain: [bar_y2_val, histogram_values_ds.max_val],
33
+ bar_y2_val: bar_y2_val,
28
34
  }
29
35
  end
30
36
 
@@ -81,10 +81,10 @@ class RailsDataExplorer
81
81
  m
82
82
  }
83
83
  {
84
- :dimensions => dimension_names,
85
- :values => dimension_values,
86
- :types => dimension_types,
87
- :alpha => 1 / ([Math.log([number_of_values, 2].max), 10].min) # from 1.0 to 0.1
84
+ dimensions: dimension_names,
85
+ values: dimension_values,
86
+ types: dimension_types,
87
+ alpha: 1 / ([Math.log([number_of_values, 2].max), 10].min) # from 1.0 to 0.1
88
88
  }
89
89
  end
90
90
 
@@ -11,7 +11,7 @@ class RailsDataExplorer
11
11
 
12
12
  def compute_chart_attrs
13
13
  dimension_data_series = @data_set.data_series.find_all { |ds|
14
- (ds.chart_roles[Chart::ParallelCoordinates] & [:dimension, :any]).any?
14
+ (ds.chart_roles[Chart::ParallelSet] & [:dimension, :any]).any?
15
15
  }
16
16
  return false if dimension_data_series.empty?
17
17
 
@@ -28,8 +28,8 @@ class RailsDataExplorer
28
28
  }
29
29
  end
30
30
  {
31
- :dimensions => dimension_names,
32
- :values => dimension_values
31
+ dimensions: dimension_names,
32
+ values: dimension_values
33
33
  }
34
34
  end
35
35
 
@@ -9,10 +9,10 @@ class RailsDataExplorer
9
9
 
10
10
  def compute_chart_attrs
11
11
  x_candidates = @data_set.data_series.find_all { |ds|
12
- (ds.chart_roles[Chart::ContingencyTable] & [:x, :any]).any?
12
+ (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:x, :any]).any?
13
13
  }.sort { |a,b| b.uniq_vals.length <=> a.uniq_vals.length }
14
14
  y_candidates = @data_set.data_series.find_all { |ds|
15
- (ds.chart_roles[Chart::ContingencyTable] & [:y, :any]).any?
15
+ (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:y, :any]).any?
16
16
  }
17
17
 
18
18
  x_ds = x_candidates.first
@@ -20,7 +20,7 @@ class RailsDataExplorer
20
20
  return false if x_ds.nil? || y_ds.nil?
21
21
 
22
22
  # initialize data_matrix
23
- data_matrix = { :_sum => { :_sum => 0 } }
23
+ data_matrix = { _sum: { _sum: 0 } }
24
24
  x_ds.uniq_vals.each { |x_val|
25
25
  data_matrix[x_val] = {}
26
26
  data_matrix[x_val][:_sum] = 0
@@ -0,0 +1,5 @@
1
+ class RailsDataExplorer
2
+
3
+ GREATER_ZERO = 1.0 / 1_000_000 # The smallest value to use if we have to avoid zero (div by zero)
4
+
5
+ end
@@ -5,20 +5,20 @@ class RailsDataExplorer
5
5
  # http://en.wikipedia.org/wiki/Significant_figures
6
6
 
7
7
  attr_reader :data_type, :name, :values, :chart_roles
8
- delegate :available_chart_types, :to => :data_type, :prefix => false
9
- delegate :available_chart_roles, :to => :data_type, :prefix => false
8
+ delegate :available_chart_types, to: :data_type, prefix: false
9
+ delegate :available_chart_roles, to: :data_type, prefix: false
10
10
 
11
11
  # Any data series with a dynamic range greater than this is considered
12
12
  # having a large dynamic range
13
13
  # We consider dynamic range the ratio between the largest and the smallest value.
14
- def self.large_dynamic_range_cutoff
15
- 1000.0
14
+ def self.large_dynamic_range_threshold
15
+ 10000.0
16
16
  end
17
17
 
18
18
  # Any data series with more than this uniq vals is considered having many
19
19
  # uniq values.
20
- def self.many_uniq_vals_cutoff
21
- 20
20
+ def self.many_uniq_vals_threshold
21
+ 30
22
22
  end
23
23
 
24
24
  # options: :chart_roles, :data_type (all optional)
@@ -40,6 +40,10 @@ class RailsDataExplorer
40
40
  @data_type.descriptive_statistics_table(values)
41
41
  end
42
42
 
43
+ def number_of_values
44
+ values.length
45
+ end
46
+
43
47
  def values_summary
44
48
  if values.length < 3 || values.inspect.length < 80
45
49
  values.inspect
@@ -66,37 +70,39 @@ class RailsDataExplorer
66
70
  data_type.axis_tick_format(values)
67
71
  end
68
72
 
69
- def axis_scale
70
- data_type.axis_scale(self)
73
+ # @param[Symbol] d3_or_vega :d3 or :vega
74
+ def axis_scale(d3_or_vega)
75
+ data_type.axis_scale(self, d3_or_vega)
71
76
  end
72
77
 
73
78
  def uniq_vals
74
- @uniq_vals = values.uniq
79
+ @uniq_vals ||= values.uniq
75
80
  end
76
81
 
77
82
  def uniq_vals_count
78
- @uniq_vals_count = uniq_vals.length
83
+ @uniq_vals_count ||= uniq_vals.length
79
84
  end
80
85
 
81
86
  def min_val
82
- @min_val = values.compact.min
87
+ @min_val ||= values.compact.min
83
88
  end
84
89
 
85
90
  def max_val
86
- @max_val = values.compact.max
91
+ @max_val ||= values.compact.max
87
92
  end
88
93
 
89
94
  # Used to decide whether we can render certain chart types
90
95
  def has_many_uniq_vals?
91
- uniq_vals_count > self.class.many_uniq_vals_cutoff
96
+ uniq_vals_count > self.class.many_uniq_vals_threshold
92
97
  end
93
98
 
94
99
  def dynamic_range
100
+ # TODO: avoid division by zero
95
101
  max_val / [min_val, max_val].min.to_f
96
102
  end
97
103
 
98
104
  def has_large_dynamic_range?
99
- dynamic_range > self.class.large_dynamic_range_cutoff
105
+ dynamic_range > self.class.large_dynamic_range_threshold
100
106
  end
101
107
 
102
108
  def label_sorter(label_val_key, value_sorter)
@@ -58,6 +58,10 @@ class RailsDataExplorer
58
58
  # TODO: all elements in a series are of same type
59
59
  end
60
60
 
61
+ def number_of_values
62
+ @data_series.first.number_of_values
63
+ end
64
+
61
65
  def dimensions_count
62
66
  @data_series.length
63
67
  end
@@ -2,20 +2,22 @@ class RailsDataExplorer
2
2
  class DataType
3
3
  class Categorical < DataType
4
4
 
5
+ # TODO: when there are too many categories, only separate the N most
6
+ # significant ones and group all other values under "Other"
5
7
  def all_available_chart_types
6
8
  [
7
9
  {
8
- :chart_class => Chart::HistogramCategorical,
9
- :chart_roles => [:x],
10
- :dimensions_count_min => 1,
11
- :dimensions_count_max => 1,
12
- },
13
- {
14
- :chart_class => Chart::PieChart,
15
- :chart_roles => [:any],
16
- :dimensions_count_min => 1,
17
- :dimensions_count_max => 1,
10
+ chart_class: Chart::HistogramCategorical,
11
+ chart_roles: [:x],
12
+ dimensions_count_min: 1,
13
+ dimensions_count_max: 1,
18
14
  },
15
+ # {
16
+ # chart_class: Chart::PieChart,
17
+ # chart_roles: [:any],
18
+ # dimensions_count_min: 1,
19
+ # dimensions_count_max: 1,
20
+ # },
19
21
  {
20
22
  chart_class: Chart::BoxPlotGroup,
21
23
  chart_roles: [:y],
@@ -38,6 +40,12 @@ class RailsDataExplorer
38
40
  dimensions_count_min: 2,
39
41
  dimensions_count_max: 2,
40
42
  },
43
+ {
44
+ chart_class: Chart::StackedHistogramTemporal,
45
+ chart_roles: [:y],
46
+ dimensions_count_min: 2,
47
+ dimensions_count_max: 2,
48
+ },
41
49
  {
42
50
  chart_class: Chart::ContingencyTable,
43
51
  chart_roles: [:any],
@@ -60,25 +68,31 @@ class RailsDataExplorer
60
68
 
61
69
  def descriptive_statistics(values)
62
70
  frequencies = values.inject(Hash.new(0)) { |m,e| m[e] += 1; m }
71
+ labels_ds = DataSeries.new('_', values.uniq)
63
72
  total_count = values.length
64
73
  ruby_formatters = {
65
- :integer => Proc.new { |v| number_with_delimiter(v.round) },
66
- :percent => Proc.new { |v| number_to_percentage(v, :precision => 3, :significant => true, :strip_insignificant_zeros => true, :delimiter => ',') },
74
+ integer: Proc.new { |v| number_with_delimiter(v.round) },
75
+ percent: Proc.new { |v| number_to_percentage(v, precision: 3, significant: true, strip_insignificant_zeros: true, delimiter: ',') },
67
76
  }
68
77
  r = frequencies.inject([]) { |m, (k,v)|
69
- m << { :label => "#{ k.to_s }_count", :value => v, :ruby_formatter => ruby_formatters[:integer] }
70
- m << { :label => "#{ k.to_s }_percent", :value => (v / total_count.to_f) * 100, :ruby_formatter => ruby_formatters[:percent] }
78
+ m << { label: "#{ k.to_s }_count", value: v, ruby_formatter: ruby_formatters[:integer] }
79
+ m << { label: "#{ k.to_s }_percent", value: (v / total_count.to_f) * 100, ruby_formatter: ruby_formatters[:percent] }
71
80
  m
72
- }.sort { |a,b| b[:value] <=> a[:value] }
73
- r.insert(0, { :label => '[Total]_count', :value => total_count, :ruby_formatter => ruby_formatters[:integer] })
74
- r.insert(0, { :label => '[Total]_percent', :value => 100, :ruby_formatter => ruby_formatters[:percent] })
81
+ }.sort(
82
+ &labels_ds.label_sorter(
83
+ :label,
84
+ lambda { |a,b| b[:value] <=> a[:value] }
85
+ )
86
+ )
87
+ r.insert(0, { label: '[Total]_count', value: total_count, ruby_formatter: ruby_formatters[:integer] })
88
+ r.insert(0, { label: '[Total]_percent', value: 100, ruby_formatter: ruby_formatters[:percent] })
75
89
  r
76
90
  end
77
91
 
78
- # Returns an OpenStruct that describes a statistics table.
92
+ # Returns an object that describes a statistics table.
79
93
  def descriptive_statistics_table(values)
80
94
  desc_stats = descriptive_statistics(values)
81
- if desc_stats.length < DataSeries.many_uniq_vals_cutoff
95
+ if desc_stats.length < DataSeries.many_uniq_vals_threshold
82
96
  descriptive_statistics_table_horizontal(desc_stats)
83
97
  else
84
98
  descriptive_statistics_table_vertical(desc_stats)
@@ -87,68 +101,64 @@ class RailsDataExplorer
87
101
 
88
102
  def descriptive_statistics_table_horizontal(desc_stats)
89
103
  labels = desc_stats.map { |e| e[:label].gsub(/_count|_percent/, '') }.uniq
90
- table = OpenStruct.new(
91
- :rows => []
92
- )
93
- table.rows << OpenStruct.new(
94
- :css_class => 'rde-column_header',
95
- :tag => :tr,
96
- :cells => labels.map { |label|
97
- OpenStruct.new(:value => label, :ruby_formatter => Proc.new { |e| e }, :tag => :th, :css_class => 'rde-cell-label')
98
- }
104
+ table = Utils::RdeTable.new([])
105
+ table.rows << Utils::RdeTableRow.new(
106
+ :tr,
107
+ labels.map { |label|
108
+ Utils::RdeTableCell.new(:th, label, ruby_formatter: Proc.new { |e| e }, css_class: 'rde-cell-label')
109
+ },
110
+ css_class: 'rde-column_header'
99
111
  )
100
- table.rows << OpenStruct.new(
101
- :css_class => 'rde-data_row',
102
- :tag => :tr,
103
- :cells => labels.map { |label|
112
+ table.rows << Utils::RdeTableRow.new(
113
+ :tr,
114
+ labels.map { |label|
104
115
  stat = desc_stats.detect { |e| "#{ label }_count" == e[:label] }
105
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
106
- }
116
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
117
+ },
118
+ css_class: 'rde-data_row'
107
119
  )
108
- table.rows << OpenStruct.new(
109
- :css_class => 'rde-data_row',
110
- :tag => :tr,
111
- :cells => labels.map { |label|
120
+ table.rows << Utils::RdeTableRow.new(
121
+ :tr,
122
+ labels.map { |label|
112
123
  stat = desc_stats.detect { |e| "#{ label }_percent" == e[:label] }
113
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
114
- }
124
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
125
+ },
126
+ css_class: 'rde-data_row'
115
127
  )
116
128
  table
117
129
  end
118
130
 
119
131
  def descriptive_statistics_table_vertical(desc_stats)
120
132
  labels = desc_stats.map { |e| e[:label].gsub(/_count|_percent/, '') }.uniq
121
- table = OpenStruct.new(
122
- :rows => []
123
- )
124
- table.rows << OpenStruct.new(
125
- :css_class => 'rde-column_header',
126
- :tag => :tr,
127
- :cells => %w[Value Count Percent].map { |label|
128
- OpenStruct.new(:value => label, :tag => :th, :css_class => 'rde-cell-label')
129
- }
133
+ table = Utils::RdeTable.new([])
134
+ table.rows << Utils::RdeTableRow.new(
135
+ :tr,
136
+ %w[Value Count Percent].map { |label|
137
+ Utils::RdeTableCell.new(:th, label, css_class: 'rde-cell-label')
138
+ },
139
+ css_class: 'rde-column_header',
130
140
  )
131
141
  labels.each { |label|
132
142
  count_stat = desc_stats.detect { |e| "#{ label }_count" == e[:label] }
133
143
  percent_stat = desc_stats.detect { |e| "#{ label }_percent" == e[:label] }
134
- table.rows << OpenStruct.new(
135
- :css_class => 'rde-data_row',
136
- :tag => :tr,
137
- :cells => [
138
- OpenStruct.new(:value => label, :tag => :td, :css_class => 'rde-cell-value'),
139
- OpenStruct.new(
140
- :value => count_stat[:value],
141
- :ruby_formatter => count_stat[:ruby_formatter],
142
- :tag => :td,
143
- :css_class => 'rde-cell-value'
144
+ table.rows << Utils::RdeTableRow.new(
145
+ :tr,
146
+ [
147
+ Utils::RdeTableCell.new(:td, label, css_class: 'rde-cell-value'),
148
+ Utils::RdeTableCell.new(
149
+ :td,
150
+ count_stat[:value],
151
+ ruby_formatter: count_stat[:ruby_formatter],
152
+ css_class: 'rde-cell-value'
144
153
  ),
145
- OpenStruct.new(
146
- :value => percent_stat[:value],
147
- :ruby_formatter => percent_stat[:ruby_formatter],
148
- :tag => :td,
149
- :css_class => 'rde-cell-value'
154
+ Utils::RdeTableCell.new(
155
+ :td,
156
+ percent_stat[:value],
157
+ ruby_formatter: percent_stat[:ruby_formatter],
158
+ css_class: 'rde-cell-value'
150
159
  ),
151
- ]
160
+ ],
161
+ css_class: 'rde-data_row',
152
162
  )
153
163
  }
154
164
  table
@@ -163,18 +173,20 @@ class RailsDataExplorer
163
173
  # @param[Proc] value_sorter the sorting proc to use if not sorted numerically
164
174
  # @return[Proc] a Proc that will be used by #sort
165
175
  def label_sorter(label_val_key, data_series, value_sorter)
166
- if data_series.uniq_vals.any? { |e| e.to_s =~ /^\d+/ }
176
+ if data_series.uniq_vals.any? { |e| e.to_s =~ /^[\+\-]?\d+/ }
167
177
  # Sort numerical categories by key ASC
168
178
  lambda { |a,b|
169
- number_extractor = lambda { |val|
179
+ number_and_full_string_extractor = lambda { |val|
170
180
  str = label_val_key ? val[label_val_key] : val
171
- number = str.gsub(/^[^\d]*/, '').to_f
181
+ number = str.gsub(/^[^\d\+\-]*/, '') # remove non-digit leading chars
182
+ .gsub(',', '') # remove delimiter commas, they throw off to_f parsing
183
+ .to_f
172
184
  number += 1 if str =~ /^>/ # increase highest threshold by one for proper sorting
173
- number
185
+ [number, str]
174
186
  }
175
- a_number = number_extractor.call(a)
176
- b_number = number_extractor.call(b)
177
- a_number <=> b_number
187
+ a_number_and_full_string = number_and_full_string_extractor.call(a)
188
+ b_number_and_full_string = number_and_full_string_extractor.call(b)
189
+ a_number_and_full_string <=> b_number_and_full_string
178
190
  }
179
191
  else
180
192
  # Use provided value sorter
@@ -47,10 +47,10 @@ class RailsDataExplorer
47
47
  non_nil_values = values.find_all { |e| !(e.nil? || Float::NAN == e) }
48
48
  stats = ::DescriptiveStatistics::Stats.new(non_nil_values)
49
49
  ruby_formatters = {
50
- :integer => Proc.new { |v|
50
+ integer: Proc.new { |v|
51
51
  v.nil? ? 'Null' : number_with_delimiter(v.round)
52
52
  },
53
- :decimal => Proc.new { |v|
53
+ decimal: Proc.new { |v|
54
54
  case
55
55
  when v.nil?
56
56
  'Null'
@@ -59,62 +59,60 @@ class RailsDataExplorer
59
59
  else
60
60
  number_with_precision(
61
61
  v,
62
- :precision => 3,
63
- :significant => true,
64
- :strip_insignificant_zeros => true,
65
- :delimiter => ','
62
+ precision: 3,
63
+ significant: true,
64
+ strip_insignificant_zeros: true,
65
+ delimiter: ','
66
66
  )
67
67
  end
68
68
  },
69
- :pass_through => Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
69
+ pass_through: Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
70
70
  }
71
71
  [
72
- { :label => 'Min', :value => stats.min, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
73
- { :label => '1%ile', :value => stats.value_from_percentile(1), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
74
- { :label => '5%ile', :value => stats.value_from_percentile(5), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
75
- { :label => '10%ile', :value => stats.value_from_percentile(10), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
76
- { :label => '25%ile', :value => stats.value_from_percentile(25), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
77
- { :label => 'Median', :value => stats.median, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
78
- { :label => '75%ile', :value => stats.value_from_percentile(75), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
79
- { :label => '90%ile', :value => stats.value_from_percentile(90), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
80
- { :label => '95%ile', :value => stats.value_from_percentile(95), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
81
- { :label => '99%ile', :value => stats.value_from_percentile(99), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
82
- { :label => 'Max', :value => stats.max, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
72
+ { label: 'Min', value: stats.min, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
73
+ { label: '1%ile', value: stats.value_from_percentile(1), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
74
+ { label: '5%ile', value: stats.value_from_percentile(5), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
75
+ { label: '10%ile', value: stats.value_from_percentile(10), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
76
+ { label: '25%ile', value: stats.value_from_percentile(25), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
77
+ { label: 'Median', value: stats.median, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
78
+ { label: '75%ile', value: stats.value_from_percentile(75), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
79
+ { label: '90%ile', value: stats.value_from_percentile(90), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
80
+ { label: '95%ile', value: stats.value_from_percentile(95), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
81
+ { label: '99%ile', value: stats.value_from_percentile(99), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
82
+ { label: 'Max', value: stats.max, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
83
83
 
84
- { :label => 'Range', :value => stats.range, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
85
- { :label => 'Mean', :value => stats.mean, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
86
- { :label => 'Mode', :value => stats.mode, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
87
- { :label => 'Count', :value => values.length, :ruby_formatter => ruby_formatters[:integer], :table_row => 2 },
88
- { :label => 'Sum', :value => non_nil_values.inject(0) { |m,e| m += e }, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
89
- { :label => 'Variance', :value => stats.variance, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
90
- { :label => 'Std. dev.', :value => stats.standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
91
- { :label => 'Rel. std. dev.', :value => stats.relative_standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
92
- { :label => 'Skewness', :value => stats.skewness, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
93
- { :label => 'Kurtosis', :value => stats.kurtosis, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
94
- { :label => '', :value => '', :ruby_formatter => ruby_formatters[:pass_through], :table_row => 2 },
84
+ { label: 'Range', value: stats.range, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
85
+ { label: 'Mean', value: stats.mean, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
86
+ { label: 'Mode', value: stats.mode, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
87
+ { label: 'Count', value: values.length, ruby_formatter: ruby_formatters[:integer], table_row: 2 },
88
+ { label: 'Sum', value: non_nil_values.inject(0) { |m,e| m += e }, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
89
+ { label: 'Variance', value: stats.variance, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
90
+ { label: 'Std. dev.', value: stats.standard_deviation, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
91
+ { label: 'Rel. std. dev.', value: stats.relative_standard_deviation, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
92
+ { label: 'Skewness', value: stats.skewness, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
93
+ { label: 'Kurtosis', value: stats.kurtosis, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
94
+ { label: '', value: '', ruby_formatter: ruby_formatters[:pass_through], table_row: 2 },
95
95
  ]
96
96
  end
97
97
 
98
- # Returns an OpenStruct that describes a statistics table.
98
+ # Returns an object that describes a statistics table.
99
99
  def descriptive_statistics_table(values)
100
100
  desc_stats = descriptive_statistics(values)
101
- table = OpenStruct.new(
102
- :rows => []
103
- )
101
+ table = Utils::RdeTable.new([])
104
102
  [1,2].each do |table_row|
105
- table.rows << OpenStruct.new(
106
- :css_class => 'rde-column_header',
107
- :tag => :tr,
108
- :cells => desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
109
- OpenStruct.new(:value => stat[:label], :ruby_formatter => Proc.new { |e| e }, :tag => :th, :css_class => 'rde-cell-label')
110
- }
103
+ table.rows << Utils::RdeTableRow.new(
104
+ :tr,
105
+ desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
106
+ Utils::RdeTableCell.new(:th, stat[:label], ruby_formatter: Proc.new { |e| e }, css_class: 'rde-cell-label')
107
+ },
108
+ css_class: 'rde-column_header'
111
109
  )
112
- table.rows << OpenStruct.new(
113
- :css_class => 'rde-data_row',
114
- :tag => :tr,
115
- :cells => desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
116
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
117
- }
110
+ table.rows << Utils::RdeTableRow.new(
111
+ :tr,
112
+ desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
113
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
114
+ },
115
+ css_class: 'rde-data_row'
118
116
  )
119
117
  end
120
118
  table
@@ -124,12 +122,12 @@ class RailsDataExplorer
124
122
  raise "Implement me in sub_class"
125
123
  end
126
124
 
127
- def axis_scale(data_series)
125
+ def axis_scale(data_series, d3_or_vega)
128
126
  # Log scales can't handle 0 values
129
127
  if data_series.min_val > 0.0 && data_series.has_large_dynamic_range?
130
- 'd3.scale.log'
128
+ { d3: 'd3.scale.log', vega: 'log' }[d3_or_vega]
131
129
  else
132
- 'd3.scale.linear'
130
+ { d3: 'd3.scale.linear', vega: 'linear' }[d3_or_vega]
133
131
  end
134
132
  end
135
133